dev #4

Merged
dashixiong merged 11 commits from dev into main 2026-04-09 01:32:13 +00:00
80 changed files with 8929 additions and 745 deletions
Showing only changes of commit 71d0b0f609 - Show all commits

View File

@ -15,6 +15,7 @@ import com.xzzn.ems.domain.vo.PointDataRequest;
import com.xzzn.ems.domain.vo.PointQueryResponse; import com.xzzn.ems.domain.vo.PointQueryResponse;
import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest; import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest;
import com.xzzn.ems.domain.vo.SiteDeviceListVo; import com.xzzn.ems.domain.vo.SiteDeviceListVo;
import com.xzzn.ems.domain.vo.WorkStatusEnumMappingSaveRequest;
import com.xzzn.ems.service.IEmsDeviceSettingService; import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsSiteService; import com.xzzn.ems.service.IEmsSiteService;
@ -232,6 +233,25 @@ public class EmsSiteConfigController extends BaseController{
return AjaxResult.success(rows); return AjaxResult.success(rows);
} }
/**
* 获取单站监控工作状态枚举映射PCS
*/
@GetMapping("/getSingleMonitorWorkStatusEnumMappings")
public AjaxResult getSingleMonitorWorkStatusEnumMappings(@RequestParam String siteId)
{
return success(iEmsDeviceSettingService.getSiteWorkStatusEnumMappings(siteId));
}
/**
* 保存单站监控工作状态枚举映射PCS
*/
@PostMapping("/saveSingleMonitorWorkStatusEnumMappings")
public AjaxResult saveSingleMonitorWorkStatusEnumMappings(@RequestBody WorkStatusEnumMappingSaveRequest request)
{
int rows = iEmsDeviceSettingService.saveSiteWorkStatusEnumMappings(request.getSiteId(), request.getMappings(), getUsername());
return AjaxResult.success(rows);
}
/** /**
* PCS设备开关机 * PCS设备开关机
*/ */

View File

@ -10,10 +10,13 @@ import com.xzzn.ems.domain.vo.DateSearchRequest;
import com.xzzn.ems.domain.vo.RunningGraphRequest; import com.xzzn.ems.domain.vo.RunningGraphRequest;
import com.xzzn.ems.domain.vo.SiteBatteryDataList; import com.xzzn.ems.domain.vo.SiteBatteryDataList;
import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest; import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest;
import com.xzzn.ems.domain.vo.SiteMonitorRuningInfoVo;
import com.xzzn.ems.service.IEmsDeviceSettingService; import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsSiteService; import com.xzzn.ems.service.IEmsSiteService;
import com.xzzn.ems.service.IEmsStatsReportService; import com.xzzn.ems.service.IEmsStatsReportService;
import com.xzzn.ems.service.ISingleSiteService; import com.xzzn.ems.service.ISingleSiteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -28,6 +31,8 @@ import java.util.List;
@RestController @RestController
@RequestMapping("/ems/siteMonitor") @RequestMapping("/ems/siteMonitor")
public class EmsSiteMonitorController extends BaseController{ public class EmsSiteMonitorController extends BaseController{
private static final Logger log = LoggerFactory.getLogger(EmsSiteMonitorController.class);
private static final String RUNNING_GRAPH_CTRL_DEBUG = "RunningGraphCtrlDebug";
@Autowired @Autowired
private ISingleSiteService iSingleSiteService; private ISingleSiteService iSingleSiteService;
@ -60,27 +65,75 @@ public class EmsSiteMonitorController extends BaseController{
* 单站监控-设备监控-实时运行曲线图数据 * 单站监控-设备监控-实时运行曲线图数据
*/ */
@GetMapping("/runningGraph/storagePower") @GetMapping("/runningGraph/storagePower")
public AjaxResult getRunningGraphStorage(RunningGraphRequest request) public AjaxResult getRunningGraphStorage(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraphStorage(request)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphStorage(request);
int deviceCount = data == null || data.getPcsPowerList() == null ? 0 : data.getPcsPowerList().size();
log.info("{} storage, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, deviceCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
deviceCount);
return success(data);
} }
@GetMapping("/runningGraph/pcsMaxTemp") @GetMapping("/runningGraph/pcsMaxTemp")
public AjaxResult getRunningGraphPcsMaxTemp(RunningGraphRequest request) public AjaxResult getRunningGraphPcsMaxTemp(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraphPcsMaxTemp(request)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphPcsMaxTemp(request);
int deviceCount = data == null || data.getPcsMaxTempList() == null ? 0 : data.getPcsMaxTempList().size();
log.info("{} pcsMaxTemp, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, deviceCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
deviceCount);
return success(data);
} }
@GetMapping("/runningGraph/batteryAveSoc") @GetMapping("/runningGraph/batteryAveSoc")
public AjaxResult getRunningGraphBatterySoc(RunningGraphRequest request) public AjaxResult getRunningGraphBatterySoc(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraphBatterySoc(request)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphBatterySoc(request);
int pointCount = data == null || data.getBatteryAveSOCList() == null ? 0 : data.getBatteryAveSOCList().size();
log.info("{} batteryAveSoc, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, pointCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
pointCount);
return success(data);
} }
@GetMapping("/runningGraph/batteryAveTemp") @GetMapping("/runningGraph/batteryAveTemp")
public AjaxResult getRunningGraphBatteryTemp(RunningGraphRequest request) public AjaxResult getRunningGraphBatteryTemp(RunningGraphRequest request,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate)
{ {
return success(iSingleSiteService.getRunningGraphBatteryTemp(request)); SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphBatteryTemp(request);
int pointCount = data == null || data.getBatteryAveTempList() == null ? 0 : data.getBatteryAveTempList().size();
log.info("{} batteryAveTemp, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, pointCount={}",
RUNNING_GRAPH_CTRL_DEBUG,
request == null ? null : request.getSiteId(),
startDate,
endDate,
request == null ? null : request.getStartDate(),
request == null ? null : request.getEndDate(),
pointCount);
return success(data);
} }
/** /**

View File

@ -128,4 +128,10 @@ public class RedisKeyConstants
/** 单站监控最新数据(按站点+模块) */ /** 单站监控最新数据(按站点+模块) */
public static final String SITE_MONITOR_LATEST = "SITE_MONITOR_LATEST_"; public static final String SITE_MONITOR_LATEST = "SITE_MONITOR_LATEST_";
/** 单站监控点位映射(按站点) */
public static final String SITE_MONITOR_POINT_MATCH = "SITE_MONITOR_POINT_MATCH_";
/** 站点保护约束(按站点) */
public static final String PROTECTION_CONSTRAINT = "PROTECTION_CONSTRAINT_";
} }

View File

