diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointCalcConfigController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointCalcConfigController.java new file mode 100644 index 0000000..465054c --- /dev/null +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointCalcConfigController.java @@ -0,0 +1,51 @@ +package com.xzzn.web.controller.ems; + +import com.xzzn.common.annotation.Log; +import com.xzzn.common.core.controller.BaseController; +import com.xzzn.common.core.domain.AjaxResult; +import com.xzzn.common.core.page.TableDataInfo; +import com.xzzn.common.enums.BusinessType; +import com.xzzn.ems.domain.EmsPointCalcConfig; +import com.xzzn.ems.service.IEmsPointCalcConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/ems/pointCalcConfig") +public class EmsPointCalcConfigController extends BaseController { + + @Autowired + private IEmsPointCalcConfigService pointCalcConfigService; + + @GetMapping("/list") + public TableDataInfo list(EmsPointCalcConfig pointCalcConfig) { + startPage(); + List list = pointCalcConfigService.selectPointCalcConfigList(pointCalcConfig); + return getDataTable(list); + } + + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(pointCalcConfigService.selectPointCalcConfigById(id)); + } + + @Log(title = "计算点配置", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody EmsPointCalcConfig pointCalcConfig) { + return toAjax(pointCalcConfigService.insertPointCalcConfig(pointCalcConfig, getUsername())); + } + + @Log(title = "计算点配置", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody EmsPointCalcConfig pointCalcConfig) { + return toAjax(pointCalcConfigService.updatePointCalcConfig(pointCalcConfig, getUsername())); + } + + @Log(title = "计算点配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(pointCalcConfigService.deletePointCalcConfigByIds(ids)); + } +} diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStrategyRuntimeConfigController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStrategyRuntimeConfigController.java new file mode 100644 index 0000000..0067ecc --- /dev/null +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStrategyRuntimeConfigController.java @@ -0,0 +1,48 @@ +package com.xzzn.web.controller.ems; + +import com.xzzn.common.core.controller.BaseController; +import com.xzzn.common.core.domain.AjaxResult; +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; +import com.xzzn.ems.service.IEmsStrategyRuntimeConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 策略运行参数配置Controller + */ +@RestController +@RequestMapping("/system/strategyRuntimeConfig") +public class EmsStrategyRuntimeConfigController extends BaseController { + + @Autowired + private IEmsStrategyRuntimeConfigService runtimeConfigService; + + /** + * 按站点ID获取策略运行参数 + */ + @GetMapping("/getBySiteId") + public AjaxResult getBySiteId(String siteId) { + if (StringUtils.isEmpty(siteId)) { + return error("缺少必填字段siteId"); + } + return success(runtimeConfigService.getBySiteId(siteId)); + } + + /** + * 保存策略运行参数(按siteId新增/更新) + */ + @PostMapping("/save") + public AjaxResult save(@RequestBody EmsStrategyRuntimeConfig config) { + if (config == null || StringUtils.isEmpty(config.getSiteId())) { + return error("缺少必填字段siteId"); + } + config.setCreateBy(getUsername()); + config.setUpdateBy(getUsername()); + return toAjax(runtimeConfigService.saveBySiteId(config)); + } +} diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java index be613db..7ec40b3 100644 --- a/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java @@ -16,6 +16,7 @@ import com.xzzn.ems.domain.EmsAmmeterData; import com.xzzn.ems.domain.EmsBatteryStack; import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsPcsSetting; +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; import com.xzzn.ems.domain.EmsStrategyLog; import com.xzzn.ems.domain.EmsStrategyTemp; import com.xzzn.ems.domain.EmsStrategyTimeConfig; @@ -25,17 +26,20 @@ import com.xzzn.ems.mapper.EmsBatteryStackMapper; import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsPcsSettingMapper; import com.xzzn.ems.mapper.EmsStrategyLogMapper; +import com.xzzn.ems.mapper.EmsStrategyRuntimeConfigMapper; import com.xzzn.ems.mapper.EmsStrategyRunningMapper; import com.xzzn.ems.mapper.EmsStrategyTempMapper; import com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -57,16 +61,20 @@ public class StrategyPoller { private static final ConcurrentHashMap strategyLocks = new ConcurrentHashMap<>(); // SOC 上下限值,默认为0%-100% - private static final BigDecimal SOC_DOWN = new BigDecimal(0); - private static final BigDecimal SOC_UP = new BigDecimal(100); + private static final BigDecimal DEFAULT_SOC_DOWN = BigDecimal.ZERO; + private static final BigDecimal DEFAULT_SOC_UP = new BigDecimal(100); // 逆变器功率下限值,默认为30kW - private static final BigDecimal ANTI_REVERSE_THRESHOLD = new BigDecimal(30); + private static final BigDecimal DEFAULT_ANTI_REVERSE_THRESHOLD = new BigDecimal(30); // 逆变器下限值范围,默认为20% - private static final BigDecimal ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20); + private static final BigDecimal DEFAULT_ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20); // 逆变器功率上限值,默认为100kW - private static final BigDecimal ANTI_REVERSE_UP = new BigDecimal(100); + private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal(100); // PCS功率降幅,默认为10% - private static final BigDecimal ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10); + private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10); + // 电网有功功率低于20kW时,强制待机 + private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal(20); + // 除法精度,避免BigDecimal除不尽异常 + private static final int POWER_SCALE = 4; @Autowired private EmsStrategyRunningMapper emsStrategyRunningMapper; @@ -85,6 +93,8 @@ public class StrategyPoller { @Autowired private EmsStrategyLogMapper emsStrategyLogMapper; @Autowired + private EmsStrategyRuntimeConfigMapper runtimeConfigMapper; + @Autowired private ModbusProcessor modbusProcessor; @Resource(name = "modbusExecutor") @@ -132,6 +142,7 @@ public class StrategyPoller { } private void dealStrategyCurveData(Long strategyId, String siteId) { + EmsStrategyRuntimeConfig runtimeConfig = getRuntimeConfig(siteId); // 1.获取当前策略的所有模板 List> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId); if (CollectionUtils.isEmpty(temps)) { @@ -174,24 +185,38 @@ public class StrategyPoller { continue; } // 判断SOC上下限 - if (isSocInRange(emsStrategyTemp)) { - BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower().divide(new BigDecimal(pcsDeviceList.size())); + if (isSocInRange(emsStrategyTemp, runtimeConfig)) { + Map pcsSettingCache = new HashMap<>(); + BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower() + .divide(new BigDecimal(pcsDeviceList.size()), POWER_SCALE, RoundingMode.HALF_UP); + BigDecimal totalActivePower = null; + if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { + // 同一站点同一轮执行只读取一次电网电表,降低重复查库和数据抖动 + EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name()); + if (emsAmmeterData != null) { + totalActivePower = emsAmmeterData.getTotalActivePower(); + } + } for (EmsDevicesSetting pcsDevice : pcsDeviceList) { - EmsPcsSetting pcsSetting = emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(pcsDevice.getId()); + EmsPcsSetting pcsSetting = pcsSettingCache.computeIfAbsent( + pcsDevice.getId(), + id -> emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(id) + ); if (pcsSetting == null || pcsSetting.getClusterNum() < 1) { logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId()); continue; } // 功率默认放大10倍,平均功率值,根据电池簇数量进行平均分配 - avgChargeDischargePower = avgChargeDischargePower.multiply(new BigDecimal(10)).divide(new BigDecimal(pcsSetting.getClusterNum())); + BigDecimal strategyPower = avgChargeDischargePower.multiply(new BigDecimal(10)) + .divide(new BigDecimal(pcsSetting.getClusterNum()), POWER_SCALE, RoundingMode.HALF_UP); // 根据充电状态,处理数据 if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { // 发送Modbus命令控制设备-充电 - sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, avgChargeDischargePower, emsStrategyTemp, false, null); + sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, strategyPower, emsStrategyTemp, false, null); } else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { boolean needAntiReverseFlow = false; Integer powerDownType = null; - BigDecimal chargeDischargePower = avgChargeDischargePower; + BigDecimal chargeDischargePower = strategyPower; // 查询策略运行日志 EmsStrategyLog lastStrategyLog = getLastStrategyLog(pcsDevice.getDeviceId(), emsStrategyTemp); if (lastStrategyLog != null) { @@ -204,32 +229,54 @@ public class StrategyPoller { } // 查询电网电表的正向有功功率,36kW-50kW范围内,稳定运行,低于36kW,降功率,高于50kW,增加功率 - EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name()); - if (emsAmmeterData == null || emsAmmeterData.getTotalActivePower() == null) { - logger.info("当前站点: {}, 未获取到最新电表数据", emsStrategyTemp.getSiteId()); + if (totalActivePower == null) { + logger.warn("当前站点: {}, 未获取到最新电表数据,执行保守策略并切换待机", emsStrategyTemp.getSiteId()); + sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, true, 0); + continue; } else { + // 电网功率过低,直接待机,不再放电 + if (totalActivePower.compareTo(runtimeConfig.getAntiReverseHardStopThreshold()) < 0) { + sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, true, 0); + continue; + } + // 放电开始先按差值限幅:差值=电网功率-防逆流阈值 + BigDecimal diffPower = totalActivePower.subtract(runtimeConfig.getAntiReverseThreshold()); + BigDecimal targetPower = diffPower.compareTo(BigDecimal.ZERO) > 0 ? diffPower : BigDecimal.ZERO; + if (targetPower.compareTo(strategyPower) > 0) { + targetPower = strategyPower; + } + if (chargeDischargePower.compareTo(targetPower) > 0) { + chargeDischargePower = targetPower; + } // 判断是否需要防逆流 - needAntiReverseFlow = isNeedAntiReverseFlow(emsAmmeterData.getTotalActivePower()); - BigDecimal power = avgChargeDischargePower.multiply(ANTI_REVERSE_POWER_DOWN_PERCENT).divide(new BigDecimal(100)); + needAntiReverseFlow = isNeedAntiReverseFlow(totalActivePower, runtimeConfig); + BigDecimal power = strategyPower.multiply(runtimeConfig.getAntiReversePowerDownPercent()) + .divide(new BigDecimal(100), POWER_SCALE, RoundingMode.HALF_UP); if (needAntiReverseFlow) { // 降功率 chargeDischargePower = chargeDischargePower.subtract(power); powerDownType = 0; } else { // 判断是否需要增加功率, - if (powerDownType != null && emsAmmeterData.getTotalActivePower().compareTo(ANTI_REVERSE_UP) > 0) { - if (chargeDischargePower.compareTo(avgChargeDischargePower) == 0) { - // 功率增加到平均值则停止 + if (powerDownType != null && totalActivePower.compareTo(runtimeConfig.getAntiReverseUp()) > 0) { + if (chargeDischargePower.compareTo(targetPower) >= 0) { + // 功率增加到限幅值则停止 continue; } // 增加功率 chargeDischargePower = chargeDischargePower.add(power); + if (chargeDischargePower.compareTo(targetPower) > 0) { + chargeDischargePower = targetPower; + } powerDownType = 1; needAntiReverseFlow = true; } } } + if (chargeDischargePower.compareTo(BigDecimal.ZERO) < 0) { + chargeDischargePower = BigDecimal.ZERO; + } if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) { // 如果已经降功率到0,则设备直接待机 // 发送Modbus命令控制设备-待机 @@ -297,11 +344,11 @@ public class StrategyPoller { return emsStrategyLogMapper.getLastStrategyLog(query); } - private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower) { + private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower, EmsStrategyRuntimeConfig runtimeConfig) { // 获取当前设定的防逆流阈值(30kW) - BigDecimal threshold = ANTI_REVERSE_THRESHOLD; + BigDecimal threshold = runtimeConfig.getAntiReverseThreshold(); // 计算20%范围的上限(36kW) - BigDecimal upperLimit = threshold.multiply(ANTI_REVERSE_RANGE_PERCENT).divide(new BigDecimal(100)).add(threshold); + BigDecimal upperLimit = threshold.multiply(runtimeConfig.getAntiReverseRangePercent()).divide(new BigDecimal(100)).add(threshold); // 判断电网电表正向有功功率是否小于36kW(接近30kW的20%范围) return totalActivePower.compareTo(upperLimit) < 0; @@ -409,8 +456,9 @@ public class StrategyPoller { continue; } else { // 充、放电,则先开机设备 - switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL); - continue; + if (!switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL)) { + continue; + } } } @@ -440,14 +488,17 @@ public class StrategyPoller { private boolean switchDevice(EmsDevicesSetting pcsDevice, EmsPcsSetting pcsSetting, WorkStatus workStatus) { String siteId = pcsDevice.getSiteId(); String deviceId = pcsDevice.getDeviceId(); + String originalWorkStatus = pcsDevice.getWorkStatus(); pcsDevice.setWorkStatus(workStatus.getCode()); DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting , null, 1); if (deviceConfig == null) { + pcsDevice.setWorkStatus(originalWorkStatus); return false; } boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig); if (!result) { - logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceConfig, workStatus.getInfo()); + pcsDevice.setWorkStatus(originalWorkStatus); + logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, workStatus.getInfo()); } return result; } @@ -461,13 +512,17 @@ public class StrategyPoller { LocalTime endLocalTime = endTime.toInstant() .atZone(zoneId) .toLocalTime(); - return now.equals(startLocalTime) || (now.isAfter(startLocalTime) && now.isBefore(endLocalTime)); + // 支持跨天时段,如23:00-01:00;边界采用闭区间 + if (!startLocalTime.isAfter(endLocalTime)) { + return !now.isBefore(startLocalTime) && !now.isAfter(endLocalTime); + } + return !now.isBefore(startLocalTime) || !now.isAfter(endLocalTime); } // 判断SOC上限和下限 - private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp) { - BigDecimal socDown = SOC_DOWN; - BigDecimal socUp = SOC_UP; + private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp, EmsStrategyRuntimeConfig runtimeConfig) { + BigDecimal socDown = runtimeConfig.getSocDown(); + BigDecimal socUp = runtimeConfig.getSocUp(); if (SocLimit.ON.getCode().equals(emsStrategyTemp.getSdcLimit())) { socDown = emsStrategyTemp.getSdcDown(); socUp = emsStrategyTemp.getSdcUp(); @@ -491,4 +546,34 @@ public class StrategyPoller { return true; } + private EmsStrategyRuntimeConfig getRuntimeConfig(String siteId) { + EmsStrategyRuntimeConfig config = runtimeConfigMapper.selectBySiteId(siteId); + if (config == null) { + config = new EmsStrategyRuntimeConfig(); + config.setSiteId(siteId); + } + if (config.getSocDown() == null) { + config.setSocDown(DEFAULT_SOC_DOWN); + } + if (config.getSocUp() == null) { + config.setSocUp(DEFAULT_SOC_UP); + } + if (config.getAntiReverseThreshold() == null) { + config.setAntiReverseThreshold(DEFAULT_ANTI_REVERSE_THRESHOLD); + } + if (config.getAntiReverseRangePercent() == null) { + config.setAntiReverseRangePercent(DEFAULT_ANTI_REVERSE_RANGE_PERCENT); + } + if (config.getAntiReverseUp() == null) { + config.setAntiReverseUp(DEFAULT_ANTI_REVERSE_UP); + } + if (config.getAntiReversePowerDownPercent() == null) { + config.setAntiReversePowerDownPercent(DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT); + } + if (config.getAntiReverseHardStopThreshold() == null) { + config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD); + } + return config; + } + } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsSetting.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsSetting.java index 2327f61..7b71d6a 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsSetting.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsSetting.java @@ -49,6 +49,10 @@ public class EmsPcsSetting extends BaseEntity @Excel(name = "关机目标功率") private BigDecimal stopPower; + /** 目标功率倍率 */ + @Excel(name = "目标功率倍率") + private BigDecimal powerMultiplier; + /** 电池簇数 */ @Excel(name = "电池簇数") private Integer clusterNum; @@ -135,6 +139,16 @@ public class EmsPcsSetting extends BaseEntity return stopPower; } + public BigDecimal getPowerMultiplier() + { + return powerMultiplier; + } + + public void setPowerMultiplier(BigDecimal powerMultiplier) + { + this.powerMultiplier = powerMultiplier; + } + public void setClusterNum(Integer clusterNum) { this.clusterNum = clusterNum; @@ -165,6 +179,7 @@ public class EmsPcsSetting extends BaseEntity .append("stopCommand", getStopCommand()) .append("startPower", getStartPower()) .append("stopPower", getStopPower()) + .append("powerMultiplier", getPowerMultiplier()) .append("clusterNum", getClusterNum()) .append("clusterPointAddress", getClusterPointAddress()) .append("createBy", getCreateBy()) diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointCalcConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointCalcConfig.java new file mode 100644 index 0000000..7c628bd --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointCalcConfig.java @@ -0,0 +1,128 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.annotation.Excel; +import com.xzzn.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class EmsPointCalcConfig extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + + @Excel(name = "点位ID") + private String pointId; + + @Excel(name = "站点ID") + private String siteId; + + @Excel(name = "设备类型") + private String deviceCategory; + + @Excel(name = "点位名称") + private String pointName; + + @Excel(name = "数据键") + private String dataKey; + + @Excel(name = "点位描述") + private String pointDesc; + + @Excel(name = "单位") + private String dataUnit; + + @Excel(name = "计算表达式") + private String calcExpression; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + public String getDeviceCategory() { + return deviceCategory; + } + + public void setDeviceCategory(String deviceCategory) { + this.deviceCategory = deviceCategory; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } + + public String getPointDesc() { + return pointDesc; + } + + public void setPointDesc(String pointDesc) { + this.pointDesc = pointDesc; + } + + public String getDataUnit() { + return dataUnit; + } + + public void setDataUnit(String dataUnit) { + this.dataUnit = dataUnit; + } + + public String getCalcExpression() { + return calcExpression; + } + + public void setCalcExpression(String calcExpression) { + this.calcExpression = calcExpression; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("pointId", getPointId()) + .append("siteId", getSiteId()) + .append("deviceCategory", getDeviceCategory()) + .append("pointName", getPointName()) + .append("dataKey", getDataKey()) + .append("pointDesc", getPointDesc()) + .append("dataUnit", getDataUnit()) + .append("calcExpression", getCalcExpression()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java index 309c0ea..c8a144b 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java @@ -15,6 +15,9 @@ public class EmsPointConfig extends BaseEntity { private Long id; + @Excel(name = "点位ID") + private String pointId; + @Excel(name = "站点ID") private String siteId; @@ -68,6 +71,14 @@ public class EmsPointConfig extends BaseEntity { this.id = id; } + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + public String getSiteId() { return siteId; } @@ -192,6 +203,7 @@ public class EmsPointConfig extends BaseEntity { public String toString() { return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) .append("id", getId()) + .append("pointId", getPointId()) .append("siteId", getSiteId()) .append("deviceCategory", getDeviceCategory()) .append("deviceId", getDeviceId()) diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java index e6db1d3..206ebf4 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java @@ -12,6 +12,8 @@ public class EmsSiteMonitorPointMatch extends BaseEntity { private String siteId; private String fieldCode; private String dataPoint; + private String fixedDataPoint; + private Integer useFixedDisplay; public Long getId() { return id; @@ -44,4 +46,20 @@ public class EmsSiteMonitorPointMatch extends BaseEntity { public void setDataPoint(String dataPoint) { this.dataPoint = dataPoint; } + + public String getFixedDataPoint() { + return fixedDataPoint; + } + + public void setFixedDataPoint(String fixedDataPoint) { + this.fixedDataPoint = fixedDataPoint; + } + + public Integer getUseFixedDisplay() { + return useFixedDisplay; + } + + public void setUseFixedDisplay(Integer useFixedDisplay) { + this.useFixedDisplay = useFixedDisplay; + } } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyRuntimeConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyRuntimeConfig.java new file mode 100644 index 0000000..a2058a1 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyRuntimeConfig.java @@ -0,0 +1,144 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.annotation.Excel; +import com.xzzn.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.math.BigDecimal; + +/** + * 策略运行参数配置对象 ems_strategy_runtime_config + * + * @author xzzn + * @date 2026-02-13 + */ +public class EmsStrategyRuntimeConfig extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** 主键 */ + private Long id; + + /** 站点ID */ + @Excel(name = "站点ID") + private String siteId; + + /** SOC下限(%) */ + @Excel(name = "SOC下限(%)") + private BigDecimal socDown; + + /** SOC上限(%) */ + @Excel(name = "SOC上限(%)") + private BigDecimal socUp; + + /** 防逆流阈值(kW) */ + @Excel(name = "防逆流阈值(kW)") + private BigDecimal antiReverseThreshold; + + /** 防逆流阈值上浮比例(%) */ + @Excel(name = "防逆流阈值上浮比例(%)") + private BigDecimal antiReverseRangePercent; + + /** 防逆流恢复上限(kW) */ + @Excel(name = "防逆流恢复上限(kW)") + private BigDecimal antiReverseUp; + + /** 防逆流降功率比例(%) */ + @Excel(name = "防逆流降功率比例(%)") + private BigDecimal antiReversePowerDownPercent; + /** 防逆流硬停阈值(kW) */ + @Excel(name = "防逆流硬停阈值(kW)") + private BigDecimal antiReverseHardStopThreshold; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public BigDecimal getSocDown() { + return socDown; + } + + public void setSocDown(BigDecimal socDown) { + this.socDown = socDown; + } + + public BigDecimal getSocUp() { + return socUp; + } + + public void setSocUp(BigDecimal socUp) { + this.socUp = socUp; + } + + public BigDecimal getAntiReverseThreshold() { + return antiReverseThreshold; + } + + public void setAntiReverseThreshold(BigDecimal antiReverseThreshold) { + this.antiReverseThreshold = antiReverseThreshold; + } + + public BigDecimal getAntiReverseRangePercent() { + return antiReverseRangePercent; + } + + public void setAntiReverseRangePercent(BigDecimal antiReverseRangePercent) { + this.antiReverseRangePercent = antiReverseRangePercent; + } + + public BigDecimal getAntiReverseUp() { + return antiReverseUp; + } + + public void setAntiReverseUp(BigDecimal antiReverseUp) { + this.antiReverseUp = antiReverseUp; + } + + public BigDecimal getAntiReversePowerDownPercent() { + return antiReversePowerDownPercent; + } + + public void setAntiReversePowerDownPercent(BigDecimal antiReversePowerDownPercent) { + this.antiReversePowerDownPercent = antiReversePowerDownPercent; + } + + public BigDecimal getAntiReverseHardStopThreshold() { + return antiReverseHardStopThreshold; + } + + public void setAntiReverseHardStopThreshold(BigDecimal antiReverseHardStopThreshold) { + this.antiReverseHardStopThreshold = antiReverseHardStopThreshold; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("siteId", getSiteId()) + .append("socDown", getSocDown()) + .append("socUp", getSocUp()) + .append("antiReverseThreshold", getAntiReverseThreshold()) + .append("antiReverseRangePercent", getAntiReverseRangePercent()) + .append("antiReverseUp", getAntiReverseUp()) + .append("antiReversePowerDownPercent", getAntiReversePowerDownPercent()) + .append("antiReverseHardStopThreshold", getAntiReverseHardStopThreshold()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyTempTimeConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyTempTimeConfig.java index 1cf9f9f..746d511 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyTempTimeConfig.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyTempTimeConfig.java @@ -35,6 +35,14 @@ public class EmsStrategyTempTimeConfig extends BaseEntity @Excel(name = "充放功率 (kW)") private BigDecimal chargeDischargePower; + /** SDC下限 (%) */ + @Excel(name = "SDC下限 (%)") + private BigDecimal sdcDown; + + /** SDC上限 (%) */ + @Excel(name = "SDC上限 (%)") + private BigDecimal sdcUp; + /** 充电状态,如“1-充电”、“2-待机” */ @Excel(name = "充电状态,如“1-充电”、“2-待机”") private String chargeStatus; @@ -83,6 +91,26 @@ public class EmsStrategyTempTimeConfig extends BaseEntity return chargeDischargePower; } + public void setSdcDown(BigDecimal sdcDown) + { + this.sdcDown = sdcDown; + } + + public BigDecimal getSdcDown() + { + return sdcDown; + } + + public void setSdcUp(BigDecimal sdcUp) + { + this.sdcUp = sdcUp; + } + + public BigDecimal getSdcUp() + { + return sdcUp; + } + public void setChargeStatus(String chargeStatus) { this.chargeStatus = chargeStatus; @@ -110,6 +138,8 @@ public class EmsStrategyTempTimeConfig extends BaseEntity .append("startTime", getStartTime()) .append("endTime", getEndTime()) .append("chargeDischargePower", getChargeDischargePower()) + .append("sdcDown", getSdcDown()) + .append("sdcUp", getSdcUp()) .append("chargeStatus", getChargeStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java index 7777305..fc15700 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java @@ -3,7 +3,9 @@ package com.xzzn.ems.domain.vo; public class PointConfigCurveRequest { private String siteId; private String deviceId; + private String pointId; private String dataKey; + private String pointType; private String rangeType; private String startTime; private String endTime; @@ -24,6 +26,14 @@ public class PointConfigCurveRequest { this.deviceId = deviceId; } + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + public String getDataKey() { return dataKey; } @@ -32,6 +42,14 @@ public class PointConfigCurveRequest { this.dataKey = dataKey; } + public String getPointType() { + return pointType; + } + + public void setPointType(String pointType) { + this.pointType = pointType; + } + public String getRangeType() { return rangeType; } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java index c1df3ab..4dc6369 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java @@ -8,6 +8,8 @@ public class SiteMonitorProjectPointMappingSaveRequest { private List mappings; + private List deletedFieldCodes; + public String getSiteId() { return siteId; } @@ -23,4 +25,12 @@ public class SiteMonitorProjectPointMappingSaveRequest { public void setMappings(List mappings) { this.mappings = mappings; } + + public List getDeletedFieldCodes() { + return deletedFieldCodes; + } + + public void setDeletedFieldCodes(List deletedFieldCodes) { + this.deletedFieldCodes = deletedFieldCodes; + } } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java index 3d518ed..b9ce240 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java @@ -18,6 +18,10 @@ public class SiteMonitorProjectPointMappingVo { private String dataPoint; + private String fixedDataPoint; + + private Integer useFixedDisplay; + public String getModuleCode() { return moduleCode; } @@ -81,4 +85,20 @@ public class SiteMonitorProjectPointMappingVo { public void setDataPoint(String dataPoint) { this.dataPoint = dataPoint; } + + public String getFixedDataPoint() { + return fixedDataPoint; + } + + public void setFixedDataPoint(String fixedDataPoint) { + this.fixedDataPoint = fixedDataPoint; + } + + public Integer getUseFixedDisplay() { + return useFixedDisplay; + } + + public void setUseFixedDisplay(Integer useFixedDisplay) { + this.useFixedDisplay = useFixedDisplay; + } } diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointCalcConfigMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointCalcConfigMapper.java new file mode 100644 index 0000000..edaef0a --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointCalcConfigMapper.java @@ -0,0 +1,24 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsPointCalcConfig; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface EmsPointCalcConfigMapper { + EmsPointCalcConfig selectEmsPointCalcConfigById(Long id); + + List selectEmsPointCalcConfigList(EmsPointCalcConfig emsPointCalcConfig); + + int insertEmsPointCalcConfig(EmsPointCalcConfig emsPointCalcConfig); + + int updateEmsPointCalcConfig(EmsPointCalcConfig emsPointCalcConfig); + + int deleteEmsPointCalcConfigById(Long id); + + int deleteEmsPointCalcConfigByIds(Long[] ids); + + int countBySiteId(@Param("siteId") String siteId); + + int deleteBySiteId(@Param("siteId") String siteId); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java index 76deb82..0e800ab 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java @@ -13,6 +13,8 @@ public interface EmsPointConfigMapper { int insertEmsPointConfig(EmsPointConfig emsPointConfig); + int insertBatchEmsPointConfig(@Param("list") List list); + int updateEmsPointConfig(EmsPointConfig emsPointConfig); int deleteEmsPointConfigById(Long id); @@ -41,4 +43,7 @@ public interface EmsPointConfigMapper { @Param("deviceCategory") String deviceCategory, @Param("pointNames") List pointNames, @Param("deviceIds") List deviceIds); + + List selectBySiteIdAndPointIds(@Param("siteId") String siteId, + @Param("pointIds") List pointIds); } diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyRuntimeConfigMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyRuntimeConfigMapper.java new file mode 100644 index 0000000..41fc0f6 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyRuntimeConfigMapper.java @@ -0,0 +1,36 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; + +/** + * 策略运行参数配置Mapper接口 + * + * @author xzzn + * @date 2026-02-13 + */ +public interface EmsStrategyRuntimeConfigMapper { + + /** + * 根据站点ID查询参数配置 + * + * @param siteId 站点ID + * @return 参数配置 + */ + EmsStrategyRuntimeConfig selectBySiteId(String siteId); + + /** + * 新增参数配置 + * + * @param config 参数配置 + * @return 结果 + */ + int insert(EmsStrategyRuntimeConfig config); + + /** + * 按站点ID更新参数配置 + * + * @param config 参数配置 + * @return 结果 + */ + int updateBySiteId(EmsStrategyRuntimeConfig config); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointCalcConfigService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointCalcConfigService.java new file mode 100644 index 0000000..0637ff6 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointCalcConfigService.java @@ -0,0 +1,19 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.EmsPointCalcConfig; + +import java.util.List; + +public interface IEmsPointCalcConfigService { + List selectPointCalcConfigList(EmsPointCalcConfig pointCalcConfig); + + EmsPointCalcConfig selectPointCalcConfigById(Long id); + + int insertPointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName); + + int updatePointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName); + + int deletePointCalcConfigByIds(Long[] ids); + + int deleteBySiteId(String siteId); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsStrategyRuntimeConfigService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsStrategyRuntimeConfigService.java new file mode 100644 index 0000000..5d31fae --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsStrategyRuntimeConfigService.java @@ -0,0 +1,28 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; + +/** + * 策略运行参数配置Service接口 + * + * @author xzzn + * @date 2026-02-13 + */ +public interface IEmsStrategyRuntimeConfigService { + + /** + * 按站点ID获取参数配置(不存在时返回默认值) + * + * @param siteId 站点ID + * @return 参数配置 + */ + EmsStrategyRuntimeConfig getBySiteId(String siteId); + + /** + * 保存参数配置(按siteId新增或更新) + * + * @param config 参数配置 + * @return 结果 + */ + int saveBySiteId(EmsStrategyRuntimeConfig config); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java index 0f26d46..296cf6e 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java @@ -61,15 +61,19 @@ import com.xzzn.ems.utils.DevicePointMatchDataProcessor; import java.lang.reflect.Field; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -77,6 +81,7 @@ import java.util.Objects; import java.util.Set; import java.util.Collections; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; @@ -104,12 +109,14 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i // 匹配DTDC+数字格式的正则(提取序号) 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 VARIABLE_SAFE_PATTERN = Pattern.compile("[^A-Za-z0-9_]"); private static final int POINT_QUEUE_CAPACITY = 100000; 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_ENQUEUE_RETRY_TIMES = 3; private static final long POINT_ENQUEUE_RETRY_WAIT_MS = 10; private static final long POINT_FLUSH_INTERVAL_MS = 100; + private static final String SITE_LEVEL_CALC_DEVICE_ID = "SITE_CALC"; @Autowired private EmsBatteryClusterMapper emsBatteryClusterMapper; @@ -169,6 +176,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i @Autowired private InfluxPointDataWriter influxPointDataWriter; private final BlockingQueue pointDataQueue = new LinkedBlockingQueue<>(POINT_QUEUE_CAPACITY); + private final Map> calcExpressionCache = new ConcurrentHashMap<>(); private final ScheduledExecutorService pointDataWriter = Executors.newSingleThreadScheduledExecutor(r -> { Thread thread = new Thread(r); thread.setName("point-data-writer"); @@ -201,6 +209,9 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i long startMs = System.currentTimeMillis(); log.info("开始处理设备数据, siteId: {}, messageSize: {}", siteId, arraylist.size()); Set deviceIds = new LinkedHashSet<>(); + Map deviceCategoryMap = buildDeviceCategoryMap(siteId, arraylist); + Map batchContextBySite = buildBatchContextBySite(siteId, arraylist, deviceCategoryMap); + Date latestDataUpdateTime = null; for (int i = 0; i < arraylist.size(); i++) { JSONObject obj = JSONObject.parseObject(arraylist.get(i).toString()); @@ -209,6 +220,9 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i String jsonData = obj.getString("Data"); Long timestamp = obj.getLong("timestamp"); Date dataUpdateTime = DateUtils.convertUpdateTime(timestamp); + if (dataUpdateTime != null && (latestDataUpdateTime == null || dataUpdateTime.after(latestDataUpdateTime))) { + latestDataUpdateTime = dataUpdateTime; + } if (StringUtils.isNotBlank(deviceId)) { deviceIds.add(deviceId); @@ -222,65 +236,210 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceId, obj); redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId, obj, 1, TimeUnit.MINUTES); - processPointConfigData(siteId, deviceId, jsonData, dataUpdateTime); - iEmsDeviceSettingService.syncSiteMonitorDataByMqtt(siteId, deviceId, jsonData, dataUpdateTime); + String deviceCategory = resolveDeviceCategory(siteId, deviceId, deviceCategoryMap); + Map pointIdValueMap = processPointConfigData(siteId, deviceId, deviceCategory, jsonData, dataUpdateTime); + iEmsDeviceSettingService.syncSiteMonitorDataByMqtt(siteId, deviceId, JSON.toJSONString(pointIdValueMap), dataUpdateTime); } catch (Exception e) { log.warn("点位映射数据处理失败,siteId: {}, deviceId: {}, err: {}", siteId, deviceId, e.getMessage(), e); } } + Map calcPointIdValueMap = executeCalcPointConfigs( + siteId, SITE_LEVEL_CALC_DEVICE_ID, getSiteCalcPointConfigs(siteId), batchContextBySite, latestDataUpdateTime + ); + if (!calcPointIdValueMap.isEmpty()) { + iEmsDeviceSettingService.syncSiteMonitorDataByMqtt( + siteId, SITE_LEVEL_CALC_DEVICE_ID, JSON.toJSONString(calcPointIdValueMap), latestDataUpdateTime + ); + } log.info("结束处理设备数据, siteId: {}, deviceCount: {}, deviceIds: {}, costMs: {}", siteId, deviceIds.size(), String.join(",", deviceIds), System.currentTimeMillis() - startMs); } - private void processPointConfigData(String siteId, String deviceId, String jsonData, Date dataUpdateTime) { + private Map processPointConfigData(String siteId, String deviceId, String deviceCategory, String jsonData, Date dataUpdateTime) { + Map pointIdValueMap = new HashMap<>(); if (StringUtils.isAnyBlank(siteId, deviceId, jsonData)) { - return; + return pointIdValueMap; } if (!DB_NAME_PATTERN.matcher(siteId).matches()) { log.warn("站点ID不合法,跳过点位映射落库,siteId: {}", siteId); - return; + return pointIdValueMap; } Map dataMap = JSON.parseObject(jsonData, new TypeReference>() { }); if (org.apache.commons.collections4.MapUtils.isEmpty(dataMap)) { - return; + return pointIdValueMap; } - List pointConfigs = getPointConfigsWithCache(siteId, deviceId); + List pointConfigs = getPointConfigsWithCache(siteId, deviceId, deviceCategory); if (CollectionUtils.isEmpty(pointConfigs)) { - return; + return pointIdValueMap; } - Map configByDataKey = pointConfigs.stream() - .filter(cfg -> StringUtils.isNotBlank(cfg.getDataKey())) - .collect(Collectors.toMap( - cfg -> cfg.getDataKey().toUpperCase(), - cfg -> cfg, - (oldValue, newValue) -> oldValue - )); - + List dataPointConfigs = new ArrayList<>(); + Set uniquePointKeys = new HashSet<>(); + for (EmsPointConfig pointConfig : pointConfigs) { + String pointContextKey = resolvePointContextKey(pointConfig); + if (pointConfig == null || StringUtils.isBlank(pointContextKey)) { + continue; + } + if (!uniquePointKeys.add(pointContextKey)) { + continue; + } + if (isCalcPoint(pointConfig)) { + continue; + } + dataPointConfigs.add(pointConfig); + } + Map rawDataValues = new HashMap<>(); for (Map.Entry entry : dataMap.entrySet()) { - String dataKey = entry.getKey(); - if (StringUtils.isBlank(dataKey)) { + if (StringUtils.isBlank(entry.getKey())) { continue; } - EmsPointConfig pointConfig = configByDataKey.get(dataKey.toUpperCase()); - if (pointConfig == null) { - continue; - } - BigDecimal pointValue = StringUtils.getBigDecimal(entry.getValue()); if (pointValue == null) { continue; } - - // 支持按配置进行二次转换:f(x)=A*x^2 + K*x + B - pointValue = convertPointValue(pointValue, pointConfig); - - enqueuePointData(siteId, deviceId, pointConfig.getDataKey(), pointValue, dataUpdateTime); + rawDataValues.put(entry.getKey().trim().toUpperCase(), pointValue); } + + for (EmsPointConfig dataPointConfig : dataPointConfigs) { + BigDecimal sourceValue = getSourceValueFromRawData(dataPointConfig, rawDataValues); + if (sourceValue == null) { + continue; + } + BigDecimal convertedValue = convertPointValue(sourceValue, dataPointConfig); + String pointId = resolveInfluxPointKey(dataPointConfig); + if (StringUtils.isBlank(pointId)) { + continue; + } + enqueuePointData(siteId, deviceId, pointId, convertedValue, dataUpdateTime); + pointIdValueMap.put(pointId, convertedValue); + } + return pointIdValueMap; + } + + private Map buildDeviceCategoryMap(String siteId, JSONArray arraylist) { + Map deviceCategoryMap = new HashMap<>(); + if (arraylist == null || arraylist.isEmpty()) { + return deviceCategoryMap; + } + for (int i = 0; i < arraylist.size(); i++) { + JSONObject item = JSONObject.parseObject(arraylist.get(i).toString()); + if (item == null) { + continue; + } + String deviceId = item.getString("Device"); + if (StringUtils.isBlank(deviceId) || deviceCategoryMap.containsKey(deviceId)) { + continue; + } + String category = getDeviceCategory(siteId, deviceId); + if (StringUtils.isNotBlank(category)) { + deviceCategoryMap.put(deviceId, category); + } + } + return deviceCategoryMap; + } + + private String resolveDeviceCategory(String siteId, String deviceId, Map deviceCategoryMap) { + if (StringUtils.isBlank(deviceId)) { + return ""; + } + String mappedCategory = deviceCategoryMap == null ? null : deviceCategoryMap.get(deviceId); + if (StringUtils.isNotBlank(mappedCategory)) { + return mappedCategory; + } + String runtimeCategory = getDeviceCategory(siteId, deviceId); + if (StringUtils.isNotBlank(runtimeCategory) && deviceCategoryMap != null) { + deviceCategoryMap.put(deviceId, runtimeCategory); + } + return StringUtils.defaultString(runtimeCategory); + } + + private Map buildBatchContextBySite(String siteId, JSONArray arraylist, Map deviceCategoryMap) { + Map contextBySite = new HashMap<>(); + if (arraylist == null || arraylist.isEmpty()) { + return contextBySite; + } + for (int i = 0; i < arraylist.size(); i++) { + JSONObject item = JSONObject.parseObject(arraylist.get(i).toString()); + if (item == null) { + continue; + } + String deviceId = item.getString("Device"); + String devicePrefix = normalizeVariablePart(deviceId); + String deviceCategory = resolveDeviceCategory(siteId, deviceId, deviceCategoryMap); + String jsonData = item.getString("Data"); + if (checkJsonDataEmpty(jsonData)) { + continue; + } + Map dataMap = JSON.parseObject(jsonData, new TypeReference>() { + }); + if (org.apache.commons.collections4.MapUtils.isEmpty(dataMap)) { + continue; + } + Map pointConfigByDataKey = new HashMap<>(); + List pointConfigs = getPointConfigsWithCache(siteId, deviceId, deviceCategory); + if (!CollectionUtils.isEmpty(pointConfigs)) { + for (EmsPointConfig pointConfig : pointConfigs) { + if (isCalcPoint(pointConfig)) { + continue; + } + String dataKey = StringUtils.defaultString(pointConfig.getDataKey()).trim().toUpperCase(); + if (StringUtils.isBlank(dataKey) || pointConfigByDataKey.containsKey(dataKey)) { + continue; + } + pointConfigByDataKey.put(dataKey, pointConfig); + } + } + for (Map.Entry entry : dataMap.entrySet()) { + if (StringUtils.isBlank(entry.getKey())) { + continue; + } + BigDecimal pointValue = StringUtils.getBigDecimal(entry.getValue()); + if (pointValue == null) { + continue; + } + String normalizedKey = normalizeVariablePart(entry.getKey()); + if (StringUtils.isBlank(normalizedKey)) { + continue; + } + // 兼容旧表达式:继续支持直接使用变量名(如 WD4) + contextBySite.put(normalizedKey, pointValue); + // 新规则:支持设备前缀变量(如 DONGHUAN_WD4) + if (StringUtils.isNotBlank(devicePrefix)) { + contextBySite.put(devicePrefix + "_" + normalizedKey, pointValue); + } + // 同时支持按点位ID引用(如 POINT4092) + EmsPointConfig pointConfig = pointConfigByDataKey.get(entry.getKey().trim().toUpperCase()); + if (pointConfig != null) { + String pointIdKey = resolvePointContextKey(pointConfig); + if (StringUtils.isNotBlank(pointIdKey)) { + BigDecimal convertedValue = convertPointValue(pointValue, pointConfig); + contextBySite.put(pointIdKey, convertedValue); + } + } + } + } + return contextBySite; + } + + private String normalizeVariablePart(String source) { + if (StringUtils.isBlank(source)) { + return null; + } + String normalized = VARIABLE_SAFE_PATTERN.matcher(source.trim().toUpperCase()).replaceAll("_"); + if (StringUtils.isBlank(normalized)) { + return null; + } + normalized = normalized.replaceAll("_+", "_"); + normalized = StringUtils.strip(normalized, "_"); + return StringUtils.isBlank(normalized) ? null : normalized; + } + + private boolean isCalcPoint(EmsPointConfig pointConfig) { + return pointConfig != null && "calc".equalsIgnoreCase(pointConfig.getPointType()); } private void enqueuePointData(String siteId, String deviceId, String pointKey, BigDecimal pointValue, Date dataTime) { @@ -325,6 +484,315 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i .add(b); } + private String resolveInfluxPointKey(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return null; + } + if (StringUtils.isNotBlank(pointConfig.getPointId())) { + return pointConfig.getPointId().trim(); + } + return null; + } + + private Map executeCalcPointConfigs(String siteId, String deviceId, List calcPointConfigs, + Map contextValues, Date dataUpdateTime) { + Map calcPointIdValueMap = new HashMap<>(); + if (CollectionUtils.isEmpty(calcPointConfigs)) { + return calcPointIdValueMap; + } + Map remaining = new HashMap<>(); + for (EmsPointConfig calcPointConfig : calcPointConfigs) { + String calcContextKey = resolveCalcContextKey(calcPointConfig); + if (calcPointConfig == null || StringUtils.isBlank(calcContextKey)) { + continue; + } + remaining.putIfAbsent(calcContextKey, calcPointConfig); + } + if (remaining.isEmpty()) { + return calcPointIdValueMap; + } + + while (!remaining.isEmpty()) { + boolean progressed = false; + List finishedKeys = new ArrayList<>(); + List unresolvedKeys = new ArrayList<>(); + + for (Map.Entry entry : remaining.entrySet()) { + String calcDataKey = entry.getKey(); + EmsPointConfig calcPointConfig = entry.getValue(); + try { + BigDecimal calcValue = evaluateCalcExpression(calcPointConfig.getCalcExpression(), contextValues); + contextValues.put(calcDataKey, calcValue); + putPointValueToContext(calcPointConfig, calcValue, contextValues); + // 计算点按站点维度统一落库,不再按配置中的 device_id 分流 + String pointId = resolveInfluxPointKey(calcPointConfig); + if (StringUtils.isNotBlank(pointId)) { + enqueuePointData(siteId, deviceId, pointId, calcValue, dataUpdateTime); + calcPointIdValueMap.put(pointId, calcValue); + } + finishedKeys.add(calcDataKey); + progressed = true; + } catch (MissingVariableException missingVariableException) { + unresolvedKeys.add(calcDataKey + " <- " + missingVariableException.getVariable()); + } catch (IllegalArgumentException expressionException) { + log.warn("计算点表达式执行失败,siteId: {}, deviceId: {}, dataKey: {}, expression: {}, err: {}", + siteId, deviceId, calcPointConfig.getDataKey(), calcPointConfig.getCalcExpression(), + expressionException.getMessage()); + finishedKeys.add(calcDataKey); + } + } + finishedKeys.forEach(remaining::remove); + + if (!progressed) { + if (!unresolvedKeys.isEmpty()) { + log.warn("计算点依赖未就绪,跳过本轮计算,siteId: {}, deviceId: {}, unresolved: {}", + siteId, deviceId, String.join(", ", unresolvedKeys)); + } + break; + } + } + return calcPointIdValueMap; + } + + private BigDecimal getSourceValueFromRawData(EmsPointConfig pointConfig, Map rawDataValues) { + if (pointConfig == null || rawDataValues == null || rawDataValues.isEmpty()) { + return null; + } + String dataKey = StringUtils.defaultString(pointConfig.getDataKey()).trim().toUpperCase(); + return StringUtils.isBlank(dataKey) ? null : rawDataValues.get(dataKey); + } + + private void putPointValueToContext(EmsPointConfig pointConfig, BigDecimal pointValue, Map contextValues) { + if (pointConfig == null || pointValue == null || contextValues == null) { + return; + } + String pointKey = resolvePointContextKey(pointConfig); + if (StringUtils.isNotBlank(pointKey)) { + contextValues.put(pointKey, pointValue); + } + } + + private String resolveCalcContextKey(EmsPointConfig pointConfig) { + return resolvePointContextKey(pointConfig); + } + + private String resolvePointContextKey(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return null; + } + String pointId = StringUtils.defaultString(pointConfig.getPointId()).trim().toUpperCase(); + return StringUtils.isNotBlank(pointId) ? pointId : null; + } + + private BigDecimal evaluateCalcExpression(String expression, Map contextValues) { + if (StringUtils.isBlank(expression)) { + throw new IllegalArgumentException("计算表达式为空"); + } + List rpnTokens = calcExpressionCache.computeIfAbsent(expression, this::compileExpressionToRpn); + return evaluateRpnTokens(rpnTokens, contextValues == null ? Collections.emptyMap() : contextValues); + } + + private List compileExpressionToRpn(String expression) { + if (StringUtils.isBlank(expression)) { + throw new IllegalArgumentException("计算表达式为空"); + } + List output = new ArrayList<>(); + Deque 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 rpnTokens, Map contextValues) { + Deque 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() { if (!pointDataFlushing.compareAndSet(false, true)) { return; @@ -366,21 +834,48 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i influxPointDataWriter.writeBatch(payloads); } - private List getPointConfigsWithCache(String siteId, String deviceId) { - String cacheKey = RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId; + private List getPointConfigsWithCache(String siteId, String deviceId, String deviceCategory) { + String normalizedCategory = StringUtils.defaultString(deviceCategory).trim().toUpperCase(); + String cacheKey = RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId + "_" + normalizedCategory; List cached = redisCache.getCacheObject(cacheKey); if (cached != null) { return cached; } + + // 1) 设备级点位(原有逻辑) EmsPointConfig query = new EmsPointConfig(); query.setSiteId(siteId); query.setDeviceId(deviceId); - List latest = emsPointConfigMapper.selectEmsPointConfigList(query); - List cacheValue = latest == null ? new ArrayList<>() : latest; + List cacheValue = emsPointConfigMapper.selectEmsPointConfigList(query); redisCache.setCacheObject(cacheKey, cacheValue); return cacheValue; } + private List getSiteCalcPointConfigs(String siteId) { + if (StringUtils.isBlank(siteId)) { + return Collections.emptyList(); + } + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setPointType("calc"); + List calcPoints = emsPointConfigMapper.selectEmsPointConfigList(query); + if (CollectionUtils.isEmpty(calcPoints)) { + return Collections.emptyList(); + } + Map uniqueByPointId = new LinkedHashMap<>(); + for (EmsPointConfig calcPoint : calcPoints) { + if (!isCalcPoint(calcPoint)) { + continue; + } + String calcKey = resolvePointContextKey(calcPoint); + if (StringUtils.isBlank(calcKey)) { + continue; + } + uniqueByPointId.putIfAbsent(calcKey, calcPoint); + } + return new ArrayList<>(uniqueByPointId.values()); + } + private static class PointDataRecord { private final String siteId; private final String deviceId; @@ -417,6 +912,71 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i } } + private static class MissingVariableException extends IllegalArgumentException { + private final String variable; + + private MissingVariableException(String variable) { + super("缺少变量: " + variable); + this.variable = variable; + } + + private String getVariable() { + return variable; + } + } + + private enum ExpressionTokenType { + NUMBER, + VARIABLE, + OPERATOR, + LEFT_PAREN, + RIGHT_PAREN + } + + private static class ExpressionToken { + private final ExpressionTokenType type; + private final String text; + private final BigDecimal number; + + private ExpressionToken(ExpressionTokenType type, String text, BigDecimal number) { + this.type = type; + this.text = text; + this.number = number; + } + + private static ExpressionToken number(BigDecimal value) { + return new ExpressionToken(ExpressionTokenType.NUMBER, null, value); + } + + private static ExpressionToken variable(String variable) { + return new ExpressionToken(ExpressionTokenType.VARIABLE, variable, null); + } + + private static ExpressionToken operator(String operator) { + return new ExpressionToken(ExpressionTokenType.OPERATOR, operator, null); + } + + private static ExpressionToken leftParen() { + return new ExpressionToken(ExpressionTokenType.LEFT_PAREN, "(", null); + } + + private static ExpressionToken rightParen() { + return new ExpressionToken(ExpressionTokenType.RIGHT_PAREN, ")", null); + } + + private ExpressionTokenType getType() { + return type; + } + + private String getText() { + return text; + } + + private BigDecimal getNumber() { + return number; + } + } + private JSONArray parseJsonData(String message) { try { JSONObject object = JSONObject.parseObject(message); diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java index f950484..71b0323 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java @@ -17,6 +17,7 @@ import com.xzzn.common.exception.ServiceException; import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.StringUtils; import com.xzzn.ems.domain.EmsDevicesSetting; +import com.xzzn.ems.domain.EmsPointConfig; import com.xzzn.ems.domain.EmsPointMatch; import com.xzzn.ems.domain.EmsPcsSetting; import com.xzzn.ems.domain.EmsSiteMonitorItem; @@ -33,6 +34,7 @@ import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingVo; import com.xzzn.ems.mapper.EmsBatteryDataMinutesMapper; import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsPcsSettingMapper; +import com.xzzn.ems.mapper.EmsPointConfigMapper; import com.xzzn.ems.mapper.EmsPointMatchMapper; import com.xzzn.ems.mapper.EmsSiteMonitorDataMapper; import com.xzzn.ems.mapper.EmsSiteMonitorItemMapper; @@ -45,12 +47,14 @@ import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.Collections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,9 +75,12 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService private static final String MODULE_HOME = "HOME"; private static final String MODULE_SBJK = "SBJK"; private static final String MODULE_TJBB = "TJBB"; + private static final Integer USE_FIXED_DISPLAY_YES = 1; private static final String HISTORY_TABLE_HOME = "ems_site_monitor_data_home_his"; private static final String HISTORY_TABLE_SBJK = "ems_site_monitor_data_sbjk_his"; private static final String HISTORY_TABLE_TJBB = "ems_site_monitor_data_tjbb_his"; + private static final String DELETED_FIELD_MARK = "__DELETED__"; + private static final long MONITOR_ITEM_CACHE_TTL_MS = 60_000L; @Autowired private EmsDevicesSettingMapper emsDevicesMapper; @Autowired @@ -81,6 +88,8 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService @Autowired private EmsPcsSettingMapper emsPcsSettingMapper; @Autowired + private EmsPointConfigMapper emsPointConfigMapper; + @Autowired private RedisCache redisCache; @Autowired private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper; @@ -95,6 +104,9 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService @Autowired private EmsSiteMonitorDataMapper emsSiteMonitorDataMapper; + private volatile List monitorItemCache = Collections.emptyList(); + private volatile long monitorItemCacheExpireAt = 0L; + /** * 获取设备详细信息 * @param id @@ -406,16 +418,30 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService if (StringUtils.isBlank(siteId)) { return result; } - List itemList = emsSiteMonitorItemMapper.selectEnabledList(); + List itemList = getEnabledMonitorItems(); if (itemList == null || itemList.isEmpty()) { return result; } List mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId); + Set deletedFieldCodeSet = mappingList.stream() + .filter(item -> StringUtils.isNotBlank(item.getFieldCode())) + .filter(item -> DELETED_FIELD_MARK.equals(item.getDataPoint())) + .map(EmsSiteMonitorPointMatch::getFieldCode) + .collect(Collectors.toSet()); + Map mappingByFieldCode = mappingList.stream() + .filter(item -> StringUtils.isNotBlank(item.getFieldCode())) + .filter(item -> !DELETED_FIELD_MARK.equals(item.getDataPoint())) + .collect(Collectors.toMap(EmsSiteMonitorPointMatch::getFieldCode, item -> item, (a, b) -> b)); Map pointMap = mappingList.stream() .filter(item -> StringUtils.isNotBlank(item.getFieldCode())) + .filter(item -> !DELETED_FIELD_MARK.equals(item.getDataPoint())) .collect(Collectors.toMap(EmsSiteMonitorPointMatch::getFieldCode, EmsSiteMonitorPointMatch::getDataPoint, (a, b) -> b)); itemList.forEach(item -> { + if (deletedFieldCodeSet.contains(item.getFieldCode())) { + return; + } + EmsSiteMonitorPointMatch pointMatch = mappingByFieldCode.get(item.getFieldCode()); SiteMonitorProjectPointMappingVo vo = new SiteMonitorProjectPointMappingVo(); vo.setModuleCode(item.getModuleCode()); vo.setModuleName(item.getModuleName()); @@ -425,6 +451,8 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService vo.setFieldCode(item.getFieldCode()); vo.setFieldName(item.getFieldName()); vo.setDataPoint(pointMap.getOrDefault(item.getFieldCode(), "")); + vo.setFixedDataPoint(pointMatch == null ? "" : pointMatch.getFixedDataPoint()); + vo.setUseFixedDisplay(pointMatch == null ? 0 : (pointMatch.getUseFixedDisplay() == null ? 0 : pointMatch.getUseFixedDisplay())); result.add(vo); }); return result; @@ -436,37 +464,70 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService throw new ServiceException("站点ID不能为空"); } String siteId = request.getSiteId(); - List itemList = emsSiteMonitorItemMapper.selectEnabledList(); + List itemList = getEnabledMonitorItems(); if (itemList == null || itemList.isEmpty()) { throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item"); } Set fieldCodeSet = itemList.stream().map(EmsSiteMonitorItem::getFieldCode).collect(Collectors.toSet()); - emsSiteMonitorPointMatchMapper.deleteBySiteId(siteId); - - if (request.getMappings() == null || request.getMappings().isEmpty()) { - return 0; + Set deletedFieldCodeSet = new HashSet<>(); + if (request.getDeletedFieldCodes() != null) { + request.getDeletedFieldCodes().stream() + .filter(StringUtils::isNotBlank) + .map(String::trim) + .filter(fieldCodeSet::contains) + .forEach(deletedFieldCodeSet::add); } + int deletedRows = emsSiteMonitorPointMatchMapper.deleteBySiteId(siteId); + List saveList = new ArrayList<>(); - for (SiteMonitorProjectPointMappingVo mapping : request.getMappings()) { - if (mapping == null || StringUtils.isBlank(mapping.getFieldCode()) || StringUtils.isBlank(mapping.getDataPoint())) { - continue; - } - String fieldCode = mapping.getFieldCode().trim(); - if (!fieldCodeSet.contains(fieldCode)) { - continue; + if (request.getMappings() != null) { + for (SiteMonitorProjectPointMappingVo mapping : request.getMappings()) { + if (mapping == null || StringUtils.isBlank(mapping.getFieldCode())) { + continue; + } + String fieldCode = mapping.getFieldCode().trim(); + if (!fieldCodeSet.contains(fieldCode) || deletedFieldCodeSet.contains(fieldCode)) { + continue; + } + String dataPoint = StringUtils.isBlank(mapping.getDataPoint()) ? "" : mapping.getDataPoint().trim(); + String fixedDataPoint = StringUtils.isBlank(mapping.getFixedDataPoint()) ? null : mapping.getFixedDataPoint().trim(); + Integer useFixedDisplay = mapping.getUseFixedDisplay() == null ? 0 : mapping.getUseFixedDisplay(); + boolean hasMapping = StringUtils.isNotBlank(dataPoint) || StringUtils.isNotBlank(fixedDataPoint) || USE_FIXED_DISPLAY_YES.equals(useFixedDisplay); + if (!hasMapping) { + continue; + } + EmsSiteMonitorPointMatch pointMatch = new EmsSiteMonitorPointMatch(); + pointMatch.setSiteId(siteId); + pointMatch.setFieldCode(fieldCode); + pointMatch.setDataPoint(dataPoint); + pointMatch.setFixedDataPoint(fixedDataPoint); + pointMatch.setUseFixedDisplay(useFixedDisplay); + pointMatch.setCreateBy(operName); + pointMatch.setUpdateBy(operName); + saveList.add(pointMatch); } + } + + for (String deletedFieldCode : deletedFieldCodeSet) { EmsSiteMonitorPointMatch pointMatch = new EmsSiteMonitorPointMatch(); pointMatch.setSiteId(siteId); - pointMatch.setFieldCode(fieldCode); - pointMatch.setDataPoint(mapping.getDataPoint().trim()); + pointMatch.setFieldCode(deletedFieldCode); + pointMatch.setDataPoint(DELETED_FIELD_MARK); + pointMatch.setFixedDataPoint(null); + pointMatch.setUseFixedDisplay(0); pointMatch.setCreateBy(operName); pointMatch.setUpdateBy(operName); saveList.add(pointMatch); } + + // 点位映射变更后清理“单站监控最新值”缓存,避免页面回退读取到旧快照 + clearSiteMonitorLatestCache(siteId); + if (saveList.isEmpty()) { - return 0; + return deletedRows; } - return emsSiteMonitorPointMatchMapper.insertBatch(saveList); + int insertedRows = emsSiteMonitorPointMatchMapper.insertBatch(saveList); + return deletedRows + insertedRows; } @Override @@ -475,6 +536,12 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService if (mappingList.isEmpty()) { return new ArrayList<>(); } + Set pointIds = mappingList.stream() + .map(SiteMonitorProjectPointMappingVo::getDataPoint) + .filter(StringUtils::isNotBlank) + .map(item -> item.trim().toUpperCase()) + .collect(Collectors.toSet()); + Map pointConfigByPointId = buildPointConfigByPointId(siteId, pointIds); Map homeLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_HOME))); Map sbjkLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_SBJK))); Map tjbbLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_TJBB))); @@ -483,6 +550,20 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService for (SiteMonitorProjectPointMappingVo mapping : mappingList) { SiteMonitorProjectDisplayVo vo = new SiteMonitorProjectDisplayVo(); BeanUtils.copyProperties(mapping, vo); + if (USE_FIXED_DISPLAY_YES.equals(mapping.getUseFixedDisplay()) && StringUtils.isNotBlank(mapping.getFixedDataPoint())) { + vo.setFieldValue(mapping.getFixedDataPoint()); + vo.setValueTime(null); + result.add(vo); + continue; + } + // 与“点位配置列表最新值”一致:按 pointId -> 点位配置(dataKey/deviceId) -> MQTT 最新报文读取 + PointLatestSnapshot latestSnapshot = getLatestSnapshotByPointId(siteId, mapping.getDataPoint(), pointConfigByPointId); + if (latestSnapshot != null && latestSnapshot.getFieldValue() != null) { + vo.setFieldValue(latestSnapshot.getFieldValue()); + vo.setValueTime(latestSnapshot.getValueTime()); + result.add(vo); + continue; + } Object cacheObj = null; if (MODULE_HOME.equals(mapping.getModuleCode())) { cacheObj = homeLatestMap.get(mapping.getFieldCode()); @@ -501,6 +582,139 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService return result; } + private Map buildPointConfigByPointId(String siteId, Set pointIds) { + Map result = new HashMap<>(); + if (StringUtils.isBlank(siteId) || pointIds == null || pointIds.isEmpty()) { + return result; + } + List configList = emsPointConfigMapper.selectBySiteIdAndPointIds( + siteId.trim(), + new ArrayList<>(pointIds) + ); + if (configList == null || configList.isEmpty()) { + return result; + } + for (EmsPointConfig config : configList) { + if (config == null || StringUtils.isBlank(config.getPointId())) { + continue; + } + String pointId = config.getPointId().trim().toUpperCase(); + result.putIfAbsent(pointId, config); + } + return result; + } + + private PointLatestSnapshot getLatestSnapshotByPointId(String siteId, String pointId, Map pointConfigByPointId) { + if (StringUtils.isAnyBlank(siteId, pointId) || pointConfigByPointId == null || pointConfigByPointId.isEmpty()) { + return null; + } + EmsPointConfig pointConfig = pointConfigByPointId.get(pointId.trim().toUpperCase()); + if (pointConfig == null || StringUtils.isAnyBlank(pointConfig.getDeviceId(), pointConfig.getDataKey())) { + return null; + } + String redisKey = RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + pointConfig.getDeviceId().trim(); + Object raw = redisCache.getCacheObject(redisKey); + if (raw == null) { + return null; + } + JSONObject root = toJsonObject(raw); + if (root == null) { + return null; + } + JSONObject dataObject = extractDataObject(root); + if (dataObject == null) { + return null; + } + Object rawValue = getValueIgnoreCase(dataObject, pointConfig.getDataKey()); + BigDecimal sourceValue = StringUtils.getBigDecimal(rawValue); + if (sourceValue == null) { + return null; + } + BigDecimal pointValue = convertPointValue(sourceValue, pointConfig); + Long timestamp = root.getLong("timestamp"); + Date valueTime = timestamp == null ? null : DateUtils.convertUpdateTime(timestamp); + return new PointLatestSnapshot(pointValue.stripTrailingZeros().toPlainString(), valueTime); + } + + private JSONObject toJsonObject(Object raw) { + if (raw == null) { + return null; + } + if (raw instanceof JSONObject) { + return (JSONObject) raw; + } + try { + return JSON.parseObject(JSON.toJSONString(raw)); + } catch (Exception ex) { + return null; + } + } + + private JSONObject extractDataObject(JSONObject root) { + if (root == null) { + return null; + } + JSONObject dataObject = root.getJSONObject("Data"); + if (dataObject != null) { + return dataObject; + } + String dataJson = root.getString("Data"); + if (StringUtils.isBlank(dataJson)) { + return null; + } + try { + return JSON.parseObject(dataJson); + } catch (Exception ex) { + return null; + } + } + + private Object getValueIgnoreCase(JSONObject dataObject, String dataKey) { + if (dataObject == null || StringUtils.isBlank(dataKey)) { + return null; + } + Object directValue = dataObject.get(dataKey); + if (directValue != null) { + return directValue; + } + for (String key : dataObject.keySet()) { + if (key != null && key.equalsIgnoreCase(dataKey)) { + return dataObject.get(key); + } + } + return null; + } + + private BigDecimal convertPointValue(BigDecimal sourceValue, EmsPointConfig pointConfig) { + if (sourceValue == null || pointConfig == null) { + return sourceValue; + } + BigDecimal a = pointConfig.getDataA() == null ? BigDecimal.ZERO : pointConfig.getDataA(); + BigDecimal k = pointConfig.getDataK() == null ? BigDecimal.ONE : pointConfig.getDataK(); + BigDecimal b = pointConfig.getDataB() == null ? BigDecimal.ZERO : pointConfig.getDataB(); + return a.multiply(sourceValue).multiply(sourceValue) + .add(k.multiply(sourceValue)) + .add(b); + } + + private static class PointLatestSnapshot { + private final String fieldValue; + private final Date valueTime; + + private PointLatestSnapshot(String fieldValue, Date valueTime) { + this.fieldValue = fieldValue; + this.valueTime = valueTime; + } + + public String getFieldValue() { + return fieldValue; + } + + public Date getValueTime() { + return valueTime; + } + } + @Override public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName) { if (request == null || StringUtils.isBlank(request.getSiteId())) { @@ -509,7 +723,7 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService if (request.getItems() == null || request.getItems().isEmpty()) { return 0; } - List itemList = emsSiteMonitorItemMapper.selectEnabledList(); + List itemList = getEnabledMonitorItems(); if (itemList == null || itemList.isEmpty()) { throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item"); } @@ -590,7 +804,7 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService if (mappingList == null || mappingList.isEmpty()) { return 0; } - List itemList = emsSiteMonitorItemMapper.selectEnabledList(); + List itemList = getEnabledMonitorItems(); if (itemList == null || itemList.isEmpty()) { return 0; } @@ -608,15 +822,24 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService Map tjbbHistoryByMinute = new HashMap<>(); for (EmsSiteMonitorPointMatch mapping : mappingList) { - if (mapping == null || StringUtils.isAnyBlank(mapping.getFieldCode(), mapping.getDataPoint())) { + if (mapping == null || StringUtils.isBlank(mapping.getFieldCode())) { + continue; + } + if (DELETED_FIELD_MARK.equals(mapping.getDataPoint())) { continue; } EmsSiteMonitorItem itemDef = itemMap.get(mapping.getFieldCode()); if (itemDef == null || StringUtils.isBlank(itemDef.getModuleCode())) { continue; } - - String point = mapping.getDataPoint().trim(); + if (USE_FIXED_DISPLAY_YES.equals(mapping.getUseFixedDisplay())) { + continue; + } + String point = mapping.getDataPoint(); + if (StringUtils.isBlank(point)) { + continue; + } + point = point.trim(); Object value = upperDataMap.get(point.toUpperCase()); if (value == null && StringUtils.isNotBlank(deviceId)) { value = upperDataMap.get((deviceId + point).toUpperCase()); @@ -715,6 +938,27 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService return source == null ? new HashMap<>() : source; } + private List getEnabledMonitorItems() { + long now = System.currentTimeMillis(); + List cached = monitorItemCache; + if (now < monitorItemCacheExpireAt && cached != null) { + return cached; + } + synchronized (this) { + now = System.currentTimeMillis(); + if (now < monitorItemCacheExpireAt && monitorItemCache != null) { + return monitorItemCache; + } + List latest = emsSiteMonitorItemMapper.selectEnabledList(); + if (latest == null) { + latest = Collections.emptyList(); + } + monitorItemCache = latest; + monitorItemCacheExpireAt = now + MONITOR_ITEM_CACHE_TTL_MS; + return latest; + } + } + private Map extractHotColumns(JSONObject merged) { Map result = new HashMap<>(); result.put("hotSoc", extractFieldValue(merged, @@ -833,6 +1077,15 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService return RedisKeyConstants.SITE_MONITOR_LATEST + siteId + "_" + moduleCode; } + private void clearSiteMonitorLatestCache(String siteId) { + if (StringUtils.isBlank(siteId)) { + return; + } + redisCache.deleteObject(buildSiteMonitorLatestRedisKey(siteId, MODULE_HOME)); + redisCache.deleteObject(buildSiteMonitorLatestRedisKey(siteId, MODULE_SBJK)); + redisCache.deleteObject(buildSiteMonitorLatestRedisKey(siteId, MODULE_TJBB)); + } + // 辅助方法:根据值查找对应的对象(用于比较器中获取完整对象) private PointQueryResponse findByValue(List list, Object value) { return list.stream() @@ -1050,6 +1303,10 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService public List getWriteTags(String workStatus, EmsPcsSetting pcsSetting) { List writeTags = new ArrayList<>(); BigDecimal power; + BigDecimal powerMultiplier = pcsSetting.getPowerMultiplier(); + if (powerMultiplier == null || BigDecimal.ZERO.compareTo(powerMultiplier) == 0) { + powerMultiplier = BigDecimal.ONE; + } if (WorkStatus.NORMAL.getCode().equals(workStatus)) { // 开机先发送开机指令再发送有功功率给定值 @@ -1073,7 +1330,7 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService if (power == null) { power = BigDecimal.ZERO; } - clusterWriteTag.setValue(power); + clusterWriteTag.setValue(power.multiply(powerMultiplier)); writeTags.add(clusterWriteTag); } if (WorkStatus.STOP.getCode().equals(workStatus)) { diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointCalcConfigServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointCalcConfigServiceImpl.java new file mode 100644 index 0000000..8d1b753 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointCalcConfigServiceImpl.java @@ -0,0 +1,144 @@ +package com.xzzn.ems.service.impl; + +import com.xzzn.common.exception.ServiceException; +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.EmsPointCalcConfig; +import com.xzzn.ems.service.IEmsPointConfigService; +import com.xzzn.ems.service.IEmsPointCalcConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Service +public class EmsPointCalcConfigServiceImpl implements IEmsPointCalcConfigService { + private static final Pattern CALC_EXPRESSION_PATTERN = Pattern.compile("^[0-9A-Za-z_+\\-*/().\\s]+$"); + private static final String CALC_POINT_TYPE = "calc"; + + @Autowired + private IEmsPointConfigService pointConfigService; + + @Override + public List selectPointCalcConfigList(EmsPointCalcConfig pointCalcConfig) { + EmsPointConfig query = toPointConfigQuery(pointCalcConfig); + List configList = pointConfigService.selectPointConfigList(query); + if (configList == null || configList.isEmpty()) { + return new ArrayList<>(); + } + return configList.stream().map(this::toCalcConfig).collect(Collectors.toList()); + } + + @Override + public EmsPointCalcConfig selectPointCalcConfigById(Long id) { + EmsPointConfig config = pointConfigService.selectPointConfigById(id); + if (config == null || !CALC_POINT_TYPE.equalsIgnoreCase(config.getPointType())) { + return null; + } + return toCalcConfig(config); + } + + @Override + public int insertPointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName) { + if (pointCalcConfig == null || StringUtils.isBlank(pointCalcConfig.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + if (StringUtils.isBlank(pointCalcConfig.getPointId())) { + throw new ServiceException("点位ID不能为空"); + } + validateCalcExpression(pointCalcConfig.getCalcExpression()); + return pointConfigService.insertPointConfig(toPointConfig(pointCalcConfig), operName); + } + + @Override + public int updatePointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName) { + if (pointCalcConfig == null || StringUtils.isBlank(pointCalcConfig.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + if (StringUtils.isBlank(pointCalcConfig.getPointId())) { + throw new ServiceException("点位ID不能为空"); + } + validateCalcExpression(pointCalcConfig.getCalcExpression()); + return pointConfigService.updatePointConfig(toPointConfig(pointCalcConfig), operName); + } + + @Override + public int deletePointCalcConfigByIds(Long[] ids) { + return pointConfigService.deletePointConfigByIds(ids); + } + + @Override + public int deleteBySiteId(String siteId) { + if (StringUtils.isBlank(siteId)) { + return 0; + } + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setPointType(CALC_POINT_TYPE); + List configList = pointConfigService.selectPointConfigList(query); + if (configList == null || configList.isEmpty()) { + return 0; + } + Long[] ids = configList.stream().map(EmsPointConfig::getId).toArray(Long[]::new); + return pointConfigService.deletePointConfigByIds(ids); + } + + private void validateCalcExpression(String expression) { + if (StringUtils.isBlank(expression)) { + throw new ServiceException("计算表达式不能为空"); + } + if (!CALC_EXPRESSION_PATTERN.matcher(expression).matches()) { + throw new ServiceException("计算表达式仅支持数字、字母、下划线、空格和四则运算符"); + } + } + + private EmsPointConfig toPointConfig(EmsPointCalcConfig calcConfig) { + EmsPointConfig pointConfig = new EmsPointConfig(); + pointConfig.setId(calcConfig.getId()); + pointConfig.setPointId(calcConfig.getPointId()); + pointConfig.setSiteId(calcConfig.getSiteId()); + pointConfig.setPointType(CALC_POINT_TYPE); + pointConfig.setDeviceCategory(calcConfig.getDeviceCategory()); + pointConfig.setDeviceId(""); + pointConfig.setRegisterAddress(""); + pointConfig.setPointName(calcConfig.getPointName()); + pointConfig.setDataKey(calcConfig.getDataKey()); + pointConfig.setPointDesc(calcConfig.getPointDesc()); + pointConfig.setDataUnit(calcConfig.getDataUnit()); + pointConfig.setCalcExpression(calcConfig.getCalcExpression()); + pointConfig.setRemark(calcConfig.getRemark()); + return pointConfig; + } + + private EmsPointConfig toPointConfigQuery(EmsPointCalcConfig calcConfig) { + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(calcConfig == null ? null : calcConfig.getSiteId()); + query.setDeviceCategory(calcConfig == null ? null : calcConfig.getDeviceCategory()); + query.setDataKey(calcConfig == null ? null : calcConfig.getDataKey()); + query.setPointDesc(calcConfig == null ? null : calcConfig.getPointDesc()); + query.setPointType(CALC_POINT_TYPE); + return query; + } + + private EmsPointCalcConfig toCalcConfig(EmsPointConfig pointConfig) { + EmsPointCalcConfig calcConfig = new EmsPointCalcConfig(); + calcConfig.setId(pointConfig.getId()); + calcConfig.setPointId(pointConfig.getPointId()); + calcConfig.setSiteId(pointConfig.getSiteId()); + calcConfig.setDeviceCategory(pointConfig.getDeviceCategory()); + calcConfig.setPointName(pointConfig.getPointName()); + calcConfig.setDataKey(pointConfig.getDataKey()); + calcConfig.setPointDesc(pointConfig.getPointDesc()); + calcConfig.setDataUnit(pointConfig.getDataUnit()); + calcConfig.setCalcExpression(pointConfig.getCalcExpression()); + calcConfig.setRemark(pointConfig.getRemark()); + calcConfig.setCreateBy(pointConfig.getCreateBy()); + calcConfig.setCreateTime(pointConfig.getCreateTime()); + calcConfig.setUpdateBy(pointConfig.getUpdateBy()); + calcConfig.setUpdateTime(pointConfig.getUpdateTime()); + return calcConfig; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java index d39dd20..1b9b939 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java @@ -17,7 +17,12 @@ import com.xzzn.ems.domain.vo.PointConfigLatestValueVo; import com.xzzn.ems.mapper.EmsPointConfigMapper; import com.xzzn.ems.service.IEmsPointConfigService; import com.xzzn.ems.service.InfluxPointDataWriter; +import org.apache.commons.collections4.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -43,7 +48,10 @@ import java.util.stream.Collectors; @Service public class EmsPointConfigServiceImpl implements IEmsPointConfigService { + private static final Logger log = LoggerFactory.getLogger(EmsPointConfigServiceImpl.class); 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 Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$"); private static final Pattern DECIMAL_PATTERN = Pattern.compile("^-?\\d+(\\.\\d+)?$"); @@ -75,7 +83,12 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { normalizeAndValidatePointConfig(pointConfig); pointConfig.setCreateBy(operName); pointConfig.setUpdateBy(operName); - int rows = emsPointConfigMapper.insertEmsPointConfig(pointConfig); + int rows; + try { + rows = emsPointConfigMapper.insertEmsPointConfig(pointConfig); + } catch (Exception ex) { + throw translatePointConfigPersistenceException(ex, pointConfig == null ? null : pointConfig.getPointId()); + } if (rows > 0) { invalidatePointConfigCache(pointConfig.getSiteId(), pointConfig.getDeviceId()); } @@ -87,7 +100,12 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { EmsPointConfig oldConfig = pointConfig.getId() == null ? null : emsPointConfigMapper.selectEmsPointConfigById(pointConfig.getId()); normalizeAndValidatePointConfig(pointConfig); pointConfig.setUpdateBy(operName); - int rows = emsPointConfigMapper.updateEmsPointConfig(pointConfig); + int rows; + try { + rows = emsPointConfigMapper.updateEmsPointConfig(pointConfig); + } catch (Exception ex) { + throw translatePointConfigPersistenceException(ex, pointConfig == null ? null : pointConfig.getPointId()); + } if (rows > 0) { if (oldConfig != null) { invalidatePointConfigCache(oldConfig.getSiteId(), oldConfig.getDeviceId()); @@ -175,12 +193,25 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { emsPointConfigMapper.deleteBySiteId(siteId); } - int importCount = 0; for (EmsPointConfig pointConfig : pointConfigList) { pointConfig.setCreateBy(operName); pointConfig.setUpdateBy(operName); - importCount += emsPointConfigMapper.insertEmsPointConfig(pointConfig); } + int importCount = 0; + long importStart = System.currentTimeMillis(); + for (int fromIndex = 0; fromIndex < pointConfigList.size(); fromIndex += CSV_IMPORT_BATCH_SIZE) { + int toIndex = Math.min(fromIndex + CSV_IMPORT_BATCH_SIZE, pointConfigList.size()); + List batchList = pointConfigList.subList(fromIndex, toIndex); + long batchStart = System.currentTimeMillis(); + try { + importCount += emsPointConfigMapper.insertBatchEmsPointConfig(batchList); + } catch (Exception ex) { + throw translatePointConfigPersistenceException(ex, null); + } + log.info("点位CSV导入批量入库完成,siteId={}, batch={}~{}, batchSize={}, costMs={}", + siteId, fromIndex + 1, toIndex, batchList.size(), System.currentTimeMillis() - batchStart); + } + log.info("点位CSV导入完成,siteId={}, total={}, costMs={}", siteId, importCount, System.currentTimeMillis() - importStart); invalidatePointConfigCacheBySite(siteId); return String.format("导入成功:站点 %s,点位 %d 条", siteId, importCount); @@ -214,17 +245,23 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { @Override public List getCurveData(PointConfigCurveRequest request) { - if (request == null || StringUtils.isAnyBlank(request.getSiteId(), request.getDeviceId(), request.getDataKey())) { + if (request == null || StringUtils.isBlank(request.getSiteId())) { return new ArrayList<>(); } String siteId = StringUtils.trim(request.getSiteId()); String deviceId = StringUtils.trim(request.getDeviceId()); - String dataKey = StringUtils.trim(request.getDataKey()); - if (StringUtils.isAnyBlank(siteId, deviceId, dataKey)) { + String pointId = StringUtils.trim(request.getPointId()); + if (StringUtils.isAnyBlank(siteId, pointId)) { + return new ArrayList<>(); + } + 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); - return queryCurveDataFromInflux(siteId, deviceId, dataKey, range[0], range[1]); + return queryCurveDataFromInflux(siteId, queryDeviceId, pointId, pointConfig, range[0], range[1]); } private PointConfigLatestValueVo queryLatestValueFromRedis(PointConfigLatestValueItemVo item, @@ -259,8 +296,13 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { return vo; } - private List queryCurveDataFromInflux(String siteId, String deviceId, String dataKey, Date startTime, Date endTime) { - List values = influxPointDataWriter.queryCurveData(siteId, deviceId, dataKey, startTime, endTime); + private List queryCurveDataFromInflux(String siteId, String deviceId, String pointId, + EmsPointConfig pointConfig, Date startTime, Date endTime) { + String influxPointKey = resolveInfluxPointKey(pointConfig, pointId); + if (StringUtils.isBlank(influxPointKey)) { + return new ArrayList<>(); + } + List values = influxPointDataWriter.queryCurveData(siteId, deviceId, influxPointKey, startTime, endTime); if (values == null || values.isEmpty()) { return new ArrayList<>(); } @@ -306,11 +348,53 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { } } + private RuntimeException translatePointConfigPersistenceException(Exception ex, String pointId) { + if (isPointIdDuplicateException(ex)) { + String normalizedPointId = StringUtils.trim(pointId); + if (StringUtils.isBlank(normalizedPointId)) { + return new ServiceException("点位ID已存在(point_id 全局唯一),请修改后重试"); + } + return new ServiceException("点位ID已存在:" + normalizedPointId + "(point_id 全局唯一),请修改后重试"); + } + if (ex instanceof RuntimeException) { + return (RuntimeException) ex; + } + return new ServiceException("点位配置保存失败,请稍后重试"); + } + + private boolean isPointIdDuplicateException(Throwable throwable) { + Throwable current = throwable; + while (current != null) { + if (current instanceof DuplicateKeyException || current instanceof DataIntegrityViolationException) { + String message = current.getMessage(); + if (StringUtils.isNotBlank(message) && message.toLowerCase(Locale.ROOT).contains("uk_point_id")) { + return true; + } + } + String message = current.getMessage(); + if (StringUtils.isNotBlank(message)) { + String lowerMessage = message.toLowerCase(Locale.ROOT); + if (lowerMessage.contains("duplicate entry") && lowerMessage.contains("uk_point_id")) { + return true; + } + } + current = current.getCause(); + } + return false; + } + private void invalidatePointConfigCache(String siteId, String deviceId) { - if (StringUtils.isAnyBlank(siteId, deviceId)) { + if (StringUtils.isBlank(siteId)) { return; } - redisCache.deleteObject(RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId); + if (StringUtils.isBlank(deviceId)) { + invalidatePointConfigCacheBySite(siteId); + return; + } + Collection keys = redisCache.keys(RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId + "_*"); + if (keys != null && !keys.isEmpty()) { + redisCache.deleteObject(keys); + } } private void invalidatePointConfigCacheBySite(String siteId) { @@ -410,6 +494,53 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { return configByKey.get(dataKey.toUpperCase(Locale.ROOT)); } + private String resolveInfluxPointKey(EmsPointConfig pointConfig, String pointId) { + if (pointConfig != null && StringUtils.isNotBlank(pointConfig.getPointId())) { + return pointConfig.getPointId().trim(); + } + if (StringUtils.isNotBlank(pointId)) { + return pointId.trim(); + } + return null; + } + + private EmsPointConfig resolvePointConfigForCurve(String siteId, String pointId) { + if (StringUtils.isAnyBlank(siteId, pointId)) { + return null; + } + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setPointId(pointId); + List pointConfigs = emsPointConfigMapper.selectEmsPointConfigList(query); + if (CollectionUtils.isEmpty(pointConfigs)) { + return null; + } + 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) { if (sourceValue == null || pointConfig == null) { return sourceValue; @@ -431,8 +562,7 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { } List headerList = parseCsvLine(removeUtf8Bom(headerLine)); Map headerIndex = buildHeaderIndex(headerList); - assertRequiredHeader(headerIndex, "device_category", "device_category"); - assertRequiredHeader(headerIndex, "device_id", "device_id"); + assertRequiredHeader(headerIndex, "point_id", "point_id"); assertRequiredHeader(headerIndex, "data_key", "data_key"); assertRequiredHeader(headerIndex, "point_desc", "point_desc"); @@ -446,8 +576,9 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { List valueList = parseCsvLine(line); EmsPointConfig pointConfig = new EmsPointConfig(); pointConfig.setSiteId(siteId); - pointConfig.setDeviceCategory(getRequiredString(valueList, headerIndex, "device_category", lineNo)); - pointConfig.setDeviceId(getRequiredString(valueList, headerIndex, "device_id", lineNo)); + pointConfig.setPointId(getRequiredString(valueList, headerIndex, "point_id", lineNo)); + pointConfig.setDeviceCategory(getString(valueList, headerIndex, "device_category")); + pointConfig.setDeviceId(getString(valueList, headerIndex, "device_id")); pointConfig.setDataKey(getRequiredString(valueList, headerIndex, "data_key", lineNo)); pointConfig.setPointDesc(getRequiredString(valueList, headerIndex, "point_desc", lineNo)); String registerAddress = getString(valueList, headerIndex, "register_address"); @@ -555,6 +686,9 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { case "siteid": case "站点id": return "site_id"; + case "pointid": + case "点位id": + return "point_id"; case "devicecategory": case "设备类型": return "device_category"; @@ -643,6 +777,12 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService { if (pointConfig == null) { return; } + pointConfig.setPointId(StringUtils.trimToNull(pointConfig.getPointId())); + if (StringUtils.isBlank(pointConfig.getPointId())) { + throw new ServiceException("点位ID不能为空"); + } + pointConfig.setDeviceCategory(StringUtils.trimToNull(pointConfig.getDeviceCategory())); + pointConfig.setDeviceId(StringUtils.trimToNull(pointConfig.getDeviceId())); pointConfig.setPointType(normalizePointType(pointConfig.getPointType())); pointConfig.setCalcExpression(StringUtils.trimToNull(pointConfig.getCalcExpression())); if ("calc".equals(pointConfig.getPointType())) { diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyRuntimeConfigServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyRuntimeConfigServiceImpl.java new file mode 100644 index 0000000..2f19806 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyRuntimeConfigServiceImpl.java @@ -0,0 +1,87 @@ +package com.xzzn.ems.service.impl; + +import com.xzzn.common.utils.DateUtils; +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; +import com.xzzn.ems.mapper.EmsStrategyRuntimeConfigMapper; +import com.xzzn.ems.service.IEmsStrategyRuntimeConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +/** + * 策略运行参数配置Service业务层处理 + * + * @author xzzn + * @date 2026-02-13 + */ +@Service +public class EmsStrategyRuntimeConfigServiceImpl implements IEmsStrategyRuntimeConfigService { + + private static final BigDecimal DEFAULT_SOC_DOWN = BigDecimal.ZERO; + private static final BigDecimal DEFAULT_SOC_UP = new BigDecimal("100"); + private static final BigDecimal DEFAULT_ANTI_REVERSE_THRESHOLD = new BigDecimal("30"); + private static final BigDecimal DEFAULT_ANTI_REVERSE_RANGE_PERCENT = new BigDecimal("20"); + 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_HARD_STOP_THRESHOLD = new BigDecimal("20"); + + @Autowired + private EmsStrategyRuntimeConfigMapper runtimeConfigMapper; + + @Override + public EmsStrategyRuntimeConfig getBySiteId(String siteId) { + EmsStrategyRuntimeConfig config = runtimeConfigMapper.selectBySiteId(siteId); + if (config == null) { + return buildDefaultConfig(siteId); + } + fillMissingWithDefault(config, siteId); + return config; + } + + @Override + public int saveBySiteId(EmsStrategyRuntimeConfig config) { + fillMissingWithDefault(config, config.getSiteId()); + EmsStrategyRuntimeConfig exist = runtimeConfigMapper.selectBySiteId(config.getSiteId()); + if (exist == null) { + config.setCreateTime(DateUtils.getNowDate()); + return runtimeConfigMapper.insert(config); + } + config.setUpdateTime(DateUtils.getNowDate()); + return runtimeConfigMapper.updateBySiteId(config); + } + + private EmsStrategyRuntimeConfig buildDefaultConfig(String siteId) { + EmsStrategyRuntimeConfig config = new EmsStrategyRuntimeConfig(); + fillMissingWithDefault(config, siteId); + return config; + } + + private void fillMissingWithDefault(EmsStrategyRuntimeConfig config, String siteId) { + if (StringUtils.isNotEmpty(siteId)) { + config.setSiteId(siteId); + } + if (config.getSocDown() == null) { + config.setSocDown(DEFAULT_SOC_DOWN); + } + if (config.getSocUp() == null) { + config.setSocUp(DEFAULT_SOC_UP); + } + if (config.getAntiReverseThreshold() == null) { + config.setAntiReverseThreshold(DEFAULT_ANTI_REVERSE_THRESHOLD); + } + if (config.getAntiReverseRangePercent() == null) { + config.setAntiReverseRangePercent(DEFAULT_ANTI_REVERSE_RANGE_PERCENT); + } + if (config.getAntiReverseUp() == null) { + config.setAntiReverseUp(DEFAULT_ANTI_REVERSE_UP); + } + if (config.getAntiReversePowerDownPercent() == null) { + config.setAntiReversePowerDownPercent(DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT); + } + if (config.getAntiReverseHardStopThreshold() == null) { + config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD); + } + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyTempServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyTempServiceImpl.java index 640cc91..c9b5bac 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyTempServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyTempServiceImpl.java @@ -92,6 +92,9 @@ public class EmsStrategyTempServiceImpl implements IEmsStrategyTempService temp.setStartTime(timeConfig.getStartTime()); temp.setEndTime(timeConfig.getEndTime()); temp.setChargeDischargePower(timeConfig.getChargeDischargePower()); + // 每个时间段可独立配置SOC上下限;为空时兼容旧的模板级配置 + temp.setSdcDown(timeConfig.getSdcDown() != null ? timeConfig.getSdcDown() : publicTemp.getSdcDown()); + temp.setSdcUp(timeConfig.getSdcUp() != null ? timeConfig.getSdcUp() : publicTemp.getSdcUp()); temp.setChargeStatus(timeConfig.getChargeStatus()); emsStrategyTempMapper.insertEmsStrategyTemp(temp); } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java index 8e634fd..7f66cf2 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java @@ -176,11 +176,15 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService } private List queryPointCurve(EmsPointConfig config, int dataUnit, Date startDate, Date endDate) { - if (config == null || config.getSiteId() == null || config.getDeviceId() == null || config.getDataKey() == null) { + if (config == null || config.getSiteId() == null || config.getDeviceId() == null) { + return Collections.emptyList(); + } + String influxPointKey = resolveInfluxPointKey(config); + if (influxPointKey == null) { return Collections.emptyList(); } List values = influxPointDataWriter.queryCurveData( - config.getSiteId(), config.getDeviceId(), config.getDataKey(), startDate, endDate + config.getSiteId(), config.getDeviceId(), influxPointKey, startDate, endDate ); if (values == null || values.isEmpty()) { return Collections.emptyList(); @@ -205,6 +209,16 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService return result; } + private String resolveInfluxPointKey(EmsPointConfig config) { + if (config == null) { + return null; + } + if (config.getPointId() != null && !"".equals(config.getPointId().trim())) { + return config.getPointId().trim(); + } + return null; + } + private String buildDisplayDeviceId(EmsPointConfig config) { String pointName = config.getPointName() == null || "".equals(config.getPointName().trim()) ? config.getDataKey() : config.getPointName().trim(); diff --git a/ems-system/src/main/resources/mapper/ems/EmsPcsSettingMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPcsSettingMapper.xml index 287ab57..7a69e04 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPcsSettingMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPcsSettingMapper.xml @@ -13,6 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -23,7 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select id, device_setting_id, point_address, power_address, start_command, stop_command, start_power, stop_power, cluster_num, cluster_point_address, create_by, create_time, update_by, update_time, remark from ems_pcs_setting + select id, device_setting_id, point_address, power_address, start_command, stop_command, start_power, stop_power, power_multiplier, cluster_num, cluster_point_address, create_by, create_time, update_by, update_time, remark from ems_pcs_setting + + where id = #{id} + + + + + + insert into ems_point_calc_config + + site_id, + device_category, + point_name, + data_key, + point_desc, + data_unit, + calc_expression, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{siteId}, + #{deviceCategory}, + #{pointName}, + #{dataKey}, + #{pointDesc}, + #{dataUnit}, + #{calcExpression}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update ems_point_calc_config + + site_id = #{siteId}, + device_category = #{deviceCategory}, + point_name = #{pointName}, + data_key = #{dataKey}, + point_desc = #{pointDesc}, + data_unit = #{dataUnit}, + calc_expression = #{calcExpression}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from ems_point_calc_config where id = #{id} + + + + delete from ems_point_calc_config where id in + + #{id} + + + + + + + delete from ems_point_calc_config + where site_id = #{siteId} + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml index 6a0add3..bd43748 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml @@ -6,6 +6,7 @@ + @@ -29,7 +30,7 @@ - select id, site_id, device_category, device_id, point_name, data_key, point_desc, register_address, + select id, point_id, site_id, device_category, device_id, point_name, data_key, point_desc, register_address, data_unit, data_a, data_k, data_b, data_bit, is_alarm, point_type, calc_expression, create_by, create_time, update_by, update_time, remark from ems_point_config @@ -44,6 +45,7 @@ and site_id = #{siteId} + and point_id = #{pointId} and device_category = #{deviceCategory} and device_id = #{deviceId} and data_key like concat('%', #{dataKey}, '%') @@ -57,6 +59,7 @@ insert into ems_point_config + point_id, site_id, device_category, device_id, @@ -79,6 +82,7 @@ remark, + #{pointId}, #{siteId}, #{deviceCategory}, #{deviceId}, @@ -102,9 +106,44 @@ + + insert into ems_point_config ( + point_id, site_id, device_category, device_id, point_name, data_key, point_desc, register_address, + data_unit, data_a, data_k, data_b, data_bit, is_alarm, point_type, calc_expression, + create_by, create_time, update_by, update_time, remark + ) + values + + ( + #{item.pointId}, + #{item.siteId}, + #{item.deviceCategory}, + #{item.deviceId}, + #{item.pointName}, + #{item.dataKey}, + #{item.pointDesc}, + #{item.registerAddress}, + #{item.dataUnit}, + #{item.dataA}, + #{item.dataK}, + #{item.dataB}, + #{item.dataBit}, + #{item.isAlarm}, + #{item.pointType}, + #{item.calcExpression}, + #{item.createBy}, + now(), + #{item.updateBy}, + now(), + #{item.remark} + ) + + + update ems_point_config + point_id = #{pointId}, site_id = #{siteId}, device_category = #{deviceCategory}, device_id = #{deviceId}, @@ -151,12 +190,12 @@ insert into ems_point_config ( - site_id, device_category, device_id, point_name, data_key, point_desc, register_address, + point_id, site_id, device_category, device_id, point_name, data_key, point_desc, register_address, data_unit, data_a, data_k, data_b, data_bit, is_alarm, point_type, calc_expression, create_by, create_time, update_by, update_time, remark ) select - #{targetSiteId}, device_category, device_id, point_name, data_key, point_desc, register_address, + concat('PT_', replace(uuid(), '-', '')), #{targetSiteId}, device_category, device_id, point_name, data_key, point_desc, register_address, data_unit, data_a, data_k, data_b, data_bit, is_alarm, point_type, calc_expression, #{operName}, now(), #{operName}, now(), remark from ems_point_config @@ -228,4 +267,13 @@ + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml index 6627ba2..c3e96f3 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml @@ -9,6 +9,8 @@ + + @@ -17,7 +19,7 @@ + + where site_id = #{siteId} + limit 1 + + + + insert into ems_strategy_runtime_config + + site_id, + soc_down, + soc_up, + anti_reverse_threshold, + anti_reverse_range_percent, + anti_reverse_up, + anti_reverse_power_down_percent, + anti_reverse_hard_stop_threshold, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{siteId}, + #{socDown}, + #{socUp}, + #{antiReverseThreshold}, + #{antiReverseRangePercent}, + #{antiReverseUp}, + #{antiReversePowerDownPercent}, + #{antiReverseHardStopThreshold}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update ems_strategy_runtime_config + + soc_down = #{socDown}, + soc_up = #{socUp}, + anti_reverse_threshold = #{antiReverseThreshold}, + anti_reverse_range_percent = #{antiReverseRangePercent}, + anti_reverse_up = #{antiReverseUp}, + anti_reverse_power_down_percent = #{antiReversePowerDownPercent}, + anti_reverse_hard_stop_threshold = #{antiReverseHardStopThreshold}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where site_id = #{siteId} + +