dev #4

Merged
dashixiong merged 11 commits from dev into main 2026-04-09 01:32:13 +00:00
74 changed files with 6476 additions and 337 deletions
Showing only changes of commit 6253fb6b2d - Show all commits

View File

@ -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<EmsPointCalcConfig> 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));
}
}

View File

@ -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));
}
}

View File

@ -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<Long, Boolean> 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<Map<String, String>> 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<Long, EmsPcsSetting> 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;
}
}

View File

@ -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())

View File

@ -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();
}
}

View File

@ -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())

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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())

View File

@ -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;
}

View File

@ -8,6 +8,8 @@ public class SiteMonitorProjectPointMappingSaveRequest {
private List<SiteMonitorProjectPointMappingVo> mappings;
private List<String> deletedFieldCodes;
public String getSiteId() {
return siteId;
}
@ -23,4 +25,12 @@ public class SiteMonitorProjectPointMappingSaveRequest {
public void setMappings(List<SiteMonitorProjectPointMappingVo> mappings) {
this.mappings = mappings;
}
public List<String> getDeletedFieldCodes() {
return deletedFieldCodes;
}
public void setDeletedFieldCodes(List<String> deletedFieldCodes) {
this.deletedFieldCodes = deletedFieldCodes;
}
}

View File

@ -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;
}
}

View File

@ -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<EmsPointCalcConfig> 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);
}

View File

@ -13,6 +13,8 @@ public interface EmsPointConfigMapper {
int insertEmsPointConfig(EmsPointConfig emsPointConfig);
int insertBatchEmsPointConfig(@Param("list") List<EmsPointConfig> list);
int updateEmsPointConfig(EmsPointConfig emsPointConfig);
int deleteEmsPointConfigById(Long id);
@ -41,4 +43,7 @@ public interface EmsPointConfigMapper {
@Param("deviceCategory") String deviceCategory,
@Param("pointNames") List<String> pointNames,
@Param("deviceIds") List<String> deviceIds);
List<EmsPointConfig> selectBySiteIdAndPointIds(@Param("siteId") String siteId,
@Param("pointIds") List<String> pointIds);
}

View File

@ -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);
}

View File

@ -0,0 +1,19 @@
package com.xzzn.ems.service;
import com.xzzn.ems.domain.EmsPointCalcConfig;
import java.util.List;
public interface IEmsPointCalcConfigService {
List<EmsPointCalcConfig> 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);
}

View File

@ -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);
}

View File

