临时修改

This commit is contained in:
2026-02-13 21:41:23 +08:00
parent 21673ecd1e
commit 6253fb6b2d
29 changed files with 2277 additions and 111 deletions

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.EmsBatteryStack;
import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsPcsSetting; import com.xzzn.ems.domain.EmsPcsSetting;
import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
import com.xzzn.ems.domain.EmsStrategyLog; import com.xzzn.ems.domain.EmsStrategyLog;
import com.xzzn.ems.domain.EmsStrategyTemp; import com.xzzn.ems.domain.EmsStrategyTemp;
import com.xzzn.ems.domain.EmsStrategyTimeConfig; import com.xzzn.ems.domain.EmsStrategyTimeConfig;
@ -25,17 +26,20 @@ import com.xzzn.ems.mapper.EmsBatteryStackMapper;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsPcsSettingMapper; import com.xzzn.ems.mapper.EmsPcsSettingMapper;
import com.xzzn.ems.mapper.EmsStrategyLogMapper; import com.xzzn.ems.mapper.EmsStrategyLogMapper;
import com.xzzn.ems.mapper.EmsStrategyRuntimeConfigMapper;
import com.xzzn.ems.mapper.EmsStrategyRunningMapper; import com.xzzn.ems.mapper.EmsStrategyRunningMapper;
import com.xzzn.ems.mapper.EmsStrategyTempMapper; import com.xzzn.ems.mapper.EmsStrategyTempMapper;
import com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper; import com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -57,16 +61,20 @@ public class StrategyPoller {
private static final ConcurrentHashMap<Long, Boolean> strategyLocks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<Long, Boolean> strategyLocks = new ConcurrentHashMap<>();
// SOC 上下限值默认为0%-100% // SOC 上下限值默认为0%-100%
private static final BigDecimal SOC_DOWN = new BigDecimal(0); private static final BigDecimal DEFAULT_SOC_DOWN = BigDecimal.ZERO;
private static final BigDecimal SOC_UP = new BigDecimal(100); private static final BigDecimal DEFAULT_SOC_UP = new BigDecimal(100);
// 逆变器功率下限值默认为30kW // 逆变器功率下限值默认为30kW
private static final BigDecimal ANTI_REVERSE_THRESHOLD = new BigDecimal(30); private static final BigDecimal DEFAULT_ANTI_REVERSE_THRESHOLD = new BigDecimal(30);
// 逆变器下限值范围默认为20% // 逆变器下限值范围默认为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 // 逆变器功率上限值默认为100kW
private static final BigDecimal ANTI_REVERSE_UP = new BigDecimal(100); private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal(100);
// PCS功率降幅默认为10% // 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 @Autowired
private EmsStrategyRunningMapper emsStrategyRunningMapper; private EmsStrategyRunningMapper emsStrategyRunningMapper;
@ -85,6 +93,8 @@ public class StrategyPoller {
@Autowired @Autowired
private EmsStrategyLogMapper emsStrategyLogMapper; private EmsStrategyLogMapper emsStrategyLogMapper;
@Autowired @Autowired
private EmsStrategyRuntimeConfigMapper runtimeConfigMapper;
@Autowired
private ModbusProcessor modbusProcessor; private ModbusProcessor modbusProcessor;
@Resource(name = "modbusExecutor") @Resource(name = "modbusExecutor")
@ -132,6 +142,7 @@ public class StrategyPoller {
} }
private void dealStrategyCurveData(Long strategyId, String siteId) { private void dealStrategyCurveData(Long strategyId, String siteId) {
EmsStrategyRuntimeConfig runtimeConfig = getRuntimeConfig(siteId);
// 1.获取当前策略的所有模板 // 1.获取当前策略的所有模板
List<Map<String, String>> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId); List<Map<String, String>> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId);
if (CollectionUtils.isEmpty(temps)) { if (CollectionUtils.isEmpty(temps)) {
@ -174,24 +185,38 @@ public class StrategyPoller {
continue; continue;
} }
// 判断SOC上下限 // 判断SOC上下限
if (isSocInRange(emsStrategyTemp)) { if (isSocInRange(emsStrategyTemp, runtimeConfig)) {
BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower().divide(new BigDecimal(pcsDeviceList.size())); 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) { 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) { if (pcsSetting == null || pcsSetting.getClusterNum() < 1) {
logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId()); logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId());
continue; continue;
} }
// 功率默认放大10倍平均功率值根据电池簇数量进行平均分配 // 功率默认放大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())) { if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
// 发送Modbus命令控制设备-充电 // 发送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())) { } else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
boolean needAntiReverseFlow = false; boolean needAntiReverseFlow = false;
Integer powerDownType = null; Integer powerDownType = null;
BigDecimal chargeDischargePower = avgChargeDischargePower; BigDecimal chargeDischargePower = strategyPower;
// 查询策略运行日志 // 查询策略运行日志
EmsStrategyLog lastStrategyLog = getLastStrategyLog(pcsDevice.getDeviceId(), emsStrategyTemp); EmsStrategyLog lastStrategyLog = getLastStrategyLog(pcsDevice.getDeviceId(), emsStrategyTemp);
if (lastStrategyLog != null) { if (lastStrategyLog != null) {
@ -204,32 +229,54 @@ public class StrategyPoller {
} }
// 查询电网电表的正向有功功率,36kW-50kW范围内稳定运行低于36kW降功率高于50kW增加功率 // 查询电网电表的正向有功功率,36kW-50kW范围内稳定运行低于36kW降功率高于50kW增加功率
EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name()); if (totalActivePower == null) {
if (emsAmmeterData == null || emsAmmeterData.getTotalActivePower() == null) { logger.warn("当前站点: {}, 未获取到最新电表数据,执行保守策略并切换待机", emsStrategyTemp.getSiteId());
logger.info("当前站点: {}, 未获取到最新电表数据", emsStrategyTemp.getSiteId()); sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, true, 0);
continue;
} else { } 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()); needAntiReverseFlow = isNeedAntiReverseFlow(totalActivePower, runtimeConfig);
BigDecimal power = avgChargeDischargePower.multiply(ANTI_REVERSE_POWER_DOWN_PERCENT).divide(new BigDecimal(100)); BigDecimal power = strategyPower.multiply(runtimeConfig.getAntiReversePowerDownPercent())
.divide(new BigDecimal(100), POWER_SCALE, RoundingMode.HALF_UP);
if (needAntiReverseFlow) { if (needAntiReverseFlow) {
// 降功率 // 降功率
chargeDischargePower = chargeDischargePower.subtract(power); chargeDischargePower = chargeDischargePower.subtract(power);
powerDownType = 0; powerDownType = 0;
} else { } else {
// 判断是否需要增加功率, // 判断是否需要增加功率,
if (powerDownType != null && emsAmmeterData.getTotalActivePower().compareTo(ANTI_REVERSE_UP) > 0) { if (powerDownType != null && totalActivePower.compareTo(runtimeConfig.getAntiReverseUp()) > 0) {
if (chargeDischargePower.compareTo(avgChargeDischargePower) == 0) { if (chargeDischargePower.compareTo(targetPower) >= 0) {
// 功率增加到平均值则停止 // 功率增加到限幅值则停止
continue; continue;
} }
// 增加功率 // 增加功率
chargeDischargePower = chargeDischargePower.add(power); chargeDischargePower = chargeDischargePower.add(power);
if (chargeDischargePower.compareTo(targetPower) > 0) {
chargeDischargePower = targetPower;
}
powerDownType = 1; powerDownType = 1;
needAntiReverseFlow = true; needAntiReverseFlow = true;
} }
} }
} }
if (chargeDischargePower.compareTo(BigDecimal.ZERO) < 0) {
chargeDischargePower = BigDecimal.ZERO;
}
if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) { if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) {
// 如果已经降功率到0则设备直接待机 // 如果已经降功率到0则设备直接待机
// 发送Modbus命令控制设备-待机 // 发送Modbus命令控制设备-待机
@ -297,11 +344,11 @@ public class StrategyPoller {
return emsStrategyLogMapper.getLastStrategyLog(query); return emsStrategyLogMapper.getLastStrategyLog(query);
} }
private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower) { private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower, EmsStrategyRuntimeConfig runtimeConfig) {
// 获取当前设定的防逆流阈值(30kW) // 获取当前设定的防逆流阈值(30kW)
BigDecimal threshold = ANTI_REVERSE_THRESHOLD; BigDecimal threshold = runtimeConfig.getAntiReverseThreshold();
// 计算20%范围的上限(36kW) // 计算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%范围) // 判断电网电表正向有功功率是否小于36kW(接近30kW的20%范围)
return totalActivePower.compareTo(upperLimit) < 0; return totalActivePower.compareTo(upperLimit) < 0;
@ -409,8 +456,9 @@ public class StrategyPoller {
continue; continue;
} else { } else {
// 充、放电,则先开机设备 // 充、放电,则先开机设备
switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL); if (!switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL)) {
continue; continue;
}
} }
} }
@ -440,14 +488,17 @@ public class StrategyPoller {
private boolean switchDevice(EmsDevicesSetting pcsDevice, EmsPcsSetting pcsSetting, WorkStatus workStatus) { private boolean switchDevice(EmsDevicesSetting pcsDevice, EmsPcsSetting pcsSetting, WorkStatus workStatus) {
String siteId = pcsDevice.getSiteId(); String siteId = pcsDevice.getSiteId();
String deviceId = pcsDevice.getDeviceId(); String deviceId = pcsDevice.getDeviceId();
String originalWorkStatus = pcsDevice.getWorkStatus();
pcsDevice.setWorkStatus(workStatus.getCode()); pcsDevice.setWorkStatus(workStatus.getCode());
DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting , null, 1); DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting , null, 1);
if (deviceConfig == null) { if (deviceConfig == null) {
pcsDevice.setWorkStatus(originalWorkStatus);
return false; return false;
} }
boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig); boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig);
if (!result) { if (!result) {
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceConfig, workStatus.getInfo()); pcsDevice.setWorkStatus(originalWorkStatus);
logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, workStatus.getInfo());
} }
return result; return result;
} }
@ -461,13 +512,17 @@ public class StrategyPoller {
LocalTime endLocalTime = endTime.toInstant() LocalTime endLocalTime = endTime.toInstant()
.atZone(zoneId) .atZone(zoneId)
.toLocalTime(); .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上限和下限 // 判断SOC上限和下限
private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp) { private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp, EmsStrategyRuntimeConfig runtimeConfig) {
BigDecimal socDown = SOC_DOWN; BigDecimal socDown = runtimeConfig.getSocDown();
BigDecimal socUp = SOC_UP; BigDecimal socUp = runtimeConfig.getSocUp();
if (SocLimit.ON.getCode().equals(emsStrategyTemp.getSdcLimit())) { if (SocLimit.ON.getCode().equals(emsStrategyTemp.getSdcLimit())) {
socDown = emsStrategyTemp.getSdcDown(); socDown = emsStrategyTemp.getSdcDown();
socUp = emsStrategyTemp.getSdcUp(); socUp = emsStrategyTemp.getSdcUp();
@ -491,4 +546,34 @@ public class StrategyPoller {
return true; 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 = "关机目标功率") @Excel(name = "关机目标功率")
private BigDecimal stopPower; private BigDecimal stopPower;
/** 目标功率倍率 */
@Excel(name = "目标功率倍率")
private BigDecimal powerMultiplier;
/** 电池簇数 */ /** 电池簇数 */
@Excel(name = "电池簇数") @Excel(name = "电池簇数")
private Integer clusterNum; private Integer clusterNum;
@ -135,6 +139,16 @@ public class EmsPcsSetting extends BaseEntity
return stopPower; return stopPower;
} }
public BigDecimal getPowerMultiplier()
{
return powerMultiplier;
}
public void setPowerMultiplier(BigDecimal powerMultiplier)
{
this.powerMultiplier = powerMultiplier;
}
public void setClusterNum(Integer clusterNum) public void setClusterNum(Integer clusterNum)
{ {
this.clusterNum = clusterNum; this.clusterNum = clusterNum;
@ -165,6 +179,7 @@ public class EmsPcsSetting extends BaseEntity
.append("stopCommand", getStopCommand()) .append("stopCommand", getStopCommand())
.append("startPower", getStartPower()) .append("startPower", getStartPower())
.append("stopPower", getStopPower()) .append("stopPower", getStopPower())
.append("powerMultiplier", getPowerMultiplier())
.append("clusterNum", getClusterNum()) .append("clusterNum", getClusterNum())
.append("clusterPointAddress", getClusterPointAddress()) .append("clusterPointAddress", getClusterPointAddress())
.append("createBy", getCreateBy()) .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; private Long id;
@Excel(name = "点位ID")
private String pointId;
@Excel(name = "站点ID") @Excel(name = "站点ID")
private String siteId; private String siteId;
@ -68,6 +71,14 @@ public class EmsPointConfig extends BaseEntity {
this.id = id; this.id = id;
} }
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getSiteId() { public String getSiteId() {
return siteId; return siteId;
} }
@ -192,6 +203,7 @@ public class EmsPointConfig extends BaseEntity {
public String toString() { public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId()) .append("id", getId())
.append("pointId", getPointId())
.append("siteId", getSiteId()) .append("siteId", getSiteId())
.append("deviceCategory", getDeviceCategory()) .append("deviceCategory", getDeviceCategory())
.append("deviceId", getDeviceId()) .append("deviceId", getDeviceId())

View File

@ -12,6 +12,8 @@ public class EmsSiteMonitorPointMatch extends BaseEntity {
private String siteId; private String siteId;
private String fieldCode; private String fieldCode;
private String dataPoint; private String dataPoint;
private String fixedDataPoint;
private Integer useFixedDisplay;
public Long getId() { public Long getId() {
return id; return id;
@ -44,4 +46,20 @@ public class EmsSiteMonitorPointMatch extends BaseEntity {
public void setDataPoint(String dataPoint) { public void setDataPoint(String dataPoint) {
this.dataPoint = 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)") @Excel(name = "充放功率 (kW)")
private BigDecimal chargeDischargePower; private BigDecimal chargeDischargePower;
/** SDC下限 (%) */
@Excel(name = "SDC下限 (%)")
private BigDecimal sdcDown;
/** SDC上限 (%) */
@Excel(name = "SDC上限 (%)")
private BigDecimal sdcUp;
/** 充电状态如“1-充电”、“2-待机” */ /** 充电状态如“1-充电”、“2-待机” */
@Excel(name = "充电状态如“1-充电”、“2-待机”") @Excel(name = "充电状态如“1-充电”、“2-待机”")
private String chargeStatus; private String chargeStatus;
@ -83,6 +91,26 @@ public class EmsStrategyTempTimeConfig extends BaseEntity
return chargeDischargePower; 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) public void setChargeStatus(String chargeStatus)
{ {
this.chargeStatus = chargeStatus; this.chargeStatus = chargeStatus;
@ -110,6 +138,8 @@ public class EmsStrategyTempTimeConfig extends BaseEntity
.append("startTime", getStartTime()) .append("startTime", getStartTime())
.append("endTime", getEndTime()) .append("endTime", getEndTime())
.append("chargeDischargePower", getChargeDischargePower()) .append("chargeDischargePower", getChargeDischargePower())
.append("sdcDown", getSdcDown())
.append("sdcUp", getSdcUp())
.append("chargeStatus", getChargeStatus()) .append("chargeStatus", getChargeStatus())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())

View File

@ -3,7 +3,9 @@ package com.xzzn.ems.domain.vo;
public class PointConfigCurveRequest { public class PointConfigCurveRequest {
private String siteId; private String siteId;
private String deviceId; private String deviceId;
private String pointId;
private String dataKey; private String dataKey;
private String pointType;
private String rangeType; private String rangeType;
private String startTime; private String startTime;
private String endTime; private String endTime;
@ -24,6 +26,14 @@ public class PointConfigCurveRequest {
this.deviceId = deviceId; this.deviceId = deviceId;
} }
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getDataKey() { public String getDataKey() {
return dataKey; return dataKey;
} }
@ -32,6 +42,14 @@ public class PointConfigCurveRequest {
this.dataKey = dataKey; this.dataKey = dataKey;
} }
public String getPointType() {
return pointType;
}
public void setPointType(String pointType) {
this.pointType = pointType;
}
public String getRangeType() { public String getRangeType() {
return rangeType; return rangeType;
} }

View File

@ -8,6 +8,8 @@ public class SiteMonitorProjectPointMappingSaveRequest {
private List<SiteMonitorProjectPointMappingVo> mappings; private List<SiteMonitorProjectPointMappingVo> mappings;
private List<String> deletedFieldCodes;
public String getSiteId() { public String getSiteId() {
return siteId; return siteId;
} }
@ -23,4 +25,12 @@ public class SiteMonitorProjectPointMappingSaveRequest {
public void setMappings(List<SiteMonitorProjectPointMappingVo> mappings) { public void setMappings(List<SiteMonitorProjectPointMappingVo> mappings) {
this.mappings = 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 dataPoint;
private String fixedDataPoint;
private Integer useFixedDisplay;
public String getModuleCode() { public String getModuleCode() {
return moduleCode; return moduleCode;
} }
@ -81,4 +85,20 @@ public class SiteMonitorProjectPointMappingVo {
public void setDataPoint(String dataPoint) { public void setDataPoint(String dataPoint) {
this.dataPoint = 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 insertEmsPointConfig(EmsPointConfig emsPointConfig);
int insertBatchEmsPointConfig(@Param("list") List<EmsPointConfig> list);
int updateEmsPointConfig(EmsPointConfig emsPointConfig); int updateEmsPointConfig(EmsPointConfig emsPointConfig);
int deleteEmsPointConfigById(Long id); int deleteEmsPointConfigById(Long id);
@ -41,4 +43,7 @@ public interface EmsPointConfigMapper {
@Param("deviceCategory") String deviceCategory, @Param("deviceCategory") String deviceCategory,
@Param("pointNames") List<String> pointNames, @Param("pointNames") List<String> pointNames,
@Param("deviceIds") List<String> deviceIds); @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.lang.reflect.Field;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -77,6 +81,7 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.Collections; import java.util.Collections;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -104,12 +109,14 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
// 匹配DTDC+数字格式的正则(提取序号) // 匹配DTDC+数字格式的正则(提取序号)
private static final Pattern DTDC_PATTERN = Pattern.compile("DTDC(\\d+)([A-Za-z]*)"); private static final Pattern DTDC_PATTERN = Pattern.compile("DTDC(\\d+)([A-Za-z]*)");
private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
private static final Pattern VARIABLE_SAFE_PATTERN = Pattern.compile("[^A-Za-z0-9_]");
private static final int POINT_QUEUE_CAPACITY = 100000; private static final int POINT_QUEUE_CAPACITY = 100000;
private static final int POINT_FLUSH_BATCH_SIZE = 2000; private static final int POINT_FLUSH_BATCH_SIZE = 2000;
private static final int POINT_FLUSH_MAX_DRAIN_PER_RUN = 20000; private static final int POINT_FLUSH_MAX_DRAIN_PER_RUN = 20000;
private static final int POINT_ENQUEUE_RETRY_TIMES = 3; private static final int POINT_ENQUEUE_RETRY_TIMES = 3;
private static final long POINT_ENQUEUE_RETRY_WAIT_MS = 10; private static final long POINT_ENQUEUE_RETRY_WAIT_MS = 10;
private static final long POINT_FLUSH_INTERVAL_MS = 100; private static final long POINT_FLUSH_INTERVAL_MS = 100;
private static final String SITE_LEVEL_CALC_DEVICE_ID = "SITE_CALC";
@Autowired @Autowired
private EmsBatteryClusterMapper emsBatteryClusterMapper; private EmsBatteryClusterMapper emsBatteryClusterMapper;
@ -169,6 +176,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
@Autowired @Autowired
private InfluxPointDataWriter influxPointDataWriter; private InfluxPointDataWriter influxPointDataWriter;
private final BlockingQueue<PointDataRecord> pointDataQueue = new LinkedBlockingQueue<>(POINT_QUEUE_CAPACITY); private final BlockingQueue<PointDataRecord> pointDataQueue = new LinkedBlockingQueue<>(POINT_QUEUE_CAPACITY);
private final Map<String, List<ExpressionToken>> calcExpressionCache = new ConcurrentHashMap<>();
private final ScheduledExecutorService pointDataWriter = Executors.newSingleThreadScheduledExecutor(r -> { private final ScheduledExecutorService pointDataWriter = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r); Thread thread = new Thread(r);
thread.setName("point-data-writer"); thread.setName("point-data-writer");
@ -201,6 +209,9 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
long startMs = System.currentTimeMillis(); long startMs = System.currentTimeMillis();
log.info("开始处理设备数据, siteId: {}, messageSize: {}", siteId, arraylist.size()); log.info("开始处理设备数据, siteId: {}, messageSize: {}", siteId, arraylist.size());
Set<String> deviceIds = new LinkedHashSet<>(); 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++) { for (int i = 0; i < arraylist.size(); i++) {
JSONObject obj = JSONObject.parseObject(arraylist.get(i).toString()); JSONObject obj = JSONObject.parseObject(arraylist.get(i).toString());
@ -209,6 +220,9 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
String jsonData = obj.getString("Data"); String jsonData = obj.getString("Data");
Long timestamp = obj.getLong("timestamp"); Long timestamp = obj.getLong("timestamp");
Date dataUpdateTime = DateUtils.convertUpdateTime(timestamp); Date dataUpdateTime = DateUtils.convertUpdateTime(timestamp);
if (dataUpdateTime != null && (latestDataUpdateTime == null || dataUpdateTime.after(latestDataUpdateTime))) {
latestDataUpdateTime = dataUpdateTime;
}
if (StringUtils.isNotBlank(deviceId)) { if (StringUtils.isNotBlank(deviceId)) {
deviceIds.add(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.ORIGINAL_MQTT_DATA + siteId + "_" + deviceId, obj);
redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId, obj, 1, TimeUnit.MINUTES); redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId, obj, 1, TimeUnit.MINUTES);
processPointConfigData(siteId, deviceId, jsonData, dataUpdateTime); String deviceCategory = resolveDeviceCategory(siteId, deviceId, deviceCategoryMap);
iEmsDeviceSettingService.syncSiteMonitorDataByMqtt(siteId, deviceId, jsonData, dataUpdateTime); Map<String, BigDecimal> pointIdValueMap = processPointConfigData(siteId, deviceId, deviceCategory, jsonData, dataUpdateTime);
iEmsDeviceSettingService.syncSiteMonitorDataByMqtt(siteId, deviceId, JSON.toJSONString(pointIdValueMap), dataUpdateTime);
} catch (Exception e) { } catch (Exception e) {
log.warn("点位映射数据处理失败siteId: {}, deviceId: {}, err: {}", log.warn("点位映射数据处理失败siteId: {}, deviceId: {}, err: {}",
siteId, deviceId, e.getMessage(), e); 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: {}", log.info("结束处理设备数据, siteId: {}, deviceCount: {}, deviceIds: {}, costMs: {}",
siteId, deviceIds.size(), String.join(",", deviceIds), System.currentTimeMillis() - startMs); 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)) { if (StringUtils.isAnyBlank(siteId, deviceId, jsonData)) {
return; return pointIdValueMap;
} }
if (!DB_NAME_PATTERN.matcher(siteId).matches()) { if (!DB_NAME_PATTERN.matcher(siteId).matches()) {
log.warn("站点ID不合法跳过点位映射落库siteId: {}", siteId); log.warn("站点ID不合法跳过点位映射落库siteId: {}", siteId);
return; return pointIdValueMap;
} }
Map<String, Object> dataMap = JSON.parseObject(jsonData, new TypeReference<Map<String, Object>>() { Map<String, Object> dataMap = JSON.parseObject(jsonData, new TypeReference<Map<String, Object>>() {
}); });
if (org.apache.commons.collections4.MapUtils.isEmpty(dataMap)) { 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)) { if (CollectionUtils.isEmpty(pointConfigs)) {
return; return pointIdValueMap;
} }
Map<String, EmsPointConfig> configByDataKey = pointConfigs.stream() List<EmsPointConfig> dataPointConfigs = new ArrayList<>();
.filter(cfg -> StringUtils.isNotBlank(cfg.getDataKey())) Set<String> uniquePointKeys = new HashSet<>();
.collect(Collectors.toMap( for (EmsPointConfig pointConfig : pointConfigs) {
cfg -> cfg.getDataKey().toUpperCase(), String pointContextKey = resolvePointContextKey(pointConfig);
cfg -> cfg, if (pointConfig == null || StringUtils.isBlank(pointContextKey)) {
(oldValue, newValue) -> oldValue 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()) { for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
String dataKey = entry.getKey(); if (StringUtils.isBlank(entry.getKey())) {
if (StringUtils.isBlank(dataKey)) {
continue; continue;
} }
EmsPointConfig pointConfig = configByDataKey.get(dataKey.toUpperCase());
if (pointConfig == null) {
continue;
}
BigDecimal pointValue = StringUtils.getBigDecimal(entry.getValue()); BigDecimal pointValue = StringUtils.getBigDecimal(entry.getValue());
if (pointValue == null) { if (pointValue == null) {
continue; continue;
} }
rawDataValues.put(entry.getKey().trim().toUpperCase(), pointValue);
// 支持按配置进行二次转换f(x)=A*x^2 + K*x + B
pointValue = convertPointValue(pointValue, pointConfig);
enqueuePointData(siteId, deviceId, pointConfig.getDataKey(), pointValue, dataUpdateTime);
} }
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) { 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); .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() { private void flushPointDataSafely() {
if (!pointDataFlushing.compareAndSet(false, true)) { if (!pointDataFlushing.compareAndSet(false, true)) {
return; return;
@ -366,21 +834,48 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i
influxPointDataWriter.writeBatch(payloads); influxPointDataWriter.writeBatch(payloads);
} }
private List<EmsPointConfig> getPointConfigsWithCache(String siteId, String deviceId) { private List<EmsPointConfig> getPointConfigsWithCache(String siteId, String deviceId, String deviceCategory) {
String cacheKey = RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId; String normalizedCategory = StringUtils.defaultString(deviceCategory).trim().toUpperCase();
String cacheKey = RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId + "_" + normalizedCategory;
List<EmsPointConfig> cached = redisCache.getCacheObject(cacheKey); List<EmsPointConfig> cached = redisCache.getCacheObject(cacheKey);
if (cached != null) { if (cached != null) {
return cached; return cached;
} }
// 1) 设备级点位(原有逻辑)
EmsPointConfig query = new EmsPointConfig(); EmsPointConfig query = new EmsPointConfig();
query.setSiteId(siteId); query.setSiteId(siteId);
query.setDeviceId(deviceId); query.setDeviceId(deviceId);
List<EmsPointConfig> latest = emsPointConfigMapper.selectEmsPointConfigList(query); List<EmsPointConfig> cacheValue = emsPointConfigMapper.selectEmsPointConfigList(query);
List<EmsPointConfig> cacheValue = latest == null ? new ArrayList<>() : latest;
redisCache.setCacheObject(cacheKey, cacheValue); redisCache.setCacheObject(cacheKey, cacheValue);
return 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 static class PointDataRecord {
private final String siteId; private final String siteId;
private final String deviceId; 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) { private JSONArray parseJsonData(String message) {
try { try {
JSONObject object = JSONObject.parseObject(message); 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.DateUtils;
import com.xzzn.common.utils.StringUtils; import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsDevicesSetting;
import com.xzzn.ems.domain.EmsPointConfig;
import com.xzzn.ems.domain.EmsPointMatch; import com.xzzn.ems.domain.EmsPointMatch;
import com.xzzn.ems.domain.EmsPcsSetting; import com.xzzn.ems.domain.EmsPcsSetting;
import com.xzzn.ems.domain.EmsSiteMonitorItem; 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.EmsBatteryDataMinutesMapper;
import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsPcsSettingMapper; import com.xzzn.ems.mapper.EmsPcsSettingMapper;
import com.xzzn.ems.mapper.EmsPointConfigMapper;
import com.xzzn.ems.mapper.EmsPointMatchMapper; import com.xzzn.ems.mapper.EmsPointMatchMapper;
import com.xzzn.ems.mapper.EmsSiteMonitorDataMapper; import com.xzzn.ems.mapper.EmsSiteMonitorDataMapper;
import com.xzzn.ems.mapper.EmsSiteMonitorItemMapper; import com.xzzn.ems.mapper.EmsSiteMonitorItemMapper;
@ -45,12 +47,14 @@ import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.Collections;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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_HOME = "HOME";
private static final String MODULE_SBJK = "SBJK"; private static final String MODULE_SBJK = "SBJK";
private static final String MODULE_TJBB = "TJBB"; 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_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_SBJK = "ems_site_monitor_data_sbjk_his";
private static final String HISTORY_TABLE_TJBB = "ems_site_monitor_data_tjbb_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 @Autowired
private EmsDevicesSettingMapper emsDevicesMapper; private EmsDevicesSettingMapper emsDevicesMapper;
@Autowired @Autowired
@ -81,6 +88,8 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
@Autowired @Autowired
private EmsPcsSettingMapper emsPcsSettingMapper; private EmsPcsSettingMapper emsPcsSettingMapper;
@Autowired @Autowired
private EmsPointConfigMapper emsPointConfigMapper;
@Autowired
private RedisCache redisCache; private RedisCache redisCache;
@Autowired @Autowired
private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper; private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper;
@ -95,6 +104,9 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
@Autowired @Autowired
private EmsSiteMonitorDataMapper emsSiteMonitorDataMapper; private EmsSiteMonitorDataMapper emsSiteMonitorDataMapper;
private volatile List<EmsSiteMonitorItem> monitorItemCache = Collections.emptyList();
private volatile long monitorItemCacheExpireAt = 0L;
/** /**
* 获取设备详细信息 * 获取设备详细信息
* @param id * @param id
@ -406,16 +418,30 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
if (StringUtils.isBlank(siteId)) { if (StringUtils.isBlank(siteId)) {
return result; return result;
} }
List<EmsSiteMonitorItem> itemList = emsSiteMonitorItemMapper.selectEnabledList(); List<EmsSiteMonitorItem> itemList = getEnabledMonitorItems();
if (itemList == null || itemList.isEmpty()) { if (itemList == null || itemList.isEmpty()) {
return result; return result;
} }
List<EmsSiteMonitorPointMatch> mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId); 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() Map<String, String> pointMap = mappingList.stream()
.filter(item -> StringUtils.isNotBlank(item.getFieldCode())) .filter(item -> StringUtils.isNotBlank(item.getFieldCode()))
.filter(item -> !DELETED_FIELD_MARK.equals(item.getDataPoint()))
.collect(Collectors.toMap(EmsSiteMonitorPointMatch::getFieldCode, EmsSiteMonitorPointMatch::getDataPoint, (a, b) -> b)); .collect(Collectors.toMap(EmsSiteMonitorPointMatch::getFieldCode, EmsSiteMonitorPointMatch::getDataPoint, (a, b) -> b));
itemList.forEach(item -> { itemList.forEach(item -> {
if (deletedFieldCodeSet.contains(item.getFieldCode())) {
return;
}
EmsSiteMonitorPointMatch pointMatch = mappingByFieldCode.get(item.getFieldCode());
SiteMonitorProjectPointMappingVo vo = new SiteMonitorProjectPointMappingVo(); SiteMonitorProjectPointMappingVo vo = new SiteMonitorProjectPointMappingVo();
vo.setModuleCode(item.getModuleCode()); vo.setModuleCode(item.getModuleCode());
vo.setModuleName(item.getModuleName()); vo.setModuleName(item.getModuleName());
@ -425,6 +451,8 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
vo.setFieldCode(item.getFieldCode()); vo.setFieldCode(item.getFieldCode());
vo.setFieldName(item.getFieldName()); vo.setFieldName(item.getFieldName());
vo.setDataPoint(pointMap.getOrDefault(item.getFieldCode(), "")); 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); result.add(vo);
}); });
return result; return result;
@ -436,37 +464,70 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
throw new ServiceException("站点ID不能为空"); throw new ServiceException("站点ID不能为空");
} }
String siteId = request.getSiteId(); String siteId = request.getSiteId();
List<EmsSiteMonitorItem> itemList = emsSiteMonitorItemMapper.selectEnabledList(); List<EmsSiteMonitorItem> itemList = getEnabledMonitorItems();
if (itemList == null || itemList.isEmpty()) { if (itemList == null || itemList.isEmpty()) {
throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item"); throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item");
} }
Set<String> fieldCodeSet = itemList.stream().map(EmsSiteMonitorItem::getFieldCode).collect(Collectors.toSet()); Set<String> fieldCodeSet = itemList.stream().map(EmsSiteMonitorItem::getFieldCode).collect(Collectors.toSet());
emsSiteMonitorPointMatchMapper.deleteBySiteId(siteId); Set<String> deletedFieldCodeSet = new HashSet<>();
if (request.getDeletedFieldCodes() != null) {
if (request.getMappings() == null || request.getMappings().isEmpty()) { request.getDeletedFieldCodes().stream()
return 0; .filter(StringUtils::isNotBlank)
.map(String::trim)
.filter(fieldCodeSet::contains)
.forEach(deletedFieldCodeSet::add);
} }
int deletedRows = emsSiteMonitorPointMatchMapper.deleteBySiteId(siteId);
List<EmsSiteMonitorPointMatch> saveList = new ArrayList<>(); List<EmsSiteMonitorPointMatch> saveList = new ArrayList<>();
for (SiteMonitorProjectPointMappingVo mapping : request.getMappings()) { if (request.getMappings() != null) {
if (mapping == null || StringUtils.isBlank(mapping.getFieldCode()) || StringUtils.isBlank(mapping.getDataPoint())) { for (SiteMonitorProjectPointMappingVo mapping : request.getMappings()) {
continue; if (mapping == null || StringUtils.isBlank(mapping.getFieldCode())) {
} continue;
String fieldCode = mapping.getFieldCode().trim(); }
if (!fieldCodeSet.contains(fieldCode)) { String fieldCode = mapping.getFieldCode().trim();
continue; 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(); EmsSiteMonitorPointMatch pointMatch = new EmsSiteMonitorPointMatch();
pointMatch.setSiteId(siteId); pointMatch.setSiteId(siteId);
pointMatch.setFieldCode(fieldCode); pointMatch.setFieldCode(deletedFieldCode);
pointMatch.setDataPoint(mapping.getDataPoint().trim()); pointMatch.setDataPoint(DELETED_FIELD_MARK);
pointMatch.setFixedDataPoint(null);
pointMatch.setUseFixedDisplay(0);
pointMatch.setCreateBy(operName); pointMatch.setCreateBy(operName);
pointMatch.setUpdateBy(operName); pointMatch.setUpdateBy(operName);
saveList.add(pointMatch); saveList.add(pointMatch);
} }
// 点位映射变更后清理“单站监控最新值”缓存,避免页面回退读取到旧快照
clearSiteMonitorLatestCache(siteId);
if (saveList.isEmpty()) { if (saveList.isEmpty()) {
return 0; return deletedRows;
} }
return emsSiteMonitorPointMatchMapper.insertBatch(saveList); int insertedRows = emsSiteMonitorPointMatchMapper.insertBatch(saveList);
return deletedRows + insertedRows;
} }
@Override @Override
@ -475,6 +536,12 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
if (mappingList.isEmpty()) { if (mappingList.isEmpty()) {
return new ArrayList<>(); 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> homeLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_HOME)));
Map<String, Object> sbjkLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_SBJK))); Map<String, Object> sbjkLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_SBJK)));
Map<String, Object> tjbbLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_TJBB))); Map<String, Object> tjbbLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(siteId, MODULE_TJBB)));
@ -483,6 +550,20 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
for (SiteMonitorProjectPointMappingVo mapping : mappingList) { for (SiteMonitorProjectPointMappingVo mapping : mappingList) {
SiteMonitorProjectDisplayVo vo = new SiteMonitorProjectDisplayVo(); SiteMonitorProjectDisplayVo vo = new SiteMonitorProjectDisplayVo();
BeanUtils.copyProperties(mapping, vo); 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; Object cacheObj = null;
if (MODULE_HOME.equals(mapping.getModuleCode())) { if (MODULE_HOME.equals(mapping.getModuleCode())) {
cacheObj = homeLatestMap.get(mapping.getFieldCode()); cacheObj = homeLatestMap.get(mapping.getFieldCode());
@ -501,6 +582,139 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
return result; 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 @Override
public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName) { public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName) {
if (request == null || StringUtils.isBlank(request.getSiteId())) { if (request == null || StringUtils.isBlank(request.getSiteId())) {
@ -509,7 +723,7 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
if (request.getItems() == null || request.getItems().isEmpty()) { if (request.getItems() == null || request.getItems().isEmpty()) {
return 0; return 0;
} }
List<EmsSiteMonitorItem> itemList = emsSiteMonitorItemMapper.selectEnabledList(); List<EmsSiteMonitorItem> itemList = getEnabledMonitorItems();
if (itemList == null || itemList.isEmpty()) { if (itemList == null || itemList.isEmpty()) {
throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item"); throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item");
} }
@ -590,7 +804,7 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
if (mappingList == null || mappingList.isEmpty()) { if (mappingList == null || mappingList.isEmpty()) {
return 0; return 0;
} }
List<EmsSiteMonitorItem> itemList = emsSiteMonitorItemMapper.selectEnabledList(); List<EmsSiteMonitorItem> itemList = getEnabledMonitorItems();
if (itemList == null || itemList.isEmpty()) { if (itemList == null || itemList.isEmpty()) {
return 0; return 0;
} }
@ -608,15 +822,24 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
Map<Date, JSONObject> tjbbHistoryByMinute = new HashMap<>(); Map<Date, JSONObject> tjbbHistoryByMinute = new HashMap<>();
for (EmsSiteMonitorPointMatch mapping : mappingList) { 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; continue;
} }
EmsSiteMonitorItem itemDef = itemMap.get(mapping.getFieldCode()); EmsSiteMonitorItem itemDef = itemMap.get(mapping.getFieldCode());
if (itemDef == null || StringUtils.isBlank(itemDef.getModuleCode())) { if (itemDef == null || StringUtils.isBlank(itemDef.getModuleCode())) {
continue; continue;
} }
if (USE_FIXED_DISPLAY_YES.equals(mapping.getUseFixedDisplay())) {
String point = mapping.getDataPoint().trim(); continue;
}
String point = mapping.getDataPoint();
if (StringUtils.isBlank(point)) {
continue;
}
point = point.trim();
Object value = upperDataMap.get(point.toUpperCase()); Object value = upperDataMap.get(point.toUpperCase());
if (value == null && StringUtils.isNotBlank(deviceId)) { if (value == null && StringUtils.isNotBlank(deviceId)) {
value = upperDataMap.get((deviceId + point).toUpperCase()); value = upperDataMap.get((deviceId + point).toUpperCase());
@ -715,6 +938,27 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
return source == null ? new HashMap<>() : source; 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) { private Map<String, String> extractHotColumns(JSONObject merged) {
Map<String, String> result = new HashMap<>(); Map<String, String> result = new HashMap<>();
result.put("hotSoc", extractFieldValue(merged, result.put("hotSoc", extractFieldValue(merged,
@ -833,6 +1077,15 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
return RedisKeyConstants.SITE_MONITOR_LATEST + siteId + "_" + moduleCode; 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) { private PointQueryResponse findByValue(List<PointQueryResponse> list, Object value) {
return list.stream() return list.stream()
@ -1050,6 +1303,10 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
public List<WriteTagConfig> getWriteTags(String workStatus, EmsPcsSetting pcsSetting) { public List<WriteTagConfig> getWriteTags(String workStatus, EmsPcsSetting pcsSetting) {
List<WriteTagConfig> writeTags = new ArrayList<>(); List<WriteTagConfig> writeTags = new ArrayList<>();
BigDecimal power; BigDecimal power;
BigDecimal powerMultiplier = pcsSetting.getPowerMultiplier();
if (powerMultiplier == null || BigDecimal.ZERO.compareTo(powerMultiplier) == 0) {
powerMultiplier = BigDecimal.ONE;
}
if (WorkStatus.NORMAL.getCode().equals(workStatus)) { if (WorkStatus.NORMAL.getCode().equals(workStatus)) {
// 开机先发送开机指令再发送有功功率给定值 // 开机先发送开机指令再发送有功功率给定值
@ -1073,7 +1330,7 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService
if (power == null) { if (power == null) {
power = BigDecimal.ZERO; power = BigDecimal.ZERO;
} }
clusterWriteTag.setValue(power); clusterWriteTag.setValue(power.multiply(powerMultiplier));
writeTags.add(clusterWriteTag); writeTags.add(clusterWriteTag);
} }
if (WorkStatus.STOP.getCode().equals(workStatus)) { 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.mapper.EmsPointConfigMapper;
import com.xzzn.ems.service.IEmsPointConfigService; import com.xzzn.ems.service.IEmsPointConfigService;
import com.xzzn.ems.service.InfluxPointDataWriter; 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.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -43,7 +48,10 @@ import java.util.stream.Collectors;
@Service @Service
public class EmsPointConfigServiceImpl implements IEmsPointConfigService { 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 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 DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$"); private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$");
private static final Pattern DECIMAL_PATTERN = Pattern.compile("^-?\\d+(\\.\\d+)?$"); private static final Pattern DECIMAL_PATTERN = Pattern.compile("^-?\\d+(\\.\\d+)?$");
@ -75,7 +83,12 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
normalizeAndValidatePointConfig(pointConfig); normalizeAndValidatePointConfig(pointConfig);
pointConfig.setCreateBy(operName); pointConfig.setCreateBy(operName);
pointConfig.setUpdateBy(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) { if (rows > 0) {
invalidatePointConfigCache(pointConfig.getSiteId(), pointConfig.getDeviceId()); invalidatePointConfigCache(pointConfig.getSiteId(), pointConfig.getDeviceId());
} }
@ -87,7 +100,12 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
EmsPointConfig oldConfig = pointConfig.getId() == null ? null : emsPointConfigMapper.selectEmsPointConfigById(pointConfig.getId()); EmsPointConfig oldConfig = pointConfig.getId() == null ? null : emsPointConfigMapper.selectEmsPointConfigById(pointConfig.getId());
normalizeAndValidatePointConfig(pointConfig); normalizeAndValidatePointConfig(pointConfig);
pointConfig.setUpdateBy(operName); 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 (rows > 0) {
if (oldConfig != null) { if (oldConfig != null) {
invalidatePointConfigCache(oldConfig.getSiteId(), oldConfig.getDeviceId()); invalidatePointConfigCache(oldConfig.getSiteId(), oldConfig.getDeviceId());
@ -175,12 +193,25 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
emsPointConfigMapper.deleteBySiteId(siteId); emsPointConfigMapper.deleteBySiteId(siteId);
} }
int importCount = 0;
for (EmsPointConfig pointConfig : pointConfigList) { for (EmsPointConfig pointConfig : pointConfigList) {
pointConfig.setCreateBy(operName); pointConfig.setCreateBy(operName);
pointConfig.setUpdateBy(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); invalidatePointConfigCacheBySite(siteId);
return String.format("导入成功:站点 %s点位 %d 条", siteId, importCount); return String.format("导入成功:站点 %s点位 %d 条", siteId, importCount);
@ -214,17 +245,23 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
@Override @Override
public List<PointConfigCurveValueVo> getCurveData(PointConfigCurveRequest request) { 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<>(); return new ArrayList<>();
} }
String siteId = StringUtils.trim(request.getSiteId()); String siteId = StringUtils.trim(request.getSiteId());
String deviceId = StringUtils.trim(request.getDeviceId()); String deviceId = StringUtils.trim(request.getDeviceId());
String dataKey = StringUtils.trim(request.getDataKey()); String pointId = StringUtils.trim(request.getPointId());
if (StringUtils.isAnyBlank(siteId, deviceId, dataKey)) { 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<>(); return new ArrayList<>();
} }
Date[] range = resolveTimeRange(request); 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, private PointConfigLatestValueVo queryLatestValueFromRedis(PointConfigLatestValueItemVo item,
@ -259,8 +296,13 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
return vo; return vo;
} }
private List<PointConfigCurveValueVo> queryCurveDataFromInflux(String siteId, String deviceId, String dataKey, Date startTime, Date endTime) { private List<PointConfigCurveValueVo> queryCurveDataFromInflux(String siteId, String deviceId, String pointId,
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveData(siteId, deviceId, dataKey, startTime, endTime); 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()) { if (values == null || values.isEmpty()) {
return new ArrayList<>(); 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) { private void invalidatePointConfigCache(String siteId, String deviceId) {
if (StringUtils.isAnyBlank(siteId, deviceId)) { if (StringUtils.isBlank(siteId)) {
return; 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) { private void invalidatePointConfigCacheBySite(String siteId) {
@ -410,6 +494,53 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
return configByKey.get(dataKey.toUpperCase(Locale.ROOT)); 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) { private BigDecimal convertPointValue(BigDecimal sourceValue, EmsPointConfig pointConfig) {
if (sourceValue == null || pointConfig == null) { if (sourceValue == null || pointConfig == null) {
return sourceValue; return sourceValue;
@ -431,8 +562,7 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
} }
List<String> headerList = parseCsvLine(removeUtf8Bom(headerLine)); List<String> headerList = parseCsvLine(removeUtf8Bom(headerLine));
Map<String, Integer> headerIndex = buildHeaderIndex(headerList); Map<String, Integer> headerIndex = buildHeaderIndex(headerList);
assertRequiredHeader(headerIndex, "device_category", "device_category"); assertRequiredHeader(headerIndex, "point_id", "point_id");
assertRequiredHeader(headerIndex, "device_id", "device_id");
assertRequiredHeader(headerIndex, "data_key", "data_key"); assertRequiredHeader(headerIndex, "data_key", "data_key");
assertRequiredHeader(headerIndex, "point_desc", "point_desc"); assertRequiredHeader(headerIndex, "point_desc", "point_desc");
@ -446,8 +576,9 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
List<String> valueList = parseCsvLine(line); List<String> valueList = parseCsvLine(line);
EmsPointConfig pointConfig = new EmsPointConfig(); EmsPointConfig pointConfig = new EmsPointConfig();
pointConfig.setSiteId(siteId); pointConfig.setSiteId(siteId);
pointConfig.setDeviceCategory(getRequiredString(valueList, headerIndex, "device_category", lineNo)); pointConfig.setPointId(getRequiredString(valueList, headerIndex, "point_id", lineNo));
pointConfig.setDeviceId(getRequiredString(valueList, headerIndex, "device_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.setDataKey(getRequiredString(valueList, headerIndex, "data_key", lineNo));
pointConfig.setPointDesc(getRequiredString(valueList, headerIndex, "point_desc", lineNo)); pointConfig.setPointDesc(getRequiredString(valueList, headerIndex, "point_desc", lineNo));
String registerAddress = getString(valueList, headerIndex, "register_address"); String registerAddress = getString(valueList, headerIndex, "register_address");
@ -555,6 +686,9 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
case "siteid": case "siteid":
case "站点id": case "站点id":
return "site_id"; return "site_id";
case "pointid":
case "点位id":
return "point_id";
case "devicecategory": case "devicecategory":
case "设备类型": case "设备类型":
return "device_category"; return "device_category";
@ -643,6 +777,12 @@ public class EmsPointConfigServiceImpl implements IEmsPointConfigService {
if (pointConfig == null) { if (pointConfig == null) {
return; 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.setPointType(normalizePointType(pointConfig.getPointType()));
pointConfig.setCalcExpression(StringUtils.trimToNull(pointConfig.getCalcExpression())); pointConfig.setCalcExpression(StringUtils.trimToNull(pointConfig.getCalcExpression()));
if ("calc".equals(pointConfig.getPointType())) { 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.setStartTime(timeConfig.getStartTime());
temp.setEndTime(timeConfig.getEndTime()); temp.setEndTime(timeConfig.getEndTime());
temp.setChargeDischargePower(timeConfig.getChargeDischargePower()); 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()); temp.setChargeStatus(timeConfig.getChargeStatus());
emsStrategyTempMapper.insertEmsStrategyTemp(temp); 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) { 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(); return Collections.emptyList();
} }
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveData( 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()) { if (values == null || values.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
@ -205,6 +209,16 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return result; 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) { private String buildDisplayDeviceId(EmsPointConfig config) {
String pointName = config.getPointName() == null || "".equals(config.getPointName().trim()) String pointName = config.getPointName() == null || "".equals(config.getPointName().trim())
? config.getDataKey() : config.getPointName().trim(); ? config.getDataKey() : config.getPointName().trim();

View File

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

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"> <resultMap type="EmsPointConfig" id="EmsPointConfigResult">
<result property="id" column="id"/> <result property="id" column="id"/>
<result property="pointId" column="point_id"/>
<result property="siteId" column="site_id"/> <result property="siteId" column="site_id"/>
<result property="deviceCategory" column="device_category"/> <result property="deviceCategory" column="device_category"/>
<result property="deviceId" column="device_id"/> <result property="deviceId" column="device_id"/>
@ -29,7 +30,7 @@
</resultMap> </resultMap>
<sql id="selectEmsPointConfigVo"> <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, 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 create_by, create_time, update_by, update_time, remark
from ems_point_config from ems_point_config
@ -44,6 +45,7 @@
<include refid="selectEmsPointConfigVo"/> <include refid="selectEmsPointConfigVo"/>
<where> <where>
<if test="siteId != null and siteId != ''">and site_id = #{siteId}</if> <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="deviceCategory != null and deviceCategory != ''">and device_category = #{deviceCategory}</if>
<if test="deviceId != null and deviceId != ''">and device_id = #{deviceId}</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> <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 id="insertEmsPointConfig" parameterType="EmsPointConfig" useGeneratedKeys="true" keyProperty="id">
insert into ems_point_config insert into ems_point_config
<trim prefix="(" suffix=")" suffixOverrides=","> <trim prefix="(" suffix=")" suffixOverrides=",">
<if test="pointId != null">point_id,</if>
<if test="siteId != null">site_id,</if> <if test="siteId != null">site_id,</if>
<if test="deviceCategory != null">device_category,</if> <if test="deviceCategory != null">device_category,</if>
<if test="deviceId != null">device_id,</if> <if test="deviceId != null">device_id,</if>
@ -79,6 +82,7 @@
<if test="remark != null">remark,</if> <if test="remark != null">remark,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="pointId != null">#{pointId},</if>
<if test="siteId != null">#{siteId},</if> <if test="siteId != null">#{siteId},</if>
<if test="deviceCategory != null">#{deviceCategory},</if> <if test="deviceCategory != null">#{deviceCategory},</if>
<if test="deviceId != null">#{deviceId},</if> <if test="deviceId != null">#{deviceId},</if>
@ -102,9 +106,44 @@
</trim> </trim>
</insert> </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 id="updateEmsPointConfig" parameterType="EmsPointConfig">
update ems_point_config update ems_point_config
<trim prefix="SET" suffixOverrides=","> <trim prefix="SET" suffixOverrides=",">
<if test="pointId != null">point_id = #{pointId},</if>
<if test="siteId != null">site_id = #{siteId},</if> <if test="siteId != null">site_id = #{siteId},</if>
<if test="deviceCategory != null">device_category = #{deviceCategory},</if> <if test="deviceCategory != null">device_category = #{deviceCategory},</if>
<if test="deviceId != null">device_id = #{deviceId},</if> <if test="deviceId != null">device_id = #{deviceId},</if>
@ -151,12 +190,12 @@
<insert id="copyTemplateToSite"> <insert id="copyTemplateToSite">
insert into ems_point_config ( 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, 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 create_by, create_time, update_by, update_time, remark
) )
select 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, data_unit, data_a, data_k, data_b, data_bit, is_alarm, point_type, calc_expression,
#{operName}, now(), #{operName}, now(), remark #{operName}, now(), #{operName}, now(), remark
from ems_point_config from ems_point_config
@ -228,4 +267,13 @@
</if> </if>
</select> </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> </mapper>

View File

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