@ -10,40 +10,14 @@ import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.AlarmLevelStatus; import com.xzzn.common.enums.AlarmLevelStatus;
import com.xzzn.common.enums.AlarmStatus; import com.xzzn.common.enums.AlarmStatus;
import com.xzzn.common.enums.ProtPlanStatus; import com.xzzn.common.enums.ProtPlanStatus;
import com.xzzn.common.enums.StrategyStatus;
import com.xzzn.common.utils.StringUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsAlarmRecords; import com.xzzn.ems.domain.EmsAlarmRecords;
import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsFaultIssueLog;
import com.xzzn.ems.domain.EmsFaultProtectionPlan; import com.xzzn.ems.domain.EmsFaultProtectionPlan;
import com.xzzn.ems.domain.EmsStrategyRunning; import com.xzzn.ems.domain.vo.ProtectionConstraintVo;
import com.xzzn.ems.domain.vo.ProtectionPlanVo;
import com.xzzn.ems.domain.vo.ProtectionSettingVo; import com.xzzn.ems.domain.vo.ProtectionSettingVo;
import com.xzzn.ems.mapper.EmsAlarmRecordsMapper; import com.xzzn.ems.mapper.EmsAlarmRecordsMapper;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsFaultIssueLogMapper;
import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper; import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.service.IEmsFaultProtectionPlanService; import com.xzzn.ems.service.IEmsFaultProtectionPlanService;
import com.xzzn.common.core.modbus.ModbusProcessor;
import com.xzzn.common.core.modbus.domain.DeviceConfig;
import com.xzzn.common.core.modbus.domain.WriteTagConfig;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -51,14 +25,29 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* 告警保护方案轮询 * 告警保护方案轮询
*
* @author xzzn
*/ */
@Component("protectionPlanTask") @Component("protectionPlanTask")
public class ProtectionPlanTask { public class ProtectionPlanTask {
private static final Logger logger = LoggerFactory.getLogger(ProtectionPlanTask.class); private static final Logger logger = LoggerFactory.getLogger(ProtectionPlanTask.class);
private static final BigDecimal DEFAULT_L1_POWER_RATIO = new BigDecimal("0.5");
private static final int CONSTRAINT_TTL_SECONDS = 120;
@Resource(name = "scheduledExecutorService") @Resource(name = "scheduledExecutorService")
private ScheduledExecutorService scheduledExecutorService; private ScheduledExecutorService scheduledExecutorService;
@Autowired @Autowired
@ -66,18 +55,11 @@ public class ProtectionPlanTask {
@Autowired @Autowired
private EmsAlarmRecordsMapper emsAlarmRecordsMapper; private EmsAlarmRecordsMapper emsAlarmRecordsMapper;
@Autowired @Autowired
private EmsStrategyRunningMapper emsStrategyRunningMapper;
@Autowired
private EmsFaultProtectionPlanMapper emsFaultProtectionPlanMapper; private EmsFaultProtectionPlanMapper emsFaultProtectionPlanMapper;
@Autowired @Autowired
private RedisCache redisCache; private RedisCache redisCache;
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired
private EmsDevicesSettingMapper emsDevicesSettingMapper;
@Autowired
private ModbusProcessor modbusProcessor;
@Autowired
private EmsFaultIssueLogMapper emsFaultIssueLogMapper;
public ProtectionPlanTask(IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService) { public ProtectionPlanTask(IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService) {
this.iEmsFaultProtectionPlanService = iEmsFaultProtectionPlanService; this.iEmsFaultProtectionPlanService = iEmsFaultProtectionPlanService;
@ -86,303 +68,320 @@ public class ProtectionPlanTask {
public void pollPlanList() { public void pollPlanList() {
Long planId = 0L; Long planId = 0L;
try { try {
// 获取所有方案,轮询
List<EmsFaultProtectionPlan> planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(null); List<EmsFaultProtectionPlan> planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(null);
for (EmsFaultProtectionPlan plan : planList) { for (EmsFaultProtectionPlan plan : planList) {
planId = plan.getId(); planId = plan.getId();
String siteId = plan.getSiteId(); String siteId = plan.getSiteId();
if (StringUtils.isEmpty(siteId)) { if (StringUtils.isEmpty(siteId)) {
return; continue;
} }
// 保护前提 List<ProtectionSettingVo> protSettings = parseProtectionSettings(plan.getProtectionSettings());
String protectionSettings = plan.getProtectionSettings(); if (CollectionUtils.isEmpty(protSettings)) {
final List<ProtectionSettingVo> protSettings = objectMapper.readValue( continue;
protectionSettings,
new TypeReference<List<ProtectionSettingVo>>() {}
);
if (protSettings == null) {
return;
}
// 处理告警保护方案
boolean isHighLevel = dealWithProtectionPlan(plan, protSettings);
if (isHighLevel) {
// 触发最高故障等级-结束循环
return;
} }
dealWithProtectionPlan(plan, protSettings);
} }
refreshProtectionConstraintCache(planList);
} catch (Exception e) { } catch (Exception e) {
logger.error("轮询失败,方案id为{}", planId, e); logger.error("轮询失败,方案id为{}", planId, e);
} }
} }
// 处理告警保护方案-返回触发下发方案时是否最高等级
// 需要同步云端
@SyncAfterInsert @SyncAfterInsert
private boolean dealWithProtectionPlan(EmsFaultProtectionPlan plan, List<ProtectionSettingVo> protSettings) { private void dealWithProtectionPlan(EmsFaultProtectionPlan plan, List<ProtectionSettingVo> protSettings) {
logger.info("<轮询保护方案> 站点:{}方案ID:{}", plan.getSiteId(), plan.getId()); logger.info("<轮询保护方案> 站点:{}方案ID:{}", plan.getSiteId(), plan.getId());
boolean isHighLevel = false;
String siteId = plan.getSiteId(); String siteId = plan.getSiteId();
final Integer isAlertAlarm = plan.getIsAlert(); Integer isAlertAlarm = plan.getIsAlert();
final Long status = plan.getStatus(); Long status = plan.getStatus();
// 看方案是否启用,走不同判断 if (status == null) {
status = ProtPlanStatus.STOP.getCode();
}
if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) { if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) {
logger.info("<方案未启用> 站点:{}方案ID:{}", siteId, plan.getId()); if (checkIsNeedIssuedPlan(protSettings, siteId)) {
// 未启用,获取方案的故障值与最新数据判断是否需要下发方案 int faultDelay = safeDelaySeconds(plan.getFaultDelaySeconds(), 0);
if(checkIsNeedIssuedPlan(protSettings, siteId)){ scheduledExecutorService.schedule(() -> {
if("3".equals(plan.getFaultLevel())){ if (!checkIsNeedIssuedPlan(protSettings, siteId)) {
isHighLevel = true;//最高故障等级 return;
}
// 延时
final int faultDelay = plan.getFaultDelaySeconds().intValue();
ScheduledFuture<?> delayTask = scheduledExecutorService.schedule(() -> {
// 延时后再次确认是否仍满足触发条件(防止期间状态变化)
if (checkIsNeedIssuedPlan(protSettings, siteId)) {
// 判断是否需要生成告警
if (isAlertAlarm == 1) {
logger.info("<生成告警> 方案ID:{},站点:{}", plan.getId(), siteId);
EmsAlarmRecords alarmRecords = addAlarmRecord(siteId,plan.getFaultName(),
getAlarmLevel(plan.getFaultLevel()));
emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
}
// 是否有保护方案有则通过modbus连接设备下发方案
String protPlanJson = plan.getProtectionPlan();
if (protPlanJson != null && !protPlanJson.isEmpty() && !"[]".equals(protPlanJson)) {
logger.info("<下发保护方案> 方案内容:{}", protPlanJson);
executeProtectionActions(protPlanJson,siteId,plan.getId(),plan.getFaultLevel()); // 执行Modbus指令
}
// 更新方案状态为“已启用”
logger.info("<方案已启用> 方案ID:{}", plan.getId());
plan.setStatus(ProtPlanStatus.RUNNING.getCode());
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
// 更新该站点策略为暂停状态
updateStrategyRunningStatus(siteId, StrategyStatus.SUSPENDED.getCode());
} }
}, faultDelay, TimeUnit.SECONDS); if (Integer.valueOf(1).equals(isAlertAlarm)) {
} EmsAlarmRecords alarmRecords = addAlarmRecord(siteId, plan.getFaultName(), getAlarmLevel(plan.getFaultLevel()));
} else { emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords);
logger.info("<方案已启用> 站点:{}方案ID:{}", siteId, plan.getId());
// 已启用,则获取方案的释放值与最新数据判断是否需要取消方案
if(checkIsNeedCancelPlan(protSettings, siteId)){
// 延时,
int releaseDelay = plan.getReleaseDelaySeconds().intValue();
ScheduledFuture<?> delayTask = scheduledExecutorService.schedule(() -> {
// 判断是否已存在未处理告警,有着取消
if(isAlertAlarm == 1){
logger.info("<取消告警>");
EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(siteId,
plan.getFaultName(),getAlarmLevel(plan.getFaultLevel()));
if(emsAlarmRecords != null){
emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode());
emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords);
}
} }
// 更新方案状态为“未启用” plan.setStatus(ProtPlanStatus.RUNNING.getCode());
logger.info("<方案变更为未启用> 方案ID:{}", plan.getId());
plan.setStatus(ProtPlanStatus.STOP.getCode());
plan.setUpdateBy("system"); plan.setUpdateBy("system");
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan); emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
// 更新该站点策略为启用状态 refreshSiteProtectionConstraint(siteId);
updateStrategyRunningStatus(siteId, StrategyStatus.RUNNING.getCode()); }, faultDelay, TimeUnit.SECONDS);
}, releaseDelay, TimeUnit.SECONDS);
} }
}
return isHighLevel;
}
// 下发保护方案
private void executeProtectionActions(String protPlanJson, String siteId, Long planId, Integer faultLevel){
final List<ProtectionPlanVo> protPlanList;
try {
protPlanList = objectMapper.readValue(
protPlanJson,
new TypeReference<List<ProtectionPlanVo>>() {}
);
if (protPlanList == null) {
return;
}
// 遍历保护方案
for (ProtectionPlanVo plan : protPlanList) {
if (StringUtils.isEmpty(plan.getDeviceId()) || StringUtils.isEmpty(plan.getPoint())) {
return;
}
// 给设备发送指令记录日志,并同步云端
EmsFaultIssueLog faultIssueLog = createLogEntity(plan,siteId);
faultIssueLog.setLogLevel(faultLevel);
emsFaultIssueLogMapper.insertEmsFaultIssueLog(faultIssueLog);
// 通过modbus连接设备发送数据
executeSinglePlan(plan,siteId);
}
} catch (Exception e) {
logger.error("下发保护方案失败,方案id为", planId, e);
}
}
private EmsFaultIssueLog createLogEntity(ProtectionPlanVo plan,String siteId) {
EmsFaultIssueLog faultIssueLog = new EmsFaultIssueLog();
faultIssueLog.setLogId(UUID.randomUUID().toString());
faultIssueLog.setLogTime(new Date());
faultIssueLog.setSiteId(siteId);
faultIssueLog.setDeviceId(plan.getDeviceId());
faultIssueLog.setPoint(plan.getPoint());
faultIssueLog.setValue(plan.getValue());
faultIssueLog.setCreateBy("sys");
faultIssueLog.setCreateTime(new Date());
return faultIssueLog;
}
private void executeSinglePlan(ProtectionPlanVo plan, String siteId) throws Exception {
String deviceId = plan.getDeviceId();
// 获取设备地址信息
EmsDevicesSetting device = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId);
if (device == null || StringUtils.isEmpty(device.getIpAddress()) || device.getIpPort()==null) {
logger.warn("设备信息不完整deviceId:{}", deviceId);
return; return;
} }
// 构建设备配置 if (checkIsNeedCancelPlan(protSettings, siteId)) {
DeviceConfig config = new DeviceConfig(); int releaseDelay = safeDelaySeconds(plan.getReleaseDelaySeconds(), 0);
config.setHost(device.getIpAddress()); scheduledExecutorService.schedule(() -> {
config.setPort(device.getIpPort().intValue()); if (Integer.valueOf(1).equals(isAlertAlarm)) {
config.setSlaveId(device.getSlaveId().intValue()); EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(
config.setDeviceName(device.getDeviceName()); siteId,
config.setDeviceNumber(device.getDeviceId()); plan.getFaultName(),
getAlarmLevel(plan.getFaultLevel())
// 构建写入标签配置 );
WriteTagConfig writeTag = new WriteTagConfig(); if (emsAlarmRecords != null) {
writeTag.setAddress(plan.getPoint()); emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode());
writeTag.setValue(plan.getValue()); emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords);
}
List<WriteTagConfig> writeTags = new ArrayList<>(); }
writeTags.add(writeTag); plan.setStatus(ProtPlanStatus.STOP.getCode());
config.setWriteTags(writeTags); plan.setUpdateBy("system");
emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan);
// 写入数据到设备 refreshSiteProtectionConstraint(siteId);
boolean success = modbusProcessor.writeDataToDeviceWithRetry(config); }, releaseDelay, TimeUnit.SECONDS);
if (!success) {
logger.error("写入失败,设备地址:{}", device.getIpAddress());
} }
} }
// 校验释放值是否取消方案 private int safeDelaySeconds(Long delay, int defaultSeconds) {
private boolean checkIsNeedCancelPlan(List<ProtectionSettingVo> protSettings, String siteId) { if (delay == null || delay < 0) {
BigDecimal releaseValue = BigDecimal.ZERO; return defaultSeconds;
}
return delay.intValue();
}
private List<ProtectionSettingVo> parseProtectionSettings(String settingsJson) {
if (StringUtils.isEmpty(settingsJson)) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(settingsJson, new TypeReference<List<ProtectionSettingVo>>() {});
} catch (Exception e) {
logger.error("解析保护前提失败json:{}", settingsJson, e);
return new ArrayList<>();
}
}
private void refreshProtectionConstraintCache(List<EmsFaultProtectionPlan> allPlans) {
Map<String, List<EmsFaultProtectionPlan>> planBySite = new HashMap<>();
for (EmsFaultProtectionPlan plan : allPlans) {
if (StringUtils.isEmpty(plan.getSiteId())) {
continue;
}
planBySite.computeIfAbsent(plan.getSiteId(), k -> new ArrayList<>()).add(plan);
}
for (Map.Entry<String, List<EmsFaultProtectionPlan>> entry : planBySite.entrySet()) {
writeSiteProtectionConstraint(entry.getKey(), entry.getValue());
}
}
private void refreshSiteProtectionConstraint(String siteId) {
if (StringUtils.isEmpty(siteId)) {
return;
}
EmsFaultProtectionPlan query = new EmsFaultProtectionPlan();
query.setSiteId(siteId);
List<EmsFaultProtectionPlan> sitePlans = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(query);
writeSiteProtectionConstraint(siteId, sitePlans);
}
private void writeSiteProtectionConstraint(String siteId, List<EmsFaultProtectionPlan> sitePlans) {
List<EmsFaultProtectionPlan> runningPlans = new ArrayList<>();
for (EmsFaultProtectionPlan plan : sitePlans) {
if (Objects.equals(plan.getStatus(), ProtPlanStatus.RUNNING.getCode())) {
runningPlans.add(plan);
}
}
String key = RedisKeyConstants.PROTECTION_CONSTRAINT + siteId;
if (runningPlans.isEmpty()) {
redisCache.deleteObject(key);
return;
}
ProtectionConstraintVo merged = ProtectionConstraintVo.empty();
for (EmsFaultProtectionPlan runningPlan : runningPlans) {
ProtectionConstraintVo single = buildConstraintFromPlan(runningPlan);
mergeConstraint(merged, single);
}
merged.setUpdateAt(System.currentTimeMillis());
redisCache.setCacheObject(key, merged, CONSTRAINT_TTL_SECONDS, TimeUnit.SECONDS);
}
private ProtectionConstraintVo buildConstraintFromPlan(EmsFaultProtectionPlan plan) {
ProtectionConstraintVo vo = ProtectionConstraintVo.empty();
int level = plan.getFaultLevel() == null ? 0 : plan.getFaultLevel();
vo.setLevel(level);
vo.setSourcePlanIds(new ArrayList<>());
vo.getSourcePlanIds().add(plan.getId());
if (level == 1) {
vo.setPowerLimitRatio(DEFAULT_L1_POWER_RATIO);
} else if (level >= 3) {
vo.setForceStop(true);
vo.setForceStandby(true);
vo.setAllowCharge(false);
vo.setAllowDischarge(false);
vo.setPowerLimitRatio(BigDecimal.ZERO);
}
String description = StringUtils.isEmpty(plan.getDescription()) ? "" : plan.getDescription();
BigDecimal ratioByDesc = parseDerateRatio(description);
if (ratioByDesc != null) {
vo.setPowerLimitRatio(minRatio(vo.getPowerLimitRatio(), ratioByDesc));
}
if (description.contains("禁止充放电")) {
vo.setAllowCharge(false);
vo.setAllowDischarge(false);
} else {
if (description.contains("禁止充电")) {
vo.setAllowCharge(false);
}
if (description.contains("禁止放电")) {
vo.setAllowDischarge(false);
}
if (description.contains("允许充电")) {
vo.setAllowCharge(true);
}
if (description.contains("允许放电")) {
vo.setAllowDischarge(true);
}
}
if (description.contains("待机")) {
vo.setForceStandby(true);
}
if (description.contains("停机") || description.contains("切断") || level >= 3) {
vo.setForceStop(true);
vo.setForceStandby(true);
vo.setAllowCharge(false);
vo.setAllowDischarge(false);
vo.setPowerLimitRatio(BigDecimal.ZERO);
}
return vo;
}
private BigDecimal parseDerateRatio(String text) {
if (StringUtils.isEmpty(text)) {
return null;
}
Matcher m = Pattern.compile("降功率\\s*(\\d+(?:\\.\\d+)?)%")
.matcher(text);
if (!m.find()) {
return null;
}
BigDecimal percent = new BigDecimal(m.group(1));
if (percent.compareTo(BigDecimal.ZERO) < 0) {
return null;
}
return percent.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP);
}
private void mergeConstraint(ProtectionConstraintVo merged, ProtectionConstraintVo incoming) {
if (incoming == null) {
return;
}
merged.setLevel(Math.max(nullSafeInt(merged.getLevel()), nullSafeInt(incoming.getLevel())));
merged.setAllowCharge(boolAnd(merged.getAllowCharge(), incoming.getAllowCharge()));
merged.setAllowDischarge(boolAnd(merged.getAllowDischarge(), incoming.getAllowDischarge()));
merged.setForceStandby(boolOr(merged.getForceStandby(), incoming.getForceStandby()));
merged.setForceStop(boolOr(merged.getForceStop(), incoming.getForceStop()));
merged.setPowerLimitRatio(minRatio(merged.getPowerLimitRatio(), incoming.getPowerLimitRatio()));
if (incoming.getSourcePlanIds() != null && !incoming.getSourcePlanIds().isEmpty()) {
if (merged.getSourcePlanIds() == null) {
merged.setSourcePlanIds(new ArrayList<>());
}
merged.getSourcePlanIds().addAll(incoming.getSourcePlanIds());
}
}
private BigDecimal minRatio(BigDecimal a, BigDecimal b) {
BigDecimal left = a == null ? BigDecimal.ONE : a;
BigDecimal right = b == null ? BigDecimal.ONE : b;
return left.min(right);
}
private int nullSafeInt(Integer value) {
return value == null ? 0 : value;
}
private Boolean boolAnd(Boolean a, Boolean b) {
boolean left = a == null || a;
boolean right = b == null || b;
return left && right;
}
private Boolean boolOr(Boolean a, Boolean b) {
boolean left = a != null && a;
boolean right = b != null && b;
return left || right;
}
private boolean checkIsNeedCancelPlan(List<ProtectionSettingVo> protSettings, String siteId) {
StringBuilder conditionSb = new StringBuilder(); StringBuilder conditionSb = new StringBuilder();
for (int i = 0; i < protSettings.size(); i++) { for (int i = 0; i < protSettings.size(); i++) {
ProtectionSettingVo vo = protSettings.get(i); ProtectionSettingVo vo = protSettings.get(i);
String deviceId = vo.getDeviceId(); String deviceId = vo.getDeviceId();
String point = vo.getPoint(); String point = vo.getPoint();
releaseValue = vo.getFaultValue(); BigDecimal releaseValue = vo.getReleaseValue();
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || releaseValue == null if (StringUtils.isEmpty(deviceId)
|| StringUtils.isEmpty(vo.getReleaseOperator())){ || StringUtils.isEmpty(point)
|| releaseValue == null
|| StringUtils.isEmpty(vo.getReleaseOperator())) {
return false; return false;
} }
// 获取点位最新值
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId); BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
logger.info("checkIsNeedCancelPlan 点位:{},最新值:{},比较方式:{},释放值:{}", point, lastPointValue, vo.getReleaseOperator(), releaseValue); if (lastPointValue == null) {
if(lastPointValue == null){
return false; return false;
} }
// 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系)
conditionSb.append(lastPointValue).append(vo.getReleaseOperator()).append(releaseValue); conditionSb.append(lastPointValue).append(vo.getReleaseOperator()).append(releaseValue);
if (i < protSettings.size() - 1) { if (i < protSettings.size() - 1) {
String relation = vo.getRelationNext(); conditionSb.append(" ").append(vo.getRelationNext()).append(" ");
conditionSb.append(" ").append(relation).append(" ");
} }
} }
// 执行比较语句
return executeWithParser(conditionSb.toString()); return executeWithParser(conditionSb.toString());
} }
// 校验故障值是否需要下发方案
private boolean checkIsNeedIssuedPlan(List<ProtectionSettingVo> protSettings, String siteId) { private boolean checkIsNeedIssuedPlan(List<ProtectionSettingVo> protSettings, String siteId) {
BigDecimal faultValue = BigDecimal.ZERO;
StringBuilder conditionSb = new StringBuilder(); StringBuilder conditionSb = new StringBuilder();
for (int i = 0; i < protSettings.size(); i++) { for (int i = 0; i < protSettings.size(); i++) {
ProtectionSettingVo vo = protSettings.get(i); ProtectionSettingVo vo = protSettings.get(i);
String deviceId = vo.getDeviceId(); String deviceId = vo.getDeviceId();
String point = vo.getPoint(); String point = vo.getPoint();
faultValue = vo.getFaultValue(); BigDecimal faultValue = vo.getFaultValue();
if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || faultValue == null if (StringUtils.isEmpty(deviceId)
|| StringUtils.isEmpty(vo.getFaultOperator())){ || StringUtils.isEmpty(point)
|| faultValue == null
|| StringUtils.isEmpty(vo.getFaultOperator())) {
return false; return false;
} }
// 获取点位最新值
BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId); BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId);
logger.info("checkIsNeedIssuedPlan 点位:{},最新值:{},比较方式:{},故障值:{}", point, lastPointValue, vo.getFaultOperator(), faultValue); if (lastPointValue == null) {
if(lastPointValue == null){
return false; return false;
} }
// 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系)
conditionSb.append(lastPointValue).append(vo.getFaultOperator()).append(faultValue); conditionSb.append(lastPointValue).append(vo.getFaultOperator()).append(faultValue);
if (i < protSettings.size() - 1) { if (i < protSettings.size() - 1) {
String relation = vo.getRelationNext(); conditionSb.append(" ").append(vo.getRelationNext()).append(" ");
conditionSb.append(" ").append(relation).append(" ");
} }
} }
// 执行比较语句
return executeWithParser(conditionSb.toString()); return executeWithParser(conditionSb.toString());
} }
private BigDecimal getPointLastValue(String deviceId, String point, String siteId) { private BigDecimal getPointLastValue(String deviceId, String point, String siteId) {
JSONObject mqttJson = redisCache.getCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId); JSONObject mqttJson = redisCache.getCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId);
if(mqttJson == null){ if (mqttJson == null) {
return null; return null;
} }
String jsonData = mqttJson.get("Data").toString(); String jsonData = mqttJson.get("Data").toString();
if(StringUtils.isEmpty(jsonData)){ if (StringUtils.isEmpty(jsonData)) {
return null; return null;
} }
Map<String, Object> obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference<Map<String, Object>>() {}); Map<String, Object> obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference<Map<String, Object>>() {});
return StringUtils.getBigDecimal(obj.get(point)); return StringUtils.getBigDecimal(obj.get(point));
} }
// 更新站点策略为启用 private EmsAlarmRecords addAlarmRecord(String siteId, String content, String level) {
private void updateStrategyRunningStatus(String siteId, String status) {
// 获取是否有正在运行的策略,如果有则不更改
EmsStrategyRunning query = new EmsStrategyRunning();
query.setSiteId(siteId);
query.setStatus(StrategyStatus.RUNNING.getCode().equals(status) ? StrategyStatus.SUSPENDED.getCode() : StrategyStatus.RUNNING.getCode());
List<EmsStrategyRunning> strategyRunningList = emsStrategyRunningMapper.selectEmsStrategyRunningList(query);
if (CollectionUtils.isNotEmpty(strategyRunningList)) {
// 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用
strategyRunningList.forEach(emsStrategyRunning -> {
emsStrategyRunning.setStatus(status);
emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning);
});
}
}
// 更新站点策略为启用
private void updateStrategyRunning(String siteId) {
// 获取是否有正在运行的策略,如果有则不更改
EmsStrategyRunning emsStrategyRunning = emsStrategyRunningMapper.getRunningStrategy(siteId);
if (emsStrategyRunning == null) {
// 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用
emsStrategyRunning = emsStrategyRunningMapper.getPendingStrategy(siteId);
emsStrategyRunning.setStatus(StrategyStatus.RUNNING.getCode());
emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning);
}
}
private EmsAlarmRecords addAlarmRecord(String siteId, String content,String level) {
EmsAlarmRecords emsAlarmRecords = new EmsAlarmRecords(); EmsAlarmRecords emsAlarmRecords = new EmsAlarmRecords();
emsAlarmRecords.setSiteId(siteId); emsAlarmRecords.setSiteId(siteId);
emsAlarmRecords.setAlarmContent(content); emsAlarmRecords.setAlarmContent(content);
@ -395,29 +394,31 @@ public class ProtectionPlanTask {
return emsAlarmRecords; return emsAlarmRecords;
} }
// 故障等级-告警等级匹配
private String getAlarmLevel(Integer faultLevel) { private String getAlarmLevel(Integer faultLevel) {
if (ObjectUtils.isEmpty(faultLevel) || faultLevel < 1 || faultLevel > 3) { if (ObjectUtils.isEmpty(faultLevel) || faultLevel < 1 || faultLevel > 3) {
logger.warn("非法故障等级:{},默认返回普通告警", faultLevel); logger.warn("非法故障等级:{},默认返回紧急告警", faultLevel);
return AlarmLevelStatus.EMERGENCY.getCode(); return AlarmLevelStatus.EMERGENCY.getCode();
} }
switch (faultLevel) { switch (faultLevel) {
case 1: return AlarmLevelStatus.GENERAL.getCode(); case 1:
case 2: return AlarmLevelStatus.SERIOUS.getCode(); return AlarmLevelStatus.GENERAL.getCode();
case 3: return AlarmLevelStatus.EMERGENCY.getCode(); case 2:
return AlarmLevelStatus.SERIOUS.getCode();
case 3:
return AlarmLevelStatus.EMERGENCY.getCode();
default: default:
logger.error("未匹配的故障等级:{}", faultLevel);
return AlarmLevelStatus.EMERGENCY.getCode(); return AlarmLevelStatus.EMERGENCY.getCode();
} }
} }
// 自定义表达式解析器(仅支持简单运算符和逻辑关系) /**
* 自定义表达式解析器(仅支持简单运算符和逻辑关系)
*/
public boolean executeWithParser(String conditionStr) { public boolean executeWithParser(String conditionStr) {
if (conditionStr == null || conditionStr.isEmpty()) { if (conditionStr == null || conditionStr.isEmpty()) {
return false; return false;
} }
// 1. 拆分逻辑关系(提取 && 或 ||
List<String> logicRelations = new ArrayList<>(); List<String> logicRelations = new ArrayList<>();
Pattern logicPattern = Pattern.compile("(&&|\\|\\|)"); Pattern logicPattern = Pattern.compile("(&&|\\|\\|)");
Matcher logicMatcher = logicPattern.matcher(conditionStr); Matcher logicMatcher = logicPattern.matcher(conditionStr);
@ -425,10 +426,7 @@ public class ProtectionPlanTask {
logicRelations.add(logicMatcher.group()); logicRelations.add(logicMatcher.group());
} }
// 2. 拆分原子条件(如 "3.55>3.52"
String[] atomicConditions = logicPattern.split(conditionStr); String[] atomicConditions = logicPattern.split(conditionStr);
// 3. 解析每个原子条件并计算结果
List<Boolean> atomicResults = new ArrayList<>(); List<Boolean> atomicResults = new ArrayList<>();
Pattern conditionPattern = Pattern.compile("(\\d+\\.?\\d*)\\s*([><]=?|==)\\s*(\\d+\\.?\\d*)"); Pattern conditionPattern = Pattern.compile("(\\d+\\.?\\d*)\\s*([><]=?|==)\\s*(\\d+\\.?\\d*)");
for (String atomic : atomicConditions) { for (String atomic : atomicConditions) {
@ -437,11 +435,10 @@ public class ProtectionPlanTask {
logger.error("无效的原子条件:{}", atomic); logger.error("无效的原子条件:{}", atomic);
return false; return false;
} }
double left = Double.parseDouble(matcher.group(1)); // 左值(最新值) double left = Double.parseDouble(matcher.group(1));
String operator = matcher.group(2); // 运算符 String operator = matcher.group(2);
double right = Double.parseDouble(matcher.group(3)); // 右值(故障值) double right = Double.parseDouble(matcher.group(3));
// 执行比较
boolean result; boolean result;
switch (operator) { switch (operator) {
case ">": case ">":
@ -466,11 +463,10 @@ public class ProtectionPlanTask {
atomicResults.add(result); atomicResults.add(result);
} }
// 4. 组合原子结果(根据逻辑关系)
boolean finalResult = atomicResults.get(0); boolean finalResult = atomicResults.get(0);
for (int i = 0; i < logicRelations.size(); i++) { for (int i = 0; i < logicRelations.size(); i++) {
String relation = logicRelations.get(i); String relation = logicRelations.get(i);
boolean nextResult = atomicResults.get(i+1); boolean nextResult = atomicResults.get(i + 1);
if ("&&".equals(relation)) { if ("&&".equals(relation)) {
finalResult = finalResult && nextResult; finalResult = finalResult && nextResult;
} else if ("||".equals(relation)) { } else if ("||".equals(relation)) {

View File

@ -2,9 +2,11 @@ package com.xzzn.quartz.task;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.xzzn.common.constant.RedisKeyConstants;
import com.xzzn.common.core.modbus.ModbusProcessor; import com.xzzn.common.core.modbus.ModbusProcessor;
import com.xzzn.common.core.modbus.domain.DeviceConfig; import com.xzzn.common.core.modbus.domain.DeviceConfig;
import com.xzzn.common.core.modbus.domain.WriteTagConfig; import com.xzzn.common.core.modbus.domain.WriteTagConfig;
import com.xzzn.common.core.redis.RedisCache;
import com.xzzn.common.enums.ChargeStatus; import com.xzzn.common.enums.ChargeStatus;
import com.xzzn.common.enums.DeviceCategory; import com.xzzn.common.enums.DeviceCategory;
import com.xzzn.common.enums.SiteDevice; import com.xzzn.common.enums.SiteDevice;
@ -20,6 +22,7 @@ import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
import com.xzzn.ems.domain.EmsStrategyLog; import com.xzzn.ems.domain.EmsStrategyLog;
import com.xzzn.ems.domain.EmsStrategyTemp; import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.EmsStrategyTimeConfig; import com.xzzn.ems.domain.EmsStrategyTimeConfig;
import com.xzzn.ems.domain.vo.ProtectionConstraintVo;
import com.xzzn.ems.domain.vo.StrategyRunningVo; import com.xzzn.ems.domain.vo.StrategyRunningVo;
import com.xzzn.ems.mapper.EmsAmmeterDataMapper; import com.xzzn.ems.mapper.EmsAmmeterDataMapper;
import com.xzzn.ems.mapper.EmsBatteryStackMapper; import com.xzzn.ems.mapper.EmsBatteryStackMapper;
@ -73,6 +76,20 @@ public class StrategyPoller {
private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10); private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10);
// 电网有功功率低于20kW时强制待机 // 电网有功功率低于20kW时强制待机
private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal(20); private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal(20);
// 设定功率倍率默认10
private static final BigDecimal DEFAULT_POWER_SET_MULTIPLIER = new BigDecimal(10);
// 保护介入默认开启
private static final Integer DEFAULT_PROTECT_INTERVENE_ENABLE = 1;
// 一级保护默认降额50%
private static final BigDecimal DEFAULT_PROTECT_L1_DERATE_PERCENT = new BigDecimal("50");
// 保护约束失效保护时长(秒)
private static final Integer DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS = 5;
// 三级保护默认锁存开启
private static final Integer DEFAULT_PROTECT_L3_LATCH_ENABLE = 1;
// 保护冲突策略默认值
private static final String DEFAULT_PROTECT_CONFLICT_POLICY = "MAX_LEVEL_WIN";
// 保护约束默认功率比例
private static final BigDecimal DEFAULT_PROTECTION_RATIO = BigDecimal.ONE;
// 除法精度避免BigDecimal除不尽异常 // 除法精度避免BigDecimal除不尽异常
private static final int POWER_SCALE = 4; private static final int POWER_SCALE = 4;
@ -95,6 +112,8 @@ public class StrategyPoller {
@Autowired @Autowired
private EmsStrategyRuntimeConfigMapper runtimeConfigMapper; private EmsStrategyRuntimeConfigMapper runtimeConfigMapper;
@Autowired @Autowired
private RedisCache redisCache;
@Autowired
private ModbusProcessor modbusProcessor; private ModbusProcessor modbusProcessor;
@Resource(name = "modbusExecutor") @Resource(name = "modbusExecutor")
@ -186,6 +205,7 @@ public class StrategyPoller {
} }
// 判断SOC上下限 // 判断SOC上下限
if (isSocInRange(emsStrategyTemp, runtimeConfig)) { if (isSocInRange(emsStrategyTemp, runtimeConfig)) {
ProtectionConstraintVo protectionConstraint = getProtectionConstraint(siteId);
Map<Long, EmsPcsSetting> pcsSettingCache = new HashMap<>(); Map<Long, EmsPcsSetting> pcsSettingCache = new HashMap<>();
BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower() BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower()
.divide(new BigDecimal(pcsDeviceList.size()), POWER_SCALE, RoundingMode.HALF_UP); .divide(new BigDecimal(pcsDeviceList.size()), POWER_SCALE, RoundingMode.HALF_UP);
@ -206,13 +226,27 @@ public class StrategyPoller {
logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId()); logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId());
continue; continue;
} }
// 功率默认放大10倍平均功率值根据电池簇数量进行平均分配 // 平均功率值根据倍率放大后,再按电池簇数量平均分配
BigDecimal strategyPower = avgChargeDischargePower.multiply(new BigDecimal(10)) BigDecimal strategyPower = avgChargeDischargePower.multiply(runtimeConfig.getPowerSetMultiplier())
.divide(new BigDecimal(pcsSetting.getClusterNum()), POWER_SCALE, RoundingMode.HALF_UP); .divide(new BigDecimal(pcsSetting.getClusterNum()), POWER_SCALE, RoundingMode.HALF_UP);
// 根据充电状态,处理数据 // 根据充电状态,处理数据
if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
StrategyCommandDecision decision = applyProtectionConstraint(
strategyPower,
ChargeStatus.CHARGING,
runtimeConfig,
protectionConstraint
);
// 发送Modbus命令控制设备-充电 // 发送Modbus命令控制设备-充电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, strategyPower, emsStrategyTemp, false, null); sendModbusCommand(
Collections.singletonList(pcsDevice),
pcsSetting,
decision.getChargeStatus(),
decision.getPower(),
emsStrategyTemp,
false,
null
);
} else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { } else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
boolean needAntiReverseFlow = false; boolean needAntiReverseFlow = false;
Integer powerDownType = null; Integer powerDownType = null;
@ -277,13 +311,21 @@ public class StrategyPoller {
if (chargeDischargePower.compareTo(BigDecimal.ZERO) < 0) { if (chargeDischargePower.compareTo(BigDecimal.ZERO) < 0) {
chargeDischargePower = BigDecimal.ZERO; chargeDischargePower = BigDecimal.ZERO;
} }
if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) { StrategyCommandDecision decision = applyProtectionConstraint(
chargeDischargePower,
ChargeStatus.DISCHARGING,
runtimeConfig,
protectionConstraint
);
ChargeStatus finalStatus = decision.getChargeStatus();
BigDecimal finalPower = decision.getPower();
if (ChargeStatus.STANDBY.equals(finalStatus) || BigDecimal.ZERO.compareTo(finalPower) == 0) {
// 如果已经降功率到0则设备直接待机 // 如果已经降功率到0则设备直接待机
// 发送Modbus命令控制设备-待机 // 发送Modbus命令控制设备-待机
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType); sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType);
} else { } else {
// 发送Modbus命令控制设备-放电 // 发送Modbus命令控制设备-放电
sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.DISCHARGING, chargeDischargePower, emsStrategyTemp, needAntiReverseFlow, powerDownType); sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, finalStatus, finalPower, emsStrategyTemp, needAntiReverseFlow, powerDownType);
} }
} else { } else {
// 发送Modbus命令控制设备-待机 // 发送Modbus命令控制设备-待机
@ -503,6 +545,103 @@ public class StrategyPoller {
return result; return result;
} }
private ProtectionConstraintVo getProtectionConstraint(String siteId) {
ProtectionConstraintVo constraint = redisCache.getCacheObject(RedisKeyConstants.PROTECTION_CONSTRAINT + siteId);
if (constraint == null) {
return ProtectionConstraintVo.empty();
}
if (constraint.getPowerLimitRatio() == null) {
constraint.setPowerLimitRatio(DEFAULT_PROTECTION_RATIO);
}
if (constraint.getAllowCharge() == null) {
constraint.setAllowCharge(true);
}
if (constraint.getAllowDischarge() == null) {
constraint.setAllowDischarge(true);
}
if (constraint.getForceStandby() == null) {
constraint.setForceStandby(false);
}
if (constraint.getForceStop() == null) {
constraint.setForceStop(false);
}
if (constraint.getLevel() == null) {
constraint.setLevel(0);
}
return constraint;
}
private StrategyCommandDecision applyProtectionConstraint(BigDecimal targetPower,
ChargeStatus targetStatus,
EmsStrategyRuntimeConfig runtimeConfig,
ProtectionConstraintVo constraint) {
if (!Integer.valueOf(1).equals(runtimeConfig.getProtectInterveneEnable())) {
return new StrategyCommandDecision(targetStatus, safePower(targetPower));
}
if (constraint == null || nullSafeInt(constraint.getLevel()) <= 0) {
return new StrategyCommandDecision(targetStatus, safePower(targetPower));
}
if (Boolean.TRUE.equals(constraint.getForceStop()) || Boolean.TRUE.equals(constraint.getForceStandby())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
if (ChargeStatus.CHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowCharge())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
if (ChargeStatus.DISCHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowDischarge())) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
BigDecimal ratio = getPowerLimitRatio(constraint, runtimeConfig);
BigDecimal finalPower = safePower(targetPower).multiply(ratio).setScale(POWER_SCALE, RoundingMode.HALF_UP);
if (finalPower.compareTo(BigDecimal.ZERO) <= 0) {
return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO);
}
return new StrategyCommandDecision(targetStatus, finalPower);
}
private BigDecimal getPowerLimitRatio(ProtectionConstraintVo constraint, EmsStrategyRuntimeConfig runtimeConfig) {
BigDecimal ratio = constraint.getPowerLimitRatio();
if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) {
ratio = DEFAULT_PROTECTION_RATIO;
}
if (nullSafeInt(constraint.getLevel()) == 1 && DEFAULT_PROTECTION_RATIO.compareTo(ratio) == 0) {
BigDecimal deratePercent = runtimeConfig.getProtectL1DeratePercent();
if (deratePercent == null || deratePercent.compareTo(BigDecimal.ZERO) < 0 || deratePercent.compareTo(new BigDecimal("100")) > 0) {
deratePercent = DEFAULT_PROTECT_L1_DERATE_PERCENT;
}
ratio = deratePercent.divide(new BigDecimal("100"), POWER_SCALE, RoundingMode.HALF_UP);
}
return ratio;
}
private BigDecimal safePower(BigDecimal power) {
if (power == null || power.compareTo(BigDecimal.ZERO) < 0) {
return BigDecimal.ZERO;
}
return power;
}
private int nullSafeInt(Integer value) {
return value == null ? 0 : value;
}
private static class StrategyCommandDecision {
private final ChargeStatus chargeStatus;
private final BigDecimal power;
private StrategyCommandDecision(ChargeStatus chargeStatus, BigDecimal power) {
this.chargeStatus = chargeStatus;
this.power = power;
}
public ChargeStatus getChargeStatus() {
return chargeStatus;
}
public BigDecimal getPower() {
return power;
}
}
// 判断当前时间是否在时间范围内 // 判断当前时间是否在时间范围内
private static boolean isTimeInRange(LocalTime now, Date startTime, Date endTime) { private static boolean isTimeInRange(LocalTime now, Date startTime, Date endTime) {
ZoneId zoneId = ZoneId.of("Asia/Shanghai"); ZoneId zoneId = ZoneId.of("Asia/Shanghai");
@ -573,6 +712,26 @@ public class StrategyPoller {
if (config.getAntiReverseHardStopThreshold() == null) { if (config.getAntiReverseHardStopThreshold() == null) {
config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD); config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD);
} }
if (config.getPowerSetMultiplier() == null || config.getPowerSetMultiplier().compareTo(BigDecimal.ZERO) <= 0) {
config.setPowerSetMultiplier(DEFAULT_POWER_SET_MULTIPLIER);
}
if (config.getProtectInterveneEnable() == null) {
config.setProtectInterveneEnable(DEFAULT_PROTECT_INTERVENE_ENABLE);
}
if (config.getProtectL1DeratePercent() == null
|| config.getProtectL1DeratePercent().compareTo(BigDecimal.ZERO) < 0
|| config.getProtectL1DeratePercent().compareTo(new BigDecimal("100")) > 0) {
config.setProtectL1DeratePercent(DEFAULT_PROTECT_L1_DERATE_PERCENT);
}
if (config.getProtectRecoveryStableSeconds() == null || config.getProtectRecoveryStableSeconds() < 0) {
config.setProtectRecoveryStableSeconds(DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS);
}
if (config.getProtectL3LatchEnable() == null) {
config.setProtectL3LatchEnable(DEFAULT_PROTECT_L3_LATCH_ENABLE);
}
if (StringUtils.isEmpty(config.getProtectConflictPolicy())) {
config.setProtectConflictPolicy(DEFAULT_PROTECT_CONFLICT_POLICY);
}
return config; return config;
} }