@ -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<PointDataRecord> pointDataQueue = new LinkedBlockingQueue<>(POINT_QUEUE_CAPACITY);
private final Map<String, List<ExpressionToken>> 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<String> deviceIds = new LinkedHashSet<>();
Map<String, String> deviceCategoryMap = buildDeviceCategoryMap(siteId, arraylist);
Map<String, BigDecimal> 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<String, BigDecimal> 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<String, BigDecimal> 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<String, BigDecimal> processPointConfigData(String siteId, String deviceId, String deviceCategory, String jsonData, Date dataUpdateTime) {
Map<String, BigDecimal> 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<String, Object> dataMap = JSON.parseObject(jsonData, new TypeReference<Map<String, Object>>() {
});
if (org.apache.commons.collections4.MapUtils.isEmpty(dataMap)) {
return;
return pointIdValueMap;
}
List<EmsPointConfig> pointConfigs = getPointConfigsWithCache(siteId, deviceId);
List<EmsPointConfig> pointConfigs = getPointConfigsWithCache(siteId, deviceId, deviceCategory);
if (CollectionUtils.isEmpty(pointConfigs)) {
return;
return pointIdValueMap;
}
Map<String, EmsPointConfig> configByDataKey = pointConfigs.stream()
.filter(cfg -> StringUtils.isNotBlank(cfg.getDataKey()))
.collect(Collectors.toMap(
cfg -> cfg.getDataKey().toUpperCase(),
cfg -> cfg,
(oldValue, newValue) -> oldValue
));
List<EmsPointConfig> dataPointConfigs = new ArrayList<>();
Set<String> 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<String, BigDecimal> rawDataValues = new HashMap<>();
for (Map.Entry<String, Object> 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<String, String> buildDeviceCategoryMap(String siteId, JSONArray arraylist) {
Map<String, String> 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<String, String> 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<String, BigDecimal> buildBatchContextBySite(String siteId, JSONArray arraylist, Map<String, String> deviceCategoryMap) {
Map<String, BigDecimal> 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<String, Object> dataMap = JSON.parseObject(jsonData, new TypeReference<Map<String, Object>>() {
});
if (org.apache.commons.collections4.MapUtils.isEmpty(dataMap)) {
continue;
}
Map<String, EmsPointConfig> pointConfigByDataKey = new HashMap<>();
List<EmsPointConfig> 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<String, Object> 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<String, BigDecimal> executeCalcPointConfigs(String siteId, String deviceId, List<EmsPointConfig> calcPointConfigs,
Map<String, BigDecimal> contextValues, Date dataUpdateTime) {
Map<String, BigDecimal> calcPointIdValueMap = new HashMap<>();
if (CollectionUtils.isEmpty(calcPointConfigs)) {
return calcPointIdValueMap;
}
Map<String, EmsPointConfig> 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<String> finishedKeys = new ArrayList<>();
List<String> unresolvedKeys = new ArrayList<>();
for (Map.Entry<String, EmsPointConfig> 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<String, BigDecimal> 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<String, BigDecimal> 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<String, BigDecimal> contextValues) {
if (StringUtils.isBlank(expression)) {
throw new IllegalArgumentException("计算表达式为空");
}
List<ExpressionToken> rpnTokens = calcExpressionCache.computeIfAbsent(expression, this::compileExpressionToRpn);
return evaluateRpnTokens(rpnTokens, contextValues == null ? Collections.emptyMap() : contextValues);
}
private List<ExpressionToken> compileExpressionToRpn(String expression) {
if (StringUtils.isBlank(expression)) {
throw new IllegalArgumentException("计算表达式为空");
}
List<ExpressionToken> output = new ArrayList<>();
Deque<ExpressionToken> operators = new ArrayDeque<>();
int index = 0;
ExpressionToken previous = null;
while (index < expression.length()) {
char ch = expression.charAt(index);
if (Character.isWhitespace(ch)) {
index++;
continue;
}
if (Character.isDigit(ch) || ch == '.') {
int start = index;
boolean hasDot = ch == '.';
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isDigit(next)) {
index++;
continue;
}
if (next == '.' && !hasDot) {
hasDot = true;
index++;
continue;
}
break;
}
String numberText = expression.substring(start, index);
try {
output.add(ExpressionToken.number(new BigDecimal(numberText)));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("数值格式错误: " + numberText);
}
previous = output.get(output.size() - 1);
continue;
}
if (Character.isLetter(ch) || ch == '_') {
int start = index;
index++;
while (index < expression.length()) {
char next = expression.charAt(index);
if (Character.isLetterOrDigit(next) || next == '_') {
index++;
continue;
}
break;
}
String variable = expression.substring(start, index).trim().toUpperCase();
output.add(ExpressionToken.variable(variable));
previous = output.get(output.size() - 1);
continue;
}
if (ch == '(') {
operators.push(ExpressionToken.leftParen());
previous = ExpressionToken.leftParen();
index++;
continue;
}
if (ch == ')') {
boolean matched = false;
while (!operators.isEmpty()) {
ExpressionToken token = operators.pop();
if (token.getType() == ExpressionTokenType.LEFT_PAREN) {
matched = true;
break;
}
output.add(token);
}
if (!matched) {
throw new IllegalArgumentException("括号不匹配");
}
previous = ExpressionToken.rightParen();
index++;
continue;
}
if (isOperator(ch)) {
boolean unaryMinus = ch == '-' && (previous == null
|| previous.getType() == ExpressionTokenType.OPERATOR
|| previous.getType() == ExpressionTokenType.LEFT_PAREN);
String operatorText = unaryMinus ? "~" : String.valueOf(ch);
ExpressionToken currentOperator = ExpressionToken.operator(operatorText);
while (!operators.isEmpty() && operators.peek().getType() == ExpressionTokenType.OPERATOR) {
ExpressionToken top = operators.peek();
if (shouldPopOperator(currentOperator, top)) {
output.add(operators.pop());
} else {
break;
}
}
operators.push(currentOperator);
previous = currentOperator;
index++;
continue;
}
throw new IllegalArgumentException("表达式包含非法字符: " + ch);
}
while (!operators.isEmpty()) {
ExpressionToken token = operators.pop();
if (token.getType() == ExpressionTokenType.LEFT_PAREN) {
throw new IllegalArgumentException("括号不匹配");
}
output.add(token);
}
return output;
}
private BigDecimal evaluateRpnTokens(List<ExpressionToken> rpnTokens, Map<String, BigDecimal> contextValues) {
Deque<BigDecimal> values = new ArrayDeque<>();
for (ExpressionToken token : rpnTokens) {
if (token.getType() == ExpressionTokenType.NUMBER) {
values.push(token.getNumber());
continue;
}
if (token.getType() == ExpressionTokenType.VARIABLE) {
BigDecimal variableValue = contextValues.get(token.getText());
if (variableValue == null) {
throw new MissingVariableException(token.getText());
}
values.push(variableValue);
continue;
}
if (token.getType() != ExpressionTokenType.OPERATOR) {
throw new IllegalArgumentException("表达式令牌类型不合法: " + token.getType());
}
if ("~".equals(token.getText())) {
if (values.isEmpty()) {
throw new IllegalArgumentException("表达式不合法,缺少操作数");
}
values.push(values.pop().negate());
continue;
}
if (values.size() < 2) {
throw new IllegalArgumentException("表达式不合法,缺少操作数");
}
BigDecimal right = values.pop();
BigDecimal left = values.pop();
values.push(applyOperator(left, right, token.getText()));
}
if (values.size() != 1) {
throw new IllegalArgumentException("表达式不合法,无法归约到单值");
}
return values.pop();
}
private BigDecimal applyOperator(BigDecimal left, BigDecimal right, String operator) {
switch (operator) {
case "+":
return left.add(right);
case "-":
return left.subtract(right);
case "*":
return left.multiply(right);
case "/":
if (BigDecimal.ZERO.compareTo(right) == 0) {
throw new IllegalArgumentException("除数不能为0");
}
return left.divide(right, 10, RoundingMode.HALF_UP);
default:
throw new IllegalArgumentException("不支持的操作符: " + operator);
}
}
private boolean shouldPopOperator(ExpressionToken currentOperator, ExpressionToken stackOperator) {
if (currentOperator == null || stackOperator == null) {
return false;
}
int currentPriority = getOperatorPriority(currentOperator.getText());
int stackPriority = getOperatorPriority(stackOperator.getText());
if (isRightAssociative(currentOperator.getText())) {
return stackPriority > currentPriority;
}
return stackPriority >= currentPriority;
}
private int getOperatorPriority(String operator) {
if ("~".equals(operator)) {
return 3;
}
if ("*".equals(operator) || "/".equals(operator)) {
return 2;
}
if ("+".equals(operator) || "-".equals(operator)) {
return 1;
}
return 0;
}
private boolean isRightAssociative(String operator) {
return "~".equals(operator);
}
private boolean isOperator(char ch) {
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
private void flushPointDataSafely() {
if (!pointDataFlushing.compareAndSet(false, true)) {
return;
@ -366,21 +834,48 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
influxPointDataWriter.writeBatch(payloads);
}
private List<EmsPointConfig> getPointConfigsWithCache(String siteId, String deviceId) {
String cacheKey = RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId;
private List<EmsPointConfig> getPointConfigsWithCache(String siteId, String deviceId, String deviceCategory) {
String normalizedCategory = StringUtils.defaultString(deviceCategory).trim().toUpperCase();
String cacheKey = RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId + "_" + normalizedCategory;
List<EmsPointConfig> cached = redisCache.getCacheObject(cacheKey);
if (cached != null) {
return cached;
}
// 1) 设备级点位(原有逻辑)
EmsPointConfig query = new EmsPointConfig();
query.setSiteId(siteId);
query.setDeviceId(deviceId);
List<EmsPointConfig> latest = emsPointConfigMapper.selectEmsPointConfigList(query);
List<EmsPointConfig> cacheValue = latest == null ? new ArrayList<>() : latest;
List<EmsPointConfig> cacheValue = emsPointConfigMapper.selectEmsPointConfigList(query);
redisCache.setCacheObject(cacheKey, cacheValue);
return cacheValue;
}
private List<EmsPointConfig> getSiteCalcPointConfigs(String siteId) {
if (StringUtils.isBlank(siteId)) {
return Collections.emptyList();
}
EmsPointConfig query = new EmsPointConfig();
query.setSiteId(siteId);
query.setPointType("calc");
List<EmsPointConfig> calcPoints = emsPointConfigMapper.selectEmsPointConfigList(query);
if (CollectionUtils.isEmpty(calcPoints)) {
return Collections.emptyList();
}
Map<String, EmsPointConfig> 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);

View File

@ -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<EmsSiteMonitorItem> 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<EmsSiteMonitorItem> itemList = emsSiteMonitorItemMapper.selectEnabledList();
List<EmsSiteMonitorItem> itemList = getEnabledMonitorItems();
if (itemList == null || itemList.isEmpty()) {
return result;
}
List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId);
Set<String> deletedFieldCodeSet = mappingList.stream()
.filter(item -> StringUtils.isNotBlank(item.getFieldCode()))
.filter(item -> DELETED_FIELD_MARK.equals(item.getDataPoint()))
.map(EmsSiteMonitorPointMatch::getFieldCode)
.collect(Collectors.toSet());
Map<String, EmsSiteMonitorPointMatch> 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<String, String> 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<EmsSiteMonitorItem> itemList = emsSiteMonitorItemMapper.selectEnabledList();
List<EmsSiteMonitorItem> itemList = getEnabledMonitorItems();
if (itemList == null || itemList.isEmpty()) {
throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item");
}
Set<String> fieldCodeSet = itemList.stream().map(EmsSiteMonitorItem::getFieldCode).collect(Collectors.toSet());
emsSiteMonitorPointMatchMapper.deleteBySiteId(siteId);
if (request.getMappings() == null || request.getMappings().isEmpty()) {
return 0;
Set<String> 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<EmsSiteMonitorPointMatch> 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<String> pointIds = mappingList.stream()
.map(SiteMonitorProjectPointMappingVo::getDataPoint)
.filter(StringUtils::isNotBlank)
.map(item -> item.trim().toUpperCase())
.collect(Collectors.toSet());
Map<String, EmsPointConfig> pointConfigByPointId = buildPointConfigByPointId(siteId, pointIds);
Map<String, Object> homeLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_HOME)));
Map<String, Object> sbjkLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_SBJK)));
Map<String, Object> 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<String, EmsPointConfig> buildPointConfigByPointId(String siteId, Set<String> pointIds) {
Map<String, EmsPointConfig> result = new HashMap<>();
if (StringUtils.isBlank(siteId) || pointIds == null || pointIds.isEmpty()) {
return result;
}
List<EmsPointConfig> 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<String, EmsPointConfig> 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<EmsSiteMonitorItem> itemList = emsSiteMonitorItemMapper.selectEnabledList();
List<EmsSiteMonitorItem> 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<EmsSiteMonitorItem> itemList = emsSiteMonitorItemMapper.selectEnabledList();
List<EmsSiteMonitorItem> itemList = getEnabledMonitorItems();
if (itemList == null || itemList.isEmpty()) {
return 0;
}
@ -608,15 +822,24 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
Map<Date, JSONObject> 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<EmsSiteMonitorItem> getEnabledMonitorItems() {
long now = System.currentTimeMillis();
List<EmsSiteMonitorItem> cached = monitorItemCache;
if (now < monitorItemCacheExpireAt && cached != null) {
return cached;
}
synchronized (this) {
now = System.currentTimeMillis();
if (now < monitorItemCacheExpireAt && monitorItemCache != null) {
return monitorItemCache;
}
List<EmsSiteMonitorItem> latest = emsSiteMonitorItemMapper.selectEnabledList();
if (latest == null) {
latest = Collections.emptyList();
}
monitorItemCache = latest;
monitorItemCacheExpireAt = now + MONITOR_ITEM_CACHE_TTL_MS;
return latest;
}
}
private Map<String, String> extractHotColumns(JSONObject merged) {
Map<String, String> 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<PointQueryResponse> list, Object value) {
return list.stream()
@ -1050,6 +1303,10 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
public List<WriteTagConfig> getWriteTags(String workStatus, EmsPcsSetting pcsSetting) {
List<WriteTagConfig> 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)) {

View File

@ -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<EmsPointCalcConfig> selectPointCalcConfigList(EmsPointCalcConfig pointCalcConfig) {
EmsPointConfig query = toPointConfigQuery(pointCalcConfig);
List<EmsPointConfig> 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<EmsPointConfig> 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;
}
}

View File

@ -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<EmsPointConfig> 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<PointConfigCurveValueVo> 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<PointConfigCurveValueVo> queryCurveDataFromInflux(String siteId, String deviceId, String dataKey, Date startTime, Date endTime) {
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveData(siteId, deviceId, dataKey, startTime, endTime);
private List<PointConfigCurveValueVo> 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<InfluxPointDataWriter.PointValue> 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<String> 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<EmsPointConfig> 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<String> headerList = parseCsvLine(removeUtf8Bom(headerLine));
Map<String, Integer> 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<String> 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())) {

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -176,11 +176,15 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
}
private List<GeneralQueryDataVo> 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<InfluxPointDataWriter.PointValue> 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();

