diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsSiteMonitorController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsSiteMonitorController.java index b788985..1bd3fbe 100644 --- a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsSiteMonitorController.java +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsSiteMonitorController.java @@ -208,9 +208,7 @@ public class EmsSiteMonitorController extends BaseController{ { startPage(); SiteBatteryDataList siteBatteryDataList = new SiteBatteryDataList(); - // 簇最大最小单体id数据 - List clusterBatteryDataList = iSingleSiteService.getClusterBatteryList(siteId,stackDeviceId,clusterDeviceId); - siteBatteryDataList.setClusterList(clusterBatteryDataList); + // 单体电池数据 List List = iSingleSiteService.getClusterDataInfoList(clusterDeviceId,siteId,stackDeviceId,batteryId); // 对batteryList进行分页处理 diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java index 65f0ec2..ebd3baf 100644 --- a/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/ProtectionPlanTask.java @@ -14,7 +14,9 @@ import com.xzzn.common.utils.StringUtils; import com.xzzn.ems.domain.EmsAlarmRecords; import com.xzzn.ems.domain.EmsFaultProtectionPlan; import com.xzzn.ems.domain.vo.ProtectionConstraintVo; +import com.xzzn.ems.domain.vo.ProtectionPlanVo; import com.xzzn.ems.domain.vo.ProtectionSettingVo; +import com.xzzn.ems.domain.vo.ProtectionSettingsGroupVo; import com.xzzn.ems.mapper.EmsAlarmRecordsMapper; import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper; import com.xzzn.ems.service.IEmsFaultProtectionPlanService; @@ -75,11 +77,12 @@ public class ProtectionPlanTask { if (StringUtils.isEmpty(siteId)) { continue; } - List protSettings = parseProtectionSettings(plan.getProtectionSettings()); - if (CollectionUtils.isEmpty(protSettings)) { + ProtectionSettingsGroupVo settingGroup = parseProtectionSettings(plan.getProtectionSettings()); + if (CollectionUtils.isEmpty(settingGroup.getFaultSettings()) + && CollectionUtils.isEmpty(settingGroup.getReleaseSettings())) { continue; } - dealWithProtectionPlan(plan, protSettings); + dealWithProtectionPlan(plan, settingGroup); } refreshProtectionConstraintCache(planList); } catch (Exception e) { @@ -88,20 +91,22 @@ public class ProtectionPlanTask { } @SyncAfterInsert - private void dealWithProtectionPlan(EmsFaultProtectionPlan plan, List protSettings) { + private void dealWithProtectionPlan(EmsFaultProtectionPlan plan, ProtectionSettingsGroupVo settingGroup) { logger.info("<轮询保护方案> 站点:{},方案ID:{}", plan.getSiteId(), plan.getId()); String siteId = plan.getSiteId(); Integer isAlertAlarm = plan.getIsAlert(); + List faultSettings = settingGroup.getFaultSettings(); + List releaseSettings = settingGroup.getReleaseSettings(); Long status = plan.getStatus(); if (status == null) { status = ProtPlanStatus.STOP.getCode(); } if (Objects.equals(status, ProtPlanStatus.STOP.getCode())) { - if (checkIsNeedIssuedPlan(protSettings, siteId)) { + if (checkIsNeedIssuedPlan(faultSettings, siteId)) { int faultDelay = safeDelaySeconds(plan.getFaultDelaySeconds(), 0); scheduledExecutorService.schedule(() -> { - if (!checkIsNeedIssuedPlan(protSettings, siteId)) { + if (!checkIsNeedIssuedPlan(faultSettings, siteId)) { return; } if (Integer.valueOf(1).equals(isAlertAlarm)) { @@ -117,7 +122,7 @@ public class ProtectionPlanTask { return; } - if (checkIsNeedCancelPlan(protSettings, siteId)) { + if (checkIsNeedCancelPlan(releaseSettings, siteId)) { int releaseDelay = safeDelaySeconds(plan.getReleaseDelaySeconds(), 0); scheduledExecutorService.schedule(() -> { if (Integer.valueOf(1).equals(isAlertAlarm)) { @@ -146,14 +151,58 @@ public class ProtectionPlanTask { return delay.intValue(); } - private List parseProtectionSettings(String settingsJson) { + private ProtectionSettingsGroupVo parseProtectionSettings(String settingsJson) { if (StringUtils.isEmpty(settingsJson)) { + return ProtectionSettingsGroupVo.empty(); + } + try { + if (settingsJson.trim().startsWith("[")) { + List legacy = objectMapper.readValue( + settingsJson, + new TypeReference>() {} + ); + ProtectionSettingsGroupVo groupVo = ProtectionSettingsGroupVo.empty(); + groupVo.setFaultSettings(legacy); + groupVo.setReleaseSettings(legacy); + return groupVo; + } + ProtectionSettingsGroupVo groupVo = objectMapper.readValue(settingsJson, ProtectionSettingsGroupVo.class); + if (groupVo == null) { + return ProtectionSettingsGroupVo.empty(); + } + if (groupVo.getFaultSettings() == null) { + groupVo.setFaultSettings(new ArrayList<>()); + } + if (groupVo.getReleaseSettings() == null) { + groupVo.setReleaseSettings(new ArrayList<>()); + } + return groupVo; + } catch (Exception e) { + logger.error("解析保护前提失败,json:{}", settingsJson, e); + return ProtectionSettingsGroupVo.empty(); + } + } + + private List parseProtectionPlan(String planJson) { + if (StringUtils.isEmpty(planJson)) { return new ArrayList<>(); } try { - return objectMapper.readValue(settingsJson, new TypeReference>() {}); + if (planJson.trim().startsWith("[")) { + List plans = objectMapper.readValue( + planJson, + new TypeReference>() {} + ); + return plans == null ? new ArrayList<>() : plans; + } + ProtectionPlanVo plan = objectMapper.readValue(planJson, ProtectionPlanVo.class); + List plans = new ArrayList<>(); + if (plan != null) { + plans.add(plan); + } + return plans; } catch (Exception e) { - logger.error("解析保护前提失败,json:{}", settingsJson, e); + logger.error("解析执行保护失败,json:{}", planJson, e); return new ArrayList<>(); } } @@ -256,9 +305,98 @@ public class ProtectionPlanTask { vo.setPowerLimitRatio(BigDecimal.ZERO); } + // 执行保护配置优先于描述文本配置 + List protectionPlans = parseProtectionPlan(plan.getProtectionPlan()); + applyCapabilityByProtectionPlan(vo, protectionPlans); + return vo; } + private void applyCapabilityByProtectionPlan(ProtectionConstraintVo vo, List protectionPlans) { + if (CollectionUtils.isEmpty(protectionPlans)) { + return; + } + for (ProtectionPlanVo item : protectionPlans) { + if (item == null) { + continue; + } + String marker = ((item.getPointName() == null ? "" : item.getPointName()) + " " + + (item.getPoint() == null ? "" : item.getPoint())).toLowerCase(); + if (StringUtils.isEmpty(marker)) { + continue; + } + + if (containsAny(marker, "降功率", "derate", "power_limit", "powerlimit")) { + BigDecimal ratio = parseDerateRatioByPlan(item); + if (ratio != null) { + vo.setPowerLimitRatio(minRatio(vo.getPowerLimitRatio(), ratio)); + } + } + + if (!isCapabilityEnabled(item.getValue())) { + continue; + } + + if (containsAny(marker, "禁止充放电", "forbid_charge_discharge", "disable_charge_discharge")) { + vo.setAllowCharge(false); + vo.setAllowDischarge(false); + continue; + } + if (containsAny(marker, "禁止充电", "forbid_charge", "disable_charge")) { + vo.setAllowCharge(false); + } + if (containsAny(marker, "禁止放电", "forbid_discharge", "disable_discharge")) { + vo.setAllowDischarge(false); + } + if (containsAny(marker, "允许充电", "allow_charge")) { + vo.setAllowCharge(true); + } + if (containsAny(marker, "允许放电", "allow_discharge")) { + vo.setAllowDischarge(true); + } + if (containsAny(marker, "待机", "standby")) { + vo.setForceStandby(true); + } + if (containsAny(marker, "关机", "停机", "切断", "shutdown", "stop")) { + vo.setForceStop(true); + vo.setForceStandby(true); + vo.setAllowCharge(false); + vo.setAllowDischarge(false); + vo.setPowerLimitRatio(BigDecimal.ZERO); + } + } + } + + private BigDecimal parseDerateRatioByPlan(ProtectionPlanVo planVo) { + BigDecimal value = planVo.getValue(); + if (value == null || value.compareTo(BigDecimal.ZERO) < 0) { + return null; + } + if (value.compareTo(BigDecimal.ONE) <= 0) { + return value; + } + if (value.compareTo(new BigDecimal("100")) <= 0) { + return value.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP); + } + return null; + } + + private boolean isCapabilityEnabled(BigDecimal value) { + return value == null || value.compareTo(BigDecimal.ZERO) != 0; + } + + private boolean containsAny(String text, String... markers) { + if (StringUtils.isEmpty(text) || markers == null) { + return false; + } + for (String marker : markers) { + if (!StringUtils.isEmpty(marker) && text.contains(marker)) { + return true; + } + } + return false; + } + private BigDecimal parseDerateRatio(String text) { if (StringUtils.isEmpty(text)) { return null; diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java index da7f50b..311feda 100644 --- a/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java @@ -575,9 +575,6 @@ public class StrategyPoller { ChargeStatus targetStatus, EmsStrategyRuntimeConfig runtimeConfig, ProtectionConstraintVo constraint) { - if (!Integer.valueOf(1).equals(runtimeConfig.getProtectInterveneEnable())) { - return new StrategyCommandDecision(targetStatus, safePower(targetPower)); - } if (constraint == null || nullSafeInt(constraint.getLevel()) <= 0) { return new StrategyCommandDecision(targetStatus, safePower(targetPower)); } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/BatteryDataStatsListVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/BatteryDataStatsListVo.java index 95cacf5..f323c09 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/BatteryDataStatsListVo.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/BatteryDataStatsListVo.java @@ -24,6 +24,18 @@ public class BatteryDataStatsListVo { /** SOH (%) */ private BigDecimal soh; + + /** 电压映射点位ID */ + private String voltagePointId; + + /** 温度映射点位ID */ + private String temperaturePointId; + + /** SOC映射点位ID */ + private String socPointId; + + /** SOH映射点位ID */ + private String sohPointId; @JsonFormat(pattern = "yyyy-MM-dd") private Date dataTimestamp; @@ -71,6 +83,38 @@ public class BatteryDataStatsListVo { this.soh = soh; } + public String getVoltagePointId() { + return voltagePointId; + } + + public void setVoltagePointId(String voltagePointId) { + this.voltagePointId = voltagePointId; + } + + public String getTemperaturePointId() { + return temperaturePointId; + } + + public void setTemperaturePointId(String temperaturePointId) { + this.temperaturePointId = temperaturePointId; + } + + public String getSocPointId() { + return socPointId; + } + + public void setSocPointId(String socPointId) { + this.socPointId = socPointId; + } + + public String getSohPointId() { + return sohPointId; + } + + public void setSohPointId(String sohPointId) { + this.sohPointId = sohPointId; + } + public Date getDataTimestamp() { return dataTimestamp; } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionConstraintVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionConstraintVo.java new file mode 100644 index 0000000..1499d27 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionConstraintVo.java @@ -0,0 +1,114 @@ +package com.xzzn.ems.domain.vo; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * 站点保护约束(供策略轮询仲裁) + */ +public class ProtectionConstraintVo implements Serializable { + private static final long serialVersionUID = 1L; + + /** 最高保护等级:0-无保护,1/2/3 对应故障等级 */ + private Integer level; + + /** 是否允许充电 */ + private Boolean allowCharge; + + /** 是否允许放电 */ + private Boolean allowDischarge; + + /** 功率上限比例(0~1) */ + private BigDecimal powerLimitRatio; + + /** 是否强制待机 */ + private Boolean forceStandby; + + /** 是否强制停机 */ + private Boolean forceStop; + + /** 生效的方案ID列表 */ + private List sourcePlanIds; + + /** 更新时间戳(毫秒) */ + private Long updateAt; + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public Boolean getAllowCharge() { + return allowCharge; + } + + public void setAllowCharge(Boolean allowCharge) { + this.allowCharge = allowCharge; + } + + public Boolean getAllowDischarge() { + return allowDischarge; + } + + public void setAllowDischarge(Boolean allowDischarge) { + this.allowDischarge = allowDischarge; + } + + public BigDecimal getPowerLimitRatio() { + return powerLimitRatio; + } + + public void setPowerLimitRatio(BigDecimal powerLimitRatio) { + this.powerLimitRatio = powerLimitRatio; + } + + public Boolean getForceStandby() { + return forceStandby; + } + + public void setForceStandby(Boolean forceStandby) { + this.forceStandby = forceStandby; + } + + public Boolean getForceStop() { + return forceStop; + } + + public void setForceStop(Boolean forceStop) { + this.forceStop = forceStop; + } + + public List getSourcePlanIds() { + return sourcePlanIds; + } + + public void setSourcePlanIds(List sourcePlanIds) { + this.sourcePlanIds = sourcePlanIds; + } + + public Long getUpdateAt() { + return updateAt; + } + + public void setUpdateAt(Long updateAt) { + this.updateAt = updateAt; + } + + public static ProtectionConstraintVo empty() { + ProtectionConstraintVo vo = new ProtectionConstraintVo(); + vo.setLevel(0); + vo.setAllowCharge(true); + vo.setAllowDischarge(true); + vo.setPowerLimitRatio(BigDecimal.ONE); + vo.setForceStandby(false); + vo.setForceStop(false); + vo.setSourcePlanIds(new ArrayList<>()); + vo.setUpdateAt(System.currentTimeMillis()); + return vo; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionSettingsGroupVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionSettingsGroupVo.java new file mode 100644 index 0000000..3c48d55 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ProtectionSettingsGroupVo.java @@ -0,0 +1,38 @@ +package com.xzzn.ems.domain.vo; + +import java.util.ArrayList; +import java.util.List; + +/** + * 告警保护方案-保护前提分组 + */ +public class ProtectionSettingsGroupVo { + /** 故障保护前提 */ + private List faultSettings; + + /** 释放保护前提 */ + private List releaseSettings; + + public static ProtectionSettingsGroupVo empty() { + ProtectionSettingsGroupVo vo = new ProtectionSettingsGroupVo(); + vo.setFaultSettings(new ArrayList<>()); + vo.setReleaseSettings(new ArrayList<>()); + return vo; + } + + public List getFaultSettings() { + return faultSettings; + } + + public void setFaultSettings(List faultSettings) { + this.faultSettings = faultSettings; + } + + public List getReleaseSettings() { + return releaseSettings; + } + + public void setReleaseSettings(List releaseSettings) { + this.releaseSettings = releaseSettings; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorPointMatchMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorPointMatchMapper.java index 0f001d6..47c5a94 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorPointMatchMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorPointMatchMapper.java @@ -13,5 +13,7 @@ public interface EmsSiteMonitorPointMatchMapper { int deleteBySiteId(@Param("siteId") String siteId); + int deleteBySiteIdAndDeviceId(@Param("siteId") String siteId, @Param("deviceId") String deviceId); + int insertBatch(@Param("list") List list); } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java index 3b9d351..38b0539 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDeviceSettingServiceImpl.java @@ -69,6 +69,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; /** @@ -215,9 +216,127 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService pcsSetting.setDeviceSettingId(devicesSetting.getId()); emsPcsSettingMapper.insertEmsPcsSetting(pcsSetting); } + initSiteMonitorPointMappingsForNewDevice(devicesSetting); return result; } + private void initSiteMonitorPointMappingsForNewDevice(DevicesSettingVo devicesSetting) { + if (devicesSetting == null) { + return; + } + String siteId = StringUtils.trim(devicesSetting.getSiteId()); + String deviceId = StringUtils.trim(devicesSetting.getDeviceId()); + String deviceCategory = StringUtils.trim(devicesSetting.getDeviceCategory()); + if (StringUtils.isAnyBlank(siteId, deviceId, deviceCategory)) { + return; + } + + List itemList = getEnabledMonitorItems(); + if (CollectionUtils.isEmpty(itemList)) { + return; + } + Set targetFieldCodes = itemList.stream() + .filter(Objects::nonNull) + .filter(item -> isDeviceDimensionItem(item)) + .filter(item -> StringUtils.equals(deviceCategory, resolveDeviceCategoryByItem(item))) + .map(EmsSiteMonitorItem::getFieldCode) + .filter(StringUtils::isNotBlank) + .map(String::trim) + .collect(Collectors.toSet()); + if (targetFieldCodes.isEmpty()) { + return; + } + + List existMappings = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId); + if (existMappings == null) { + existMappings = Collections.emptyList(); + } + + Set deletedFieldCodes = existMappings.stream() + .filter(Objects::nonNull) + .filter(item -> DELETED_FIELD_MARK.equals(item.getDataPoint())) + .map(EmsSiteMonitorPointMatch::getFieldCode) + .filter(StringUtils::isNotBlank) + .map(String::trim) + .collect(Collectors.toSet()); + targetFieldCodes.removeAll(deletedFieldCodes); + if (targetFieldCodes.isEmpty()) { + return; + } + + Set existsFieldAndDevice = existMappings.stream() + .filter(Objects::nonNull) + .map(item -> buildMatchKey(StringUtils.trim(item.getFieldCode()), StringUtils.trim(item.getDeviceId()))) + .collect(Collectors.toSet()); + + String operName = StringUtils.defaultIfBlank(StringUtils.trim(devicesSetting.getCreateBy()), "system"); + List insertList = new ArrayList<>(); + for (String fieldCode : targetFieldCodes) { + String key = buildMatchKey(fieldCode, deviceId); + if (existsFieldAndDevice.contains(key)) { + continue; + } + EmsSiteMonitorPointMatch template = findTemplateMappingForNewDevice(existMappings, fieldCode); + EmsSiteMonitorPointMatch insertItem = new EmsSiteMonitorPointMatch(); + insertItem.setSiteId(siteId); + insertItem.setFieldCode(fieldCode); + insertItem.setDeviceId(deviceId); + insertItem.setDataPoint(template == null ? "" : StringUtils.defaultString(template.getDataPoint())); + insertItem.setFixedDataPoint(template == null ? null : StringUtils.trimToNull(template.getFixedDataPoint())); + insertItem.setUseFixedDisplay(template == null ? 0 : (template.getUseFixedDisplay() == null ? 0 : template.getUseFixedDisplay())); + insertItem.setCreateBy(operName); + insertItem.setUpdateBy(operName); + insertList.add(insertItem); + } + + if (insertList.isEmpty()) { + return; + } + emsSiteMonitorPointMatchMapper.insertBatch(insertList); + redisCache.deleteObject(buildSiteMonitorPointMatchRedisKey(siteId)); + clearSiteMonitorLatestCache(siteId); + projectDisplayCache.remove(siteId); + } + + private EmsSiteMonitorPointMatch findTemplateMappingForNewDevice(List mappings, String fieldCode) { + if (CollectionUtils.isEmpty(mappings) || StringUtils.isBlank(fieldCode)) { + return null; + } + String normalizedFieldCode = fieldCode.trim(); + EmsSiteMonitorPointMatch fallback = null; + for (EmsSiteMonitorPointMatch item : mappings) { + if (item == null || !normalizedFieldCode.equals(StringUtils.trim(item.getFieldCode()))) { + continue; + } + if (DELETED_FIELD_MARK.equals(item.getDataPoint())) { + continue; + } + if (StringUtils.isNotBlank(item.getDeviceId())) { + return item; + } + if (fallback == null) { + fallback = item; + } + } + return fallback; + } + + private String resolveDeviceCategoryByItem(EmsSiteMonitorItem item) { + if (item == null) { + return null; + } + String byMenu = StringUtils.trim(MENU_DEVICE_CATEGORY_MAP.get(StringUtils.trim(item.getMenuCode()))); + if (StringUtils.isNotBlank(byMenu)) { + return byMenu; + } + String fieldCode = StringUtils.trim(item.getFieldCode()); + if (StringUtils.isBlank(fieldCode) || !fieldCode.contains("__")) { + return null; + } + String menuCode = fieldCode.substring(0, fieldCode.indexOf("__")); + return StringUtils.trim(MENU_DEVICE_CATEGORY_MAP.get(StringUtils.trim(menuCode))); + } + /** * 更新设备 * @param devicesSetting @@ -277,9 +396,26 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService * @return 结果 */ @Override + @Transactional(rollbackFor = Exception.class) public int deleteEmsDevicesSettingById(Long id){ - - return emsDevicesMapper.deleteEmsDevicesSettingById(id); + if (id == null) { + return 0; + } + EmsDevicesSetting existDevice = emsDevicesMapper.selectEmsDevicesSettingById(id); + int rows = emsDevicesMapper.deleteEmsDevicesSettingById(id); + if (rows <= 0 || existDevice == null) { + return rows; + } + String siteId = StringUtils.trim(existDevice.getSiteId()); + String deviceId = StringUtils.trim(existDevice.getDeviceId()); + if (StringUtils.isAnyBlank(siteId, deviceId)) { + return rows; + } + emsSiteMonitorPointMatchMapper.deleteBySiteIdAndDeviceId(siteId, deviceId); + redisCache.deleteObject(buildSiteMonitorPointMatchRedisKey(siteId)); + clearSiteMonitorLatestCache(siteId); + projectDisplayCache.remove(siteId); + return rows; } @Override diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/SingleSiteServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/SingleSiteServiceImpl.java index f1618a1..2d8a8f1 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/SingleSiteServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/SingleSiteServiceImpl.java @@ -12,6 +12,7 @@ import com.xzzn.ems.domain.EmsBatteryData; import com.xzzn.ems.domain.EmsBatteryStack; import com.xzzn.ems.domain.EmsCoolingData; import com.xzzn.ems.domain.EmsDhData; +import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsEmsData; import com.xzzn.ems.domain.EmsPcsBranchData; import com.xzzn.ems.domain.EmsSiteMonitorPointMatch; @@ -32,6 +33,7 @@ import com.xzzn.ems.mapper.EmsSiteMonitorPointMatchMapper; import com.xzzn.ems.mapper.EmsStrategyRunningMapper; import com.xzzn.ems.mapper.EmsStrategyTempMapper; import com.xzzn.ems.service.IEmsEnergyPriceConfigService; +import com.xzzn.ems.service.IEmsDeviceSettingService; import com.xzzn.ems.service.ISingleSiteService; import com.xzzn.ems.service.InfluxPointDataWriter; import com.xzzn.ems.utils.DevicePointMatchDataProcessor; @@ -90,6 +92,10 @@ public class SingleSiteServiceImpl implements ISingleSiteService { private static final Set PCS_DETAIL_META_FIELDS = new HashSet<>(Arrays.asList( "siteId", "deviceId", "deviceName", "alarmNum", "pcsBranchInfoList", "dataUpdateTime" )); + private static final Set BATTERY_DETAIL_META_FIELDS = new HashSet<>(Arrays.asList( + "deviceId", "clusterDeviceId", "dataTimestamp" + )); + private static final String MENU_SBJK_DTDC = "SBJK_DTDC"; private static final String CLUSTER_DATA_TEP = "温度"; @@ -135,6 +141,8 @@ public class SingleSiteServiceImpl implements ISingleSiteService { @Autowired private IEmsEnergyPriceConfigService iEmsEnergyPriceConfigService; @Autowired + private IEmsDeviceSettingService iEmsDeviceSettingService; + @Autowired private DevicePointMatchDataProcessor devicePointMatchDataProcessor; @Override @@ -1007,45 +1015,292 @@ public class SingleSiteServiceImpl implements ISingleSiteService { public List getClusterDataInfoList(String clusterDeviceId,String siteId, String stackDeviceId, String batteryId) { List batteryDataStatsListVo = new ArrayList<>(); + log.info("getClusterDataInfoList start, siteId={}, stackDeviceId={}, clusterDeviceId={}, batteryId={}", + siteId, stackDeviceId, clusterDeviceId, batteryId); + String targetStackDeviceId = StringUtils.trimToEmpty(stackDeviceId); + String targetClusterDeviceId = StringUtils.trimToEmpty(clusterDeviceId); + String targetBatteryId = StringUtils.trimToEmpty(batteryId); + + if ("undefined".equalsIgnoreCase(targetStackDeviceId) || "null".equalsIgnoreCase(targetStackDeviceId)) { + targetStackDeviceId = ""; + } + if ("undefined".equalsIgnoreCase(targetClusterDeviceId) || "null".equalsIgnoreCase(targetClusterDeviceId)) { + targetClusterDeviceId = ""; + } + if ("undefined".equalsIgnoreCase(targetBatteryId) || "null".equalsIgnoreCase(targetBatteryId)) { + targetBatteryId = ""; + } + log.info("getClusterDataInfoList normalized params, siteId={}, stackDeviceId={}, clusterDeviceId={}, batteryId={}", + siteId, targetStackDeviceId, targetClusterDeviceId, targetBatteryId); + + if (StringUtils.isEmpty(siteId)) { + log.warn("getClusterDataInfoList early return, siteId is empty"); + return batteryDataStatsListVo; + } + Map> batteryDisplayMap = + buildBatteryDisplayMap(iEmsDeviceSettingService.getSiteMonitorProjectDisplay(siteId)); + if (batteryDisplayMap.isEmpty()) { + log.warn("getClusterDataInfoList no battery display data from projectDisplay, siteId={}", siteId); + } + log.info("getClusterDataInfoList battery list source projectDisplay, siteId={}, batteryCount={}", + siteId, batteryDisplayMap.size()); + + Map batteryDeviceCache = new HashMap<>(); + Map clusterDeviceCache = new HashMap<>(); + List sortedBatteryIds = new ArrayList<>(batteryDisplayMap.keySet()); + Collections.sort(sortedBatteryIds); + + for (String batteryDeviceId : sortedBatteryIds) { + if (StringUtils.isEmpty(batteryDeviceId)) { + continue; + } + if (StringUtils.isNotEmpty(targetBatteryId) && !batteryDeviceId.equals(targetBatteryId)) { + continue; + } + + EmsDevicesSetting batterySetting = batteryDeviceCache.computeIfAbsent( + batteryDeviceId, key -> emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(key, siteId)); + String currentClusterDeviceId = batterySetting == null ? "" : StringUtils.trimToEmpty(batterySetting.getParentId()); + + if (StringUtils.isNotEmpty(targetClusterDeviceId) && !targetClusterDeviceId.equals(currentClusterDeviceId)) { + continue; + } + if (StringUtils.isNotEmpty(targetStackDeviceId)) { + if (StringUtils.isEmpty(currentClusterDeviceId)) { + continue; + } + EmsDevicesSetting clusterSetting = clusterDeviceCache.computeIfAbsent( + currentClusterDeviceId, key -> emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(key, siteId)); + String parentStackDeviceId = clusterSetting == null ? "" : StringUtils.trimToEmpty(clusterSetting.getParentId()); + if (!targetStackDeviceId.equals(parentStackDeviceId)) { + continue; + } + } + + BatteryDataStatsListVo batteryDataStatsVo = new BatteryDataStatsListVo(); + fillBatteryValueFromProjectDisplay(batteryDataStatsVo, batteryDisplayMap.get(batteryDeviceId)); + batteryDataStatsVo.setDeviceId(batteryDeviceId); + batteryDataStatsVo.setClusterDeviceId(currentClusterDeviceId); + batteryDataStatsListVo.add(batteryDataStatsVo); + log.info("getClusterDataInfoList battery from projectDisplay list, siteId={}, batteryDeviceId={}, clusterDeviceId={}, voltage={}, temperature={}, soc={}, soh={}, voltagePointId={}, temperaturePointId={}, socPointId={}, sohPointId={}, dataTimestamp={}", + siteId, batteryDeviceId, currentClusterDeviceId, batteryDataStatsVo.getVoltage(), + batteryDataStatsVo.getTemperature(), batteryDataStatsVo.getSoc(), + batteryDataStatsVo.getSoh(), batteryDataStatsVo.getVoltagePointId(), + batteryDataStatsVo.getTemperaturePointId(), batteryDataStatsVo.getSocPointId(), + batteryDataStatsVo.getSohPointId(), batteryDataStatsVo.getDataTimestamp()); + } + + if (batteryDataStatsListVo.isEmpty()) { + log.info("getClusterDataInfoList fallback to realtime cache, siteId={}, stackDeviceId={}, clusterDeviceId={}, batteryId={}", + siteId, targetStackDeviceId, targetClusterDeviceId, targetBatteryId); + appendBatteryFromRealtimeCache(siteId, targetStackDeviceId, targetClusterDeviceId, targetBatteryId, + batteryDisplayMap, batteryDataStatsListVo); + } + + log.info("getClusterDataInfoList end, siteId={}, stackDeviceId={}, clusterDeviceId={}, batteryId={}, total={}", + siteId, targetStackDeviceId, targetClusterDeviceId, targetBatteryId, batteryDataStatsListVo.size()); + return batteryDataStatsListVo; + } + + private void appendBatteryFromRealtimeCache(String siteId, + String targetStackDeviceId, + String targetClusterDeviceId, + String targetBatteryId, + Map> batteryDisplayMap, + List targetList) { + if (StringUtils.isBlank(siteId) || targetList == null) { + return; + } List> clusterIds = new ArrayList<>(); - if (StringUtils.isEmpty(clusterDeviceId)) { - clusterIds = emsDevicesSettingMapper.getClusterIdsByFuzzyQuery(siteId, DeviceCategory.CLUSTER.getCode(),stackDeviceId); - } else { + if (StringUtils.isNotBlank(targetClusterDeviceId)) { Map map = new HashMap<>(); - map.put("id", clusterDeviceId); + map.put("id", targetClusterDeviceId); clusterIds.add(map); + } else { + clusterIds = emsDevicesSettingMapper.getClusterIdsByFuzzyQuery(siteId, + DeviceCategory.CLUSTER.getCode(), StringUtils.trimToEmpty(targetStackDeviceId)); + } + if (CollectionUtils.isEmpty(clusterIds)) { + return; } for (Map clusterDevice : clusterIds) { - // 从redis取单个簇详细数据 - String clusterId = clusterDevice.get("id").toString(); + if (clusterDevice == null || clusterDevice.get("id") == null) { + continue; + } + String clusterId = StringUtils.trimToEmpty(String.valueOf(clusterDevice.get("id"))); + if (StringUtils.isBlank(clusterId)) { + continue; + } List batteryDataList = redisCache.getCacheList(RedisKeyConstants.BATTERY + siteId + "_" + clusterId); - if (batteryDataList != null) { - for (EmsBatteryData batteryData : batteryDataList) { - // 判断是否需要筛选:batteryId不为空时才进行匹配 - if (batteryId == null || batteryId.trim().isEmpty()) { - // 空值情况:直接添加所有数据 - BatteryDataStatsListVo batteryDataStatsVo = new BatteryDataStatsListVo(); - BeanUtils.copyProperties(batteryData, batteryDataStatsVo); - batteryDataStatsListVo.add(batteryDataStatsVo); - } else { - // 有值情况:只添加匹配的数据 - if (batteryId.equals(batteryData.getDeviceId())) { - BatteryDataStatsListVo batteryDataStatsVo = new BatteryDataStatsListVo(); - BeanUtils.copyProperties(batteryData, batteryDataStatsVo); - batteryDataStatsListVo.add(batteryDataStatsVo); - // 找到匹配项后可提前退出当前簇的循环 - break; - } - } - } + if (CollectionUtils.isEmpty(batteryDataList)) { + continue; + } + for (EmsBatteryData batteryData : batteryDataList) { + if (batteryData == null) { + continue; + } + String currentBatteryId = StringUtils.trimToEmpty(batteryData.getDeviceId()); + if (StringUtils.isNotEmpty(targetBatteryId) && !targetBatteryId.equals(currentBatteryId)) { + continue; + } + BatteryDataStatsListVo batteryDataStatsVo = new BatteryDataStatsListVo(); + BeanUtils.copyProperties(batteryData, batteryDataStatsVo); + if (StringUtils.isBlank(batteryDataStatsVo.getClusterDeviceId())) { + batteryDataStatsVo.setClusterDeviceId(clusterId); + } + fillBatteryPointIdFromProjectDisplay(batteryDataStatsVo, batteryDisplayMap == null ? null : batteryDisplayMap.get(currentBatteryId)); + targetList.add(batteryDataStatsVo); } } - //TODO 临时删除排序 - // 排序 -// List sortedList = batteryDataStatsListVo.stream() -// .sorted((u1, u2) -> Integer.parseInt(u1.getClusterDeviceId()) - Integer.parseInt(u2.getClusterDeviceId())) -// .collect(Collectors.toList()); - return batteryDataStatsListVo; + } + + private void fillBatteryDetailByLatestPointMapping(String siteId, + String deviceId, + BatteryDataStatsListVo target, + Map mappingByFieldAndDevice, + Map latestPointCache) { + if (StringUtils.isAnyBlank(siteId, deviceId) || target == null || mappingByFieldAndDevice == null) { + return; + } + BeanWrapper beanWrapper = new BeanWrapperImpl(target); + Date latestDataTime = null; + for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) { + if (pd == null || StringUtils.isBlank(pd.getName()) || pd.getWriteMethod() == null) { + continue; + } + String fieldCode = pd.getName(); + if (BATTERY_DETAIL_META_FIELDS.contains(fieldCode)) { + continue; + } + EmsSiteMonitorPointMatch pointMatch = resolvePointMatchByFieldAndDevice(mappingByFieldAndDevice, fieldCode, deviceId); + if (pointMatch == null) { + continue; + } + Object fieldValue; + Date valueTime = null; + if (USE_FIXED_DISPLAY_YES == (pointMatch.getUseFixedDisplay() == null ? 0 : pointMatch.getUseFixedDisplay()) + && StringUtils.isNotBlank(pointMatch.getFixedDataPoint())) { + fieldValue = pointMatch.getFixedDataPoint().trim(); + } else { + InfluxPointDataWriter.PointValue latestValue = getLatestPointValueByPointId(siteId, pointMatch.getDataPoint(), latestPointCache); + if (latestValue == null || latestValue.getPointValue() == null) { + continue; + } + fieldValue = latestValue.getPointValue(); + valueTime = latestValue.getDataTime(); + } + Object convertedValue = convertFieldValueByType(fieldValue, pd.getPropertyType()); + if (convertedValue == null) { + continue; + } + beanWrapper.setPropertyValue(fieldCode, convertedValue); + if (valueTime != null && (latestDataTime == null || valueTime.after(latestDataTime))) { + latestDataTime = valueTime; + } + } + if (latestDataTime != null) { + target.setDataTimestamp(latestDataTime); + } + } + + private Map> buildBatteryDisplayMap(List displayList) { + Map> result = new HashMap<>(); + if (CollectionUtils.isEmpty(displayList)) { + return result; + } + for (SiteMonitorProjectDisplayVo item : displayList) { + if (item == null) { + continue; + } + String menuCode = StringUtils.trimToEmpty(item.getMenuCode()); + String deviceId = StringUtils.trimToEmpty(item.getDeviceId()); + String fieldCode = StringUtils.trimToEmpty(item.getFieldCode()); + if (!MENU_SBJK_DTDC.equals(menuCode) || StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(fieldCode)) { + continue; + } + Map fieldMap = result.computeIfAbsent(deviceId, key -> new HashMap<>()); + fieldMap.put(fieldCode, item); + String normalizedFieldCode = normalizeBatteryFieldCode(fieldCode); + if (StringUtils.isNotBlank(normalizedFieldCode)) { + fieldMap.put(normalizedFieldCode, item); + } + } + return result; + } + + private String normalizeBatteryFieldCode(String fieldCode) { + String normalized = StringUtils.trimToEmpty(fieldCode); + if (StringUtils.isBlank(normalized)) { + return normalized; + } + int splitIndex = normalized.lastIndexOf("__"); + if (splitIndex >= 0 && splitIndex + 2 < normalized.length()) { + normalized = normalized.substring(splitIndex + 2); + } + return normalized.toLowerCase(); + } + + private void fillBatteryValueFromProjectDisplay(BatteryDataStatsListVo target, + Map fieldMap) { + if (target == null || fieldMap == null || fieldMap.isEmpty()) { + return; + } + SiteMonitorProjectDisplayVo voltageVo = fieldMap.get("voltage"); + SiteMonitorProjectDisplayVo temperatureVo = fieldMap.get("temperature"); + SiteMonitorProjectDisplayVo socVo = fieldMap.get("soc"); + SiteMonitorProjectDisplayVo sohVo = fieldMap.get("soh"); + + target.setVoltagePointId(voltageVo == null ? null : StringUtils.trimToNull(voltageVo.getDataPoint())); + target.setTemperaturePointId(temperatureVo == null ? null : StringUtils.trimToNull(temperatureVo.getDataPoint())); + target.setSocPointId(socVo == null ? null : StringUtils.trimToNull(socVo.getDataPoint())); + target.setSohPointId(sohVo == null ? null : StringUtils.trimToNull(sohVo.getDataPoint())); + + if (voltageVo != null && StringUtils.isNotBlank(voltageVo.getFieldValue())) { + target.setVoltage(StringUtils.getBigDecimal(voltageVo.getFieldValue())); + } + if (temperatureVo != null && StringUtils.isNotBlank(temperatureVo.getFieldValue())) { + target.setTemperature(StringUtils.getBigDecimal(temperatureVo.getFieldValue())); + } + if (socVo != null && StringUtils.isNotBlank(socVo.getFieldValue())) { + target.setSoc(StringUtils.getBigDecimal(socVo.getFieldValue())); + } + if (sohVo != null && StringUtils.isNotBlank(sohVo.getFieldValue())) { + target.setSoh(StringUtils.getBigDecimal(sohVo.getFieldValue())); + } + + Date latest = null; + latest = pickLaterTime(latest, voltageVo == null ? null : voltageVo.getValueTime()); + latest = pickLaterTime(latest, temperatureVo == null ? null : temperatureVo.getValueTime()); + latest = pickLaterTime(latest, socVo == null ? null : socVo.getValueTime()); + latest = pickLaterTime(latest, sohVo == null ? null : sohVo.getValueTime()); + if (latest != null) { + target.setDataTimestamp(latest); + } + } + + private void fillBatteryPointIdFromProjectDisplay(BatteryDataStatsListVo target, + Map fieldMap) { + if (target == null || fieldMap == null || fieldMap.isEmpty()) { + return; + } + SiteMonitorProjectDisplayVo voltageVo = fieldMap.get("voltage"); + SiteMonitorProjectDisplayVo temperatureVo = fieldMap.get("temperature"); + SiteMonitorProjectDisplayVo socVo = fieldMap.get("soc"); + SiteMonitorProjectDisplayVo sohVo = fieldMap.get("soh"); + target.setVoltagePointId(voltageVo == null ? null : StringUtils.trimToNull(voltageVo.getDataPoint())); + target.setTemperaturePointId(temperatureVo == null ? null : StringUtils.trimToNull(temperatureVo.getDataPoint())); + target.setSocPointId(socVo == null ? null : StringUtils.trimToNull(socVo.getDataPoint())); + target.setSohPointId(sohVo == null ? null : StringUtils.trimToNull(sohVo.getDataPoint())); + } + + private Date pickLaterTime(Date left, Date right) { + if (left == null) { + return right; + } + if (right == null) { + return left; + } + return right.after(left) ? right : left; } // 获取单站的最大最小温度和电压单体数据id diff --git a/ems-system/src/main/resources/mapper/ems/EmsDevicesSettingMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsDevicesSettingMapper.xml index 5557599..e1ac098 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsDevicesSettingMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsDevicesSettingMapper.xml @@ -175,6 +175,7 @@ from ems_devices_setting t where t.site_id = #{siteId} and t.parent_id = #{parentId} + order by t.device_id - where device_id = #{deviceId} - AND site_id = #{siteId} - limit 1 + where site_id = #{siteId} + + AND device_id = #{deviceId} + - \ No newline at end of file + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml index 14538db..daac50a 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml @@ -31,6 +31,12 @@ where site_id = #{siteId} + + delete from ems_site_monitor_point_match + where site_id = #{siteId} + and device_id = #{deviceId} + + insert into ems_site_monitor_point_match (site_id, field_code, device_id, data_point, fixed_data_point, use_fixed_display, create_by, create_time, update_by, update_time)