View File

@ -11,6 +11,7 @@ public class EmsSiteMonitorPointMatch extends BaseEntity {
private Long id; private Long id;
private String siteId; private String siteId;
private String fieldCode; private String fieldCode;
private String deviceId;
private String dataPoint; private String dataPoint;
private String fixedDataPoint; private String fixedDataPoint;
private Integer useFixedDisplay; private Integer useFixedDisplay;
@ -47,6 +48,14 @@ public class EmsSiteMonitorPointMatch extends BaseEntity {
this.dataPoint = dataPoint; this.dataPoint = dataPoint;
} }
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getFixedDataPoint() { public String getFixedDataPoint() {
return fixedDataPoint; return fixedDataPoint;
} }

View File

@ -49,6 +49,29 @@ public class EmsStrategyRuntimeConfig extends BaseEntity {
/** 防逆流硬停阈值(kW) */ /** 防逆流硬停阈值(kW) */
@Excel(name = "防逆流硬停阈值(kW)") @Excel(name = "防逆流硬停阈值(kW)")
private BigDecimal antiReverseHardStopThreshold; private BigDecimal antiReverseHardStopThreshold;
/** 设定功率倍率 */
@Excel(name = "设定功率倍率")
private BigDecimal powerSetMultiplier;
/** 保护介入开关1-启用0-禁用) */
@Excel(name = "保护介入开关")
private Integer protectInterveneEnable;
/** 一级保护降额比例(%) */
@Excel(name = "一级保护降额比例(%)")
private BigDecimal protectL1DeratePercent;
/** 保护释放稳定时长(秒) */
@Excel(name = "保护释放稳定时长(秒)")
private Integer protectRecoveryStableSeconds;
/** 三级保护锁存开关1-启用0-禁用) */
@Excel(name = "三级保护锁存开关")
private Integer protectL3LatchEnable;
/** 保护冲突策略 */
@Excel(name = "保护冲突策略")
private String protectConflictPolicy;
public Long getId() { public Long getId() {
return id; return id;
@ -122,6 +145,54 @@ public class EmsStrategyRuntimeConfig extends BaseEntity {
this.antiReverseHardStopThreshold = antiReverseHardStopThreshold; this.antiReverseHardStopThreshold = antiReverseHardStopThreshold;
} }
public BigDecimal getPowerSetMultiplier() {
return powerSetMultiplier;
}
public void setPowerSetMultiplier(BigDecimal powerSetMultiplier) {
this.powerSetMultiplier = powerSetMultiplier;
}
public Integer getProtectInterveneEnable() {
return protectInterveneEnable;
}
public void setProtectInterveneEnable(Integer protectInterveneEnable) {
this.protectInterveneEnable = protectInterveneEnable;
}
public BigDecimal getProtectL1DeratePercent() {
return protectL1DeratePercent;
}
public void setProtectL1DeratePercent(BigDecimal protectL1DeratePercent) {
this.protectL1DeratePercent = protectL1DeratePercent;
}
public Integer getProtectRecoveryStableSeconds() {
return protectRecoveryStableSeconds;
}
public void setProtectRecoveryStableSeconds(Integer protectRecoveryStableSeconds) {
this.protectRecoveryStableSeconds = protectRecoveryStableSeconds;
}
public Integer getProtectL3LatchEnable() {
return protectL3LatchEnable;
}
public void setProtectL3LatchEnable(Integer protectL3LatchEnable) {
this.protectL3LatchEnable = protectL3LatchEnable;
}
public String getProtectConflictPolicy() {
return protectConflictPolicy;
}
public void setProtectConflictPolicy(String protectConflictPolicy) {
this.protectConflictPolicy = protectConflictPolicy;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
@ -134,6 +205,12 @@ public class EmsStrategyRuntimeConfig extends BaseEntity {
.append("antiReverseUp", getAntiReverseUp()) .append("antiReverseUp", getAntiReverseUp())
.append("antiReversePowerDownPercent", getAntiReversePowerDownPercent()) .append("antiReversePowerDownPercent", getAntiReversePowerDownPercent())
.append("antiReverseHardStopThreshold", getAntiReverseHardStopThreshold()) .append("antiReverseHardStopThreshold", getAntiReverseHardStopThreshold())
.append("powerSetMultiplier", getPowerSetMultiplier())
.append("protectInterveneEnable", getProtectInterveneEnable())
.append("protectL1DeratePercent", getProtectL1DeratePercent())
.append("protectRecoveryStableSeconds", getProtectRecoveryStableSeconds())
.append("protectL3LatchEnable", getProtectL3LatchEnable())
.append("protectConflictPolicy", getProtectConflictPolicy())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
.append("updateBy", getUpdateBy()) .append("updateBy", getUpdateBy())

View File

@ -1,10 +1,19 @@
package com.xzzn.ems.domain.vo; package com.xzzn.ems.domain.vo;
public class GeneralQueryPointOptionVo { public class GeneralQueryPointOptionVo {
private String pointId;
private String pointName; private String pointName;
private String dataKey; private String dataKey;
private String pointDesc; private String pointDesc;
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getPointName() { public String getPointName() {
return pointName; return pointName;
} }

View File

@ -15,6 +15,8 @@ public class PointNameRequest {
private String pointName; private String pointName;
private List<String> pointNames; private List<String> pointNames;
private String pointId;
private List<String> pointIds;
/** 数据分组 1-分钟 2-小时 3-天 */ /** 数据分组 1-分钟 2-小时 3-天 */
private int dataUnit; private int dataUnit;
@ -59,6 +61,22 @@ public class PointNameRequest {
this.pointNames = pointNames; this.pointNames = pointNames;
} }
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public List<String> getPointIds() {
return pointIds;
}
public void setPointIds(List<String> pointIds) {
this.pointIds = pointIds;
}
public int getDataUnit() { public int getDataUnit() {
return dataUnit; return dataUnit;
} }

View File

@ -1,6 +1,7 @@
package com.xzzn.ems.domain.vo; package com.xzzn.ems.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date; import java.util.Date;
@ -10,10 +11,12 @@ import java.util.Date;
public class RunningGraphRequest { public class RunningGraphRequest {
/** 开始时间 */ /** 开始时间 */
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate; private Date startDate;
/** 结束时间 */ /** 结束时间 */
@DateTimeFormat(pattern = "yyyy-MM-dd")
@JsonFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate; private Date endDate;

View File

@ -16,6 +16,10 @@ public class SiteMonitorProjectPointMappingVo {
private String fieldName; private String fieldName;
private String deviceId;
private String deviceName;
private String dataPoint; private String dataPoint;
private String fixedDataPoint; private String fixedDataPoint;
@ -86,6 +90,22 @@ public class SiteMonitorProjectPointMappingVo {
this.dataPoint = dataPoint; this.dataPoint = dataPoint;
} }
public String getDeviceId() {
return deviceId;
}
public void setDeviceId(String deviceId) {
this.deviceId = deviceId;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getFixedDataPoint() { public String getFixedDataPoint() {
return fixedDataPoint; return fixedDataPoint;
} }

View File

@ -0,0 +1,26 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
public class WorkStatusEnumMappingSaveRequest {
private String siteId;
private List<WorkStatusEnumMappingVo> mappings;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public List<WorkStatusEnumMappingVo> getMappings() {
return mappings;
}
public void setMappings(List<WorkStatusEnumMappingVo> mappings) {
this.mappings = mappings;
}
}

View File

@ -0,0 +1,74 @@
package com.xzzn.ems.domain.vo;
public class WorkStatusEnumMappingVo {
private String deviceCategory;
private String matchField;
private String matchFieldName;
private String enumCode;
private String enumName;
private String enumDesc;
private String dataEnumCode;
public String getEnumCode() {
return enumCode;
}
public void setEnumCode(String enumCode) {
this.enumCode = enumCode;
}
public String getEnumName() {
return enumName;
}
public void setEnumName(String enumName) {
this.enumName = enumName;
}
public String getEnumDesc() {
return enumDesc;
}
public void setEnumDesc(String enumDesc) {
this.enumDesc = enumDesc;
}
public String getDataEnumCode() {
return dataEnumCode;
}
public void setDataEnumCode(String dataEnumCode) {
this.dataEnumCode = dataEnumCode;
}
public String getDeviceCategory() {
return deviceCategory;
}
public void setDeviceCategory(String deviceCategory) {
this.deviceCategory = deviceCategory;
}
public String getMatchField() {
return matchField;
}
public void setMatchField(String matchField) {
this.matchField = matchField;
}
public String getMatchFieldName() {
return matchFieldName;
}
public void setMatchFieldName(String matchFieldName) {
this.matchFieldName = matchFieldName;
}
}

View File

@ -41,6 +41,7 @@ public interface EmsPointConfigMapper {
List<EmsPointConfig> getConfigListForGeneralQuery(@Param("siteIds") List<String> siteIds, List<EmsPointConfig> getConfigListForGeneralQuery(@Param("siteIds") List<String> siteIds,
@Param("deviceCategory") String deviceCategory, @Param("deviceCategory") String deviceCategory,
@Param("pointIds") List<String> pointIds,
@Param("pointNames") List<String> pointNames, @Param("pointNames") List<String> pointNames,
@Param("deviceIds") List<String> deviceIds); @Param("deviceIds") List<String> deviceIds);

View File

@ -75,4 +75,8 @@ public interface EmsPointEnumMatchMapper
int copyTemplateToSite(@Param("templateSiteId") String templateSiteId, int copyTemplateToSite(@Param("templateSiteId") String templateSiteId,
@Param("targetSiteId") String targetSiteId, @Param("targetSiteId") String targetSiteId,
@Param("operName") String operName); @Param("operName") String operName);
int deleteByScope(@Param("siteId") String siteId,
@Param("deviceCategory") String deviceCategory,
@Param("matchField") String matchField);
} }

View File

@ -14,15 +14,10 @@ public interface EmsSiteMonitorDataMapper {
@Param("siteId") String siteId, @Param("siteId") String siteId,
@Param("statisMinute") java.util.Date statisMinute, @Param("statisMinute") java.util.Date statisMinute,
@Param("dataJson") String dataJson, @Param("dataJson") String dataJson,
@Param("hotSoc") String hotSoc,
@Param("hotTotalActivePower") String hotTotalActivePower,
@Param("hotTotalReactivePower") String hotTotalReactivePower,
@Param("hotDayChargedCap") String hotDayChargedCap,
@Param("hotDayDisChargedCap") String hotDayDisChargedCap,
@Param("operName") String operName); @Param("operName") String operName);
int updateHistoryHotColumns(@Param("tableName") String tableName,
@Param("siteId") String siteId,
@Param("statisMinute") java.util.Date statisMinute,
@Param("hotSoc") String hotSoc,
@Param("hotTotalActivePower") String hotTotalActivePower,
@Param("hotTotalReactivePower") String hotTotalReactivePower,
@Param("hotDayChargedCap") String hotDayChargedCap,
@Param("hotDayDisChargedCap") String hotDayDisChargedCap,
@Param("operName") String operName);
} }

View File

@ -10,6 +10,7 @@ import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest;
import com.xzzn.ems.domain.vo.SiteMonitorProjectDisplayVo; import com.xzzn.ems.domain.vo.SiteMonitorProjectDisplayVo;
import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest; import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest;
import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingVo; import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingVo;
import com.xzzn.ems.domain.vo.WorkStatusEnumMappingVo;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -46,6 +47,10 @@ public interface IEmsDeviceSettingService
public int saveSiteMonitorProjectPointMapping(SiteMonitorProjectPointMappingSaveRequest request, String operName); public int saveSiteMonitorProjectPointMapping(SiteMonitorProjectPointMappingSaveRequest request, String operName);
public List<WorkStatusEnumMappingVo> getSiteWorkStatusEnumMappings(String siteId);
public int saveSiteWorkStatusEnumMappings(String siteId, List<WorkStatusEnumMappingVo> mappings, String operName);
public List<SiteMonitorProjectDisplayVo> getSiteMonitorProjectDisplay(String siteId); public List<SiteMonitorProjectDisplayVo> getSiteMonitorProjectDisplay(String siteId);
public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName); public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName);

View File

@ -181,6 +181,102 @@ public class InfluxPointDataWriter {
} }
} }
public List<PointValue> queryCurveDataByPointKey(String siteId, String pointKey, Date startTime, Date endTime) {
if (!enabled) {
return Collections.emptyList();
}
if (isBlank(siteId) || isBlank(pointKey) || startTime == null || endTime == null) {
return Collections.emptyList();
}
String normalizedSiteId = siteId.trim();
String normalizedPointKey = pointKey.trim();
String influxQl = String.format(
"SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" = '%s' " +
"AND time >= %dms AND time <= %dms ORDER BY time ASC",
measurement,
escapeTagValue(normalizedSiteId),
escapeTagValue(normalizedPointKey),
startTime.getTime(),
endTime.getTime()
);
try {
String queryUrl = buildQueryUrl(influxQl);
List<PointValue> values = parseInfluxQlResponse(executeRequestWithResponse(methodOrDefault(readMethod, "GET"), queryUrl));
if (!values.isEmpty()) {
return values;
}
// 兼容 pointId 大小写差异
String regexQuery = String.format(
"SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" =~ /(?i)^%s$/ " +
"AND time >= %dms AND time <= %dms ORDER BY time ASC",
measurement,
escapeTagValue(normalizedSiteId),
escapeRegex(normalizedPointKey),
startTime.getTime(),
endTime.getTime()
);
return parseInfluxQlResponse(
executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(regexQuery))
);
} catch (Exception e) {
log.warn("按 pointKey 查询 InfluxDB 曲线失败: {}", e.getMessage());
return Collections.emptyList();
}
}
public PointValue queryLatestPointValueByPointKey(String siteId, String pointKey, Date startTime, Date endTime) {
if (!enabled) {
return null;
}
if (isBlank(siteId) || isBlank(pointKey) || startTime == null || endTime == null) {
return null;
}
String normalizedSiteId = siteId.trim();
String normalizedPointKey = pointKey.trim();
String influxQl = String.format(
"SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" = '%s' " +
"AND time >= %dms AND time <= %dms ORDER BY time DESC LIMIT 1",
measurement,
escapeTagValue(normalizedSiteId),
escapeTagValue(normalizedPointKey),
startTime.getTime(),
endTime.getTime()
);
try {
List<PointValue> values = parseInfluxQlResponse(
executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(influxQl))
);
if (!values.isEmpty()) {
return values.get(0);
}
String regexQuery = String.format(
"SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" =~ /(?i)^%s$/ " +
"AND time >= %dms AND time <= %dms ORDER BY time DESC LIMIT 1",
measurement,
escapeTagValue(normalizedSiteId),
escapeRegex(normalizedPointKey),
startTime.getTime(),
endTime.getTime()
);
values = parseInfluxQlResponse(
executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(regexQuery))
);
return values.isEmpty() ? null : values.get(0);
} catch (Exception e) {
log.warn("按 pointKey 查询 InfluxDB 最新值失败: {}", e.getMessage());
return null;
}
}
private String buildWriteUrl() { private String buildWriteUrl() {
if (isV2WritePath()) { if (isV2WritePath()) {
return buildV2WriteUrl(); return buildV2WriteUrl();
@ -488,6 +584,7 @@ public class InfluxPointDataWriter {
return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'"); return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'");
} }
private String escapeRegex(String value) { private String escapeRegex(String value) {
if (value == null) { if (value == null) {
return ""; return "";
@ -546,6 +643,7 @@ public class InfluxPointDataWriter {
return values; return values;
} }
private boolean isBlank(String value) { private boolean isBlank(String value) {
return value == null || value.trim().isEmpty(); return value == null || value.trim().isEmpty();
} }

View File

@ -110,6 +110,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
private static final Pattern DTDC_PATTERN = Pattern.compile("DTDC(\\d+)([A-Za-z]*)"); private static final Pattern DTDC_PATTERN = Pattern.compile("DTDC(\\d+)([A-Za-z]*)");
private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
private static final Pattern VARIABLE_SAFE_PATTERN = Pattern.compile("[^A-Za-z0-9_]"); private static final Pattern VARIABLE_SAFE_PATTERN = Pattern.compile("[^A-Za-z0-9_]");
private static final Pattern SIMPLE_CALC_EXPRESSION_PATTERN = Pattern.compile("^[0-9A-Za-z_+\\-*/().\\s]+$");
private static final int POINT_QUEUE_CAPACITY = 100000; private static final int POINT_QUEUE_CAPACITY = 100000;
private static final int POINT_FLUSH_BATCH_SIZE = 2000; private static final int POINT_FLUSH_BATCH_SIZE = 2000;
private static final int POINT_FLUSH_MAX_DRAIN_PER_RUN = 20000; private static final int POINT_FLUSH_MAX_DRAIN_PER_RUN = 20000;
@ -176,7 +177,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
@Autowired @Autowired
private InfluxPointDataWriter influxPointDataWriter; private InfluxPointDataWriter influxPointDataWriter;
private final BlockingQueue<PointDataRecord> pointDataQueue = new LinkedBlockingQueue<>(POINT_QUEUE_CAPACITY); private final BlockingQueue<PointDataRecord> pointDataQueue = new LinkedBlockingQueue<>(POINT_QUEUE_CAPACITY);
private final Map<String, List<ExpressionToken>> calcExpressionCache = new ConcurrentHashMap<>(); private final Map<String, CompiledExpression> calcExpressionCache = new ConcurrentHashMap<>();
private final ScheduledExecutorService pointDataWriter = Executors.newSingleThreadScheduledExecutor(r -> { private final ScheduledExecutorService pointDataWriter = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r); Thread thread = new Thread(r);
thread.setName("point-data-writer"); thread.setName("point-data-writer");
@ -287,7 +288,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
if (!uniquePointKeys.add(pointContextKey)) { if (!uniquePointKeys.add(pointContextKey)) {
continue; continue;
} }
if (isCalcPoint(pointConfig)) { if (isComputedPoint(pointConfig)) {
continue; continue;
} }
dataPointConfigs.add(pointConfig); dataPointConfigs.add(pointConfig);
@ -383,7 +384,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
List<EmsPointConfig> pointConfigs = getPointConfigsWithCache(siteId, deviceId, deviceCategory); List<EmsPointConfig> pointConfigs = getPointConfigsWithCache(siteId, deviceId, deviceCategory);
if (!CollectionUtils.isEmpty(pointConfigs)) { if (!CollectionUtils.isEmpty(pointConfigs)) {
for (EmsPointConfig pointConfig : pointConfigs) { for (EmsPointConfig pointConfig : pointConfigs) {
if (isCalcPoint(pointConfig)) { if (isComputedPoint(pointConfig)) {
continue; continue;
} }
String dataKey = StringUtils.defaultString(pointConfig.getDataKey()).trim().toUpperCase(); String dataKey = StringUtils.defaultString(pointConfig.getDataKey()).trim().toUpperCase();
@ -438,8 +439,12 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
return StringUtils.isBlank(normalized) ? null : normalized; return StringUtils.isBlank(normalized) ? null : normalized;
} }
private boolean isCalcPoint(EmsPointConfig pointConfig) { private boolean isComputedPoint(EmsPointConfig pointConfig) {
return pointConfig != null && "calc".equalsIgnoreCase(pointConfig.getPointType()); if (pointConfig == null) {
return false;
}
String pointType = StringUtils.defaultString(pointConfig.getPointType()).trim().toLowerCase();
return "calc".equals(pointType);
} }
private void enqueuePointData(String siteId, String deviceId, String pointKey, BigDecimal pointValue, Date dataTime) { private void enqueuePointData(String siteId, String deviceId, String pointKey, BigDecimal pointValue, Date dataTime) {
@ -521,14 +526,17 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
String calcDataKey = entry.getKey(); String calcDataKey = entry.getKey();
EmsPointConfig calcPointConfig = entry.getValue(); EmsPointConfig calcPointConfig = entry.getValue();
try { try {
BigDecimal calcValue = evaluateCalcExpression(calcPointConfig.getCalcExpression(), contextValues); ExpressionValue calcValue = evaluateCalcExpression(calcPointConfig.getCalcExpression(), contextValues);
contextValues.put(calcDataKey, calcValue); if (calcValue.isNumber()) {
putPointValueToContext(calcPointConfig, calcValue, contextValues); contextValues.put(calcDataKey, calcValue.asNumber());
putPointValueToContext(calcPointConfig, calcValue.asNumber(), contextValues);
}
// 计算点按站点维度统一落库,不再按配置中的 device_id 分流 // 计算点按站点维度统一落库,不再按配置中的 device_id 分流
String pointId = resolveInfluxPointKey(calcPointConfig); String pointId = resolveInfluxPointKey(calcPointConfig);
if (StringUtils.isNotBlank(pointId)) { if (StringUtils.isNotBlank(pointId)) {
enqueuePointData(siteId, deviceId, pointId, calcValue, dataUpdateTime); BigDecimal storedValue = calcValue.asNumber();
calcPointIdValueMap.put(pointId, calcValue); enqueuePointData(siteId, deviceId, pointId, storedValue, dataUpdateTime);
calcPointIdValueMap.put(pointId, storedValue);
} }
finishedKeys.add(calcDataKey); finishedKeys.add(calcDataKey);
progressed = true; progressed = true;
@ -584,213 +592,19 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
return StringUtils.isNotBlank(pointId) ? pointId : null; return StringUtils.isNotBlank(pointId) ? pointId : null;
} }
private BigDecimal evaluateCalcExpression(String expression, Map<String, BigDecimal> contextValues) { private ExpressionValue evaluateCalcExpression(String expression, Map<String, BigDecimal> contextValues) {
if (StringUtils.isBlank(expression)) { if (StringUtils.isBlank(expression)) {
throw new IllegalArgumentException("计算表达式为空"); throw new IllegalArgumentException("计算表达式为空");
} }
List<ExpressionToken> rpnTokens = calcExpressionCache.computeIfAbsent(expression, this::compileExpressionToRpn); if (!SIMPLE_CALC_EXPRESSION_PATTERN.matcher(expression).matches()) {
return evaluateRpnTokens(rpnTokens, contextValues == null ? Collections.emptyMap() : contextValues); throw new IllegalArgumentException("计算表达式仅支持四则运算");
}
CompiledExpression compiledExpression = calcExpressionCache.computeIfAbsent(expression, this::compileExpression);
return compiledExpression.evaluate(contextValues == null ? Collections.emptyMap() : contextValues);
} }
private List<ExpressionToken> compileExpressionToRpn(String expression) { private CompiledExpression compileExpression(String expression) {
if (StringUtils.isBlank(expression)) { return new CompiledExpression(expression);
throw new IllegalArgumentException("计算表达式为空");
}
List<ExpressionToken> output = new ArrayList<>();
Deque<ExpressionToken> operators = new ArrayDeque<>();
int index = 0;
ExpressionToken previous = null;
while (index < expression.length()) {
char ch = expression.charAt(index);
if (Character.isWhitespace(ch)) {
index++;
continue;
}
if (Character.isDigit(ch) || ch == '.') {
int start = index;
boolean hasDot = ch == '.';
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isDigit(next)) {
index++;
continue;
}
if (next == '.' && !hasDot) {
hasDot = true;
index++;
continue;
}
break;
}
String numberText = expression.substring(start, index);
try {
output.add(ExpressionToken.number(new BigDecimal(numberText)));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("数值格式错误: " + numberText);
}
previous = output.get(output.size() - 1);
continue;
}
if (Character.isLetter(ch) || ch == '_') {
int start = index;
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isLetterOrDigit(next) || next == '_') {
index++;
continue;
}
break;
}
String variable = expression.substring(start, index).trim().toUpperCase();
output.add(ExpressionToken.variable(variable));
previous = output.get(output.size() - 1);
continue;
}
if (ch == '(') {
operators.push(ExpressionToken.leftParen());
previous = ExpressionToken.leftParen();
index++;
continue;
}
if (ch == ')') {
boolean matched = false;
while (!operators.isEmpty()) {
ExpressionToken token = operators.pop();
if (token.getType() == ExpressionTokenType.LEFT_PAREN) {
matched = true;
break;
}
output.add(token);
}
if (!matched) {
throw new IllegalArgumentException("括号不匹配");
}
previous = ExpressionToken.rightParen();
index++;
continue;
}
if (isOperator(ch)) {
boolean unaryMinus = ch == '-' && (previous == null
|| previous.getType() == ExpressionTokenType.OPERATOR
|| previous.getType() == ExpressionTokenType.LEFT_PAREN);
String operatorText = unaryMinus ? "~" : String.valueOf(ch);
ExpressionToken currentOperator = ExpressionToken.operator(operatorText);
while (!operators.isEmpty() && operators.peek().getType() == ExpressionTokenType.OPERATOR) {
ExpressionToken top = operators.peek();
if (shouldPopOperator(currentOperator, top)) {
output.add(operators.pop());
} else {
break;
}
}
operators.push(currentOperator);
previous = currentOperator;
index++;
continue;
}
throw new IllegalArgumentException("表达式包含非法字符: " + ch);
}
while (!operators.isEmpty()) {
ExpressionToken token = operators.pop();
if (token.getType() == ExpressionTokenType.LEFT_PAREN) {
throw new IllegalArgumentException("括号不匹配");
}
output.add(token);
}
return output;
}
private BigDecimal evaluateRpnTokens(List<ExpressionToken> rpnTokens, Map<String, BigDecimal> contextValues) {
Deque<BigDecimal> values = new ArrayDeque<>();
for (ExpressionToken token : rpnTokens) {
if (token.getType() == ExpressionTokenType.NUMBER) {
values.push(token.getNumber());
continue;
}
if (token.getType() == ExpressionTokenType.VARIABLE) {
BigDecimal variableValue = contextValues.get(token.getText());
if (variableValue == null) {
throw new MissingVariableException(token.getText());
}
values.push(variableValue);
continue;
}
if (token.getType() != ExpressionTokenType.OPERATOR) {
throw new IllegalArgumentException("表达式令牌类型不合法: " + token.getType());
}
if ("~".equals(token.getText())) {
if (values.isEmpty()) {
throw new IllegalArgumentException("表达式不合法,缺少操作数");
}
values.push(values.pop().negate());
continue;
}
if (values.size() < 2) {
throw new IllegalArgumentException("表达式不合法,缺少操作数");
}
BigDecimal right = values.pop();
BigDecimal left = values.pop();
values.push(applyOperator(left, right, token.getText()));
}
if (values.size() != 1) {
throw new IllegalArgumentException("表达式不合法,无法归约到单值");
}
return values.pop();
}
private BigDecimal applyOperator(BigDecimal left, BigDecimal right, String operator) {
switch (operator) {
case "+":
return left.add(right);
case "-":
return left.subtract(right);
case "*":
return left.multiply(right);
case "/":
if (BigDecimal.ZERO.compareTo(right) == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return left.divide(right, 10, RoundingMode.HALF_UP);
default:
throw new IllegalArgumentException("不支持的操作符: " + operator);
}
}
private boolean shouldPopOperator(ExpressionToken currentOperator, ExpressionToken stackOperator) {
if (currentOperator == null || stackOperator == null) {
return false;
}
int currentPriority = getOperatorPriority(currentOperator.getText());
int stackPriority = getOperatorPriority(stackOperator.getText());
if (isRightAssociative(currentOperator.getText())) {
return stackPriority > currentPriority;
}
return stackPriority >= currentPriority;
}
private int getOperatorPriority(String operator) {
if ("~".equals(operator)) {
return 3;
}
if ("*".equals(operator) || "/".equals(operator)) {
return 2;
}
if ("+".equals(operator) || "-".equals(operator)) {
return 1;
}
return 0;
}
private boolean isRightAssociative(String operator) {
return "~".equals(operator);
}
private boolean isOperator(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
} }
private void flushPointDataSafely() { private void flushPointDataSafely() {
@ -864,7 +678,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
} }
Map<String, EmsPointConfig> uniqueByPointId = new LinkedHashMap<>(); Map<String, EmsPointConfig> uniqueByPointId = new LinkedHashMap<>();
for (EmsPointConfig calcPoint : calcPoints) { for (EmsPointConfig calcPoint : calcPoints) {
if (!isCalcPoint(calcPoint)) { if (!isComputedPoint(calcPoint)) {
continue; continue;
} }
String calcKey = resolvePointContextKey(calcPoint); String calcKey = resolvePointContextKey(calcPoint);
@ -925,56 +739,622 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
} }
} }
private enum ExpressionTokenType { private enum ExprTokenType {
NUMBER, NUMBER,
VARIABLE, STRING,
IDENTIFIER,
OPERATOR, OPERATOR,
LEFT_PAREN, LEFT_PAREN,
RIGHT_PAREN RIGHT_PAREN,
COMMA,
QUESTION,
COLON,
EOF
} }
private static class ExpressionToken { private static class ExprToken {
private final ExpressionTokenType type; private final ExprTokenType type;
private final String text; private final String text;
private final BigDecimal number; private final BigDecimal number;
private final String stringValue;
private ExpressionToken(ExpressionTokenType type, String text, BigDecimal number) { private ExprToken(ExprTokenType type, String text, BigDecimal number, String stringValue) {
this.type = type; this.type = type;
this.text = text; this.text = text;
this.number = number; this.number = number;
this.stringValue = stringValue;
} }
private static ExpressionToken number(BigDecimal value) { private static ExprToken number(BigDecimal value) {
return new ExpressionToken(ExpressionTokenType.NUMBER, null, value); return new ExprToken(ExprTokenType.NUMBER, null, value, null);
} }
private static ExpressionToken variable(String variable) { private static ExprToken string(String value) {
return new ExpressionToken(ExpressionTokenType.VARIABLE, variable, null); return new ExprToken(ExprTokenType.STRING, null, null, value);
} }
private static ExpressionToken operator(String operator) { private static ExprToken identifier(String value) {
return new ExpressionToken(ExpressionTokenType.OPERATOR, operator, null); return new ExprToken(ExprTokenType.IDENTIFIER, value, null, null);
} }
private static ExpressionToken leftParen() { private static ExprToken operator(String value) {
return new ExpressionToken(ExpressionTokenType.LEFT_PAREN, "(", null); return new ExprToken(ExprTokenType.OPERATOR, value, null, null);
} }
private static ExpressionToken rightParen() { private static ExprToken symbol(ExprTokenType type, String text) {
return new ExpressionToken(ExpressionTokenType.RIGHT_PAREN, ")", null); return new ExprToken(type, text, null, null);
}
}
private static class ExpressionValue {
private final BigDecimal numberValue;
private final String textValue;
private ExpressionValue(BigDecimal numberValue, String textValue) {
this.numberValue = numberValue;
this.textValue = textValue;
} }
private ExpressionTokenType getType() { private static ExpressionValue ofNumber(BigDecimal numberValue) {
return type; return new ExpressionValue(numberValue, null);
} }
private String getText() { private static ExpressionValue ofText(String textValue) {
return text; return new ExpressionValue(null, textValue == null ? "" : textValue);
} }
private BigDecimal getNumber() { private boolean isNumber() {
return number; return numberValue != null;
} }
private BigDecimal asNumber() {
if (numberValue == null) {
throw new IllegalArgumentException("表达式值不是数值类型: " + textValue);
}
return numberValue;
}
private String asText() {
return numberValue != null ? numberValue.stripTrailingZeros().toPlainString() : textValue;
}
private boolean asBoolean() {
return isNumber() ? BigDecimal.ZERO.compareTo(numberValue) != 0 : StringUtils.isNotBlank(textValue);
}
}
private interface ExpressionNode {
ExpressionValue evaluate(Map<String, BigDecimal> contextValues);
}
private static class NumberNode implements ExpressionNode {
private final BigDecimal value;
private NumberNode(BigDecimal value) {
this.value = value;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
return ExpressionValue.ofNumber(value);
}
}
private static class StringNode implements ExpressionNode {
private final String value;
private StringNode(String value) {
this.value = value;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
return ExpressionValue.ofText(value);
}
}
private static class VariableNode implements ExpressionNode {
private final String name;
private VariableNode(String name) {
this.name = name;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
BigDecimal value = contextValues.get(name);
if (value == null) {
throw new MissingVariableException(name);
}
return ExpressionValue.ofNumber(value);
}
}
private static class UnaryNode implements ExpressionNode {
private final String operator;
private final ExpressionNode node;
private UnaryNode(String operator, ExpressionNode node) {
this.operator = operator;
this.node = node;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
ExpressionValue value = node.evaluate(contextValues);
switch (operator) {
case "+":
return value;
case "-":
return ExpressionValue.ofNumber(value.asNumber().negate());
case "!":
return value.asBoolean() ? ExpressionValue.ofNumber(BigDecimal.ZERO) : ExpressionValue.ofNumber(BigDecimal.ONE);
default:
throw new IllegalArgumentException("不支持的一元操作符: " + operator);
}
}
}
private static class BinaryNode implements ExpressionNode {
private final String operator;
private final ExpressionNode left;
private final ExpressionNode right;
private BinaryNode(String operator, ExpressionNode left, ExpressionNode right) {
this.operator = operator;
this.left = left;
this.right = right;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
if ("&&".equals(operator)) {
ExpressionValue leftValue = left.evaluate(contextValues);
if (!leftValue.asBoolean()) {
return ExpressionValue.ofNumber(BigDecimal.ZERO);
}
return right.evaluate(contextValues).asBoolean()
? ExpressionValue.ofNumber(BigDecimal.ONE)
: ExpressionValue.ofNumber(BigDecimal.ZERO);
}
if ("||".equals(operator)) {
ExpressionValue leftValue = left.evaluate(contextValues);
if (leftValue.asBoolean()) {
return ExpressionValue.ofNumber(BigDecimal.ONE);
}
return right.evaluate(contextValues).asBoolean()
? ExpressionValue.ofNumber(BigDecimal.ONE)
: ExpressionValue.ofNumber(BigDecimal.ZERO);
}
ExpressionValue leftValue = left.evaluate(contextValues);
ExpressionValue rightValue = right.evaluate(contextValues);
switch (operator) {
case "+":
if (!leftValue.isNumber() || !rightValue.isNumber()) {
return ExpressionValue.ofText(leftValue.asText() + rightValue.asText());
}
return ExpressionValue.ofNumber(leftValue.asNumber().add(rightValue.asNumber()));
case "-":
return ExpressionValue.ofNumber(leftValue.asNumber().subtract(rightValue.asNumber()));
case "*":
return ExpressionValue.ofNumber(leftValue.asNumber().multiply(rightValue.asNumber()));
case "/":
if (BigDecimal.ZERO.compareTo(rightValue.asNumber()) == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return ExpressionValue.ofNumber(leftValue.asNumber().divide(rightValue.asNumber(), 10, RoundingMode.HALF_UP));
case ">":
return leftValue.asNumber().compareTo(rightValue.asNumber()) > 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case ">=":
return leftValue.asNumber().compareTo(rightValue.asNumber()) >= 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case "<":
return leftValue.asNumber().compareTo(rightValue.asNumber()) < 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case "<=":
return leftValue.asNumber().compareTo(rightValue.asNumber()) <= 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case "==":
if (leftValue.isNumber() && rightValue.isNumber()) {
return leftValue.asNumber().compareTo(rightValue.asNumber()) == 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
}
return Objects.equals(leftValue.asText(), rightValue.asText())
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
case "!=":
if (leftValue.isNumber() && rightValue.isNumber()) {
return leftValue.asNumber().compareTo(rightValue.asNumber()) != 0
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
}
return !Objects.equals(leftValue.asText(), rightValue.asText())
? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO);
default:
throw new IllegalArgumentException("不支持的操作符: " + operator);
}
}
}
private static class TernaryNode implements ExpressionNode {
private final ExpressionNode condition;
private final ExpressionNode trueNode;
private final ExpressionNode falseNode;
private TernaryNode(ExpressionNode condition, ExpressionNode trueNode, ExpressionNode falseNode) {
this.condition = condition;
this.trueNode = trueNode;
this.falseNode = falseNode;
}
@Override
public ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
return condition.evaluate(contextValues).asBoolean()
? trueNode.evaluate(contextValues)
: falseNode.evaluate(contextValues);
}
}
private static class CompiledExpression {
private final ExpressionNode root;
private CompiledExpression(String expression) {
List<ExprToken> tokens = tokenizeExpression(expression);
ExpressionParser parser = new ExpressionParser(tokens);
this.root = parser.parseExpression();
if (!parser.isEnd()) {
ExprToken token = parser.peek();
throw new IllegalArgumentException("表达式尾部有多余内容: " + token.text);
}
}
private ExpressionValue evaluate(Map<String, BigDecimal> contextValues) {
return root.evaluate(contextValues);
}
}
private static class ExpressionParser {
private final List<ExprToken> tokens;
private int index;
private ExpressionParser(List<ExprToken> tokens) {
this.tokens = tokens;
this.index = 0;
}
private ExpressionNode parseExpression() {
return parseTernary();
}
private ExpressionNode parseTernary() {
ExpressionNode condition = parseOr();
if (match(ExprTokenType.QUESTION)) {
ExpressionNode trueNode = parseTernary();
expect(ExprTokenType.COLON, "三元表达式缺少 ':'");
ExpressionNode falseNode = parseTernary();
return new TernaryNode(condition, trueNode, falseNode);
}
return condition;
}
private ExpressionNode parseOr() {
ExpressionNode left = parseAnd();
while (matchOperator("||")) {
left = new BinaryNode("||", left, parseAnd());
}
return left;
}
private ExpressionNode parseAnd() {
ExpressionNode left = parseEquality();
while (matchOperator("&&")) {
left = new BinaryNode("&&", left, parseEquality());
}
return left;
}
private ExpressionNode parseEquality() {
ExpressionNode left = parseComparison();
while (true) {
if (matchOperator("==")) {
left = new BinaryNode("==", left, parseComparison());
continue;
}
if (matchOperator("!=")) {
left = new BinaryNode("!=", left, parseComparison());
continue;
}
break;
}
return left;
}
private ExpressionNode parseComparison() {
ExpressionNode left = parseAddSub();
while (true) {
if (matchOperator(">=")) {
left = new BinaryNode(">=", left, parseAddSub());
continue;
}
if (matchOperator("<=")) {
left = new BinaryNode("<=", left, parseAddSub());
continue;
}
if (matchOperator(">")) {
left = new BinaryNode(">", left, parseAddSub());
continue;
}
if (matchOperator("<")) {
left = new BinaryNode("<", left, parseAddSub());
continue;
}
break;
}
return left;
}
private ExpressionNode parseAddSub() {
ExpressionNode left = parseMulDiv();
while (true) {
if (matchOperator("+")) {
left = new BinaryNode("+", left, parseMulDiv());
continue;
}
if (matchOperator("-")) {
left = new BinaryNode("-", left, parseMulDiv());
continue;
}
break;
}
return left;
}
private ExpressionNode parseMulDiv() {
ExpressionNode left = parseUnary();
while (true) {
if (matchOperator("*")) {
left = new BinaryNode("*", left, parseUnary());
continue;
}
if (matchOperator("/")) {
left = new BinaryNode("/", left, parseUnary());
continue;
}
break;
}
return left;
}
private ExpressionNode parseUnary() {
if (matchOperator("+")) {
return new UnaryNode("+", parseUnary());
}
if (matchOperator("-")) {
return new UnaryNode("-", parseUnary());
}
if (matchOperator("!")) {
return new UnaryNode("!", parseUnary());
}
return parsePrimary();
}
private ExpressionNode parsePrimary() {
ExprToken token = peek();
if (match(ExprTokenType.NUMBER)) {
return new NumberNode(token.number);
}
if (match(ExprTokenType.STRING)) {
return new StringNode(token.stringValue);
}
if (match(ExprTokenType.IDENTIFIER)) {
String identifier = token.text;
if (match(ExprTokenType.LEFT_PAREN)) {
if (!"IF".equalsIgnoreCase(identifier)) {
throw new IllegalArgumentException("不支持的函数: " + identifier);
}
ExpressionNode condition = parseExpression();
expect(ExprTokenType.COMMA, "IF函数参数格式错误缺少第1个逗号");
ExpressionNode trueNode = parseExpression();
expect(ExprTokenType.COMMA, "IF函数参数格式错误缺少第2个逗号");
ExpressionNode falseNode = parseExpression();
expect(ExprTokenType.RIGHT_PAREN, "IF函数缺少右括号");
return new TernaryNode(condition, trueNode, falseNode);
}
return new VariableNode(identifier.toUpperCase());
}
if (match(ExprTokenType.LEFT_PAREN)) {
ExpressionNode nested = parseExpression();
expect(ExprTokenType.RIGHT_PAREN, "括号不匹配,缺少右括号");
return nested;
}
throw new IllegalArgumentException("表达式语法错误,当前位置: " + token.text);
}
private ExprToken peek() {
if (index >= tokens.size()) {
return ExprToken.symbol(ExprTokenType.EOF, "<eof>");
}
return tokens.get(index);
}
private boolean isEnd() {
return peek().type == ExprTokenType.EOF;
}
private boolean match(ExprTokenType type) {
if (peek().type != type) {
return false;
}
index++;
return true;
}
private boolean matchOperator(String operator) {
ExprToken token = peek();
if (token.type != ExprTokenType.OPERATOR || !operator.equals(token.text)) {
return false;
}
index++;
return true;
}
private void expect(ExprTokenType type, String message) {
if (!match(type)) {
throw new IllegalArgumentException(message);
}
}
}
private static List<ExprToken> tokenizeExpression(String expression) {
if (StringUtils.isBlank(expression)) {
throw new IllegalArgumentException("计算表达式为空");
}
List<ExprToken> tokens = new ArrayList<>();
int index = 0;
while (index < expression.length()) {
char ch = expression.charAt(index);
if (Character.isWhitespace(ch)) {
index++;
continue;
}
if (Character.isDigit(ch) || ch == '.') {
int start = index;
boolean hasDot = ch == '.';
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isDigit(next)) {
index++;
continue;
}
if (next == '.' && !hasDot) {
hasDot = true;
index++;
continue;
}
break;
}
String text = expression.substring(start, index);
try {
tokens.add(ExprToken.number(new BigDecimal(text)));
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("数值格式错误: " + text);
}
continue;
}
if (Character.isLetter(ch) || ch == '_') {
int start = index;
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isLetterOrDigit(next) || next == '_') {
index++;
continue;
}
break;
}
String text = expression.substring(start, index).trim().toUpperCase();
tokens.add(ExprToken.identifier(text));
continue;
}
if (ch == '\'' || ch == '"') {
char quote = ch;
index++;
StringBuilder sb = new StringBuilder();
boolean escaped = false;
while (index < expression.length()) {
char current = expression.charAt(index++);
if (escaped) {
switch (current) {
case 'n':
sb.append('\n');
break;
case 't':
sb.append('\t');
break;
case 'r':
sb.append('\r');
break;
case '\\':
sb.append('\\');
break;
case '\'':
sb.append('\'');
break;
case '"':
sb.append('"');
break;
default:
sb.append(current);
break;
}
escaped = false;
continue;
}
if (current == '\\') {
escaped = true;
continue;
}
if (current == quote) {
break;
}
sb.append(current);
}
if (escaped || index > expression.length() || expression.charAt(index - 1) != quote) {
throw new IllegalArgumentException("字符串字面量未闭合");
}
tokens.add(ExprToken.string(sb.toString()));
continue;
}
if (ch == '(') {
tokens.add(ExprToken.symbol(ExprTokenType.LEFT_PAREN, "("));
index++;
continue;
}
if (ch == ')') {
tokens.add(ExprToken.symbol(ExprTokenType.RIGHT_PAREN, ")"));
index++;
continue;
}
if (ch == ',') {
tokens.add(ExprToken.symbol(ExprTokenType.COMMA, ","));
index++;
continue;
}
if (ch == '?') {
tokens.add(ExprToken.symbol(ExprTokenType.QUESTION, "?"));
index++;
continue;
}
if (ch == ':') {
tokens.add(ExprToken.symbol(ExprTokenType.COLON, ":"));
index++;
continue;
}
if (index + 1 < expression.length()) {
String twoChars = expression.substring(index, index + 2);
if ("&&".equals(twoChars)
|| "||".equals(twoChars)
|| ">=".equals(twoChars)
|| "<=".equals(twoChars)
|| "==".equals(twoChars)
|| "!=".equals(twoChars)) {
tokens.add(ExprToken.operator(twoChars));
index += 2;
continue;
}
}
if (ch == '+' || ch == '-' || ch == '*' || ch == '/'
|| ch == '>' || ch == '<' || ch == '!') {
tokens.add(ExprToken.operator(String.valueOf(ch)));
index++;
continue;
}
throw new IllegalArgumentException("表达式包含非法字符: " + ch);
}
tokens.add(ExprToken.symbol(ExprTokenType.EOF, "<eof>"));
return tokens;
} }
private JSONArray parseJsonData(String message) { private JSONArray parseJsonData(String message) {

View File

@ -50,7 +50,6 @@ import java.util.stream.Collectors;
public class EmsPointConfigServiceImpl implements IEmsPointConfigService { public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
private static final Logger log = LoggerFactory.getLogger(EmsPointConfigServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(EmsPointConfigServiceImpl.class);
private static final String TEMPLATE_SITE_ID = "DEFAULT"; private static final String TEMPLATE_SITE_ID = "DEFAULT";
private static final String SITE_LEVEL_CALC_DEVICE_ID = "SITE_CALC";
private static final int CSV_IMPORT_BATCH_SIZE = 200; private static final int CSV_IMPORT_BATCH_SIZE = 200;
private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$"); private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$");
@ -249,19 +248,13 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
return new ArrayList<>(); return new ArrayList<>();
} }
String siteId = StringUtils.trim(request.getSiteId()); String siteId = StringUtils.trim(request.getSiteId());
String deviceId = StringUtils.trim(request.getDeviceId());
String pointId = StringUtils.trim(request.getPointId()); String pointId = StringUtils.trim(request.getPointId());
if (StringUtils.isAnyBlank(siteId, pointId)) { if (StringUtils.isAnyBlank(siteId, pointId)) {
return new ArrayList<>(); return new ArrayList<>();
} }
EmsPointConfig pointConfig = resolvePointConfigForCurve(siteId, pointId); EmsPointConfig pointConfig = resolvePointConfigForCurve(siteId, pointId);
String pointType = resolvePointTypeForCurve(request, pointConfig);
String queryDeviceId = resolveCurveDeviceId(pointType, deviceId, pointConfig);
if (StringUtils.isBlank(queryDeviceId)) {
return new ArrayList<>();
}
Date[] range = resolveTimeRange(request); Date[] range = resolveTimeRange(request);
return queryCurveDataFromInflux(siteId, queryDeviceId, pointId, pointConfig, range[0], range[1]); return queryCurveDataFromInflux(siteId, pointId, pointConfig, range[0], range[1]);
} }
private PointConfigLatestValueVo queryLatestValueFromRedis(PointConfigLatestValueItemVo item, private PointConfigLatestValueVo queryLatestValueFromRedis(PointConfigLatestValueItemVo item,
@ -296,13 +289,13 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
return vo; return vo;
} }
private List<PointConfigCurveValueVo> queryCurveDataFromInflux(String siteId, String deviceId, String pointId, private List<PointConfigCurveValueVo> queryCurveDataFromInflux(String siteId, String pointId,
EmsPointConfig pointConfig, Date startTime, Date endTime) { EmsPointConfig pointConfig, Date startTime, Date endTime) {
String influxPointKey = resolveInfluxPointKey(pointConfig, pointId); String influxPointKey = resolveInfluxPointKey(pointConfig, pointId);
if (StringUtils.isBlank(influxPointKey)) { if (StringUtils.isBlank(influxPointKey)) {
return new ArrayList<>(); return new ArrayList<>();
} }
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveData(siteId, deviceId, influxPointKey, startTime, endTime); List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveDataByPointKey(siteId, influxPointKey, startTime, endTime);
if (values == null || values.isEmpty()) { if (values == null || values.isEmpty()) {
return new ArrayList<>(); return new ArrayList<>();
} }
@ -518,29 +511,6 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
return pointConfigs.get(0); return pointConfigs.get(0);
} }
private String resolvePointTypeForCurve(PointConfigCurveRequest request, EmsPointConfig pointConfig) {
if (pointConfig != null && StringUtils.isNotBlank(pointConfig.getPointType())) {
return StringUtils.trim(pointConfig.getPointType()).toLowerCase(Locale.ROOT);
}
if (request == null || StringUtils.isBlank(request.getPointType())) {
return "data";
}
return StringUtils.trim(request.getPointType()).toLowerCase(Locale.ROOT);
}
private String resolveCurveDeviceId(String pointType, String requestDeviceId, EmsPointConfig pointConfig) {
if ("calc".equalsIgnoreCase(StringUtils.defaultString(pointType))) {
return SITE_LEVEL_CALC_DEVICE_ID;
}
if (StringUtils.isNotBlank(requestDeviceId)) {
return StringUtils.trim(requestDeviceId);
}
if (pointConfig != null && StringUtils.isNotBlank(pointConfig.getDeviceId())) {
return StringUtils.trim(pointConfig.getDeviceId());
}
return null;
}
private BigDecimal convertPointValue(BigDecimal sourceValue, EmsPointConfig pointConfig) { private BigDecimal convertPointValue(BigDecimal sourceValue, EmsPointConfig pointConfig) {
if (sourceValue == null || pointConfig == null) { if (sourceValue == null || pointConfig == null) {
return sourceValue; return sourceValue;

View File

@ -26,6 +26,12 @@ public class EmsStrategyRuntimeConfigServiceImpl implements IEmsStrategyRuntimeC
private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal("100"); private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal("100");
private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal("10"); private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal("10");
private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal("20"); private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal("20");
private static final BigDecimal DEFAULT_POWER_SET_MULTIPLIER = new BigDecimal("10");
private static final Integer DEFAULT_PROTECT_INTERVENE_ENABLE = 1;
private static final BigDecimal DEFAULT_PROTECT_L1_DERATE_PERCENT = new BigDecimal("50");
private static final Integer DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS = 5;
private static final Integer DEFAULT_PROTECT_L3_LATCH_ENABLE = 1;
private static final String DEFAULT_PROTECT_CONFLICT_POLICY = "MAX_LEVEL_WIN";
@Autowired @Autowired
private EmsStrategyRuntimeConfigMapper runtimeConfigMapper; private EmsStrategyRuntimeConfigMapper runtimeConfigMapper;
@ -83,5 +89,23 @@ public class EmsStrategyRuntimeConfigServiceImpl implements IEmsStrategyRuntimeC
if (config.getAntiReverseHardStopThreshold() == null) { if (config.getAntiReverseHardStopThreshold() == null) {
config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD); config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD);
} }
if (config.getPowerSetMultiplier() == null || config.getPowerSetMultiplier().compareTo(BigDecimal.ZERO) <= 0) {
config.setPowerSetMultiplier(DEFAULT_POWER_SET_MULTIPLIER);
}
if (config.getProtectInterveneEnable() == null) {
config.setProtectInterveneEnable(DEFAULT_PROTECT_INTERVENE_ENABLE);
}
if (config.getProtectL1DeratePercent() == null || config.getProtectL1DeratePercent().compareTo(BigDecimal.ZERO) < 0) {
config.setProtectL1DeratePercent(DEFAULT_PROTECT_L1_DERATE_PERCENT);
}
if (config.getProtectRecoveryStableSeconds() == null || config.getProtectRecoveryStableSeconds() < 0) {
config.setProtectRecoveryStableSeconds(DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS);
}
if (config.getProtectL3LatchEnable() == null) {
config.setProtectL3LatchEnable(DEFAULT_PROTECT_L3_LATCH_ENABLE);
}
if (StringUtils.isEmpty(config.getProtectConflictPolicy())) {
config.setProtectConflictPolicy(DEFAULT_PROTECT_CONFLICT_POLICY);
}
} }
} }