View File

@ -13,6 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="stopCommand" column="stop_command" />
<result property="startPower" column="start_power" />
<result property="stopPower" column="stop_power" />
<result property="powerMultiplier" column="power_multiplier" />
<result property="clusterNum" column="cluster_num" />
<result property="clusterPointAddress" column="cluster_point_address" />
<result property="createBy" column="create_by" />
@ -23,7 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectEmsPcsSettingVo">
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
</sql>
<select id="selectEmsPcsSettingList" parameterType="EmsPcsSetting" resultMap="EmsPcsSettingResult">
@ -36,6 +37,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="stopCommand != null and stopCommand != ''"> and stop_command = #{stopCommand}</if>
<if test="startPower != null "> and start_power = #{startPower}</if>
<if test="stopPower != null "> and stop_power = #{stopPower}</if>
<if test="powerMultiplier != null "> and power_multiplier = #{powerMultiplier}</if>
<if test="clusterNum != null "> and cluster_num = #{clusterNum}</if>
<if test="clusterPointAddress != null and clusterPointAddress != ''"> and cluster_point_address = #{clusterPointAddress}</if>
</where>
@ -61,6 +63,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="stopCommand != null">stop_command,</if>
<if test="startPower != null">start_power,</if>
<if test="stopPower != null">stop_power,</if>
<if test="powerMultiplier != null">power_multiplier,</if>
<if test="clusterNum != null">cluster_num,</if>
<if test="clusterPointAddress != null">cluster_point_address,</if>
<if test="createBy != null">create_by,</if>
@ -77,6 +80,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="stopCommand != null">#{stopCommand},</if>
<if test="startPower != null">#{startPower},</if>
<if test="stopPower != null">#{stopPower},</if>
<if test="powerMultiplier != null">#{powerMultiplier},</if>
<if test="clusterNum != null">#{clusterNum},</if>
<if test="clusterPointAddress != null">#{clusterPointAddress},</if>
<if test="createBy != null">#{createBy},</if>
@ -97,6 +101,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="stopCommand != null">stop_command = #{stopCommand},</if>
<if test="startPower != null">start_power = #{startPower},</if>
<if test="stopPower != null">stop_power = #{stopPower},</if>
<if test="powerMultiplier != null">power_multiplier = #{powerMultiplier},</if>
<if test="clusterNum != null">cluster_num = #{clusterNum},</if>
<if test="clusterPointAddress != null">cluster_point_address = #{clusterPointAddress},</if>
<if test="createBy != null">create_by = #{createBy},</if>
@ -129,6 +134,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="stopCommand != null">stop_command = #{stopCommand},</if>
<if test="startPower != null">start_power = #{startPower},</if>
<if test="stopPower != null">stop_power = #{stopPower},</if>
<if test="powerMultiplier != null">power_multiplier = #{powerMultiplier},</if>
<if test="clusterNum != null">cluster_num = #{clusterNum},</if>
<if test="clusterPointAddress != null">cluster_point_address = #{clusterPointAddress},</if>
<if test="createBy != null">create_by = #{createBy},</if>
@ -140,4 +146,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where device_setting_id = #{deviceSettingId}
</update>
</mapper>
</mapper>

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xzzn.ems.mapper.EmsPointCalcConfigMapper">
<resultMap type="EmsPointCalcConfig" id="EmsPointCalcConfigResult">
<result property="id" column="id"/>
<result property="siteId" column="site_id"/>
<result property="deviceCategory" column="device_category"/>
<result property="pointName" column="point_name"/>
<result property="dataKey" column="data_key"/>
<result property="pointDesc" column="point_desc"/>
<result property="dataUnit" column="data_unit"/>
<result property="calcExpression" column="calc_expression"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectEmsPointCalcConfigVo">
select id, site_id, device_category, point_name, data_key, point_desc, data_unit, calc_expression,
create_by, create_time, update_by, update_time, remark
from ems_point_calc_config
</sql>
<select id="selectEmsPointCalcConfigById" parameterType="Long" resultMap="EmsPointCalcConfigResult">
<include refid="selectEmsPointCalcConfigVo"/>
where id = #{id}
</select>
<select id="selectEmsPointCalcConfigList" parameterType="EmsPointCalcConfig" resultMap="EmsPointCalcConfigResult">
<include refid="selectEmsPointCalcConfigVo"/>
<where>
<if test="siteId != null and siteId != ''">and site_id = #{siteId}</if>
<if test="deviceCategory != null and deviceCategory != ''">and device_category = #{deviceCategory}</if>
<if test="dataKey != null and dataKey != ''">and data_key like concat('%', #{dataKey}, '%')</if>
<if test="pointDesc != null and pointDesc != ''">and point_desc like concat('%', #{pointDesc}, '%')</if>
</where>
order by update_time desc, id desc
</select>
<insert id="insertEmsPointCalcConfig" parameterType="EmsPointCalcConfig" useGeneratedKeys="true" keyProperty="id">
insert into ems_point_calc_config
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="siteId != null">site_id,</if>
<if test="deviceCategory != null">device_category,</if>
<if test="pointName != null">point_name,</if>
<if test="dataKey != null">data_key,</if>
<if test="pointDesc != null">point_desc,</if>
<if test="dataUnit != null">data_unit,</if>
<if test="calcExpression != null">calc_expression,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="siteId != null">#{siteId},</if>
<if test="deviceCategory != null">#{deviceCategory},</if>
<if test="pointName != null">#{pointName},</if>
<if test="dataKey != null">#{dataKey},</if>
<if test="pointDesc != null">#{pointDesc},</if>
<if test="dataUnit != null">#{dataUnit},</if>
<if test="calcExpression != null">#{calcExpression},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateEmsPointCalcConfig" parameterType="EmsPointCalcConfig">
update ems_point_calc_config
<trim prefix="SET" suffixOverrides=",">
<if test="siteId != null">site_id = #{siteId},</if>
<if test="deviceCategory != null">device_category = #{deviceCategory},</if>
<if test="pointName != null">point_name = #{pointName},</if>
<if test="dataKey != null">data_key = #{dataKey},</if>
<if test="pointDesc != null">point_desc = #{pointDesc},</if>
<if test="dataUnit != null">data_unit = #{dataUnit},</if>
<if test="calcExpression != null">calc_expression = #{calcExpression},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteEmsPointCalcConfigById" parameterType="Long">
delete from ems_point_calc_config where id = #{id}
</delete>
<delete id="deleteEmsPointCalcConfigByIds" parameterType="String">
delete from ems_point_calc_config where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="countBySiteId" resultType="int">
select count(1)
from ems_point_calc_config
where site_id = #{siteId}
</select>
<delete id="deleteBySiteId">
delete from ems_point_calc_config
where site_id = #{siteId}
</delete>
</mapper>

View File

@ -6,6 +6,7 @@
<resultMap type="EmsPointConfig" id="EmsPointConfigResult">
<result property="id" column="id"/>
<result property="pointId" column="point_id"/>
<result property="siteId" column="site_id"/>
<result property="deviceCategory" column="device_category"/>
<result property="deviceId" column="device_id"/>
@ -29,7 +30,7 @@
</resultMap>
<sql id="selectEmsPointConfigVo">
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 @@
<include refid="selectEmsPointConfigVo"/>
<where>
<if test="siteId != null and siteId != ''">and site_id = #{siteId}</if>
<if test="pointId != null and pointId != ''">and point_id = #{pointId}</if>
<if test="deviceCategory != null and deviceCategory != ''">and device_category = #{deviceCategory}</if>
<if test="deviceId != null and deviceId != ''">and device_id = #{deviceId}</if>
<if test="dataKey != null and dataKey != ''">and data_key like concat('%', #{dataKey}, '%')</if>
@ -57,6 +59,7 @@
<insert id="insertEmsPointConfig" parameterType="EmsPointConfig" useGeneratedKeys="true" keyProperty="id">
insert into ems_point_config
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="pointId != null">point_id,</if>
<if test="siteId != null">site_id,</if>
<if test="deviceCategory != null">device_category,</if>
<if test="deviceId != null">device_id,</if>
@ -79,6 +82,7 @@
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="pointId != null">#{pointId},</if>
<if test="siteId != null">#{siteId},</if>
<if test="deviceCategory != null">#{deviceCategory},</if>
<if test="deviceId != null">#{deviceId},</if>
@ -102,9 +106,44 @@
</trim>
</insert>
<insert id="insertBatchEmsPointConfig">
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
<foreach collection="list" item="item" separator=",">
(
#{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}
)
</foreach>
</insert>
<update id="updateEmsPointConfig" parameterType="EmsPointConfig">
update ems_point_config
<trim prefix="SET" suffixOverrides=",">
<if test="pointId != null">point_id = #{pointId},</if>
<if test="siteId != null">site_id = #{siteId},</if>
<if test="deviceCategory != null">device_category = #{deviceCategory},</if>
<if test="deviceId != null">device_id = #{deviceId},</if>
@ -151,12 +190,12 @@
<insert id="copyTemplateToSite">
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 @@
</if>
</select>
<select id="selectBySiteIdAndPointIds" resultMap="EmsPointConfigResult">
<include refid="selectEmsPointConfigVo"/>
where site_id = #{siteId}
and point_id in
<foreach collection="pointIds" item="pointId" open="(" separator="," close=")">
#{pointId}
</foreach>
</select>
</mapper>

View File

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

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xzzn.ems.mapper.EmsStrategyRuntimeConfigMapper">
<resultMap type="EmsStrategyRuntimeConfig" id="EmsStrategyRuntimeConfigResult">
<result property="id" column="id"/>
<result property="siteId" column="site_id"/>
<result property="socDown" column="soc_down"/>
<result property="socUp" column="soc_up"/>
<result property="antiReverseThreshold" column="anti_reverse_threshold"/>
<result property="antiReverseRangePercent" column="anti_reverse_range_percent"/>
<result property="antiReverseUp" column="anti_reverse_up"/>
<result property="antiReversePowerDownPercent" column="anti_reverse_power_down_percent"/>
<result property="antiReverseHardStopThreshold" column="anti_reverse_hard_stop_threshold"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectVo">
select id,
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
from ems_strategy_runtime_config
</sql>
<select id="selectBySiteId" parameterType="String" resultMap="EmsStrategyRuntimeConfigResult">
<include refid="selectVo"/>
where site_id = #{siteId}
limit 1
</select>
<insert id="insert" parameterType="EmsStrategyRuntimeConfig" useGeneratedKeys="true" keyProperty="id">
insert into ems_strategy_runtime_config
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="siteId != null and siteId != ''">site_id,</if>
<if test="socDown != null">soc_down,</if>
<if test="socUp != null">soc_up,</if>
<if test="antiReverseThreshold != null">anti_reverse_threshold,</if>
<if test="antiReverseRangePercent != null">anti_reverse_range_percent,</if>
<if test="antiReverseUp != null">anti_reverse_up,</if>
<if test="antiReversePowerDownPercent != null">anti_reverse_power_down_percent,</if>
<if test="antiReverseHardStopThreshold != null">anti_reverse_hard_stop_threshold,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="siteId != null and siteId != ''">#{siteId},</if>
<if test="socDown != null">#{socDown},</if>
<if test="socUp != null">#{socUp},</if>
<if test="antiReverseThreshold != null">#{antiReverseThreshold},</if>
<if test="antiReverseRangePercent != null">#{antiReverseRangePercent},</if>
<if test="antiReverseUp != null">#{antiReverseUp},</if>
<if test="antiReversePowerDownPercent != null">#{antiReversePowerDownPercent},</if>
<if test="antiReverseHardStopThreshold != null">#{antiReverseHardStopThreshold},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateBySiteId" parameterType="EmsStrategyRuntimeConfig">
update ems_strategy_runtime_config
<trim prefix="SET" suffixOverrides=",">
<if test="socDown != null">soc_down = #{socDown},</if>
<if test="socUp != null">soc_up = #{socUp},</if>
<if test="antiReverseThreshold != null">anti_reverse_threshold = #{antiReverseThreshold},</if>
<if test="antiReverseRangePercent != null">anti_reverse_range_percent = #{antiReverseRangePercent},</if>
<if test="antiReverseUp != null">anti_reverse_up = #{antiReverseUp},</if>
<if test="antiReversePowerDownPercent != null">anti_reverse_power_down_percent = #{antiReversePowerDownPercent},</if>
<if test="antiReverseHardStopThreshold != null">anti_reverse_hard_stop_threshold = #{antiReverseHardStopThreshold},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where site_id = #{siteId}
</update>
</mapper>