View File

@ -49,15 +49,12 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
if (siteIds == null || siteIds.isEmpty()) { if (siteIds == null || siteIds.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
return emsPointConfigMapper.getPointNameList(
String deviceCategory = request.getDeviceCategory(); siteIds,
String deviceId = request.getDeviceId(); request.getDeviceCategory(),
if ((deviceCategory == null || "".equals(deviceCategory.trim())) request.getDeviceId(),
&& (deviceId == null || "".equals(deviceId.trim()))) { request.getPointName()
return Collections.emptyList(); );
}
return emsPointConfigMapper.getPointNameList(siteIds, deviceCategory, deviceId, request.getPointName());
} }
@Override @Override
@ -94,15 +91,10 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
} }
String deviceCategory = request.getDeviceCategory(); String deviceCategory = request.getDeviceCategory();
String requestDeviceId = request.getDeviceId(); List<String> pointIds = resolvePointIds(request);
if ((deviceCategory == null || "".equals(deviceCategory.trim()))
&& (requestDeviceId == null || "".equals(requestDeviceId.trim()))
) {
return Collections.emptyList();
}
List<String> pointNames = resolvePointNames(request); List<String> pointNames = resolvePointNames(request);
if (pointNames.isEmpty()) { if (pointIds.isEmpty() && pointNames.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
@ -114,17 +106,19 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
endDate = DateUtils.adjustToEndOfDay(request.getEndDate()); endDate = DateUtils.adjustToEndOfDay(request.getEndDate());
} }
List<String> selectedDeviceIds = resolveSelectedDeviceIds(request); List<String> selectedDeviceIds = pointIds.isEmpty() ? resolveSelectedDeviceIds(request) : Collections.emptyList();
List<EmsPointConfig> pointConfigs = emsPointConfigMapper.getConfigListForGeneralQuery( List<EmsPointConfig> pointConfigs = emsPointConfigMapper.getConfigListForGeneralQuery(
siteIds, deviceCategory, pointNames, selectedDeviceIds siteIds, deviceCategory, pointIds, pointNames, selectedDeviceIds
); );
if (pointConfigs == null || pointConfigs.isEmpty()) { if (pointConfigs == null || pointConfigs.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
Map<String, String> selectedPointNameById = buildSelectedPointNameById(request);
List<GeneralQueryDataVo> dataVoList = new ArrayList<>(); List<GeneralQueryDataVo> dataVoList = new ArrayList<>();
for (EmsPointConfig pointConfig : pointConfigs) { for (EmsPointConfig pointConfig : pointConfigs) {
dataVoList.addAll(queryPointCurve(pointConfig, request.getDataUnit(), startDate, endDate)); String selectedPointName = selectedPointNameById.get(resolveInfluxPointKey(pointConfig));
dataVoList.addAll(queryPointCurve(pointConfig, request.getDataUnit(), startDate, endDate, selectedPointName));
} }
if (dataVoList.isEmpty()) { if (dataVoList.isEmpty()) {
@ -154,6 +148,19 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return names.stream().distinct().collect(Collectors.toList()); return names.stream().distinct().collect(Collectors.toList());
} }
private List<String> resolvePointIds(PointNameRequest request) {
List<String> ids = new ArrayList<>();
if (request.getPointIds() != null && !request.getPointIds().isEmpty()) {
ids.addAll(request.getPointIds());
} else if (request.getPointId() != null && !"".equals(request.getPointId().trim())) {
ids.addAll(Arrays.stream(request.getPointId().split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.toList()));
}
return ids.stream().distinct().collect(Collectors.toList());
}
private List<String> resolveSelectedDeviceIds(PointNameRequest request) { private List<String> resolveSelectedDeviceIds(PointNameRequest request) {
List<String> selected = new ArrayList<>(); List<String> selected = new ArrayList<>();
if (request.getDeviceId() != null && !"".equals(request.getDeviceId().trim())) { if (request.getDeviceId() != null && !"".equals(request.getDeviceId().trim())) {
@ -175,16 +182,18 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return selected.stream().distinct().collect(Collectors.toList()); return selected.stream().distinct().collect(Collectors.toList());
} }
private List<GeneralQueryDataVo> queryPointCurve(EmsPointConfig config, int dataUnit, Date startDate, Date endDate) { private List<GeneralQueryDataVo> queryPointCurve(EmsPointConfig config, int dataUnit, Date startDate, Date endDate,
if (config == null || config.getSiteId() == null || config.getDeviceId() == null) { String selectedPointName) {
if (config == null || config.getSiteId() == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
String influxPointKey = resolveInfluxPointKey(config); String influxPointKey = resolveInfluxPointKey(config);
if (influxPointKey == null) { if (influxPointKey == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveData( // 与点位列表曲线保持一致:按 siteId + pointKey 查询,避免 deviceId 维度导致综合查询漏数
config.getSiteId(), config.getDeviceId(), influxPointKey, startDate, endDate List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveDataByPointKey(
config.getSiteId(), influxPointKey, startDate, endDate
); );
if (values == null || values.isEmpty()) { if (values == null || values.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
@ -197,7 +206,7 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
} }
List<GeneralQueryDataVo> result = new ArrayList<>(); List<GeneralQueryDataVo> result = new ArrayList<>();
String displayDeviceId = buildDisplayDeviceId(config); String displayDeviceId = buildDisplayDeviceId(config, selectedPointName);
for (Map.Entry<String, Object> entry : latestByBucket.entrySet()) { for (Map.Entry<String, Object> entry : latestByBucket.entrySet()) {
GeneralQueryDataVo vo = new GeneralQueryDataVo(); GeneralQueryDataVo vo = new GeneralQueryDataVo();
vo.setSiteId(config.getSiteId()); vo.setSiteId(config.getSiteId());
@ -219,10 +228,32 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return null; return null;
} }
private String buildDisplayDeviceId(EmsPointConfig config) { private String buildDisplayDeviceId(EmsPointConfig config, String selectedPointName) {
if (selectedPointName != null && !"".equals(selectedPointName.trim())) {
return selectedPointName.trim();
}
String pointName = config.getPointName() == null || "".equals(config.getPointName().trim()) String pointName = config.getPointName() == null || "".equals(config.getPointName().trim())
? config.getDataKey() : config.getPointName().trim(); ? config.getDataKey() : config.getPointName().trim();
return config.getDeviceId() + "-" + pointName; return pointName;
}
private Map<String, String> buildSelectedPointNameById(PointNameRequest request) {
Map<String, String> selectedNameById = new HashMap<>();
if (request == null || request.getPointIds() == null || request.getPointNames() == null) {
return selectedNameById;
}
List<String> pointIds = request.getPointIds();
List<String> pointNames = request.getPointNames();
int size = Math.min(pointIds.size(), pointNames.size());
for (int i = 0; i < size; i++) {
String pointId = pointIds.get(i);
String pointName = pointNames.get(i);
if (pointId == null || "".equals(pointId.trim()) || pointName == null || "".equals(pointName.trim())) {
continue;
}
selectedNameById.put(pointId.trim(), pointName.trim());
}
return selectedNameById;
} }
private String formatByDataUnit(Date dataTime, int dataUnit) { private String formatByDataUnit(Date dataTime, int dataUnit) {

View File

@ -14,7 +14,7 @@ import com.xzzn.ems.domain.EmsCoolingData;
import com.xzzn.ems.domain.EmsDhData; import com.xzzn.ems.domain.EmsDhData;
import com.xzzn.ems.domain.EmsEmsData; import com.xzzn.ems.domain.EmsEmsData;
import com.xzzn.ems.domain.EmsPcsBranchData; import com.xzzn.ems.domain.EmsPcsBranchData;
import com.xzzn.ems.domain.EmsPcsData; import com.xzzn.ems.domain.EmsSiteMonitorPointMatch;
import com.xzzn.ems.domain.EmsStrategyTemp; import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.EmsXfData; import com.xzzn.ems.domain.EmsXfData;
import com.xzzn.ems.domain.vo.*; import com.xzzn.ems.domain.vo.*;
@ -28,12 +28,15 @@ import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsEnergyPriceConfigMapper; import com.xzzn.ems.mapper.EmsEnergyPriceConfigMapper;
import com.xzzn.ems.mapper.EmsPcsDataMapper; import com.xzzn.ems.mapper.EmsPcsDataMapper;
import com.xzzn.ems.mapper.EmsPointMatchMapper; import com.xzzn.ems.mapper.EmsPointMatchMapper;
import com.xzzn.ems.mapper.EmsSiteMonitorPointMatchMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper; import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper; import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.service.IEmsEnergyPriceConfigService; import com.xzzn.ems.service.IEmsEnergyPriceConfigService;
import com.xzzn.ems.service.ISingleSiteService; import com.xzzn.ems.service.ISingleSiteService;
import com.xzzn.ems.service.InfluxPointDataWriter;
import com.xzzn.ems.utils.DevicePointMatchDataProcessor; import com.xzzn.ems.utils.DevicePointMatchDataProcessor;
import java.beans.PropertyDescriptor;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
@ -42,6 +45,7 @@ import java.time.LocalTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -53,7 +57,9 @@ import java.util.stream.Collectors;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -65,6 +71,25 @@ import org.springframework.util.CollectionUtils;
public class SingleSiteServiceImpl implements ISingleSiteService { public class SingleSiteServiceImpl implements ISingleSiteService {
private static final Logger log = LoggerFactory.getLogger(SingleSiteServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(SingleSiteServiceImpl.class);
private static final String RUNNING_GRAPH_DEBUG = "RunningGraphDebug";
private static final String FIELD_CURVE_PCS_ACTIVE_POWER = "SBJK_SSYX__curvePcsActivePower";
private static final String FIELD_CURVE_PCS_REACTIVE_POWER = "SBJK_SSYX__curvePcsReactivePower";
private static final String FIELD_CURVE_PCS_MAX_TEMP = "SBJK_SSYX__curvePcsMaxTemp";
private static final String FIELD_CURVE_BATTERY_AVE_SOC = "SBJK_SSYX__curveBatteryAveSoc";
private static final String FIELD_CURVE_BATTERY_AVE_TEMP = "SBJK_SSYX__curveBatteryAveTemp";
private static final String FIELD_TOTAL_ACTIVE_POWER = "SBJK_SSYX__totalActivePower";
private static final String FIELD_TOTAL_REACTIVE_POWER = "SBJK_SSYX__totalReactivePower";
private static final String FIELD_SOC = "SBJK_SSYX__soc";
private static final String FIELD_HOME_AVG_TEMP = "HOME__avgTemp";
private static final String RUNNING_GRAPH_DEFAULT_DEVICE = "SITE";
private static final int USE_FIXED_DISPLAY_YES = 1;
private static final String DEVICE_INFO_ID = "id";
private static final String DEVICE_INFO_NAME = "deviceName";
private static final String DEVICE_INFO_COMM_STATUS = "communicationStatus";
private static final String DEVICE_INFO_DEVICE_STATUS = "deviceStatus";
private static final Set<String> PCS_DETAIL_META_FIELDS = new HashSet<>(Arrays.asList(
"siteId", "deviceId", "deviceName", "alarmNum", "pcsBranchInfoList", "dataUpdateTime"
));
private static final String CLUSTER_DATA_TEP = "温度"; private static final String CLUSTER_DATA_TEP = "温度";
@ -90,6 +115,10 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
private EmsDevicesSettingMapper emsDevicesSettingMapper; private EmsDevicesSettingMapper emsDevicesSettingMapper;
@Autowired @Autowired
private EmsPointMatchMapper emsPointMatchMapper; private EmsPointMatchMapper emsPointMatchMapper;
@Autowired
private EmsSiteMonitorPointMatchMapper emsSiteMonitorPointMatchMapper;
@Autowired
private InfluxPointDataWriter influxPointDataWriter;
@Autowired @Autowired
private RedisCache redisCache; private RedisCache redisCache;
@ -217,46 +246,61 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
public SiteMonitorRuningInfoVo getRunningGraphStorage(RunningGraphRequest request) { public SiteMonitorRuningInfoVo getRunningGraphStorage(RunningGraphRequest request) {
SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo(); SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) { if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
log.info("{} storage skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
return siteMonitorRuningInfoVo; return siteMonitorRuningInfoVo;
} }
// // 时间暂定今日+昨日 String siteId = request.getSiteId();
// Date today = DateUtils.getNowDate(); Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
// Date yesterday = DateUtils.addDays(today, -1); Date startDate = dateRange[0];
Date startDate = request.getStartDate(); Date endDate = dateRange[1];
Date endDate = request.getEndDate(); List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
List<DeviceMeta> pcsDeviceList = getDeviceMetaListByCategory(siteId, DeviceCategory.PCS.getCode());
if (CollectionUtils.isEmpty(pcsDeviceList)) {
pcsDeviceList = Collections.singletonList(new DeviceMeta(RUNNING_GRAPH_DEFAULT_DEVICE, RUNNING_GRAPH_DEFAULT_DEVICE));
}
//pcs有功无功
List<PcsPowerList> pcsPowerList = new ArrayList<>(); List<PcsPowerList> pcsPowerList = new ArrayList<>();
List<EnergyStoragePowVo> energyStoragePowList = emsPcsDataMapper.getStoragePowerList(request.getSiteId(), startDate, endDate); for (DeviceMeta pcsDevice : pcsDeviceList) {
String deviceId = StringUtils.defaultString(pcsDevice.getDeviceId());
String activePointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_ACTIVE_POWER);
String reactivePointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_REACTIVE_POWER);
// List<EnergyStoragePowVo> -> 按pcs的deviceId分组转成List<PcsPowerList> List<InfluxPointDataWriter.PointValue> activeValues = queryInfluxPointValues(siteId, activePointId, startDate, endDate);
if (!CollectionUtils.isEmpty(energyStoragePowList)) { List<InfluxPointDataWriter.PointValue> reactiveValues = queryInfluxPointValues(siteId, reactivePointId, startDate, endDate);
Map<String, List<EnergyStoragePowVo>> dataMap = energyStoragePowList.stream() Map<Long, BigDecimal> reactiveByTs = reactiveValues.stream()
.collect(Collectors.groupingBy( .filter(v -> v != null && v.getDataTime() != null && v.getPointValue() != null)
EnergyStoragePowVo::getDeviceId, .collect(Collectors.toMap(v -> v.getDataTime().getTime(), InfluxPointDataWriter.PointValue::getPointValue, (a, b) -> b));
Collectors.toList()));
pcsPowerList = dataMap.entrySet().stream() List<EnergyStoragePowVo> energyStoragePowList = new ArrayList<>();
.map(entry -> { for (InfluxPointDataWriter.PointValue activeValue : activeValues) {
PcsPowerList pcdData = new PcsPowerList(); if (activeValue == null || activeValue.getDataTime() == null || activeValue.getPointValue() == null) {
pcdData.setDeviceId(entry.getKey()); continue;
pcdData.setEnergyStoragePowList(entry.getValue()); }
return pcdData; Date dataTime = activeValue.getDataTime();
}).collect(Collectors.toList()); EnergyStoragePowVo vo = new EnergyStoragePowVo();
vo.setDeviceId(deviceId);
vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
vo.setGroupTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, dataTime));
vo.setPcsTotalActPower(activeValue.getPointValue());
vo.setPcsTotalReactivePower(reactiveByTs.get(dataTime.getTime()));
energyStoragePowList.add(vo);
}
// // 生成时间列表每5分钟一个 PcsPowerList pcdData = new PcsPowerList();
// List<LocalDateTime> targetMinutes = new ArrayList<>(12); pcdData.setDeviceId(deviceId);
// LocalDateTime startLocalDate = DateUtils.toLocalDateTime(startDate).truncatedTo(ChronoUnit.DAYS); pcdData.setEnergyStoragePowList(energyStoragePowList);
// LocalDateTime endLocalDate = DateUtils.toLocalDateTime(endDate).with(LocalDateTime.now().toLocalTime()); pcsPowerList.add(pcdData);
// while (startLocalDate.isBefore(endLocalDate)) {
// targetMinutes.add(startLocalDate);
// startLocalDate = startLocalDate.plusMinutes(5); // 递增5分钟
// }
// // 根据时间列表填充数据
// pcsPowerList = fullFillData(pcsPowerList, targetMinutes);
} }
siteMonitorRuningInfoVo.setPcsPowerList(pcsPowerList); siteMonitorRuningInfoVo.setPcsPowerList(pcsPowerList);
int pointCount = pcsPowerList.stream()
.filter(item -> item != null && item.getEnergyStoragePowList() != null)
.mapToInt(item -> item.getEnergyStoragePowList().size())
.sum();
log.info("{} storage, siteId={}, startDate={}, endDate={}, deviceCount={}, pointCount={}",
RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pcsPowerList.size(), pointCount);
return siteMonitorRuningInfoVo; return siteMonitorRuningInfoVo;
} }
@ -316,49 +360,52 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
@Override @Override
public SiteMonitorRuningInfoVo getRunningGraphPcsMaxTemp(RunningGraphRequest request) { public SiteMonitorRuningInfoVo getRunningGraphPcsMaxTemp(RunningGraphRequest request) {
SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo(); SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
List<PcsMaxTempList> pcsMaxTempList = new ArrayList<>(); if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
// // 时间暂定今日+昨日 log.info("{} pcsMaxTemp skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
// Date today = new Date(); return siteMonitorRuningInfoVo;
// Date yesterday = DateUtils.addDays(today, -1); }
String siteId = request.getSiteId(); String siteId = request.getSiteId();
Date startDate = request.getStartDate(); Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
Date endDate = request.getEndDate(); Date startDate = dateRange[0];
//PCS最高温度list Date endDate = dateRange[1];
List<PcsMaxTempVo> pcsMaxTempVos = emsPcsDataMapper.getPcsMaxTemp(siteId, startDate, endDate); List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
// if (SiteEnum.FX.getCode().equals(siteId)) { Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
// pcsMaxTempVos = emsPcsDataMapper.getFXMaxTemp(siteId, startDate, endDate); List<DeviceMeta> pcsDeviceList = getDeviceMetaListByCategory(siteId, DeviceCategory.PCS.getCode());
// } else if (SiteEnum.DDS.getCode().equals(siteId)) { if (CollectionUtils.isEmpty(pcsDeviceList)) {
// pcsMaxTempVos = emsPcsDataMapper.getDDSMaxTemp(siteId, startDate, endDate); pcsDeviceList = Collections.singletonList(new DeviceMeta(RUNNING_GRAPH_DEFAULT_DEVICE, RUNNING_GRAPH_DEFAULT_DEVICE));
// } }
// List<PcsMaxTempVo> -> 按pcs的deviceId分组转成List<PcsMaxTempList> List<PcsMaxTempList> pcsMaxTempList = new ArrayList<>();
if (!CollectionUtils.isEmpty(pcsMaxTempVos)) { for (DeviceMeta pcsDevice : pcsDeviceList) {
Map<String, List<PcsMaxTempVo>> dataMap = pcsMaxTempVos.stream() String deviceId = StringUtils.defaultString(pcsDevice.getDeviceId());
.collect(Collectors.groupingBy( String pointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_MAX_TEMP);
PcsMaxTempVo::getDeviceId, List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(siteId, pointId, startDate, endDate);
Collectors.toList())); List<PcsMaxTempVo> pcsMaxTempVos = new ArrayList<>();
for (InfluxPointDataWriter.PointValue value : values) {
pcsMaxTempList = dataMap.entrySet().stream() if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
.map(entry -> { continue;
PcsMaxTempList pcdData = new PcsMaxTempList(); }
pcdData.setDeviceId(entry.getKey()); Date dataTime = value.getDataTime();
pcdData.setMaxTempVoList(entry.getValue()); PcsMaxTempVo vo = new PcsMaxTempVo();
return pcdData; vo.setDeviceId(deviceId);
}).collect(Collectors.toList()); vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
// // 生成时间列表(每小时一个) vo.setTemp(value.getPointValue());
// List<LocalDateTime> targetHours = new ArrayList<>(60); pcsMaxTempVos.add(vo);
// LocalDateTime startDate = DateUtils.toLocalDateTime(yesterday).truncatedTo(ChronoUnit.DAYS); }
// LocalDateTime endDate = DateUtils.toLocalDateTime(today); PcsMaxTempList pcdData = new PcsMaxTempList();
// while (startDate.isBefore(endDate)) { pcdData.setDeviceId(deviceId);
// targetHours.add(startDate); pcdData.setMaxTempVoList(pcsMaxTempVos);
// startDate = startDate.plusHours(1); // 递增1小时 pcsMaxTempList.add(pcdData);
// }
// // 根据时间列表填充数据
// pcsMaxTempList = fullFillMaxTempData(pcsMaxTempList,targetHours);
} }
siteMonitorRuningInfoVo.setPcsMaxTempList(pcsMaxTempList); siteMonitorRuningInfoVo.setPcsMaxTempList(pcsMaxTempList);
int pointCount = pcsMaxTempList.stream()
.filter(item -> item != null && item.getMaxTempVoList() != null)
.mapToInt(item -> item.getMaxTempVoList().size())
.sum();
log.info("{} pcsMaxTemp, siteId={}, startDate={}, endDate={}, deviceCount={}, pointCount={}",
RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pcsMaxTempList.size(), pointCount);
return siteMonitorRuningInfoVo; return siteMonitorRuningInfoVo;
} }
private List<PcsMaxTempList> fullFillMaxTempData(List<PcsMaxTempList> pcsMaxTempList, List<LocalDateTime> targetHours) { private List<PcsMaxTempList> fullFillMaxTempData(List<PcsMaxTempList> pcsMaxTempList, List<LocalDateTime> targetHours) {
@ -415,14 +462,32 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
@Override @Override
public SiteMonitorRuningInfoVo getRunningGraphBatterySoc(RunningGraphRequest request) { public SiteMonitorRuningInfoVo getRunningGraphBatterySoc(RunningGraphRequest request) {
SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo(); SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
if (!StringUtils.isEmpty(request.getSiteId())) { if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
// // 时间暂定今日+昨日 log.info("{} batteryAveSoc skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
// Date today = new Date(); return siteMonitorRuningInfoVo;
// Date yesterday = DateUtils.addDays(today, -1);
//电池平均soclist
List<BatteryAveSOCVo> batteryAveSOCList = emsBatteryStackMapper.getAveSocList(request.getSiteId(), request.getStartDate(), request.getEndDate());
siteMonitorRuningInfoVo.setBatteryAveSOCList(batteryAveSOCList);
} }
Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
Date startDate = dateRange[0];
Date endDate = dateRange[1];
Map<String, EmsSiteMonitorPointMatch> mappingByField = getMonitorPointMappingByField(request.getSiteId());
String pointId = firstNonBlankPoint(mappingByField, FIELD_CURVE_BATTERY_AVE_SOC, FIELD_SOC);
List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(request.getSiteId(), pointId, startDate, endDate);
List<BatteryAveSOCVo> batteryAveSOCList = new ArrayList<>();
for (InfluxPointDataWriter.PointValue value : values) {
if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
continue;
}
Date dataTime = value.getDataTime();
BatteryAveSOCVo vo = new BatteryAveSOCVo();
vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
vo.setBatterySOC(value.getPointValue());
batteryAveSOCList.add(vo);
}
siteMonitorRuningInfoVo.setBatteryAveSOCList(batteryAveSOCList);
int pointCount = CollectionUtils.isEmpty(batteryAveSOCList) ? 0 : batteryAveSOCList.size();
log.info("{} batteryAveSoc, siteId={}, startDate={}, endDate={}, pointId={}, pointCount={}",
RUNNING_GRAPH_DEBUG, request.getSiteId(), startDate, endDate, pointId, pointCount);
return siteMonitorRuningInfoVo; return siteMonitorRuningInfoVo;
} }
// 获取单站监控实时运行-电池平均温度 // 获取单站监控实时运行-电池平均温度
@ -430,40 +495,178 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
public SiteMonitorRuningInfoVo getRunningGraphBatteryTemp(RunningGraphRequest request) { public SiteMonitorRuningInfoVo getRunningGraphBatteryTemp(RunningGraphRequest request) {
SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo(); SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo();
if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) { if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) {
log.info("{} batteryAveTemp skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request);
return siteMonitorRuningInfoVo; return siteMonitorRuningInfoVo;
} }
String siteId = request.getSiteId(); String siteId = request.getSiteId();
Date startDate = request.getStartDate(); Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate());
Date endDate = request.getEndDate(); Date startDate = dateRange[0];
//电池平均温度list,优先从电池堆取,电池堆没有的话再从电池簇取 Date endDate = dateRange[1];
Map<String, EmsSiteMonitorPointMatch> mappingByField = getMonitorPointMappingByField(siteId);
String pointId = firstNonBlankPoint(mappingByField, FIELD_CURVE_BATTERY_AVE_TEMP, FIELD_HOME_AVG_TEMP);
List<InfluxPointDataWriter.PointValue> values = queryInfluxPointValues(siteId, pointId, startDate, endDate);
List<BatteryAveTempVo> batteryAveTempList = new ArrayList<>(); List<BatteryAveTempVo> batteryAveTempList = new ArrayList<>();
batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, startDate, endDate); for (InfluxPointDataWriter.PointValue value : values) {
// 电池堆暂无数据,从电池簇取 if (value == null || value.getDataTime() == null || value.getPointValue() == null) {
if (CollectionUtils.isEmpty(batteryAveTempList)) { continue;
batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, startDate, endDate); }
Date dataTime = value.getDataTime();
BatteryAveTempVo vo = new BatteryAveTempVo();
vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime));
vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime));
vo.setBatteryTemp(value.getPointValue());
batteryAveTempList.add(vo);
} }
// if (SiteEnum.FX.getCode().equals(siteId)) {
// batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, startDate, endDate);
// } else if (SiteEnum.DDS.getCode().equals(siteId)) {
// batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, startDate, endDate);
// }
siteMonitorRuningInfoVo.setBatteryAveTempList(batteryAveTempList); siteMonitorRuningInfoVo.setBatteryAveTempList(batteryAveTempList);
// if (!StringUtils.isEmpty(siteId)) { int pointCount = CollectionUtils.isEmpty(batteryAveTempList) ? 0 : batteryAveTempList.size();
// // 时间暂定今日+昨日 log.info("{} batteryAveTemp, siteId={}, startDate={}, endDate={}, pointId={}, pointCount={}",
// Date today = new Date(); RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pointId, pointCount);
// Date yesterday = DateUtils.addDays(today, -1);
// //电池平均温度list
// List<BatteryAveTempVo> batteryAveTempList = new ArrayList<>();
// if (SiteEnum.FX.getCode().equals(siteId)) {
// batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, yesterday, today);
// } else if (SiteEnum.DDS.getCode().equals(siteId)) {
// batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, yesterday, today);
// }
// siteMonitorRuningInfoVo.setBatteryAveTempList(batteryAveTempList);
// }
return siteMonitorRuningInfoVo; return siteMonitorRuningInfoVo;
} }
private Date[] normalizeRunningGraphDateRange(Date startDate, Date endDate) {
Date normalizedStart = startDate;
Date normalizedEnd = endDate;
if (normalizedStart == null || normalizedEnd == null) {
Date today = DateUtils.getNowDate();
Date yesterday = DateUtils.addDays(today, -1);
normalizedStart = normalizedStart == null ? yesterday : normalizedStart;
normalizedEnd = normalizedEnd == null ? today : normalizedEnd;
}
LocalDate startDay = DateUtils.toLocalDateTime(normalizedStart).toLocalDate();
LocalDate endDay = DateUtils.toLocalDateTime(normalizedEnd).toLocalDate();
Date dayStart = DateUtils.toDate(startDay.atStartOfDay());
Date dayEnd = DateUtils.toDate(endDay.plusDays(1).atStartOfDay().minusNanos(1_000_000));
return new Date[]{dayStart, dayEnd};
}
private Map<String, EmsSiteMonitorPointMatch> getMonitorPointMappingByField(String siteId) {
if (StringUtils.isBlank(siteId)) {
return Collections.emptyMap();
}
List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId.trim());
if (CollectionUtils.isEmpty(mappingList)) {
return Collections.emptyMap();
}
return mappingList.stream()
.filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode()))
.collect(Collectors.toMap(
item -> item.getFieldCode().trim(),
item -> item,
(a, b) -> b
));
}
private Map<String, EmsSiteMonitorPointMatch> buildMonitorPointMappingByFieldAndDevice(List<EmsSiteMonitorPointMatch> mappingList) {
if (CollectionUtils.isEmpty(mappingList)) {
return Collections.emptyMap();
}
return mappingList.stream()
.filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode()))
.collect(Collectors.toMap(
item -> buildFieldDeviceKey(item.getFieldCode(), item.getDeviceId()),
item -> item,
(a, b) -> b
));
}
private String buildFieldDeviceKey(String fieldCode, String deviceId) {
return StringUtils.defaultString(fieldCode).trim() + "|" + StringUtils.defaultString(deviceId).trim();
}
private String firstNonBlankPointByDevice(Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
String deviceId, String... fieldCodes) {
if (mappingByFieldAndDevice == null || fieldCodes == null) {
return null;
}
String normalizedDeviceId = StringUtils.defaultString(deviceId).trim();
for (String fieldCode : fieldCodes) {
if (StringUtils.isBlank(fieldCode)) {
continue;
}
EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildFieldDeviceKey(fieldCode, normalizedDeviceId));
if (exact != null && StringUtils.isNotBlank(exact.getDataPoint())) {
return exact.getDataPoint().trim();
}
EmsSiteMonitorPointMatch fallback = mappingByFieldAndDevice.get(buildFieldDeviceKey(fieldCode, ""));
if (fallback != null && StringUtils.isNotBlank(fallback.getDataPoint())) {
return fallback.getDataPoint().trim();
}
}
return null;
}
private List<DeviceMeta> getDeviceMetaListByCategory(String siteId, String deviceCategory) {
if (StringUtils.isBlank(siteId) || StringUtils.isBlank(deviceCategory)) {
return Collections.emptyList();
}
List<Map<String, Object>> deviceList = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, deviceCategory);
if (CollectionUtils.isEmpty(deviceList)) {
return Collections.emptyList();
}
List<DeviceMeta> result = new ArrayList<>();
for (Map<String, Object> item : deviceList) {
if (item == null || item.get("id") == null) {
continue;
}
String deviceId = String.valueOf(item.get("id")).trim();
if (StringUtils.isBlank(deviceId)) {
continue;
}
String deviceName = item.get("deviceName") == null ? deviceId : String.valueOf(item.get("deviceName")).trim();
result.add(new DeviceMeta(deviceId, deviceName));
}
result.sort(Comparator.comparing(DeviceMeta::getDeviceName, Comparator.nullsLast(String::compareTo))
.thenComparing(DeviceMeta::getDeviceId, Comparator.nullsLast(String::compareTo)));
return result;
}
private static class DeviceMeta {
private final String deviceId;
private final String deviceName;
private DeviceMeta(String deviceId, String deviceName) {
this.deviceId = deviceId;
this.deviceName = deviceName;
}
public String getDeviceId() {
return deviceId;
}
public String getDeviceName() {
return deviceName;
}
}
private String firstNonBlankPoint(Map<String, EmsSiteMonitorPointMatch> mappingByField, String... fieldCodes) {
if (mappingByField == null || fieldCodes == null) {
return null;
}
for (String fieldCode : fieldCodes) {
if (StringUtils.isBlank(fieldCode)) {
continue;
}
EmsSiteMonitorPointMatch match = mappingByField.get(fieldCode);
if (match != null && StringUtils.isNotBlank(match.getDataPoint())) {
return match.getDataPoint().trim();
}
}
return null;
}
private List<InfluxPointDataWriter.PointValue> queryInfluxPointValues(String siteId, String pointId, Date startDate, Date endDate) {
if (StringUtils.isBlank(siteId) || StringUtils.isBlank(pointId) || startDate == null || endDate == null) {
return Collections.emptyList();
}
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveDataByPointKey(siteId, pointId, startDate, endDate);
if (CollectionUtils.isEmpty(values)) {
return Collections.emptyList();
}
values.sort(Comparator.comparing(InfluxPointDataWriter.PointValue::getDataTime, Comparator.nullsLast(Date::compareTo)));
return values;
}
// 根据site_id获取pcs详细数据+支路数据 // 根据site_id获取pcs详细数据+支路数据
@Override @Override
public List<PcsDetailInfoVo> getPcsDetailInfo(String siteId) { public List<PcsDetailInfoVo> getPcsDetailInfo(String siteId) {
@ -472,17 +675,27 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
if (!StringUtils.isEmpty(siteId)) { if (!StringUtils.isEmpty(siteId)) {
// 获取该设备下所有pcs的id // 获取该设备下所有pcs的id
List<Map<String, Object>> pcsIds = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.PCS.getCode()); List<Map<String, Object>> pcsIds = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.PCS.getCode());
List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList);
Map<String, InfluxPointDataWriter.PointValue> latestPointCache = new HashMap<>();
for (Map<String, Object> pcsDevice : pcsIds) { for (Map<String, Object> pcsDevice : pcsIds) {
PcsDetailInfoVo pcsDetailInfoVo = new PcsDetailInfoVo(); PcsDetailInfoVo pcsDetailInfoVo = new PcsDetailInfoVo();
pcsDetailInfoVo.setDeviceName(pcsDevice.get("deviceName").toString()); String pcsId = String.valueOf(pcsDevice.get(DEVICE_INFO_ID));
pcsDetailInfoVo.setCommunicationStatus(pcsDevice.get("communicationStatus") == null ? pcsDetailInfoVo.setSiteId(siteId);
"" :pcsDevice.get("communicationStatus").toString()); pcsDetailInfoVo.setDeviceId(pcsId);
// 从redis取pcs单个详细数据 pcsDetailInfoVo.setDeviceName(String.valueOf(pcsDevice.get(DEVICE_INFO_NAME)));
String pcsId = pcsDevice.get("id").toString();
EmsPcsData pcsData = redisCache.getCacheObject(RedisKeyConstants.PCS +siteId+"_"+pcsId); fillPcsDetailByLatestPointMapping(siteId, pcsId, pcsDetailInfoVo, mappingByFieldAndDevice, latestPointCache);
if (pcsData != null) { if (StringUtils.isBlank(pcsDetailInfoVo.getCommunicationStatus())) {
BeanUtils.copyProperties(pcsData, pcsDetailInfoVo); pcsDetailInfoVo.setCommunicationStatus(pcsDevice.get(DEVICE_INFO_COMM_STATUS) == null
? ""
: pcsDevice.get(DEVICE_INFO_COMM_STATUS).toString());
}
if (StringUtils.isBlank(pcsDetailInfoVo.getDeviceStatus())) {
pcsDetailInfoVo.setDeviceStatus(pcsDevice.get(DEVICE_INFO_DEVICE_STATUS) == null
? ""
: pcsDevice.get(DEVICE_INFO_DEVICE_STATUS).toString());
} }
// 支路信息数据 // 支路信息数据
List<PcsBranchInfo> pcsBranchInfoList = new ArrayList<>(); List<PcsBranchInfo> pcsBranchInfoList = new ArrayList<>();
@ -493,7 +706,6 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
// // 告警设备点位个数 // // 告警设备点位个数
// int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, pcsId, DeviceCategory.PCS.getCode()); // int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, pcsId, DeviceCategory.PCS.getCode());
pcsDetailInfoVo.setAlarmNum(alarmNum); pcsDetailInfoVo.setAlarmNum(alarmNum);
pcsDetailInfoVo.setDeviceStatus(pcsDevice.get("deviceStatus") == null ? "" : pcsDevice.get("deviceStatus").toString());
// 处理枚举匹配字段 // 处理枚举匹配字段
devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.PCS.getCode(), pcsDetailInfoVo); devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.PCS.getCode(), pcsDetailInfoVo);
@ -504,6 +716,114 @@ public class SingleSiteServiceImpl implements ISingleSiteService {
return pcsDetailInfoVoList; return pcsDetailInfoVoList;
} }
private void fillPcsDetailByLatestPointMapping(String siteId,
String deviceId,
PcsDetailInfoVo target,
Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
Map<String, InfluxPointDataWriter.PointValue> latestPointCache) {
if (StringUtils.isAnyBlank(siteId, deviceId) || target == null || mappingByFieldAndDevice == null) {
return;
}
BeanWrapper beanWrapper = new BeanWrapperImpl(target);
Date latestDataTime = null;
for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) {
if (pd == null || StringUtils.isBlank(pd.getName()) || pd.getWriteMethod() == null) {
continue;
}
String fieldCode = pd.getName();
if (PCS_DETAIL_META_FIELDS.contains(fieldCode)) {
continue;
}
EmsSiteMonitorPointMatch pointMatch = resolvePointMatchByFieldAndDevice(mappingByFieldAndDevice, fieldCode, deviceId);
if (pointMatch == null) {
continue;
}
Object fieldValue;
Date valueTime = null;
if (USE_FIXED_DISPLAY_YES == (pointMatch.getUseFixedDisplay() == null ? 0 : pointMatch.getUseFixedDisplay())
&& StringUtils.isNotBlank(pointMatch.getFixedDataPoint())) {
fieldValue = pointMatch.getFixedDataPoint().trim();
} else {
InfluxPointDataWriter.PointValue latestValue = getLatestPointValueByPointId(siteId, pointMatch.getDataPoint(), latestPointCache);
if (latestValue == null || latestValue.getPointValue() == null) {
continue;
}
fieldValue = latestValue.getPointValue();
valueTime = latestValue.getDataTime();
}
Object convertedValue = convertFieldValueByType(fieldValue, pd.getPropertyType());
if (convertedValue == null) {
continue;
}
beanWrapper.setPropertyValue(fieldCode, convertedValue);
if (valueTime != null && (latestDataTime == null || valueTime.after(latestDataTime))) {
latestDataTime = valueTime;
}
}
if (latestDataTime != null) {
target.setDataUpdateTime(latestDataTime);
}
}
private EmsSiteMonitorPointMatch resolvePointMatchByFieldAndDevice(Map<String, EmsSiteMonitorPointMatch> mappingByFieldAndDevice,
String fieldCode,
String deviceId) {
if (mappingByFieldAndDevice == null || StringUtils.isBlank(fieldCode)) {
return null;
}
String normalizedField = fieldCode.trim();
String normalizedDeviceId = StringUtils.defaultString(deviceId).trim();
EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildFieldDeviceKey(normalizedField, normalizedDeviceId));
if (exact != null) {
return exact;
}
return mappingByFieldAndDevice.get(buildFieldDeviceKey(normalizedField, ""));
}
private InfluxPointDataWriter.PointValue getLatestPointValueByPointId(String siteId,
String pointId,
Map<String, InfluxPointDataWriter.PointValue> latestPointCache) {
if (StringUtils.isAnyBlank(siteId, pointId)) {
return null;
}
String pointCacheKey = pointId.trim().toUpperCase();
if (latestPointCache.containsKey(pointCacheKey)) {
return latestPointCache.get(pointCacheKey);
}
Date endTime = DateUtils.getNowDate();
Date fastStartTime = DateUtils.addDays(endTime, -1);
InfluxPointDataWriter.PointValue latest = influxPointDataWriter.queryLatestPointValueByPointKey(
siteId.trim(), pointId.trim(), fastStartTime, endTime
);
if (latest == null) {
Date fallbackStartTime = DateUtils.addDays(endTime, -7);
latest = influxPointDataWriter.queryLatestPointValueByPointKey(
siteId.trim(), pointId.trim(), fallbackStartTime, endTime
);
}
latestPointCache.put(pointCacheKey, latest);
return latest;
}
private Object convertFieldValueByType(Object rawValue, Class<?> targetType) {
if (rawValue == null || targetType == null) {
return null;
}
if (BigDecimal.class.equals(targetType)) {
return StringUtils.getBigDecimal(rawValue);
}
if (String.class.equals(targetType)) {
if (rawValue instanceof BigDecimal) {
return ((BigDecimal) rawValue).stripTrailingZeros().toPlainString();
}
return String.valueOf(rawValue);
}
if (Date.class.equals(targetType) && rawValue instanceof Date) {
return rawValue;
}
return null;
}
private void processBranchDataInfo(String siteId, String pcsId, List<PcsBranchInfo> pcsBranchInfoList) { private void processBranchDataInfo(String siteId, String pcsId, List<PcsBranchInfo> pcsBranchInfoList) {
if (!StringUtils.isEmpty(pcsId)) { if (!StringUtils.isEmpty(pcsId)) {
List<EmsPcsBranchData> pcsBranchData = redisCache.getCacheObject(RedisKeyConstants.BRANCH +siteId+"_"+pcsId); List<EmsPcsBranchData> pcsBranchData = redisCache.getCacheObject(RedisKeyConstants.BRANCH +siteId+"_"+pcsId);

View File

@ -213,7 +213,8 @@
</select> </select>
<select id="getPointNameList" resultType="com.xzzn.ems.domain.vo.GeneralQueryPointOptionVo"> <select id="getPointNameList" resultType="com.xzzn.ems.domain.vo.GeneralQueryPointOptionVo">
select point_name as pointName, select point_id as pointId,
point_name as pointName,
data_key as dataKey, data_key as dataKey,
point_desc as pointDesc point_desc as pointDesc
from ems_point_config from ems_point_config
@ -237,8 +238,8 @@
or point_desc like concat('%', #{pointName}, '%') or point_desc like concat('%', #{pointName}, '%')
) )
</if> </if>
group by point_name, data_key, point_desc group by point_id, point_name, data_key, point_desc
order by point_name asc, data_key asc order by point_id asc, point_name asc, data_key asc
</select> </select>
<select id="getConfigListForGeneralQuery" resultMap="EmsPointConfigResult"> <select id="getConfigListForGeneralQuery" resultMap="EmsPointConfigResult">
@ -250,21 +251,31 @@
#{siteId} #{siteId}
</foreach> </foreach>
</if> </if>
<if test="deviceCategory != null and deviceCategory != ''"> <choose>
and device_category = #{deviceCategory} <when test="pointIds != null and pointIds.size() > 0">
</if> and point_id in
<if test="pointNames != null and pointNames.size() > 0"> <foreach collection="pointIds" item="pointId" open="(" separator="," close=")">
and point_name in #{pointId}
<foreach collection="pointNames" item="pointName" open="(" separator="," close=")"> </foreach>
#{pointName} </when>
</foreach> <otherwise>
</if> <if test="deviceCategory != null and deviceCategory != ''">
<if test="deviceIds != null and deviceIds.size() > 0"> and device_category = #{deviceCategory}
and device_id in </if>
<foreach collection="deviceIds" item="deviceId" open="(" separator="," close=")"> <if test="pointNames != null and pointNames.size() > 0">
#{deviceId} and point_name in
</foreach> <foreach collection="pointNames" item="pointName" open="(" separator="," close=")">
</if> #{pointName}
</foreach>
</if>
<if test="deviceIds != null and deviceIds.size() > 0">
and device_id in
<foreach collection="deviceIds" item="deviceId" open="(" separator="," close=")">
#{deviceId}
</foreach>
</if>
</otherwise>
</choose>
</select> </select>
<select id="selectBySiteIdAndPointIds" resultMap="EmsPointConfigResult"> <select id="selectBySiteIdAndPointIds" resultMap="EmsPointConfigResult">

View File

@ -162,4 +162,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where site_id = #{templateSiteId} where site_id = #{templateSiteId}
</insert> </insert>
<delete id="deleteByScope">
delete from ems_point_enum_match
where site_id = #{siteId}
and device_category = #{deviceCategory}
and match_field = #{matchField}
</delete>
</mapper> </mapper>

View File

@ -14,27 +14,23 @@
<insert id="upsertHistoryJsonByMinute"> <insert id="upsertHistoryJsonByMinute">
insert into ${tableName} ( insert into ${tableName} (
site_id, statis_minute, data_json, create_by, create_time, update_by, update_time site_id, statis_minute, data_json,
hot_soc, hot_total_active_power, hot_total_reactive_power, hot_day_charged_cap, hot_day_dis_charged_cap,
create_by, create_time, update_by, update_time
) values ( ) values (
#{siteId}, #{statisMinute}, #{dataJson}, #{operName}, now(), #{operName}, now() #{siteId}, #{statisMinute}, #{dataJson},
#{hotSoc}, #{hotTotalActivePower}, #{hotTotalReactivePower}, #{hotDayChargedCap}, #{hotDayDisChargedCap},
#{operName}, now(), #{operName}, now()
) )
on duplicate key update on duplicate key update
data_json = values(data_json), data_json = values(data_json),
hot_soc = values(hot_soc),
hot_total_active_power = values(hot_total_active_power),
hot_total_reactive_power = values(hot_total_reactive_power),
hot_day_charged_cap = values(hot_day_charged_cap),
hot_day_dis_charged_cap = values(hot_day_dis_charged_cap),
update_by = values(update_by), update_by = values(update_by),
update_time = now() update_time = now()
</insert> </insert>
<update id="updateHistoryHotColumns">
update ${tableName}
set hot_soc = #{hotSoc},
hot_total_active_power = #{hotTotalActivePower},
hot_total_reactive_power = #{hotTotalReactivePower},
hot_day_charged_cap = #{hotDayChargedCap},
hot_day_dis_charged_cap = #{hotDayDisChargedCap},
update_by = #{operName},
update_time = now()
where site_id = #{siteId}
and statis_minute = #{statisMinute}
</update>
</mapper> </mapper>

View File

@ -8,6 +8,7 @@
<result property="id" column="id"/> <result property="id" column="id"/>
<result property="siteId" column="site_id"/> <result property="siteId" column="site_id"/>
<result property="fieldCode" column="field_code"/> <result property="fieldCode" column="field_code"/>
<result property="deviceId" column="device_id"/>
<result property="dataPoint" column="data_point"/> <result property="dataPoint" column="data_point"/>
<result property="fixedDataPoint" column="fixed_data_point"/> <result property="fixedDataPoint" column="fixed_data_point"/>
<result property="useFixedDisplay" column="use_fixed_display"/> <result property="useFixedDisplay" column="use_fixed_display"/>
@ -19,7 +20,7 @@
</resultMap> </resultMap>
<select id="selectBySiteId" resultMap="EmsSiteMonitorPointMatchResult"> <select id="selectBySiteId" resultMap="EmsSiteMonitorPointMatchResult">
select id, site_id, field_code, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time, remark select id, site_id, field_code, device_id, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time, remark
from ems_site_monitor_point_match from ems_site_monitor_point_match
where site_id = #{siteId} where site_id = #{siteId}
order by id asc order by id asc
@ -32,10 +33,10 @@
<insert id="insertBatch"> <insert id="insertBatch">
insert into ems_site_monitor_point_match insert into ems_site_monitor_point_match
(site_id, field_code, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time) (site_id, field_code, device_id, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time)
values values
<foreach collection="list" item="item" separator=","> <foreach collection="list" item="item" separator=",">
(#{item.siteId}, #{item.fieldCode}, #{item.dataPoint}, #{item.fixedDataPoint}, #{item.useFixedDisplay}, #{item.createBy}, now(), #{item.updateBy}, now()) (#{item.siteId}, #{item.fieldCode}, #{item.deviceId}, #{item.dataPoint}, #{item.fixedDataPoint}, #{item.useFixedDisplay}, #{item.createBy}, now(), #{item.updateBy}, now())
</foreach> </foreach>
</insert> </insert>

View File

@ -134,17 +134,17 @@
</select> </select>
<select id="getAllSiteDeviceList" parameterType="String" resultType="com.xzzn.ems.domain.vo.SiteDeviceListVo"> <select id="getAllSiteDeviceList" parameterType="String" resultType="com.xzzn.ems.domain.vo.SiteDeviceListVo">
select es.site_id as siteId,es.site_name as siteName, select ed.site_id as siteId,
ed.device_id as deviceId,ed.device_name as deviceName, ed.device_id as deviceId,ed.device_name as deviceName,
ed.device_type as deviceType,ed.device_status as deviceStatus,ed.work_status as workStatus, ed.device_type as deviceType,ed.device_status as deviceStatus,ed.work_status as workStatus,
ed.device_category as deviceCategory, ed.device_category as deviceCategory,
ed.picture_url as pictureUrl, ed.picture_url as pictureUrl,
ed.id, ed.id,
ed.parent_id as parentId ed.parent_id as parentId
from ems_site_setting es INNER JOIN ems_devices_setting ed on es.site_id = ed.site_id from ems_devices_setting ed
where 1=1 where 1=1
<if test="siteId != null and siteId != ''"> <if test="siteId != null and siteId != ''">
and es.site_id = #{siteId} and ed.site_id = #{siteId}
</if> </if>
<if test="deviceCategory != null and deviceCategory != ''"> <if test="deviceCategory != null and deviceCategory != ''">
and ed.device_category = #{deviceCategory} and ed.device_category = #{deviceCategory}
@ -153,18 +153,19 @@
</select> </select>
<select id="getAllSiteDeviceListNoDisp" parameterType="String" resultType="com.xzzn.ems.domain.vo.SiteDeviceListVo"> <select id="getAllSiteDeviceListNoDisp" parameterType="String" resultType="com.xzzn.ems.domain.vo.SiteDeviceListVo">
select es.site_id as siteId,es.site_name as siteName, select ed.site_id as siteId,
ed.device_id as deviceId,ed.device_name as deviceName, ed.device_id as deviceId,ed.device_name as deviceName,
ed.device_type as deviceType,ed.device_status as deviceStatus,ed.work_status as workStatus, ed.device_type as deviceType,ed.device_status as deviceStatus,ed.work_status as workStatus,
ed.device_category as deviceCategory, ed.device_category as deviceCategory,
ed.picture_url as pictureUrl, ed.picture_url as pictureUrl,
ed.id, ed.id,
ed.parent_id as parentId ed.parent_id as parentId
from ems_site_setting es INNER JOIN ems_devices_setting ed on es.site_id = ed.site_id and (ed.display_flg is null or ed.display_flg != '1') from ems_devices_setting ed
where 1 = 1 where 1 = 1
<if test="siteId != null and siteId != ''"> <if test="siteId != null and siteId != ''">
and es.site_id = #{siteId} and ed.site_id = #{siteId}
</if> </if>
and (ed.display_flg is null or ed.display_flg != '1')
<if test="deviceCategory != null and deviceCategory != ''"> <if test="deviceCategory != null and deviceCategory != ''">
and ed.device_category = #{deviceCategory} and ed.device_category = #{deviceCategory}
</if> </if>
@ -173,4 +174,4 @@
<select id="getAllSiteId" resultType="String"> <select id="getAllSiteId" resultType="String">
select distinct site_id from ems_site_setting select distinct site_id from ems_site_setting
</select> </select>
</mapper> </mapper>

View File

@ -14,6 +14,12 @@
<result property="antiReverseUp" column="anti_reverse_up"/> <result property="antiReverseUp" column="anti_reverse_up"/>
<result property="antiReversePowerDownPercent" column="anti_reverse_power_down_percent"/> <result property="antiReversePowerDownPercent" column="anti_reverse_power_down_percent"/>
<result property="antiReverseHardStopThreshold" column="anti_reverse_hard_stop_threshold"/> <result property="antiReverseHardStopThreshold" column="anti_reverse_hard_stop_threshold"/>
<result property="powerSetMultiplier" column="power_set_multiplier"/>
<result property="protectInterveneEnable" column="protect_intervene_enable"/>
<result property="protectL1DeratePercent" column="protect_l1_derate_percent"/>
<result property="protectRecoveryStableSeconds" column="protect_recovery_stable_seconds"/>
<result property="protectL3LatchEnable" column="protect_l3_latch_enable"/>
<result property="protectConflictPolicy" column="protect_conflict_policy"/>
<result property="createBy" column="create_by"/> <result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/> <result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/> <result property="updateBy" column="update_by"/>
@ -31,6 +37,12 @@
anti_reverse_up, anti_reverse_up,
anti_reverse_power_down_percent, anti_reverse_power_down_percent,
anti_reverse_hard_stop_threshold, anti_reverse_hard_stop_threshold,
power_set_multiplier,
protect_intervene_enable,
protect_l1_derate_percent,
protect_recovery_stable_seconds,
protect_l3_latch_enable,
protect_conflict_policy,
create_by, create_by,
create_time, create_time,
update_by, update_by,
@ -56,6 +68,12 @@
<if test="antiReverseUp != null">anti_reverse_up,</if> <if test="antiReverseUp != null">anti_reverse_up,</if>
<if test="antiReversePowerDownPercent != null">anti_reverse_power_down_percent,</if> <if test="antiReversePowerDownPercent != null">anti_reverse_power_down_percent,</if>
<if test="antiReverseHardStopThreshold != null">anti_reverse_hard_stop_threshold,</if> <if test="antiReverseHardStopThreshold != null">anti_reverse_hard_stop_threshold,</if>
<if test="powerSetMultiplier != null">power_set_multiplier,</if>
<if test="protectInterveneEnable != null">protect_intervene_enable,</if>
<if test="protectL1DeratePercent != null">protect_l1_derate_percent,</if>
<if test="protectRecoveryStableSeconds != null">protect_recovery_stable_seconds,</if>
<if test="protectL3LatchEnable != null">protect_l3_latch_enable,</if>
<if test="protectConflictPolicy != null and protectConflictPolicy != ''">protect_conflict_policy,</if>
<if test="createBy != null">create_by,</if> <if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if> <if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if> <if test="updateBy != null">update_by,</if>
@ -71,6 +89,12 @@
<if test="antiReverseUp != null">#{antiReverseUp},</if> <if test="antiReverseUp != null">#{antiReverseUp},</if>
<if test="antiReversePowerDownPercent != null">#{antiReversePowerDownPercent},</if> <if test="antiReversePowerDownPercent != null">#{antiReversePowerDownPercent},</if>
<if test="antiReverseHardStopThreshold != null">#{antiReverseHardStopThreshold},</if> <if test="antiReverseHardStopThreshold != null">#{antiReverseHardStopThreshold},</if>
<if test="powerSetMultiplier != null">#{powerSetMultiplier},</if>
<if test="protectInterveneEnable != null">#{protectInterveneEnable},</if>
<if test="protectL1DeratePercent != null">#{protectL1DeratePercent},</if>
<if test="protectRecoveryStableSeconds != null">#{protectRecoveryStableSeconds},</if>
<if test="protectL3LatchEnable != null">#{protectL3LatchEnable},</if>
<if test="protectConflictPolicy != null and protectConflictPolicy != ''">#{protectConflictPolicy},</if>
<if test="createBy != null">#{createBy},</if> <if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if> <if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if> <if test="updateBy != null">#{updateBy},</if>
@ -89,6 +113,12 @@
<if test="antiReverseUp != null">anti_reverse_up = #{antiReverseUp},</if> <if test="antiReverseUp != null">anti_reverse_up = #{antiReverseUp},</if>
<if test="antiReversePowerDownPercent != null">anti_reverse_power_down_percent = #{antiReversePowerDownPercent},</if> <if test="antiReversePowerDownPercent != null">anti_reverse_power_down_percent = #{antiReversePowerDownPercent},</if>
<if test="antiReverseHardStopThreshold != null">anti_reverse_hard_stop_threshold = #{antiReverseHardStopThreshold},</if> <if test="antiReverseHardStopThreshold != null">anti_reverse_hard_stop_threshold = #{antiReverseHardStopThreshold},</if>
<if test="powerSetMultiplier != null">power_set_multiplier = #{powerSetMultiplier},</if>
<if test="protectInterveneEnable != null">protect_intervene_enable = #{protectInterveneEnable},</if>
<if test="protectL1DeratePercent != null">protect_l1_derate_percent = #{protectL1DeratePercent},</if>
<if test="protectRecoveryStableSeconds != null">protect_recovery_stable_seconds = #{protectRecoveryStableSeconds},</if>
<if test="protectL3LatchEnable != null">protect_l3_latch_enable = #{protectL3LatchEnable},</if>
<if test="protectConflictPolicy != null and protectConflictPolicy != ''">protect_conflict_policy = #{protectConflictPolicy},</if>
<if test="updateBy != null">update_by = #{updateBy},</if> <if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if> <if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if> <if test="remark != null">remark = #{remark},</if>