diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsAlarmRecordsController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsAlarmRecordsController.java index e3fc301..5ac7ffa 100644 --- a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsAlarmRecordsController.java +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsAlarmRecordsController.java @@ -55,4 +55,21 @@ public class EmsAlarmRecordsController extends BaseController } } + /** + * 关闭告警 + */ + @PostMapping("/closeAlarm") + public AjaxResult closeAlarm(@RequestBody EmsAlarmRecords emsAlarmRecords) + { + Long id = emsAlarmRecords.getId(); + if (id == null) { + return error("告警id不能为空"); + } + String result = iEmsAlarmRecordsService.closeAlarm(id, getUserId()); + if ("success".equals(result) || "告警已关闭".equals(result)) { + return AjaxResult.success("操作成功"); + } + return error(result); + } + } diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsDailyChargeDataController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsDailyChargeDataController.java new file mode 100644 index 0000000..ee53904 --- /dev/null +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsDailyChargeDataController.java @@ -0,0 +1,61 @@ +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.EmsDailyChargeData; +import com.xzzn.ems.service.IEmsDailyChargeDataService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 站点管理-充放电修正 Controller + */ +@RestController +@RequestMapping("/ems/dailyChargeData") +public class EmsDailyChargeDataController extends BaseController { + + @Autowired + private IEmsDailyChargeDataService emsDailyChargeDataService; + + @GetMapping("/list") + public TableDataInfo list(EmsDailyChargeData emsDailyChargeData) { + startPage(); + List list = emsDailyChargeDataService.selectEmsDailyChargeDataList(emsDailyChargeData); + return getDataTable(list); + } + + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(emsDailyChargeDataService.selectEmsDailyChargeDataById(id)); + } + + @Log(title = "站点管理-充放电修正", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody EmsDailyChargeData emsDailyChargeData) { + return toAjax(emsDailyChargeDataService.insertEmsDailyChargeData(emsDailyChargeData, getUsername())); + } + + @Log(title = "站点管理-充放电修正", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody EmsDailyChargeData emsDailyChargeData) { + return toAjax(emsDailyChargeDataService.updateEmsDailyChargeData(emsDailyChargeData, getUsername())); + } + + @Log(title = "站点管理-充放电修正", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(emsDailyChargeDataService.deleteEmsDailyChargeDataByIds(ids)); + } +} diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsDailyEnergyDataController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsDailyEnergyDataController.java new file mode 100644 index 0000000..2294304 --- /dev/null +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsDailyEnergyDataController.java @@ -0,0 +1,61 @@ +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.EmsDailyEnergyData; +import com.xzzn.ems.service.IEmsDailyEnergyDataService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 站点管理-数据修正 Controller + */ +@RestController +@RequestMapping("/ems/dailyEnergyData") +public class EmsDailyEnergyDataController extends BaseController { + + @Autowired + private IEmsDailyEnergyDataService emsDailyEnergyDataService; + + @GetMapping("/list") + public TableDataInfo list(EmsDailyEnergyData emsDailyEnergyData) { + startPage(); + List list = emsDailyEnergyDataService.selectEmsDailyEnergyDataList(emsDailyEnergyData); + return getDataTable(list); + } + + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(emsDailyEnergyDataService.selectEmsDailyEnergyDataById(id)); + } + + @Log(title = "站点管理-数据修正", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody EmsDailyEnergyData emsDailyEnergyData) { + return toAjax(emsDailyEnergyDataService.insertEmsDailyEnergyData(emsDailyEnergyData, getUsername())); + } + + @Log(title = "站点管理-数据修正", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody EmsDailyEnergyData emsDailyEnergyData) { + return toAjax(emsDailyEnergyDataService.updateEmsDailyEnergyData(emsDailyEnergyData, getUsername())); + } + + @Log(title = "站点管理-数据修正", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(emsDailyEnergyDataService.deleteEmsDailyEnergyDataByIds(ids)); + } +} diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointCalcConfigController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointCalcConfigController.java new file mode 100644 index 0000000..465054c --- /dev/null +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointCalcConfigController.java @@ -0,0 +1,51 @@ +package com.xzzn.web.controller.ems; + +import com.xzzn.common.annotation.Log; +import com.xzzn.common.core.controller.BaseController; +import com.xzzn.common.core.domain.AjaxResult; +import com.xzzn.common.core.page.TableDataInfo; +import com.xzzn.common.enums.BusinessType; +import com.xzzn.ems.domain.EmsPointCalcConfig; +import com.xzzn.ems.service.IEmsPointCalcConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/ems/pointCalcConfig") +public class EmsPointCalcConfigController extends BaseController { + + @Autowired + private IEmsPointCalcConfigService pointCalcConfigService; + + @GetMapping("/list") + public TableDataInfo list(EmsPointCalcConfig pointCalcConfig) { + startPage(); + List list = pointCalcConfigService.selectPointCalcConfigList(pointCalcConfig); + return getDataTable(list); + } + + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(pointCalcConfigService.selectPointCalcConfigById(id)); + } + + @Log(title = "计算点配置", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody EmsPointCalcConfig pointCalcConfig) { + return toAjax(pointCalcConfigService.insertPointCalcConfig(pointCalcConfig, getUsername())); + } + + @Log(title = "计算点配置", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody EmsPointCalcConfig pointCalcConfig) { + return toAjax(pointCalcConfigService.updatePointCalcConfig(pointCalcConfig, getUsername())); + } + + @Log(title = "计算点配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(pointCalcConfigService.deletePointCalcConfigByIds(ids)); + } +} diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointConfigController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointConfigController.java new file mode 100644 index 0000000..71a2347 --- /dev/null +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointConfigController.java @@ -0,0 +1,103 @@ +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.EmsPointConfig; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveRequest; +import com.xzzn.ems.domain.vo.PointConfigGenerateRecentRequest; +import com.xzzn.ems.domain.vo.PointConfigLatestValueRequest; +import com.xzzn.ems.service.IEmsPointConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.Valid; +import java.util.List; + +@RestController +@RequestMapping("/ems/pointConfig") +public class EmsPointConfigController extends BaseController { + + @Autowired + private IEmsPointConfigService pointConfigService; + + @GetMapping("/list") + public TableDataInfo list(EmsPointConfig pointConfig) { + startPage(); + List list = pointConfigService.selectPointConfigList(pointConfig); + return getDataTable(list); + } + + @GetMapping("/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) { + return success(pointConfigService.selectPointConfigById(id)); + } + + @Log(title = "点位配置", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody EmsPointConfig pointConfig) { + return toAjax(pointConfigService.insertPointConfig(pointConfig, getUsername())); + } + + @Log(title = "点位配置", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody EmsPointConfig pointConfig) { + return toAjax(pointConfigService.updatePointConfig(pointConfig, getUsername())); + } + + @Log(title = "点位配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) { + return toAjax(pointConfigService.deletePointConfigByIds(ids)); + } + + @Log(title = "点位配置", businessType = BusinessType.IMPORT) + @PostMapping("/importTemplateBySite") + public AjaxResult importTemplateBySite(@Valid @RequestBody ImportPointTemplateRequest request) { + return success(pointConfigService.importTemplateBySite(request, getUsername())); + } + + @Log(title = "点位配置", businessType = BusinessType.IMPORT) + @PostMapping("/importCsv") + public AjaxResult importCsv(@RequestParam String siteId, + @RequestParam(required = false) Boolean overwrite, + @RequestParam("file") MultipartFile file) { + return success(pointConfigService.importCsvBySite(siteId, overwrite, file, getUsername())); + } + + @GetMapping("/registerAddress") + public AjaxResult getRegisterAddress(@RequestParam String siteId, + @RequestParam String deviceCategory, + @RequestParam String deviceId, + @RequestParam String dataKey) { + return success(pointConfigService.getRegisterAddress(siteId, deviceCategory, deviceId, dataKey)); + } + + @PostMapping("/latestValues") + public AjaxResult latestValues(@RequestBody PointConfigLatestValueRequest request) { + return success(pointConfigService.getLatestValues(request)); + } + + @PostMapping("/curve") + public AjaxResult curve(@RequestBody PointConfigCurveRequest request) { + return success(pointConfigService.getCurveData(request)); + } + + @Log(title = "点位配置", businessType = BusinessType.INSERT) + @PostMapping("/generateRecent7Days") + public AjaxResult generateRecent7Days(@Valid @RequestBody PointConfigGenerateRecentRequest request) { + return success(pointConfigService.generateRecent7DaysData(request)); + } +} diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointMatchController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointMatchController.java index 8441650..fd8e697 100644 --- a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointMatchController.java +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsPointMatchController.java @@ -7,8 +7,12 @@ import javax.validation.Valid; import org.apache.commons.collections4.CollectionUtils; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.xzzn.common.annotation.Log; @@ -19,6 +23,7 @@ import com.xzzn.ems.domain.EmsPointMatch; import com.xzzn.ems.domain.vo.DevicePointMatchExportVo; import com.xzzn.ems.domain.vo.DevicePointMatchVo; import com.xzzn.ems.domain.vo.ImportPointDataRequest; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; import com.xzzn.ems.service.IEmsPointMatchService; import com.xzzn.common.utils.poi.ExcelUtil; import org.springframework.web.multipart.MultipartFile; @@ -49,6 +54,58 @@ public class EmsPointMatchController extends BaseController util.exportExcel(response, list, "点位匹配数据"); } + /** + * 查询点位配置列表 + */ + @GetMapping("/list") + public com.xzzn.common.core.page.TableDataInfo list(EmsPointMatch emsPointMatch) + { + startPage(); + List list = emsPointMatchService.selectPointMatchConfigList(emsPointMatch); + return getDataTable(list); + } + + /** + * 查询点位配置详情 + */ + @GetMapping(value = "/{id}") + public AjaxResult getInfo(@PathVariable("id") Long id) + { + return success(emsPointMatchService.selectPointMatchById(id)); + } + + /** + * 新增点位配置 + */ + @Log(title = "点位配置", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody EmsPointMatch emsPointMatch) + { + emsPointMatch.setCreateBy(getUsername()); + return toAjax(emsPointMatchService.insertPointMatch(emsPointMatch)); + } + + /** + * 修改点位配置 + */ + @Log(title = "点位配置", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody EmsPointMatch emsPointMatch) + { + emsPointMatch.setUpdateBy(getUsername()); + return toAjax(emsPointMatchService.updatePointMatch(emsPointMatch)); + } + + /** + * 删除点位配置 + */ + @Log(title = "点位配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public AjaxResult remove(@PathVariable Long[] ids) + { + return toAjax(emsPointMatchService.deletePointMatchByIds(ids)); + } + /** * 上传点位清单 * @param file @@ -85,4 +142,18 @@ public class EmsPointMatchController extends BaseController } } + /** + * 根据站点导入模板点位配置 + * @param request 请求参数 + * @return 导入结果 + */ + @PreAuthorize("@ss.hasPermi('system:user:import')") + @Log(title = "点位配置", businessType = BusinessType.IMPORT) + @PostMapping("/importTemplateBySite") + public AjaxResult importTemplateBySite(@Valid @RequestBody ImportPointTemplateRequest request) + { + String message = emsPointMatchService.importTemplateBySite(request, getUsername()); + return success(message); + } + } diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsSiteConfigController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsSiteConfigController.java index aec2ad4..1d2bc7a 100644 --- a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsSiteConfigController.java +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsSiteConfigController.java @@ -13,7 +13,9 @@ import com.xzzn.ems.domain.vo.DeviceUpdateRequest; import com.xzzn.ems.domain.vo.DevicesSettingVo; import com.xzzn.ems.domain.vo.PointDataRequest; import com.xzzn.ems.domain.vo.PointQueryResponse; +import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest; import com.xzzn.ems.domain.vo.SiteDeviceListVo; +import com.xzzn.ems.domain.vo.WorkStatusEnumMappingSaveRequest; import com.xzzn.ems.service.IEmsDeviceSettingService; import com.xzzn.ems.service.IEmsSiteService; @@ -59,6 +61,26 @@ public class EmsSiteConfigController extends BaseController{ return getDataTable(list); } + /** + * 新增站点 + */ + @PostMapping("/addSite") + public AjaxResult addSite(@RequestBody EmsSiteSetting emsSiteSetting) + { + emsSiteSetting.setCreateBy(getUsername()); + return toAjax(iEmsSiteService.addSite(emsSiteSetting)); + } + + /** + * 编辑站点 + */ + @PostMapping("/updateSite") + public AjaxResult updateSite(@RequestBody EmsSiteSetting emsSiteSetting) + { + emsSiteSetting.setUpdateBy(getUsername()); + return toAjax(iEmsSiteService.updateSite(emsSiteSetting)); + } + /** * 获取设备列表-分页 */ @@ -192,6 +214,44 @@ public class EmsSiteConfigController extends BaseController{ return success(iEmsDeviceSettingService.getDeviceListBySiteAndCategory(siteId, deviceCategory)); } + /** + * 获取单站监控项目点位映射 + */ + @GetMapping("/getSingleMonitorProjectPointMapping") + public AjaxResult getSingleMonitorProjectPointMapping(@RequestParam String siteId) + { + return success(iEmsDeviceSettingService.getSiteMonitorProjectPointMapping(siteId)); + } + + /** + * 保存单站监控项目点位映射 + */ + @PostMapping("/saveSingleMonitorProjectPointMapping") + public AjaxResult saveSingleMonitorProjectPointMapping(@RequestBody SiteMonitorProjectPointMappingSaveRequest request) + { + int rows = iEmsDeviceSettingService.saveSiteMonitorProjectPointMapping(request, getUsername()); + return AjaxResult.success(rows); + } + + /** + * 获取单站监控工作状态枚举映射(PCS) + */ + @GetMapping("/getSingleMonitorWorkStatusEnumMappings") + public AjaxResult getSingleMonitorWorkStatusEnumMappings(@RequestParam String siteId) + { + return success(iEmsDeviceSettingService.getSiteWorkStatusEnumMappings(siteId)); + } + + /** + * 保存单站监控工作状态枚举映射(PCS) + */ + @PostMapping("/saveSingleMonitorWorkStatusEnumMappings") + public AjaxResult saveSingleMonitorWorkStatusEnumMappings(@RequestBody WorkStatusEnumMappingSaveRequest request) + { + int rows = iEmsDeviceSettingService.saveSiteWorkStatusEnumMappings(request.getSiteId(), request.getMappings(), getUsername()); + return AjaxResult.success(rows); + } + /** * PCS设备开关机 */ 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 3e959f5..079ad52 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 @@ -9,9 +9,14 @@ import com.xzzn.ems.domain.vo.BatteryDataStatsListVo; import com.xzzn.ems.domain.vo.DateSearchRequest; import com.xzzn.ems.domain.vo.RunningGraphRequest; import com.xzzn.ems.domain.vo.SiteBatteryDataList; +import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest; +import com.xzzn.ems.domain.vo.SiteMonitorRuningInfoVo; +import com.xzzn.ems.service.IEmsDeviceSettingService; import com.xzzn.ems.service.IEmsSiteService; import com.xzzn.ems.service.IEmsStatsReportService; import com.xzzn.ems.service.ISingleSiteService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -26,6 +31,8 @@ import java.util.List; @RestController @RequestMapping("/ems/siteMonitor") public class EmsSiteMonitorController extends BaseController{ + private static final Logger log = LoggerFactory.getLogger(EmsSiteMonitorController.class); + private static final String RUNNING_GRAPH_CTRL_DEBUG = "RunningGraphCtrlDebug"; @Autowired private ISingleSiteService iSingleSiteService; @@ -33,6 +40,8 @@ public class EmsSiteMonitorController extends BaseController{ private IEmsSiteService iEmsSiteService; @Autowired private IEmsStatsReportService iemsStatsReportService; + @Autowired + private IEmsDeviceSettingService iEmsDeviceSettingService; /** * 获取单站首页数据 @@ -43,6 +52,15 @@ public class EmsSiteMonitorController extends BaseController{ return success(iSingleSiteService.getSiteMonitorDataVo(siteId)); } + /** + * 获取单站首页总累计运行数据(基于日表) + */ + @GetMapping("/homeTotalView") + public AjaxResult getSingleSiteHomeTotalView(@RequestParam String siteId) + { + return success(iSingleSiteService.getSiteMonitorTotalDataVo(siteId)); + } + /** * 单站监控-设备监控-实时运行头部数据 */ @@ -56,27 +74,75 @@ public class EmsSiteMonitorController extends BaseController{ * 单站监控-设备监控-实时运行曲线图数据 */ @GetMapping("/runningGraph/storagePower") - public AjaxResult getRunningGraphStorage(RunningGraphRequest request) + public AjaxResult getRunningGraphStorage(RunningGraphRequest request, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate) { - return success(iSingleSiteService.getRunningGraphStorage(request)); + SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphStorage(request); + int deviceCount = data == null || data.getPcsPowerList() == null ? 0 : data.getPcsPowerList().size(); + log.info("{} storage, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, deviceCount={}", + RUNNING_GRAPH_CTRL_DEBUG, + request == null ? null : request.getSiteId(), + startDate, + endDate, + request == null ? null : request.getStartDate(), + request == null ? null : request.getEndDate(), + deviceCount); + return success(data); } @GetMapping("/runningGraph/pcsMaxTemp") - public AjaxResult getRunningGraphPcsMaxTemp(RunningGraphRequest request) + public AjaxResult getRunningGraphPcsMaxTemp(RunningGraphRequest request, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate) { - return success(iSingleSiteService.getRunningGraphPcsMaxTemp(request)); + SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphPcsMaxTemp(request); + int deviceCount = data == null || data.getPcsMaxTempList() == null ? 0 : data.getPcsMaxTempList().size(); + log.info("{} pcsMaxTemp, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, deviceCount={}", + RUNNING_GRAPH_CTRL_DEBUG, + request == null ? null : request.getSiteId(), + startDate, + endDate, + request == null ? null : request.getStartDate(), + request == null ? null : request.getEndDate(), + deviceCount); + return success(data); } @GetMapping("/runningGraph/batteryAveSoc") - public AjaxResult getRunningGraphBatterySoc(RunningGraphRequest request) + public AjaxResult getRunningGraphBatterySoc(RunningGraphRequest request, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate) { - return success(iSingleSiteService.getRunningGraphBatterySoc(request)); + SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphBatterySoc(request); + int pointCount = data == null || data.getBatteryAveSOCList() == null ? 0 : data.getBatteryAveSOCList().size(); + log.info("{} batteryAveSoc, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, pointCount={}", + RUNNING_GRAPH_CTRL_DEBUG, + request == null ? null : request.getSiteId(), + startDate, + endDate, + request == null ? null : request.getStartDate(), + request == null ? null : request.getEndDate(), + pointCount); + return success(data); } @GetMapping("/runningGraph/batteryAveTemp") - public AjaxResult getRunningGraphBatteryTemp(RunningGraphRequest request) + public AjaxResult getRunningGraphBatteryTemp(RunningGraphRequest request, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate) { - return success(iSingleSiteService.getRunningGraphBatteryTemp(request)); + SiteMonitorRuningInfoVo data = iSingleSiteService.getRunningGraphBatteryTemp(request); + int pointCount = data == null || data.getBatteryAveTempList() == null ? 0 : data.getBatteryAveTempList().size(); + log.info("{} batteryAveTemp, siteId={}, rawStartDate={}, rawEndDate={}, bindStartDate={}, bindEndDate={}, pointCount={}", + RUNNING_GRAPH_CTRL_DEBUG, + request == null ? null : request.getSiteId(), + startDate, + endDate, + request == null ? null : request.getStartDate(), + request == null ? null : request.getEndDate(), + pointCount); + return success(data); } /** @@ -151,9 +217,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进行分页处理 @@ -228,4 +292,31 @@ public class EmsSiteMonitorController extends BaseController{ return error("缺少必传项"); } } + + /** + * 单站监控项目点位配置查询 + */ + @GetMapping("/getProjectPointMapping") + public AjaxResult getProjectPointMapping(@RequestParam String siteId) + { + return success(iEmsDeviceSettingService.getSiteMonitorProjectPointMapping(siteId)); + } + + /** + * 单站监控项目展示数据查询(配置字段 + 字段值) + */ + @GetMapping("/getProjectDisplayData") + public AjaxResult getProjectDisplayData(@RequestParam String siteId) + { + return success(iEmsDeviceSettingService.getSiteMonitorProjectDisplay(siteId)); + } + + /** + * 单站监控项目展示数据写入 + */ + @PostMapping("/saveProjectDisplayData") + public AjaxResult saveProjectDisplayData(@RequestBody SiteMonitorDataSaveRequest request) + { + return AjaxResult.success(iEmsDeviceSettingService.saveSiteMonitorProjectData(request, getUsername())); + } } diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStatisticalReportController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStatisticalReportController.java index 330ecb0..c82a3d3 100644 --- a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStatisticalReportController.java +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStatisticalReportController.java @@ -12,7 +12,9 @@ import com.xzzn.ems.domain.vo.ClusterStatisListVo; import com.xzzn.ems.domain.vo.DateSearchRequest; import com.xzzn.ems.domain.vo.StatisAmmeterDateRequest; import com.xzzn.ems.domain.vo.StatisClusterDateRequest; +import com.xzzn.ems.domain.vo.WeatherSyncResultVo; import com.xzzn.ems.service.IEmsStatsReportService; +import com.xzzn.ems.service.IEmsWeatherSyncService; import java.util.ArrayList; import java.util.List; @@ -38,6 +40,8 @@ public class EmsStatisticalReportController extends BaseController @Autowired private IEmsStatsReportService ieEmsStatsReportService; + @Autowired + private IEmsWeatherSyncService iEmsWeatherSyncService; /** * 概率统计-收益指标查询 @@ -130,6 +134,17 @@ public class EmsStatisticalReportController extends BaseController return getDataTable(dataList); } + /** + * 统计报表-电表报表(直接基于 ems_daily_energy_data) + */ + @GetMapping("/getAmmeterDataFromDaily") + public TableDataInfo getAmmeterDataFromDaily(StatisAmmeterDateRequest requestVo) + { + startPage(); + List dataList = ieEmsStatsReportService.getAmmeterDataResult(requestVo); + return getDataTable(dataList); + } + /** * 导出电表报表 */ @@ -141,6 +156,17 @@ public class EmsStatisticalReportController extends BaseController ieEmsStatsReportService.exportAmmeterData(response, requestVo); } + /** + * 导出电表报表(直接基于 ems_daily_energy_data) + */ + @PreAuthorize("@ss.hasPermi('system:ammeterData:export')") + @Log(title = "电表报表", businessType = BusinessType.EXPORT) + @PostMapping("/exportAmmeterDataFromDaily") + public void exportAmmeterDataFromDaily(HttpServletResponse response, StatisAmmeterDateRequest requestVo) + { + ieEmsStatsReportService.exportAmmeterData(response, requestVo); + } + /** * 概率统计-电表收益报表 */ @@ -176,4 +202,19 @@ public class EmsStatisticalReportController extends BaseController } } + /** + * 手动触发天气同步 + */ + @PostMapping("/syncWeatherByDateRange") + public AjaxResult syncWeatherByDateRange(StatisAmmeterDateRequest requestVo) + { + if (StringUtils.isEmpty(requestVo.getSiteId()) + || StringUtils.isEmpty(requestVo.getStartTime()) + || StringUtils.isEmpty(requestVo.getEndTime())) { + return error("缺少必传项: siteId/startTime/endTime"); + } + WeatherSyncResultVo resultVo = iEmsWeatherSyncService.syncWeatherByDateRange(requestVo); + return success(resultVo); + } + } diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStrategyRuntimeConfigController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStrategyRuntimeConfigController.java new file mode 100644 index 0000000..0067ecc --- /dev/null +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/EmsStrategyRuntimeConfigController.java @@ -0,0 +1,48 @@ +package com.xzzn.web.controller.ems; + +import com.xzzn.common.core.controller.BaseController; +import com.xzzn.common.core.domain.AjaxResult; +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; +import com.xzzn.ems.service.IEmsStrategyRuntimeConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 策略运行参数配置Controller + */ +@RestController +@RequestMapping("/system/strategyRuntimeConfig") +public class EmsStrategyRuntimeConfigController extends BaseController { + + @Autowired + private IEmsStrategyRuntimeConfigService runtimeConfigService; + + /** + * 按站点ID获取策略运行参数 + */ + @GetMapping("/getBySiteId") + public AjaxResult getBySiteId(String siteId) { + if (StringUtils.isEmpty(siteId)) { + return error("缺少必填字段siteId"); + } + return success(runtimeConfigService.getBySiteId(siteId)); + } + + /** + * 保存策略运行参数(按siteId新增/更新) + */ + @PostMapping("/save") + public AjaxResult save(@RequestBody EmsStrategyRuntimeConfig config) { + if (config == null || StringUtils.isEmpty(config.getSiteId())) { + return error("缺少必填字段siteId"); + } + config.setCreateBy(getUsername()); + config.setUpdateBy(getUsername()); + return toAjax(runtimeConfigService.saveBySiteId(config)); + } +} diff --git a/ems-admin/src/main/java/com/xzzn/web/controller/ems/MqttMessageController.java b/ems-admin/src/main/java/com/xzzn/web/controller/ems/MqttMessageController.java index ff74aa9..405efa7 100644 --- a/ems-admin/src/main/java/com/xzzn/web/controller/ems/MqttMessageController.java +++ b/ems-admin/src/main/java/com/xzzn/web/controller/ems/MqttMessageController.java @@ -6,12 +6,7 @@ import com.xzzn.common.enums.TopicHandleType; import com.xzzn.common.utils.StringUtils; import com.xzzn.ems.domain.EmsMqttTopicConfig; import com.xzzn.ems.mapper.EmsMqttTopicConfigMapper; -import com.xzzn.ems.service.IDDSDataProcessService; import com.xzzn.ems.service.IDeviceDataProcessService; -import com.xzzn.ems.service.IEmsMqttMessageService; -import com.xzzn.ems.service.IEmsStrategyService; -import com.xzzn.ems.service.IFXXAlarmDataProcessService; -import com.xzzn.ems.service.IFXXDataProcessService; import com.xzzn.ems.service.IMqttSyncLogService; import com.xzzn.framework.manager.MqttLifecycleManager; import com.xzzn.framework.web.service.MqttPublisher; @@ -27,6 +22,7 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -38,29 +34,19 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber { private final MqttLifecycleManager mqttLifecycleManager; - @Autowired - private IEmsMqttMessageService emsMqttMessageService; - - @Autowired - private IFXXDataProcessService fXXDataProcessService; - - @Autowired - private IDDSDataProcessService dDSDataProcessService; @Autowired private IDeviceDataProcessService deviceDataProcessService; - @Autowired - private IFXXAlarmDataProcessService fXXAlarmDataProcessService; @Autowired private EmsMqttTopicConfigMapper emsMqttTopicConfigMapper; @Autowired - private IEmsStrategyService emsStrategyService; - @Autowired private IMqttSyncLogService iMqttSyncLogService; @Autowired private RedisCache redisCache; + @Autowired + private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Autowired public MqttMessageController(MqttLifecycleManager mqttLifecycleManager) { @@ -136,51 +122,32 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber { private void handleSystemStatus(String topic, MqttMessage message) { String payload = new String(message.getPayload()); System.out.println("[SYSTEM] Status update: " + payload); - - try { - emsMqttMessageService.insertMqttOriginalMessage(topic,payload); - - } catch (Exception e) { - log.error("Failed to process system status message: " + e.getMessage(), e); - } } // 处理设备数据 private void handleDeviceData(String topic, MqttMessage message) { String payload = new String(message.getPayload()); - log.error("[DEVICE] data: " + payload); - try { - // 业务处理逻辑 -// if (topic.startsWith("021_DDS")) { -// dDSDataProcessService.handleDdsData(payload); -// } else if (topic.startsWith("021_FXX")) { -// fXXDataProcessService.handleFxData(payload); -// } - deviceDataProcessService.handleDeviceData(payload, getSiteIdByTopic(topic)); - - emsMqttMessageService.insertMqttOriginalMessage(topic, payload); - } catch (Exception e) { - log.error("Failed to process device data message: " + e.getMessage(), e); - } + threadPoolTaskExecutor.execute(() -> { + log.debug("[DEVICE] data: {}", payload); + try { + deviceDataProcessService.handleDeviceData(payload, getSiteIdByTopic(topic)); + } catch (Exception e) { + log.error("Failed to process device data message: {}", e.getMessage(), e); + } + }); } // 处理告警数据 private void handleAlarmData(String topic, MqttMessage message) { String payload = new String(message.getPayload()); - System.out.println("[DEVICE] data: " + payload); - try { - // 业务处理逻辑 -// if (topic.startsWith("021_FXX")) { -// fXXAlarmDataProcessService.handleFxAlarmData(payload); -// } - deviceDataProcessService.handleAlarmData(payload, getSiteIdByTopic(topic)); - - emsMqttMessageService.insertMqttOriginalMessage(topic, payload); - } catch (Exception e) { - e.printStackTrace(); - log.error("Failed to process device alarm data message: " + e.getMessage(), e); - } + threadPoolTaskExecutor.execute(() -> { + try { + deviceDataProcessService.handleAlarmData(payload, getSiteIdByTopic(topic)); + } catch (Exception e) { + log.error("Failed to process device alarm data message: {}", e.getMessage(), e); + } + }); } @@ -191,78 +158,67 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber { siteId = topicConfig.getSiteId(); redisCache.setCacheObject(RedisKeyConstants.SITE_ID + topic, siteId); } - log.info("当前处理数据站点:" + siteId + ",topic: " + topic); return siteId; } // 处理运行策略数据:云端-本地 private void handleStrategyData(String topic, MqttMessage message) { String payload = new String(message.getPayload()); - System.out.println("[处理运行策略数据] data: " + payload); - try { - // 业务处理逻辑 - iMqttSyncLogService.handleMqttStrategyData(payload); - - emsMqttMessageService.insertMqttOriginalMessage(topic,payload); - } catch (Exception e) { - log.error("Failed to process strategy data message: " + e.getMessage(), e); - } + threadPoolTaskExecutor.execute(() -> { + try { + iMqttSyncLogService.handleMqttStrategyData(payload); + } catch (Exception e) { + log.error("Failed to process strategy data message: {}", e.getMessage(), e); + } + }); } // 处理设备保护告警策略数据:云端-本地 private void handleFaultProtPlanData(String topic, MqttMessage message) { String payload = new String(message.getPayload()); - System.out.println("[处理设备保护告警策略数据] data: " + payload); - try { - // 业务处理逻辑 - iMqttSyncLogService.handleMqttPlanData(payload); - - emsMqttMessageService.insertMqttOriginalMessage(topic,payload); - } catch (Exception e) { - log.error("Failed to process fault plan data message: " + e.getMessage(), e); - } + threadPoolTaskExecutor.execute(() -> { + try { + iMqttSyncLogService.handleMqttPlanData(payload); + } catch (Exception e) { + log.error("Failed to process fault plan data message: {}", e.getMessage(), e); + } + }); } // 处理保护策略告警信息:本地-云端 private void handleFaultAlarmData(String topic, MqttMessage message) { String payload = new String(message.getPayload()); - System.out.println("[处理本地保护策略告警信息到云端] data: " + payload); - try { - // 业务处理逻辑 - iMqttSyncLogService.handleFaultAlarmData(payload); - - emsMqttMessageService.insertMqttOriginalMessage(topic,payload); - } catch (Exception e) { - log.error("Failed to process fault plan alarm data message: " + e.getMessage(), e); - } + threadPoolTaskExecutor.execute(() -> { + try { + iMqttSyncLogService.handleFaultAlarmData(payload); + } catch (Exception e) { + log.error("Failed to process fault plan alarm data message: {}", e.getMessage(), e); + } + }); } // 处理保护策略下发日志:本地-云端 private void handleFaultPlanIssueData(String topic, MqttMessage message) { String payload = new String(message.getPayload()); - System.out.println("[处理本地保护策略下发日志到云端] data: " + payload); - try { - // 业务处理逻辑 - iMqttSyncLogService.handleFaultPlanIssueData(payload); - - emsMqttMessageService.insertMqttOriginalMessage(topic,payload); - } catch (Exception e) { - log.error("Failed to process fault plan issue log message: " + e.getMessage(), e); - } + threadPoolTaskExecutor.execute(() -> { + try { + iMqttSyncLogService.handleFaultPlanIssueData(payload); + } catch (Exception e) { + log.error("Failed to process fault plan issue log message: {}", e.getMessage(), e); + } + }); } // 处理设备状态变更日志:本地-云端 private void handleDeviceChangeLogData(String topic, MqttMessage message) { String payload = new String(message.getPayload()); - System.out.println("[处理本地的保护策略告警信息到云端] data: " + payload); - try { - // 业务处理逻辑 - iMqttSyncLogService.handleDeviceChangeLogData(payload); - - emsMqttMessageService.insertMqttOriginalMessage(topic,payload); - } catch (Exception e) { - log.error("Failed to process device change log message: " + e.getMessage(), e); - } + threadPoolTaskExecutor.execute(() -> { + try { + iMqttSyncLogService.handleDeviceChangeLogData(payload); + } catch (Exception e) { + log.error("Failed to process device change log message: {}", e.getMessage(), e); + } + }); } @Override @@ -292,4 +248,4 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber { } } -} \ No newline at end of file +} diff --git a/ems-admin/src/main/resources/application-local.yml b/ems-admin/src/main/resources/application-local.yml index 7654dc9..49882e6 100644 --- a/ems-admin/src/main/resources/application-local.yml +++ b/ems-admin/src/main/resources/application-local.yml @@ -205,3 +205,10 @@ modbus: poll: interval: "0 */5 * * * *" # 5分钟间隔 timeout: 30000 # 30秒超时 + +weather: + api: + enabled: true + base-url: https://archive-api.open-meteo.com/v1/archive + api-key: + timezone: Asia/Shanghai diff --git a/ems-admin/src/main/resources/application-prod.yml b/ems-admin/src/main/resources/application-prod.yml index fee9ed4..6b6ecc0 100644 --- a/ems-admin/src/main/resources/application-prod.yml +++ b/ems-admin/src/main/resources/application-prod.yml @@ -205,3 +205,10 @@ modbus: poll: interval: "0 */5 * * * *" # 5分钟间隔 timeout: 30000 # 30秒超时 + +weather: + api: + enabled: true + base-url: https://archive-api.open-meteo.com/v1/archive + api-key: + timezone: Asia/Shanghai diff --git a/ems-admin/src/main/resources/application.yml b/ems-admin/src/main/resources/application.yml index cf6c040..01cabff 100644 --- a/ems-admin/src/main/resources/application.yml +++ b/ems-admin/src/main/resources/application.yml @@ -199,6 +199,20 @@ mqtt: topic: siteId: +influxdb: + enabled: true + url: http://122.51.194.184:8086/ + api-token: F2XcmBzZsWcz90ikU2_t7UXY2fzWuf2ruVp1BkusNkIS_gwrQZuiaIjl33XQMQajm7vSI6TQSRnpPSx5CXThlA== + write-method: POST + read-method: GET + write-path: /api/v2/write + query-path: /query + org: ems + bucket: point_data + database: ems_point_data + retention-policy: autogen + measurement: mqtt_point_data + modbus: pool: max-total: 20 @@ -207,3 +221,10 @@ modbus: poll: interval: "0 */5 * * * *" # 5分钟间隔 timeout: 30000 # 30秒超时 + +weather: + api: + enabled: true + base-url: https://archive-api.open-meteo.com/v1/archive + api-key: + timezone: Asia/Shanghai diff --git a/ems-admin/src/main/resources/logback.xml b/ems-admin/src/main/resources/logback.xml index 8f26067..96d503c 100644 --- a/ems-admin/src/main/resources/logback.xml +++ b/ems-admin/src/main/resources/logback.xml @@ -1,7 +1,7 @@ - + @@ -90,4 +90,4 @@ - \ No newline at end of file + diff --git a/ems-common/src/main/java/com/xzzn/common/constant/RedisKeyConstants.java b/ems-common/src/main/java/com/xzzn/common/constant/RedisKeyConstants.java index 82c0a75..b5aedc8 100644 --- a/ems-common/src/main/java/com/xzzn/common/constant/RedisKeyConstants.java +++ b/ems-common/src/main/java/com/xzzn/common/constant/RedisKeyConstants.java @@ -103,6 +103,9 @@ public class RedisKeyConstants /** 设备信息初始化 */ public static final String INIT_DEVICE_INFO = "init_device_info"; + + /** 设备配置缓存(按站点+设备) */ + public static final String DEVICE_SETTING = "DEVICE_SETTING_"; /** 告警匹配信息 */ public static final String ALARM_MATCH_INFO = "alarm_message_info"; @@ -122,4 +125,19 @@ public class RedisKeyConstants /** 每个设备最新数据-设置失效时间-判断是否正常同步数据 */ public static final String SYNC_DATA_ALARM = "SYNC_DATA_ALARM_"; + + /** 点位配置缓存(按站点+设备) */ + public static final String POINT_CONFIG_DEVICE = "POINT_CONFIG_DEVICE_"; + + /** 点位配置缓存(按站点+pointId) */ + public static final String POINT_CONFIG_POINT = "POINT_CONFIG_POINT_"; + + /** 单站监控最新数据(按站点+模块) */ + public static final String SITE_MONITOR_LATEST = "SITE_MONITOR_LATEST_"; + + /** 单站监控点位映射(按站点) */ + public static final String SITE_MONITOR_POINT_MATCH = "SITE_MONITOR_POINT_MATCH_"; + + /** 站点保护约束(按站点) */ + public static final String PROTECTION_CONSTRAINT = "PROTECTION_CONSTRAINT_"; } diff --git a/ems-framework/src/main/java/com/xzzn/framework/manager/MqttLifecycleManager.java b/ems-framework/src/main/java/com/xzzn/framework/manager/MqttLifecycleManager.java index b4c05ce..bdc0b45 100644 --- a/ems-framework/src/main/java/com/xzzn/framework/manager/MqttLifecycleManager.java +++ b/ems-framework/src/main/java/com/xzzn/framework/manager/MqttLifecycleManager.java @@ -3,21 +3,37 @@ package com.xzzn.framework.manager; import com.xzzn.ems.service.IEmsAlarmRecordsService; import org.eclipse.paho.client.mqttv3.*; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.context.SmartLifecycle; import org.springframework.stereotype.Component; -import java.util.HashSet; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; @Component -public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, MqttCallback { +public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, MqttCallbackExtended { + + private static final Logger log = LoggerFactory.getLogger(MqttLifecycleManager.class); + private static final long RECONNECT_DELAY_SECONDS = 5; private final MqttConnectOptions connectOptions; private final IEmsAlarmRecordsService iEmsAlarmRecordsService; + private final ScheduledExecutorService reconnectExecutor = Executors.newSingleThreadScheduledExecutor(r -> { + Thread thread = new Thread(r); + thread.setName("mqtt-reconnect"); + thread.setDaemon(true); + return thread; + }); + private final AtomicBoolean reconnectScheduled = new AtomicBoolean(false); + private volatile ScheduledFuture reconnectFuture; private MqttClient mqttClient; private volatile boolean running = false; @@ -41,7 +57,9 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, if (running) return; try { - String clientId = connectOptions.getUserName() + "-" + System.currentTimeMillis(); + String prefix = connectOptions.getUserName() == null || connectOptions.getUserName().isEmpty() + ? "mqtt-client" : connectOptions.getUserName(); + String clientId = prefix + "-" + System.currentTimeMillis(); mqttClient = new MqttClient( connectOptions.getServerURIs()[0], clientId, @@ -51,27 +69,28 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, mqttClient.setCallback(this); mqttClient.connect(connectOptions); - // 重连后自动重新订阅 resubscribeAll(); - running = true; - System.out.println("MQTT client connected to: " + connectOptions.getServerURIs()[0]); + log.info("MQTT client connected to: {}", connectOptions.getServerURIs()[0]); } catch (MqttException e) { - System.err.println("MQTT connection failed: " + e.getMessage()); - // 添加重试逻辑 + running = false; + log.error("MQTT connection failed: {}", e.getMessage(), e); + scheduleReconnect(); } } @Override public void stop() { + cancelReconnectTask(); if (mqttClient != null && mqttClient.isConnected()) { try { mqttClient.disconnect(); mqttClient.close(); } catch (MqttException e) { - System.err.println("Error disconnecting MQTT client: " + e.getMessage()); + log.warn("Error disconnecting MQTT client: {}", e.getMessage(), e); } } + reconnectExecutor.shutdownNow(); running = false; } @@ -83,9 +102,17 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, // MQTT 回调方法 @Override public void connectionLost(Throwable cause) { - System.err.println("MQTT connection lost: " + cause.getMessage()); + log.warn("MQTT connection lost: {}", cause == null ? "unknown" : cause.getMessage(), cause); running = false; - // 自动重连由 MqttConnectOptions 处理 + scheduleReconnect(); + } + + @Override + public void connectComplete(boolean reconnect, String serverURI) { + running = true; + cancelReconnectTask(); + log.info("MQTT connection complete, reconnect: {}, serverURI: {}", reconnect, serverURI); + resubscribeAll(); } @Override @@ -106,14 +133,16 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, try { if (mqttClient != null && mqttClient.isConnected()) { mqttClient.subscribe(topic, qos); + log.info("MQTT subscribe success, topic: {}, qos: {}", topic, qos); + } else { + log.warn("MQTT subscribe deferred, client not connected, topic: {}", topic); } subscriptions.put(topic, new SubscriptionInfo(listener, qos)); } catch (MqttException e) { - System.err.println("Subscribe failed: " + e.getMessage()); - // 订阅失败-增加告警 + log.error("Subscribe failed, topic: {}, err: {}", topic, e.getMessage(), e); iEmsAlarmRecordsService.addSubFailedAlarmRecord(topic); + scheduleReconnect(); } - // 订阅成功了-校验是否存在未处理或者处理中的订阅失败信息 iEmsAlarmRecordsService.checkFailedRecord(topic); } @@ -135,12 +164,52 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, subscriptions.forEach((topic, info) -> { try { mqttClient.subscribe(topic, info.getQos()); + log.info("MQTT resubscribe success, topic: {}, qos: {}", topic, info.getQos()); } catch (MqttException e) { - System.err.println("Resubscribe failed for topic: " + topic); + log.error("Resubscribe failed for topic: {}, err: {}", topic, e.getMessage(), e); } }); } + private void scheduleReconnect() { + if (mqttClient == null || reconnectExecutor.isShutdown()) { + return; + } + if (!reconnectScheduled.compareAndSet(false, true)) { + return; + } + + reconnectFuture = reconnectExecutor.scheduleWithFixedDelay(() -> { + if (mqttClient == null) { + cancelReconnectTask(); + return; + } + if (mqttClient.isConnected()) { + cancelReconnectTask(); + return; + } + try { + log.info("MQTT reconnecting..."); + mqttClient.connect(connectOptions); + running = true; + cancelReconnectTask(); + resubscribeAll(); + log.info("MQTT reconnect success."); + } catch (MqttException e) { + running = false; + log.warn("MQTT reconnect failed: {}", e.getMessage()); + } + }, RECONNECT_DELAY_SECONDS, RECONNECT_DELAY_SECONDS, TimeUnit.SECONDS); + } + + private void cancelReconnectTask() { + reconnectScheduled.set(false); + ScheduledFuture future = reconnectFuture; + if (future != null && !future.isCancelled()) { + future.cancel(false); + } + } + // 订阅信息内部类 private static class SubscriptionInfo { private final IMqttMessageListener listener; @@ -159,4 +228,4 @@ public class MqttLifecycleManager implements ApplicationRunner, SmartLifecycle, return qos; } } -} \ No newline at end of file +} diff --git a/ems-generator/src/main/java/com/xzzn/generator/controller/GenController.java b/ems-generator/src/main/java/com/xzzn/generator/controller/GenController.java index a3c8e2d..248b0b6 100644 --- a/ems-generator/src/main/java/com/xzzn/generator/controller/GenController.java +++ b/ems-generator/src/main/java/com/xzzn/generator/controller/GenController.java @@ -1,7 +1,6 @@ package com.xzzn.generator.controller; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,10 +17,6 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import com.alibaba.druid.DbType; -import com.alibaba.druid.sql.SQLUtils; -import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; import com.xzzn.common.annotation.Log; import com.xzzn.common.core.controller.BaseController; import com.xzzn.common.core.domain.AjaxResult; @@ -29,7 +24,6 @@ import com.xzzn.common.core.page.TableDataInfo; import com.xzzn.common.core.text.Convert; import com.xzzn.common.enums.BusinessType; import com.xzzn.common.utils.SecurityUtils; -import com.xzzn.common.utils.sql.SqlUtil; import com.xzzn.generator.config.GenConfig; import com.xzzn.generator.domain.GenTable; import com.xzzn.generator.domain.GenTableColumn; @@ -121,43 +115,6 @@ public class GenController extends BaseController return success(); } - /** - * 创建表结构(保存) - */ - @PreAuthorize("@ss.hasRole('admin')") - @Log(title = "创建表", businessType = BusinessType.OTHER) - @PostMapping("/createTable") - public AjaxResult createTableSave(String sql) - { - try - { - SqlUtil.filterKeyword(sql); - List sqlStatements = SQLUtils.parseStatements(sql, DbType.mysql); - List tableNames = new ArrayList<>(); - for (SQLStatement sqlStatement : sqlStatements) - { - if (sqlStatement instanceof MySqlCreateTableStatement) - { - MySqlCreateTableStatement createTableStatement = (MySqlCreateTableStatement) sqlStatement; - if (genTableService.createTable(createTableStatement.toString())) - { - String tableName = createTableStatement.getTableName().replaceAll("`", ""); - tableNames.add(tableName); - } - } - } - List tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); - String operName = SecurityUtils.getUsername(); - genTableService.importGenTable(tableList, operName); - return AjaxResult.success(); - } - catch (Exception e) - { - logger.error(e.getMessage(), e); - return AjaxResult.error("创建表结构异常"); - } - } - /** * 修改保存代码生成业务 */ @@ -260,4 +217,4 @@ public class GenController extends BaseController response.setContentType("application/octet-stream; charset=UTF-8"); IOUtils.write(data, response.getOutputStream()); } -} \ No newline at end of file +} diff --git a/ems-generator/src/main/java/com/xzzn/generator/mapper/GenTableMapper.java b/ems-generator/src/main/java/com/xzzn/generator/mapper/GenTableMapper.java index 0bcd959..c10cd22 100644 --- a/ems-generator/src/main/java/com/xzzn/generator/mapper/GenTableMapper.java +++ b/ems-generator/src/main/java/com/xzzn/generator/mapper/GenTableMapper.java @@ -81,11 +81,4 @@ public interface GenTableMapper */ public int deleteGenTableByIds(Long[] ids); - /** - * 创建表 - * - * @param sql 表结构 - * @return 结果 - */ - public int createTable(String sql); } diff --git a/ems-generator/src/main/java/com/xzzn/generator/service/GenTableServiceImpl.java b/ems-generator/src/main/java/com/xzzn/generator/service/GenTableServiceImpl.java index 7b0ff5a..0e51245 100644 --- a/ems-generator/src/main/java/com/xzzn/generator/service/GenTableServiceImpl.java +++ b/ems-generator/src/main/java/com/xzzn/generator/service/GenTableServiceImpl.java @@ -149,18 +149,6 @@ public class GenTableServiceImpl implements IGenTableService genTableColumnMapper.deleteGenTableColumnByIds(tableIds); } - /** - * 创建表 - * - * @param sql 创建表语句 - * @return 结果 - */ - @Override - public boolean createTable(String sql) - { - return genTableMapper.createTable(sql) == 0; - } - /** * 导入表结构 * @@ -528,4 +516,4 @@ public class GenTableServiceImpl implements IGenTableService } return genPath + File.separator + VelocityUtils.getFileName(template, table); } -} \ No newline at end of file +} diff --git a/ems-generator/src/main/java/com/xzzn/generator/service/IGenTableService.java b/ems-generator/src/main/java/com/xzzn/generator/service/IGenTableService.java index f64f132..6b78595 100644 --- a/ems-generator/src/main/java/com/xzzn/generator/service/IGenTableService.java +++ b/ems-generator/src/main/java/com/xzzn/generator/service/IGenTableService.java @@ -66,14 +66,6 @@ public interface IGenTableService */ public void deleteGenTableByIds(Long[] tableIds); - /** - * 创建表 - * - * @param sql 创建表语句 - * @return 结果 - */ - public boolean createTable(String sql); - /** * 导入表结构 * diff --git a/ems-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/ems-generator/src/main/resources/mapper/generator/GenTableMapper.xml index 2322914..3bf7137 100644 --- a/ems-generator/src/main/resources/mapper/generator/GenTableMapper.xml +++ b/ems-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -171,10 +171,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ) - - ${sql} - - update gen_table @@ -207,4 +203,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - \ No newline at end of file + diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/CalcPointTask.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/CalcPointTask.java new file mode 100644 index 0000000..fb2f177 --- /dev/null +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/CalcPointTask.java @@ -0,0 +1,35 @@ +package com.xzzn.quartz.task; + +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.service.impl.DeviceDataProcessServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component("calcPointTask") +public class CalcPointTask { + + private static final Logger log = LoggerFactory.getLogger(CalcPointTask.class); + + @Autowired + private DeviceDataProcessServiceImpl deviceDataProcessService; + + public void syncAllSites() { + log.info("开始执行计算点轮询任务-全站点"); + deviceDataProcessService.syncCalcPointDataFromInfluxByQuartz(); + log.info("结束执行计算点轮询任务-全站点"); + } + + public void syncBySite(String siteId) { + if (StringUtils.isBlank(siteId)) { + log.warn("计算点轮询任务参数为空,转为全站点执行"); + syncAllSites(); + return; + } + String normalizedSiteId = siteId.trim(); + log.info("开始执行计算点轮询任务-单站点, siteId: {}", normalizedSiteId); + deviceDataProcessService.syncCalcPointDataFromInfluxByQuartz(normalizedSiteId); + log.info("结束执行计算点轮询任务-单站点, siteId: {}", normalizedSiteId); + } +} diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/DailyChargeDataTask.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/DailyChargeDataTask.java new file mode 100644 index 0000000..4837538 --- /dev/null +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/DailyChargeDataTask.java @@ -0,0 +1,35 @@ +package com.xzzn.quartz.task; + +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.service.impl.DeviceDataProcessServiceImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component("dailyChargeDataTask") +public class DailyChargeDataTask { + + private static final Logger log = LoggerFactory.getLogger(DailyChargeDataTask.class); + + @Autowired + private DeviceDataProcessServiceImpl deviceDataProcessService; + + public void syncAllSites() { + log.info("开始执行每日充放电数据轮询任务-全站点"); + deviceDataProcessService.syncDailyChargeDataFromInfluxByQuartz(); + log.info("结束执行每日充放电数据轮询任务-全站点"); + } + + public void syncBySite(String siteId) { + if (StringUtils.isBlank(siteId)) { + log.warn("每日充放电数据轮询任务参数为空,转为全站点执行"); + syncAllSites(); + return; + } + String normalizedSiteId = siteId.trim(); + log.info("开始执行每日充放电数据轮询任务-单站点, siteId: {}", normalizedSiteId); + deviceDataProcessService.syncDailyChargeDataFromInfluxByQuartz(normalizedSiteId); + log.info("结束执行每日充放电数据轮询任务-单站点, siteId: {}", normalizedSiteId); + } +} diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java index 97be991..126a4b4 100644 --- a/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java +++ b/ems-quartz/src/main/java/com/xzzn/quartz/task/ModbusPoller.java @@ -2,31 +2,25 @@ package com.xzzn.quartz.task; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; -import com.fasterxml.jackson.databind.ObjectMapper; import com.serotonin.modbus4j.ModbusMaster; import com.xzzn.common.constant.RedisKeyConstants; import com.xzzn.common.core.modbus.ModbusProcessor; import com.xzzn.common.core.modbus.domain.DeviceConfig; +import com.xzzn.common.core.modbus.domain.TagConfig; import com.xzzn.common.core.redis.RedisCache; import com.xzzn.common.enums.DeviceRunningStatus; -import com.xzzn.common.utils.DateUtils; +import com.xzzn.common.utils.StringUtils; import com.xzzn.ems.domain.EmsDevicesSetting; +import com.xzzn.ems.domain.EmsPointConfig; import com.xzzn.ems.mapper.EmsDevicesSettingMapper; +import com.xzzn.ems.mapper.EmsPointConfigMapper; import com.xzzn.ems.service.IEmsAlarmRecordsService; import com.xzzn.ems.service.impl.DeviceDataProcessServiceImpl; -import com.xzzn.framework.manager.MqttLifecycleManager; import com.xzzn.framework.web.service.MqttPublisher; -import com.xzzn.quartz.config.ScheduledTask; -import com.xzzn.quartz.domain.SysJob; -import com.xzzn.quartz.service.ISysJobService; -import com.xzzn.quartz.util.CronUtils; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -54,10 +48,8 @@ import org.springframework.util.CollectionUtils; @Component("modbusPoller") public class ModbusPoller { private static final Logger log = LoggerFactory.getLogger(ModbusPoller.class); + private static final int SITE_DEVICE_OFFLINE_THRESHOLD = 6; - private final MqttLifecycleManager mqttLifecycleManager; - private final ScheduledTask scheduledTask; - private final ObjectMapper objectMapper = new ObjectMapper(); private final Map deviceFailureCounts = new ConcurrentHashMap<>(); private final AtomicBoolean polling = new AtomicBoolean(false); @@ -69,77 +61,46 @@ public class ModbusPoller { @Autowired private IEmsAlarmRecordsService iEmsAlarmRecordsService; - @Autowired - private ISysJobService iSysJobService; @Autowired private DeviceDataProcessServiceImpl deviceDataProcessServiceImpl; @Autowired private EmsDevicesSettingMapper emsDevicesSettingMapper; @Autowired + private EmsPointConfigMapper emsPointConfigMapper; + @Autowired private RedisCache redisCache; @Autowired private MqttPublisher mqttPublisher; @Value("${mqtt.topic}") private String topic; - @Value("${mqtt.siteId}") - private String siteId; - - @Autowired - public ModbusPoller(MqttLifecycleManager mqttLifecycleManager, ScheduledTask scheduledTask) { - this.mqttLifecycleManager = mqttLifecycleManager; - this.scheduledTask = scheduledTask; - } public void pollAllDevices() { if (!polling.compareAndSet(false, true)) { log.warn("上一次轮询尚未完成,本次轮询跳过"); return; } - Path devicesDir = Paths.get(System.getProperty("user.dir"), "devices"); - if (!Files.exists(devicesDir)) { - log.error("Devices目录不存在: {}", devicesDir); + List pollingTasks = buildPollingTasks(); + if (CollectionUtils.isEmpty(pollingTasks)) { + log.warn("未查询到可用的Modbus采集点位配置,跳过本轮轮询"); polling.set(false); return; } - List jsonFiles = null; - try { - jsonFiles = Files.list(devicesDir) - .filter(path -> path.toString().endsWith(".json")) - .collect(Collectors.toList()); - } catch (IOException e) { - log.error("modbusPoller.loadConfigs 获取设备配置文件失败: {}", devicesDir, e); - polling.set(false); - return; - } + // 按主机IP分组,同一网关串行访问,避免连接抖动 + Map> groupedByHost = pollingTasks.stream() + .collect(Collectors.groupingBy( + task -> task.getDeviceConfig().getHost(), + HashMap::new, + Collectors.toList())); - // 按主机IP分组(同一网关的不同端口也归为一组,避免并发访问导致Connection Reset) - Map> groupedByHost = new HashMap<>(); - for (Path filePath : jsonFiles) { - DeviceConfig config = null; - try { - config = objectMapper.readValue(filePath.toFile(), DeviceConfig.class); - } catch (IOException e) { - log.error("modbusPoller.loadConfigs 解析设备配置文件失败: {}", filePath, e); - continue; - } - if (config.isEnabled()) { - // 只按主机IP分组,确保同一网关的所有端口串行访问 - String hostKey = config.getHost(); - groupedByHost.computeIfAbsent(hostKey, k -> new ArrayList<>()).add(config); - } - } - - // 使用单线程 executor 串行执行所有主机的 Modbus 操作 - // 将所有主机的设备按顺序串行处理,避免任何并发访问 Future future = modbusExecutor.submit(() -> { - for (Map.Entry> entry : groupedByHost.entrySet()) { + for (Map.Entry> entry : groupedByHost.entrySet()) { String hostKey = entry.getKey(); - List configs = entry.getValue(); - for (DeviceConfig config : configs) { + List tasks = entry.getValue(); + for (PollingTask task : tasks) { try { - scheduledStart(config); + scheduledStart(task.getSiteId(), task.getDeviceConfig()); // 每次读取后等待200ms,给Modbus网关足够的处理时间 Thread.sleep(200); } catch (InterruptedException ie) { @@ -147,7 +108,8 @@ public class ModbusPoller { log.warn("Modbus轮询被中断"); return; } catch (Exception e) { - log.error("采集设备数据异常: {}", config.getDeviceName(), e); + log.error("采集设备数据异常: siteId={}, deviceId={}", + task.getSiteId(), task.getDeviceConfig().getDeviceNumber(), e); } } log.info("采集设备数据{}轮询任务执行完成", hostKey); @@ -165,7 +127,139 @@ public class ModbusPoller { } } - public void scheduledStart(DeviceConfig config) { + private List buildPollingTasks() { + List pointConfigs = emsPointConfigMapper.selectModbusCollectPointConfigs(null); + if (CollectionUtils.isEmpty(pointConfigs)) { + return Collections.emptyList(); + } + + List allDevices = emsDevicesSettingMapper.selectEmsDevicesSettingList(null); + Map deviceMap = allDevices.stream() + .filter(Objects::nonNull) + .filter(device -> StringUtils.isNoneBlank(device.getSiteId(), device.getDeviceId())) + .collect(Collectors.toMap( + this::buildSiteDeviceKey, + device -> device, + (left, right) -> left)); + + Map> pointsByDevice = pointConfigs.stream() + .filter(Objects::nonNull) + .filter(point -> StringUtils.isNoneBlank(point.getSiteId(), point.getDeviceId())) + .collect(Collectors.groupingBy( + point -> point.getSiteId() + "_" + point.getDeviceId(), + HashMap::new, + Collectors.toList())); + + List tasks = new ArrayList<>(); + for (Map.Entry> entry : pointsByDevice.entrySet()) { + String siteDeviceKey = entry.getKey(); + EmsDevicesSetting device = deviceMap.get(siteDeviceKey); + if (device == null) { + log.warn("未找到设备连接配置,跳过采集: key={}", siteDeviceKey); + continue; + } + DeviceConfig deviceConfig = buildDeviceConfig(device, entry.getValue()); + if (deviceConfig == null) { + continue; + } + tasks.add(new PollingTask(device.getSiteId(), deviceConfig)); + } + return tasks; + } + + private DeviceConfig buildDeviceConfig(EmsDevicesSetting device, List pointConfigs) { + if (device == null || CollectionUtils.isEmpty(pointConfigs)) { + return null; + } + if (StringUtils.isBlank(device.getIpAddress()) || device.getIpPort() == null || device.getSlaveId() == null) { + log.warn("设备连接参数不完整,跳过采集: siteId={}, deviceId={}", device.getSiteId(), device.getDeviceId()); + return null; + } + List tags = pointConfigs.stream() + .sorted(Comparator.comparing(point -> point.getModbusReadOrder() == null ? 0 : point.getModbusReadOrder())) + .map(this::toTagConfig) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (CollectionUtils.isEmpty(tags)) { + log.warn("设备无有效Modbus点位配置,跳过采集: siteId={}, deviceId={}", device.getSiteId(), device.getDeviceId()); + return null; + } + + DeviceConfig deviceConfig = new DeviceConfig(); + deviceConfig.setEnabled(true); + deviceConfig.setDeviceName(device.getDeviceName()); + deviceConfig.setDeviceNumber(device.getDeviceId()); + deviceConfig.setHost(device.getIpAddress()); + deviceConfig.setPort(device.getIpPort().intValue()); + deviceConfig.setSlaveId(device.getSlaveId().intValue()); + deviceConfig.setTags(tags); + return deviceConfig; + } + + private TagConfig toTagConfig(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return null; + } + if (StringUtils.isBlank(pointConfig.getDataKey())) { + return null; + } + String address = normalizeAddress(pointConfig.getRegisterAddress(), pointConfig.getModbusRegisterType()); + if (StringUtils.isBlank(address)) { + return null; + } + if (StringUtils.isBlank(pointConfig.getModbusDataType())) { + return null; + } + + TagConfig tag = new TagConfig(); + tag.setKey(pointConfig.getDataKey().trim()); + tag.setAddress(address); + tag.setDataType(pointConfig.getModbusDataType().trim()); + tag.setA(pointConfig.getDataA() == null ? 0F : pointConfig.getDataA().floatValue()); + tag.setK(pointConfig.getDataK() == null ? 1F : pointConfig.getDataK().floatValue()); + tag.setB(pointConfig.getDataB() == null ? 0F : pointConfig.getDataB().floatValue()); + tag.setBit(pointConfig.getDataBit()); + return tag; + } + + private String normalizeAddress(String registerAddress, String registerType) { + if (StringUtils.isBlank(registerAddress)) { + return null; + } + String normalizedAddress = registerAddress.trim(); + if (!normalizedAddress.chars().allMatch(Character::isDigit)) { + log.warn("寄存器地址必须为数字,当前值: {}", normalizedAddress); + return null; + } + if (normalizedAddress.length() > 1) { + char first = normalizedAddress.charAt(0); + if (first >= '0' && first <= '4') { + return normalizedAddress; + } + } + return getRegisterPrefix(registerType) + normalizedAddress; + } + + private String getRegisterPrefix(String registerType) { + String normalized = StringUtils.defaultString(registerType).trim().toUpperCase(); + switch (normalized) { + case "COIL": + return "0"; + case "DISCRETE_INPUT": + return "1"; + case "INPUT_REGISTER": + return "3"; + case "HOLDING_REGISTER": + default: + return "4"; + } + } + + private String buildSiteDeviceKey(EmsDevicesSetting device) { + return device.getSiteId() + "_" + device.getDeviceId(); + } + + public void scheduledStart(String siteId, DeviceConfig config) { if (config.isEnabled()) { log.info("Reading data from devices: {}", config.getDeviceName()); @@ -205,7 +299,7 @@ public class ModbusPoller { log.info("Data from {}: {}", config.getDeviceName(), data); String deviceNumber = config.getDeviceNumber(); //处理数据并发送MQTT消息、保存Redis数据和数据入库 - processingData(data, deviceNumber); + processingData(siteId, data, deviceNumber); } } @@ -254,24 +348,25 @@ public class ModbusPoller { return data; } - private void processingData(Map data, String deviceNumber) { + private void processingData(String siteId, Map data, String deviceNumber) { + String siteDeviceKey = siteId + "_" + deviceNumber; if (CollectionUtils.isEmpty(data)) { // 增加失败计数 - int failureCount = deviceFailureCounts.getOrDefault(deviceNumber, 0) + 1; - deviceFailureCounts.put(deviceNumber, failureCount); + int failureCount = deviceFailureCounts.getOrDefault(siteDeviceKey, 0) + 1; + deviceFailureCounts.put(siteDeviceKey, failureCount); - log.warn("设备 {} 数据读取失败,当前连续失败次数: {}", deviceNumber, failureCount); + log.warn("设备 {} 数据读取失败,当前连续失败次数: {}", siteDeviceKey, failureCount); // 连续6次失败触发报警 - if (failureCount >= 6) { + if (failureCount >= SITE_DEVICE_OFFLINE_THRESHOLD) { addDeviceOfflineRecord(siteId, deviceNumber); - log.error("设备 {} 连续 {} 次未读取到数据,触发报警", deviceNumber, failureCount); + log.error("设备 {} 连续 {} 次未读取到数据,触发报警", siteDeviceKey, failureCount); } return; } // 数据读取成功,重置计数器 - deviceFailureCounts.remove(deviceNumber); + deviceFailureCounts.remove(siteDeviceKey); // 读取到数据后告警自恢复 deleteDeviceOfflineRecord(siteId, deviceNumber); @@ -281,9 +376,42 @@ public class ModbusPoller { json.put("Data", data); json.put("timestamp", timestamp); json.put("Device", deviceNumber); - sendMqttMsg(json); - saveRedisData(json, deviceNumber); - saveDataToDatabase(data, deviceNumber, timestamp); + if (shouldSendMqttOnChange(siteId, deviceNumber, data)) { + sendMqttMsg(json); + } else { + sendMqttHeartbeat(deviceNumber, timestamp); + log.info("设备 {} 数据无变化,已发送心跳MQTT", siteDeviceKey); + } + saveRedisData(siteId, json, deviceNumber); + saveDataToDatabase(siteId, data, deviceNumber, timestamp); + } + + /** + * 逢变上送:仅当Data发生变化时才发送MQTT + */ + private boolean shouldSendMqttOnChange(String siteId, String deviceNumber, Map currentData) { + JSONObject lastPayload = redisCache.getCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceNumber); + if (lastPayload == null) { + return true; + } + + Object lastDataObj = lastPayload.get("Data"); + if (lastDataObj == null) { + return true; + } + + JSONObject lastData = JSON.parseObject(JSON.toJSONString(lastDataObj)); + JSONObject currentDataJson = JSON.parseObject(JSON.toJSONString(currentData)); + return !Objects.equals(lastData, currentDataJson); + } + + private void sendMqttHeartbeat(String deviceNumber, Long timestamp) { + JSONObject heartbeat = new JSONObject(); + heartbeat.put("Device", deviceNumber); + heartbeat.put("timestamp", timestamp); + heartbeat.put("Heartbeat", 1); + heartbeat.put("Data", new JSONObject()); + sendMqttMsg(heartbeat); } public void sendMqttMsg(JSONObject json) { @@ -296,7 +424,7 @@ public class ModbusPoller { } - public void saveRedisData(JSONObject obj, String deviceNumber) { + public void saveRedisData(String siteId, JSONObject obj, String deviceNumber) { try { // 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据 redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceNumber, obj); @@ -308,8 +436,12 @@ public class ModbusPoller { } } - private void saveDataToDatabase(Map data, String deviceNumber, Long timestamp) { - deviceDataProcessServiceImpl.processingDeviceData(siteId, deviceNumber, JSON.toJSONString(data), DateUtils.convertUpdateTime(timestamp)); + private void saveDataToDatabase(String siteId, Map data, String deviceNumber, Long timestamp) { + JSONObject payload = new JSONObject(); + payload.put("Device", deviceNumber); + payload.put("Data", JSON.toJSONString(data)); + payload.put("timestamp", timestamp); + deviceDataProcessServiceImpl.handleDeviceData(Collections.singletonList(payload).toString(), siteId); } //处理设备连接失败的情况,更新设备状态为离线,添加报警记录 @@ -333,11 +465,22 @@ public class ModbusPoller { } } - private int getScheduledTaskInterval() { - SysJob query = new SysJob(); - query.setInvokeTarget("modbusPoller.pollAllDevices"); - List sysJobs = iSysJobService.selectJobList(query); - return Math.toIntExact(CronUtils.getNextExecutionIntervalMillis(sysJobs.get(0).getCronExpression())); + private static class PollingTask { + private final String siteId; + private final DeviceConfig deviceConfig; + + private PollingTask(String siteId, DeviceConfig deviceConfig) { + this.siteId = siteId; + this.deviceConfig = deviceConfig; + } + + public String getSiteId() { + return siteId; + } + + public DeviceConfig getDeviceConfig() { + return deviceConfig; + } } } 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 9842bf4..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 @@ -10,40 +10,16 @@ import com.xzzn.common.core.redis.RedisCache; import com.xzzn.common.enums.AlarmLevelStatus; import com.xzzn.common.enums.AlarmStatus; import com.xzzn.common.enums.ProtPlanStatus; -import com.xzzn.common.enums.StrategyStatus; import com.xzzn.common.utils.StringUtils; import com.xzzn.ems.domain.EmsAlarmRecords; -import com.xzzn.ems.domain.EmsDevicesSetting; -import com.xzzn.ems.domain.EmsFaultIssueLog; import com.xzzn.ems.domain.EmsFaultProtectionPlan; -import com.xzzn.ems.domain.EmsStrategyRunning; +import com.xzzn.ems.domain.vo.ProtectionConstraintVo; import com.xzzn.ems.domain.vo.ProtectionPlanVo; import com.xzzn.ems.domain.vo.ProtectionSettingVo; +import com.xzzn.ems.domain.vo.ProtectionSettingsGroupVo; import com.xzzn.ems.mapper.EmsAlarmRecordsMapper; -import com.xzzn.ems.mapper.EmsDevicesSettingMapper; -import com.xzzn.ems.mapper.EmsFaultIssueLogMapper; import com.xzzn.ems.mapper.EmsFaultProtectionPlanMapper; -import com.xzzn.ems.mapper.EmsStrategyRunningMapper; import com.xzzn.ems.service.IEmsFaultProtectionPlanService; -import com.xzzn.common.core.modbus.ModbusProcessor; -import com.xzzn.common.core.modbus.domain.DeviceConfig; -import com.xzzn.common.core.modbus.domain.WriteTagConfig; - -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.annotation.Resource; - import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,14 +27,29 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * 告警保护方案轮询 - * - * @author xzzn */ @Component("protectionPlanTask") public class ProtectionPlanTask { private static final Logger logger = LoggerFactory.getLogger(ProtectionPlanTask.class); + private static final BigDecimal DEFAULT_L1_POWER_RATIO = new BigDecimal("0.5"); + private static final int CONSTRAINT_TTL_SECONDS = 120; + @Resource(name = "scheduledExecutorService") private ScheduledExecutorService scheduledExecutorService; @Autowired @@ -66,18 +57,11 @@ public class ProtectionPlanTask { @Autowired private EmsAlarmRecordsMapper emsAlarmRecordsMapper; @Autowired - private EmsStrategyRunningMapper emsStrategyRunningMapper; - @Autowired private EmsFaultProtectionPlanMapper emsFaultProtectionPlanMapper; @Autowired private RedisCache redisCache; + private static final ObjectMapper objectMapper = new ObjectMapper(); - @Autowired - private EmsDevicesSettingMapper emsDevicesSettingMapper; - @Autowired - private ModbusProcessor modbusProcessor; - @Autowired - private EmsFaultIssueLogMapper emsFaultIssueLogMapper; public ProtectionPlanTask(IEmsFaultProtectionPlanService iEmsFaultProtectionPlanService) { this.iEmsFaultProtectionPlanService = iEmsFaultProtectionPlanService; @@ -86,303 +70,456 @@ public class ProtectionPlanTask { public void pollPlanList() { Long planId = 0L; try { - // 获取所有方案,轮询 List planList = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(null); - for (EmsFaultProtectionPlan plan : planList) { planId = plan.getId(); String siteId = plan.getSiteId(); if (StringUtils.isEmpty(siteId)) { - return; + continue; } - // 保护前提 - String protectionSettings = plan.getProtectionSettings(); - final List protSettings = objectMapper.readValue( - protectionSettings, - new TypeReference>() {} - ); - if (protSettings == null) { - return; - } - - // 处理告警保护方案 - boolean isHighLevel = dealWithProtectionPlan(plan, protSettings); - if (isHighLevel) { - // 触发最高故障等级-结束循环 - return; + ProtectionSettingsGroupVo settingGroup = parseProtectionSettings(plan.getProtectionSettings()); + if (CollectionUtils.isEmpty(settingGroup.getFaultSettings()) + && CollectionUtils.isEmpty(settingGroup.getReleaseSettings())) { + continue; } + dealWithProtectionPlan(plan, settingGroup); } + refreshProtectionConstraintCache(planList); } catch (Exception e) { logger.error("轮询失败,方案id为:{}", planId, e); } } - // 处理告警保护方案-返回触发下发方案时是否最高等级 - // 需要同步云端 @SyncAfterInsert - private boolean dealWithProtectionPlan(EmsFaultProtectionPlan plan, List protSettings) { + private void dealWithProtectionPlan(EmsFaultProtectionPlan plan, ProtectionSettingsGroupVo settingGroup) { logger.info("<轮询保护方案> 站点:{},方案ID:{}", plan.getSiteId(), plan.getId()); - boolean isHighLevel = false; - String siteId = plan.getSiteId(); - final Integer isAlertAlarm = plan.getIsAlert(); - final Long status = plan.getStatus(); - // 看方案是否启用,走不同判断 + 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())) { - logger.info("<方案未启用> 站点:{},方案ID:{}", siteId, plan.getId()); - // 未启用,获取方案的故障值与最新数据判断是否需要下发方案 - if(checkIsNeedIssuedPlan(protSettings, siteId)){ - if("3".equals(plan.getFaultLevel())){ - isHighLevel = true;//最高故障等级 - } - // 延时 - final int faultDelay = plan.getFaultDelaySeconds().intValue(); - ScheduledFuture delayTask = scheduledExecutorService.schedule(() -> { - // 延时后再次确认是否仍满足触发条件(防止期间状态变化) - if (checkIsNeedIssuedPlan(protSettings, siteId)) { - // 判断是否需要生成告警 - if (isAlertAlarm == 1) { - logger.info("<生成告警> 方案ID:{},站点:{}", plan.getId(), siteId); - EmsAlarmRecords alarmRecords = addAlarmRecord(siteId,plan.getFaultName(), - getAlarmLevel(plan.getFaultLevel())); - emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords); - } - - // 是否有保护方案,有则通过modbus连接设备下发方案 - String protPlanJson = plan.getProtectionPlan(); - if (protPlanJson != null && !protPlanJson.isEmpty() && !"[]".equals(protPlanJson)) { - logger.info("<下发保护方案> 方案内容:{}", protPlanJson); - executeProtectionActions(protPlanJson,siteId,plan.getId(),plan.getFaultLevel()); // 执行Modbus指令 - } - - // 更新方案状态为“已启用” - logger.info("<方案已启用> 方案ID:{}", plan.getId()); - plan.setStatus(ProtPlanStatus.RUNNING.getCode()); - emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan); - - // 更新该站点策略为暂停状态 - updateStrategyRunningStatus(siteId, StrategyStatus.SUSPENDED.getCode()); + if (checkIsNeedIssuedPlan(faultSettings, siteId)) { + int faultDelay = safeDelaySeconds(plan.getFaultDelaySeconds(), 0); + scheduledExecutorService.schedule(() -> { + if (!checkIsNeedIssuedPlan(faultSettings, siteId)) { + return; } - }, faultDelay, TimeUnit.SECONDS); - } - } else { - logger.info("<方案已启用> 站点:{},方案ID:{}", siteId, plan.getId()); - // 已启用,则获取方案的释放值与最新数据判断是否需要取消方案 - if(checkIsNeedCancelPlan(protSettings, siteId)){ - // 延时, - int releaseDelay = plan.getReleaseDelaySeconds().intValue(); - ScheduledFuture delayTask = scheduledExecutorService.schedule(() -> { - // 判断是否已存在未处理告警,有着取消 - if(isAlertAlarm == 1){ - logger.info("<取消告警>"); - EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord(siteId, - plan.getFaultName(),getAlarmLevel(plan.getFaultLevel())); - if(emsAlarmRecords != null){ - emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode()); - emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords); - } + if (Integer.valueOf(1).equals(isAlertAlarm)) { + EmsAlarmRecords alarmRecords = addAlarmRecord(siteId, plan.getFaultName(), getAlarmLevel(plan.getFaultLevel())); + emsAlarmRecordsMapper.insertEmsAlarmRecords(alarmRecords); } - // 更新方案状态为“未启用” - logger.info("<方案变更为未启用> 方案ID:{}", plan.getId()); - plan.setStatus(ProtPlanStatus.STOP.getCode()); + plan.setStatus(ProtPlanStatus.RUNNING.getCode()); plan.setUpdateBy("system"); emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan); - // 更新该站点策略为启用状态 - updateStrategyRunningStatus(siteId, StrategyStatus.RUNNING.getCode()); - }, releaseDelay, TimeUnit.SECONDS); + refreshSiteProtectionConstraint(siteId); + }, faultDelay, TimeUnit.SECONDS); } - } - - return isHighLevel; - } - - // 下发保护方案 - private void executeProtectionActions(String protPlanJson, String siteId, Long planId, Integer faultLevel){ - final List protPlanList; - try { - protPlanList = objectMapper.readValue( - protPlanJson, - new TypeReference>() {} - ); - if (protPlanList == null) { - return; - } - - // 遍历保护方案 - for (ProtectionPlanVo plan : protPlanList) { - if (StringUtils.isEmpty(plan.getDeviceId()) || StringUtils.isEmpty(plan.getPoint())) { - return; - } - // 给设备发送指令记录日志,并同步云端 - EmsFaultIssueLog faultIssueLog = createLogEntity(plan,siteId); - faultIssueLog.setLogLevel(faultLevel); - emsFaultIssueLogMapper.insertEmsFaultIssueLog(faultIssueLog); - - // 通过modbus连接设备,发送数据 - executeSinglePlan(plan,siteId); - } - - - } catch (Exception e) { - logger.error("下发保护方案失败,方案id为:", planId, e); - } - } - - private EmsFaultIssueLog createLogEntity(ProtectionPlanVo plan,String siteId) { - EmsFaultIssueLog faultIssueLog = new EmsFaultIssueLog(); - faultIssueLog.setLogId(UUID.randomUUID().toString()); - faultIssueLog.setLogTime(new Date()); - faultIssueLog.setSiteId(siteId); - faultIssueLog.setDeviceId(plan.getDeviceId()); - faultIssueLog.setPoint(plan.getPoint()); - faultIssueLog.setValue(plan.getValue()); - faultIssueLog.setCreateBy("sys"); - faultIssueLog.setCreateTime(new Date()); - return faultIssueLog; - } - - private void executeSinglePlan(ProtectionPlanVo plan, String siteId) throws Exception { - String deviceId = plan.getDeviceId(); - // 获取设备地址信息 - EmsDevicesSetting device = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId); - if (device == null || StringUtils.isEmpty(device.getIpAddress()) || device.getIpPort()==null) { - logger.warn("设备信息不完整,deviceId:{}", deviceId); return; } - // 构建设备配置 - DeviceConfig config = new DeviceConfig(); - config.setHost(device.getIpAddress()); - config.setPort(device.getIpPort().intValue()); - config.setSlaveId(device.getSlaveId().intValue()); - config.setDeviceName(device.getDeviceName()); - config.setDeviceNumber(device.getDeviceId()); - - // 构建写入标签配置 - WriteTagConfig writeTag = new WriteTagConfig(); - writeTag.setAddress(plan.getPoint()); - writeTag.setValue(plan.getValue()); - - List writeTags = new ArrayList<>(); - writeTags.add(writeTag); - config.setWriteTags(writeTags); - - // 写入数据到设备 - boolean success = modbusProcessor.writeDataToDeviceWithRetry(config); - - if (!success) { - logger.error("写入失败,设备地址:{}", device.getIpAddress()); + if (checkIsNeedCancelPlan(releaseSettings, siteId)) { + int releaseDelay = safeDelaySeconds(plan.getReleaseDelaySeconds(), 0); + scheduledExecutorService.schedule(() -> { + if (Integer.valueOf(1).equals(isAlertAlarm)) { + EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.getFailedRecord( + siteId, + plan.getFaultName(), + getAlarmLevel(plan.getFaultLevel()) + ); + if (emsAlarmRecords != null) { + emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode()); + emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords); + } + } + plan.setStatus(ProtPlanStatus.STOP.getCode()); + plan.setUpdateBy("system"); + emsFaultProtectionPlanMapper.updateEmsFaultProtectionPlan(plan); + refreshSiteProtectionConstraint(siteId); + }, releaseDelay, TimeUnit.SECONDS); } } - // 校验释放值是否取消方案 - private boolean checkIsNeedCancelPlan(List protSettings, String siteId) { - BigDecimal releaseValue = BigDecimal.ZERO; + private int safeDelaySeconds(Long delay, int defaultSeconds) { + if (delay == null || delay < 0) { + return defaultSeconds; + } + return delay.intValue(); + } + 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 { + 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:{}", planJson, e); + return new ArrayList<>(); + } + } + + private void refreshProtectionConstraintCache(List allPlans) { + Map> planBySite = new HashMap<>(); + for (EmsFaultProtectionPlan plan : allPlans) { + if (StringUtils.isEmpty(plan.getSiteId())) { + continue; + } + planBySite.computeIfAbsent(plan.getSiteId(), k -> new ArrayList<>()).add(plan); + } + for (Map.Entry> entry : planBySite.entrySet()) { + writeSiteProtectionConstraint(entry.getKey(), entry.getValue()); + } + } + + private void refreshSiteProtectionConstraint(String siteId) { + if (StringUtils.isEmpty(siteId)) { + return; + } + EmsFaultProtectionPlan query = new EmsFaultProtectionPlan(); + query.setSiteId(siteId); + List sitePlans = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(query); + writeSiteProtectionConstraint(siteId, sitePlans); + } + + private void writeSiteProtectionConstraint(String siteId, List sitePlans) { + List runningPlans = new ArrayList<>(); + for (EmsFaultProtectionPlan plan : sitePlans) { + if (Objects.equals(plan.getStatus(), ProtPlanStatus.RUNNING.getCode())) { + runningPlans.add(plan); + } + } + + String key = RedisKeyConstants.PROTECTION_CONSTRAINT + siteId; + if (runningPlans.isEmpty()) { + redisCache.deleteObject(key); + return; + } + + ProtectionConstraintVo merged = ProtectionConstraintVo.empty(); + for (EmsFaultProtectionPlan runningPlan : runningPlans) { + ProtectionConstraintVo single = buildConstraintFromPlan(runningPlan); + mergeConstraint(merged, single); + } + merged.setUpdateAt(System.currentTimeMillis()); + redisCache.setCacheObject(key, merged, CONSTRAINT_TTL_SECONDS, TimeUnit.SECONDS); + } + + private ProtectionConstraintVo buildConstraintFromPlan(EmsFaultProtectionPlan plan) { + ProtectionConstraintVo vo = ProtectionConstraintVo.empty(); + int level = plan.getFaultLevel() == null ? 0 : plan.getFaultLevel(); + vo.setLevel(level); + vo.setSourcePlanIds(new ArrayList<>()); + vo.getSourcePlanIds().add(plan.getId()); + + if (level == 1) { + vo.setPowerLimitRatio(DEFAULT_L1_POWER_RATIO); + } else if (level >= 3) { + vo.setForceStop(true); + vo.setForceStandby(true); + vo.setAllowCharge(false); + vo.setAllowDischarge(false); + vo.setPowerLimitRatio(BigDecimal.ZERO); + } + + String description = StringUtils.isEmpty(plan.getDescription()) ? "" : plan.getDescription(); + BigDecimal ratioByDesc = parseDerateRatio(description); + if (ratioByDesc != null) { + vo.setPowerLimitRatio(minRatio(vo.getPowerLimitRatio(), ratioByDesc)); + } + + if (description.contains("禁止充放电")) { + vo.setAllowCharge(false); + vo.setAllowDischarge(false); + } else { + if (description.contains("禁止充电")) { + vo.setAllowCharge(false); + } + if (description.contains("禁止放电")) { + vo.setAllowDischarge(false); + } + if (description.contains("允许充电")) { + vo.setAllowCharge(true); + } + if (description.contains("允许放电")) { + vo.setAllowDischarge(true); + } + } + + if (description.contains("待机")) { + vo.setForceStandby(true); + } + if (description.contains("停机") || description.contains("切断") || level >= 3) { + vo.setForceStop(true); + vo.setForceStandby(true); + vo.setAllowCharge(false); + vo.setAllowDischarge(false); + vo.setPowerLimitRatio(BigDecimal.ZERO); + } + + // 执行保护配置优先于描述文本配置 + 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; + } + Matcher m = Pattern.compile("降功率\\s*(\\d+(?:\\.\\d+)?)%") + .matcher(text); + if (!m.find()) { + return null; + } + BigDecimal percent = new BigDecimal(m.group(1)); + if (percent.compareTo(BigDecimal.ZERO) < 0) { + return null; + } + return percent.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP); + } + + private void mergeConstraint(ProtectionConstraintVo merged, ProtectionConstraintVo incoming) { + if (incoming == null) { + return; + } + merged.setLevel(Math.max(nullSafeInt(merged.getLevel()), nullSafeInt(incoming.getLevel()))); + merged.setAllowCharge(boolAnd(merged.getAllowCharge(), incoming.getAllowCharge())); + merged.setAllowDischarge(boolAnd(merged.getAllowDischarge(), incoming.getAllowDischarge())); + merged.setForceStandby(boolOr(merged.getForceStandby(), incoming.getForceStandby())); + merged.setForceStop(boolOr(merged.getForceStop(), incoming.getForceStop())); + merged.setPowerLimitRatio(minRatio(merged.getPowerLimitRatio(), incoming.getPowerLimitRatio())); + + if (incoming.getSourcePlanIds() != null && !incoming.getSourcePlanIds().isEmpty()) { + if (merged.getSourcePlanIds() == null) { + merged.setSourcePlanIds(new ArrayList<>()); + } + merged.getSourcePlanIds().addAll(incoming.getSourcePlanIds()); + } + } + + private BigDecimal minRatio(BigDecimal a, BigDecimal b) { + BigDecimal left = a == null ? BigDecimal.ONE : a; + BigDecimal right = b == null ? BigDecimal.ONE : b; + return left.min(right); + } + + private int nullSafeInt(Integer value) { + return value == null ? 0 : value; + } + + private Boolean boolAnd(Boolean a, Boolean b) { + boolean left = a == null || a; + boolean right = b == null || b; + return left && right; + } + + private Boolean boolOr(Boolean a, Boolean b) { + boolean left = a != null && a; + boolean right = b != null && b; + return left || right; + } + + private boolean checkIsNeedCancelPlan(List protSettings, String siteId) { StringBuilder conditionSb = new StringBuilder(); for (int i = 0; i < protSettings.size(); i++) { ProtectionSettingVo vo = protSettings.get(i); String deviceId = vo.getDeviceId(); String point = vo.getPoint(); - releaseValue = vo.getFaultValue(); - if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || releaseValue == null - || StringUtils.isEmpty(vo.getReleaseOperator())){ + BigDecimal releaseValue = vo.getReleaseValue(); + if (StringUtils.isEmpty(deviceId) + || StringUtils.isEmpty(point) + || releaseValue == null + || StringUtils.isEmpty(vo.getReleaseOperator())) { return false; } - // 获取点位最新值 BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId); - logger.info("checkIsNeedCancelPlan 点位:{},最新值:{},比较方式:{},释放值:{}", point, lastPointValue, vo.getReleaseOperator(), releaseValue); - if(lastPointValue == null){ + if (lastPointValue == null) { return false; } - // 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系) conditionSb.append(lastPointValue).append(vo.getReleaseOperator()).append(releaseValue); if (i < protSettings.size() - 1) { - String relation = vo.getRelationNext(); - conditionSb.append(" ").append(relation).append(" "); + conditionSb.append(" ").append(vo.getRelationNext()).append(" "); } - } - // 执行比较语句 return executeWithParser(conditionSb.toString()); } - // 校验故障值是否需要下发方案 private boolean checkIsNeedIssuedPlan(List protSettings, String siteId) { - BigDecimal faultValue = BigDecimal.ZERO; - StringBuilder conditionSb = new StringBuilder(); for (int i = 0; i < protSettings.size(); i++) { ProtectionSettingVo vo = protSettings.get(i); String deviceId = vo.getDeviceId(); String point = vo.getPoint(); - faultValue = vo.getFaultValue(); - if(StringUtils.isEmpty(deviceId) || StringUtils.isEmpty(point) || faultValue == null - || StringUtils.isEmpty(vo.getFaultOperator())){ + BigDecimal faultValue = vo.getFaultValue(); + if (StringUtils.isEmpty(deviceId) + || StringUtils.isEmpty(point) + || faultValue == null + || StringUtils.isEmpty(vo.getFaultOperator())) { return false; } - // 获取点位最新值 BigDecimal lastPointValue = getPointLastValue(deviceId, point, siteId); - logger.info("checkIsNeedIssuedPlan 点位:{},最新值:{},比较方式:{},故障值:{}", point, lastPointValue, vo.getFaultOperator(), faultValue); - if(lastPointValue == null){ + if (lastPointValue == null) { return false; } - // 拼接校验语句-最新值+比较方式+故障值+与下一点位关系(最后一个条件后不加关系) conditionSb.append(lastPointValue).append(vo.getFaultOperator()).append(faultValue); if (i < protSettings.size() - 1) { - String relation = vo.getRelationNext(); - conditionSb.append(" ").append(relation).append(" "); + conditionSb.append(" ").append(vo.getRelationNext()).append(" "); } - } - // 执行比较语句 return executeWithParser(conditionSb.toString()); } private BigDecimal getPointLastValue(String deviceId, String point, String siteId) { JSONObject mqttJson = redisCache.getCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId); - if(mqttJson == null){ + if (mqttJson == null) { return null; } String jsonData = mqttJson.get("Data").toString(); - if(StringUtils.isEmpty(jsonData)){ + if (StringUtils.isEmpty(jsonData)) { return null; } Map obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference>() {}); return StringUtils.getBigDecimal(obj.get(point)); } - // 更新站点策略为启用 - private void updateStrategyRunningStatus(String siteId, String status) { - // 获取是否有正在运行的策略,如果有则不更改 - EmsStrategyRunning query = new EmsStrategyRunning(); - query.setSiteId(siteId); - query.setStatus(StrategyStatus.RUNNING.getCode().equals(status) ? StrategyStatus.SUSPENDED.getCode() : StrategyStatus.RUNNING.getCode()); - List strategyRunningList = emsStrategyRunningMapper.selectEmsStrategyRunningList(query); - if (CollectionUtils.isNotEmpty(strategyRunningList)) { - // 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用 - strategyRunningList.forEach(emsStrategyRunning -> { - emsStrategyRunning.setStatus(status); - emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning); - }); - } - } - - // 更新站点策略为启用 - private void updateStrategyRunning(String siteId) { - // 获取是否有正在运行的策略,如果有则不更改 - EmsStrategyRunning emsStrategyRunning = emsStrategyRunningMapper.getRunningStrategy(siteId); - if (emsStrategyRunning == null) { - // 获取已存在并且状态为:未启用和已暂停的最晚一条策略,更新为已启用 - emsStrategyRunning = emsStrategyRunningMapper.getPendingStrategy(siteId); - emsStrategyRunning.setStatus(StrategyStatus.RUNNING.getCode()); - emsStrategyRunningMapper.updateEmsStrategyRunning(emsStrategyRunning); - } - } - - private EmsAlarmRecords addAlarmRecord(String siteId, String content,String level) { + private EmsAlarmRecords addAlarmRecord(String siteId, String content, String level) { EmsAlarmRecords emsAlarmRecords = new EmsAlarmRecords(); emsAlarmRecords.setSiteId(siteId); emsAlarmRecords.setAlarmContent(content); @@ -395,29 +532,31 @@ public class ProtectionPlanTask { return emsAlarmRecords; } - // 故障等级-告警等级匹配 private String getAlarmLevel(Integer faultLevel) { if (ObjectUtils.isEmpty(faultLevel) || faultLevel < 1 || faultLevel > 3) { - logger.warn("非法故障等级:{},默认返回普通告警", faultLevel); + logger.warn("非法故障等级:{},默认返回紧急告警", faultLevel); return AlarmLevelStatus.EMERGENCY.getCode(); } switch (faultLevel) { - case 1: return AlarmLevelStatus.GENERAL.getCode(); - case 2: return AlarmLevelStatus.SERIOUS.getCode(); - case 3: return AlarmLevelStatus.EMERGENCY.getCode(); + case 1: + return AlarmLevelStatus.GENERAL.getCode(); + case 2: + return AlarmLevelStatus.SERIOUS.getCode(); + case 3: + return AlarmLevelStatus.EMERGENCY.getCode(); default: - logger.error("未匹配的故障等级:{}", faultLevel); return AlarmLevelStatus.EMERGENCY.getCode(); } } - // 自定义表达式解析器(仅支持简单运算符和逻辑关系) + /** + * 自定义表达式解析器(仅支持简单运算符和逻辑关系) + */ public boolean executeWithParser(String conditionStr) { if (conditionStr == null || conditionStr.isEmpty()) { return false; } - // 1. 拆分逻辑关系(提取 && 或 ||) List logicRelations = new ArrayList<>(); Pattern logicPattern = Pattern.compile("(&&|\\|\\|)"); Matcher logicMatcher = logicPattern.matcher(conditionStr); @@ -425,10 +564,7 @@ public class ProtectionPlanTask { logicRelations.add(logicMatcher.group()); } - // 2. 拆分原子条件(如 "3.55>3.52") String[] atomicConditions = logicPattern.split(conditionStr); - - // 3. 解析每个原子条件并计算结果 List atomicResults = new ArrayList<>(); Pattern conditionPattern = Pattern.compile("(\\d+\\.?\\d*)\\s*([><]=?|==)\\s*(\\d+\\.?\\d*)"); for (String atomic : atomicConditions) { @@ -437,11 +573,10 @@ public class ProtectionPlanTask { logger.error("无效的原子条件:{}", atomic); return false; } - double left = Double.parseDouble(matcher.group(1)); // 左值(最新值) - String operator = matcher.group(2); // 运算符 - double right = Double.parseDouble(matcher.group(3)); // 右值(故障值) + double left = Double.parseDouble(matcher.group(1)); + String operator = matcher.group(2); + double right = Double.parseDouble(matcher.group(3)); - // 执行比较 boolean result; switch (operator) { case ">": @@ -466,11 +601,10 @@ public class ProtectionPlanTask { atomicResults.add(result); } - // 4. 组合原子结果(根据逻辑关系) boolean finalResult = atomicResults.get(0); for (int i = 0; i < logicRelations.size(); i++) { String relation = logicRelations.get(i); - boolean nextResult = atomicResults.get(i+1); + boolean nextResult = atomicResults.get(i + 1); if ("&&".equals(relation)) { finalResult = finalResult && nextResult; } else if ("||".equals(relation)) { diff --git a/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java b/ems-quartz/src/main/java/com/xzzn/quartz/task/StrategyPoller.java index be613db..c9b1f56 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 @@ -2,11 +2,16 @@ package com.xzzn.quartz.task; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; +import com.xzzn.common.constant.RedisKeyConstants; import com.xzzn.common.core.modbus.ModbusProcessor; import com.xzzn.common.core.modbus.domain.DeviceConfig; import com.xzzn.common.core.modbus.domain.WriteTagConfig; +import com.xzzn.common.core.redis.RedisCache; +import com.xzzn.common.enums.BusinessStatus; +import com.xzzn.common.enums.BusinessType; import com.xzzn.common.enums.ChargeStatus; import com.xzzn.common.enums.DeviceCategory; +import com.xzzn.common.enums.OperatorType; import com.xzzn.common.enums.SiteDevice; import com.xzzn.common.enums.SocLimit; import com.xzzn.common.enums.WorkStatus; @@ -16,26 +21,33 @@ import com.xzzn.ems.domain.EmsAmmeterData; import com.xzzn.ems.domain.EmsBatteryStack; import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsPcsSetting; +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; import com.xzzn.ems.domain.EmsStrategyLog; import com.xzzn.ems.domain.EmsStrategyTemp; import com.xzzn.ems.domain.EmsStrategyTimeConfig; +import com.xzzn.ems.domain.vo.ProtectionConstraintVo; import com.xzzn.ems.domain.vo.StrategyRunningVo; import com.xzzn.ems.mapper.EmsAmmeterDataMapper; import com.xzzn.ems.mapper.EmsBatteryStackMapper; import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsPcsSettingMapper; import com.xzzn.ems.mapper.EmsStrategyLogMapper; +import com.xzzn.ems.mapper.EmsStrategyRuntimeConfigMapper; import com.xzzn.ems.mapper.EmsStrategyRunningMapper; import com.xzzn.ems.mapper.EmsStrategyTempMapper; import com.xzzn.ems.mapper.EmsStrategyTimeConfigMapper; +import com.xzzn.system.domain.SysOperLog; +import com.xzzn.system.service.ISysOperLogService; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -57,16 +69,34 @@ public class StrategyPoller { private static final ConcurrentHashMap strategyLocks = new ConcurrentHashMap<>(); // SOC 上下限值,默认为0%-100% - private static final BigDecimal SOC_DOWN = new BigDecimal(0); - private static final BigDecimal SOC_UP = new BigDecimal(100); + private static final BigDecimal DEFAULT_SOC_DOWN = BigDecimal.ZERO; + private static final BigDecimal DEFAULT_SOC_UP = new BigDecimal(100); // 逆变器功率下限值,默认为30kW - private static final BigDecimal ANTI_REVERSE_THRESHOLD = new BigDecimal(30); + private static final BigDecimal DEFAULT_ANTI_REVERSE_THRESHOLD = new BigDecimal(30); // 逆变器下限值范围,默认为20% - private static final BigDecimal ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20); + private static final BigDecimal DEFAULT_ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20); // 逆变器功率上限值,默认为100kW - private static final BigDecimal ANTI_REVERSE_UP = new BigDecimal(100); + private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal(100); // PCS功率降幅,默认为10% - private static final BigDecimal ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10); + private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10); + // 电网有功功率低于20kW时,强制待机 + private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal(20); + // 设定功率倍率,默认10 + private static final BigDecimal DEFAULT_POWER_SET_MULTIPLIER = new BigDecimal(10); + // 保护介入默认开启 + private static final Integer DEFAULT_PROTECT_INTERVENE_ENABLE = 1; + // 一级保护默认降额50% + private static final BigDecimal DEFAULT_PROTECT_L1_DERATE_PERCENT = new BigDecimal("50"); + // 保护约束失效保护时长(秒) + private static final Integer DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS = 5; + // 三级保护默认锁存开启 + private static final Integer DEFAULT_PROTECT_L3_LATCH_ENABLE = 1; + // 保护冲突策略默认值 + private static final String DEFAULT_PROTECT_CONFLICT_POLICY = "MAX_LEVEL_WIN"; + // 保护约束默认功率比例 + private static final BigDecimal DEFAULT_PROTECTION_RATIO = BigDecimal.ONE; + // 除法精度,避免BigDecimal除不尽异常 + private static final int POWER_SCALE = 4; @Autowired private EmsStrategyRunningMapper emsStrategyRunningMapper; @@ -85,7 +115,13 @@ public class StrategyPoller { @Autowired private EmsStrategyLogMapper emsStrategyLogMapper; @Autowired + private EmsStrategyRuntimeConfigMapper runtimeConfigMapper; + @Autowired + private RedisCache redisCache; + @Autowired private ModbusProcessor modbusProcessor; + @Autowired + private ISysOperLogService operLogService; @Resource(name = "modbusExecutor") private ExecutorService modbusExecutor; @@ -132,6 +168,7 @@ public class StrategyPoller { } private void dealStrategyCurveData(Long strategyId, String siteId) { + EmsStrategyRuntimeConfig runtimeConfig = getRuntimeConfig(siteId); // 1.获取当前策略的所有模板 List> temps = emsStrategyTempMapper.getTempNameList(strategyId, siteId); if (CollectionUtils.isEmpty(temps)) { @@ -174,24 +211,53 @@ public class StrategyPoller { continue; } // 判断SOC上下限 - if (isSocInRange(emsStrategyTemp)) { - BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower().divide(new BigDecimal(pcsDeviceList.size())); + if (isSocInRange(emsStrategyTemp, runtimeConfig)) { + ProtectionConstraintVo protectionConstraint = getProtectionConstraint(siteId); + Map pcsSettingCache = new HashMap<>(); + BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower() + .divide(new BigDecimal(pcsDeviceList.size()), POWER_SCALE, RoundingMode.HALF_UP); + BigDecimal totalActivePower = null; + if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { + // 同一站点同一轮执行只读取一次电网电表,降低重复查库和数据抖动 + EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name()); + if (emsAmmeterData != null) { + totalActivePower = emsAmmeterData.getTotalActivePower(); + } + } for (EmsDevicesSetting pcsDevice : pcsDeviceList) { - EmsPcsSetting pcsSetting = emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(pcsDevice.getId()); + EmsPcsSetting pcsSetting = pcsSettingCache.computeIfAbsent( + pcsDevice.getId(), + id -> emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(id) + ); if (pcsSetting == null || pcsSetting.getClusterNum() < 1) { logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId()); continue; } - // 功率默认放大10倍,平均功率值,根据电池簇数量进行平均分配 - avgChargeDischargePower = avgChargeDischargePower.multiply(new BigDecimal(10)).divide(new BigDecimal(pcsSetting.getClusterNum())); + // 平均功率值根据倍率放大后,再按电池簇数量平均分配 + BigDecimal strategyPower = avgChargeDischargePower.multiply(runtimeConfig.getPowerSetMultiplier()) + .divide(new BigDecimal(pcsSetting.getClusterNum()), POWER_SCALE, RoundingMode.HALF_UP); // 根据充电状态,处理数据 if (ChargeStatus.CHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { + StrategyCommandDecision decision = applyProtectionConstraint( + strategyPower, + ChargeStatus.CHARGING, + runtimeConfig, + protectionConstraint + ); // 发送Modbus命令控制设备-充电 - sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.CHARGING, avgChargeDischargePower, emsStrategyTemp, false, null); + sendModbusCommand( + Collections.singletonList(pcsDevice), + pcsSetting, + decision.getChargeStatus(), + decision.getPower(), + emsStrategyTemp, + false, + null + ); } else if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) { boolean needAntiReverseFlow = false; Integer powerDownType = null; - BigDecimal chargeDischargePower = avgChargeDischargePower; + BigDecimal chargeDischargePower = strategyPower; // 查询策略运行日志 EmsStrategyLog lastStrategyLog = getLastStrategyLog(pcsDevice.getDeviceId(), emsStrategyTemp); if (lastStrategyLog != null) { @@ -204,39 +270,69 @@ public class StrategyPoller { } // 查询电网电表的正向有功功率,36kW-50kW范围内,稳定运行,低于36kW,降功率,高于50kW,增加功率 - EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name()); - if (emsAmmeterData == null || emsAmmeterData.getTotalActivePower() == null) { - logger.info("当前站点: {}, 未获取到最新电表数据", emsStrategyTemp.getSiteId()); + if (totalActivePower == null) { + logger.warn("当前站点: {}, 未获取到最新电表数据,执行保守策略并切换待机", emsStrategyTemp.getSiteId()); + sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, true, 0); + continue; } else { + // 电网功率过低,直接待机,不再放电 + if (totalActivePower.compareTo(runtimeConfig.getAntiReverseHardStopThreshold()) < 0) { + sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, true, 0); + continue; + } + // 放电开始先按差值限幅:差值=电网功率-防逆流阈值 + BigDecimal diffPower = totalActivePower.subtract(runtimeConfig.getAntiReverseThreshold()); + BigDecimal targetPower = diffPower.compareTo(BigDecimal.ZERO) > 0 ? diffPower : BigDecimal.ZERO; + if (targetPower.compareTo(strategyPower) > 0) { + targetPower = strategyPower; + } + if (chargeDischargePower.compareTo(targetPower) > 0) { + chargeDischargePower = targetPower; + } // 判断是否需要防逆流 - needAntiReverseFlow = isNeedAntiReverseFlow(emsAmmeterData.getTotalActivePower()); - BigDecimal power = avgChargeDischargePower.multiply(ANTI_REVERSE_POWER_DOWN_PERCENT).divide(new BigDecimal(100)); + needAntiReverseFlow = isNeedAntiReverseFlow(totalActivePower, runtimeConfig); + BigDecimal power = strategyPower.multiply(runtimeConfig.getAntiReversePowerDownPercent()) + .divide(new BigDecimal(100), POWER_SCALE, RoundingMode.HALF_UP); if (needAntiReverseFlow) { // 降功率 chargeDischargePower = chargeDischargePower.subtract(power); powerDownType = 0; } else { // 判断是否需要增加功率, - if (powerDownType != null && emsAmmeterData.getTotalActivePower().compareTo(ANTI_REVERSE_UP) > 0) { - if (chargeDischargePower.compareTo(avgChargeDischargePower) == 0) { - // 功率增加到平均值则停止 + if (powerDownType != null && totalActivePower.compareTo(runtimeConfig.getAntiReverseUp()) > 0) { + if (chargeDischargePower.compareTo(targetPower) >= 0) { + // 功率增加到限幅值则停止 continue; } // 增加功率 chargeDischargePower = chargeDischargePower.add(power); + if (chargeDischargePower.compareTo(targetPower) > 0) { + chargeDischargePower = targetPower; + } powerDownType = 1; needAntiReverseFlow = true; } } } - if (BigDecimal.ZERO.compareTo(chargeDischargePower) == 0) { + if (chargeDischargePower.compareTo(BigDecimal.ZERO) < 0) { + chargeDischargePower = BigDecimal.ZERO; + } + StrategyCommandDecision decision = applyProtectionConstraint( + chargeDischargePower, + ChargeStatus.DISCHARGING, + runtimeConfig, + protectionConstraint + ); + ChargeStatus finalStatus = decision.getChargeStatus(); + BigDecimal finalPower = decision.getPower(); + if (ChargeStatus.STANDBY.equals(finalStatus) || BigDecimal.ZERO.compareTo(finalPower) == 0) { // 如果已经降功率到0,则设备直接待机 // 发送Modbus命令控制设备-待机 sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.STANDBY, BigDecimal.ZERO, emsStrategyTemp, needAntiReverseFlow, powerDownType); } else { // 发送Modbus命令控制设备-放电 - sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, ChargeStatus.DISCHARGING, chargeDischargePower, emsStrategyTemp, needAntiReverseFlow, powerDownType); + sendModbusCommand(Collections.singletonList(pcsDevice), pcsSetting, finalStatus, finalPower, emsStrategyTemp, needAntiReverseFlow, powerDownType); } } else { // 发送Modbus命令控制设备-待机 @@ -297,11 +393,11 @@ public class StrategyPoller { return emsStrategyLogMapper.getLastStrategyLog(query); } - private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower) { + private boolean isNeedAntiReverseFlow(BigDecimal totalActivePower, EmsStrategyRuntimeConfig runtimeConfig) { // 获取当前设定的防逆流阈值(30kW) - BigDecimal threshold = ANTI_REVERSE_THRESHOLD; + BigDecimal threshold = runtimeConfig.getAntiReverseThreshold(); // 计算20%范围的上限(36kW) - BigDecimal upperLimit = threshold.multiply(ANTI_REVERSE_RANGE_PERCENT).divide(new BigDecimal(100)).add(threshold); + BigDecimal upperLimit = threshold.multiply(runtimeConfig.getAntiReverseRangePercent()).divide(new BigDecimal(100)).add(threshold); // 判断电网电表正向有功功率是否小于36kW(接近30kW的20%范围) return totalActivePower.compareTo(upperLimit) < 0; @@ -409,8 +505,9 @@ public class StrategyPoller { continue; } else { // 充、放电,则先开机设备 - switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL); - continue; + if (!switchDevice(pcsDevice, pcsSetting, WorkStatus.NORMAL)) { + continue; + } } } @@ -421,8 +518,10 @@ public class StrategyPoller { boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig); if (!result) { logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, chargeStatus.getInfo()); + recordDeviceOperationLog(siteId, deviceId, "写功率", chargeDischargePower, false, chargeStatus.getInfo() + "功率下发失败"); continue; } else { + recordDeviceOperationLog(siteId, deviceId, "写功率", chargeDischargePower, true, null); if (ChargeStatus.STANDBY.equals(chargeStatus)) { // 待机,先写功率值,再关机 if (!switchDevice(pcsDevice, pcsSetting, WorkStatus.STOP)) { @@ -440,18 +539,147 @@ public class StrategyPoller { private boolean switchDevice(EmsDevicesSetting pcsDevice, EmsPcsSetting pcsSetting, WorkStatus workStatus) { String siteId = pcsDevice.getSiteId(); String deviceId = pcsDevice.getDeviceId(); + String originalWorkStatus = pcsDevice.getWorkStatus(); pcsDevice.setWorkStatus(workStatus.getCode()); DeviceConfig deviceConfig = getDeviceConfig(siteId, deviceId, pcsDevice, pcsSetting , null, 1); if (deviceConfig == null) { + pcsDevice.setWorkStatus(originalWorkStatus); return false; } boolean result = modbusProcessor.writeDataToDeviceWithRetry(deviceConfig); if (!result) { - logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceConfig, workStatus.getInfo()); + pcsDevice.setWorkStatus(originalWorkStatus); + logger.info("当前站点: {}, PCS设备: {} modbus控制设备{}指令发送失败", siteId, deviceId, workStatus.getInfo()); + recordDeviceOperationLog(siteId, deviceId, "开关机", workStatus.getInfo(), false, workStatus.getInfo() + "指令发送失败"); + } else { + recordDeviceOperationLog(siteId, deviceId, "开关机", workStatus.getInfo(), true, null); } return result; } + private void recordDeviceOperationLog(String siteId, String deviceId, String action, Object param, boolean success, String errorMsg) { + try { + SysOperLog operLog = new SysOperLog(); + operLog.setTitle("策略设备控制-" + action); + operLog.setBusinessType(BusinessType.UPDATE.ordinal()); + operLog.setMethod(this.getClass().getName() + "." + action); + operLog.setRequestMethod("SCHEDULE"); + operLog.setOperatorType(OperatorType.OTHER.ordinal()); + operLog.setOperName("system"); + operLog.setOperIp("127.0.0.1"); + operLog.setOperUrl("/quartz/strategyPoller"); + operLog.setOperTime(DateUtils.getNowDate()); + Map operParam = new HashMap<>(); + operParam.put("siteId", siteId); + operParam.put("deviceId", deviceId); + operParam.put("action", action); + operParam.put("param", param); + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(operParam), 0, 2000)); + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(Collections.singletonMap("success", success)), 0, 2000)); + operLog.setStatus(success ? BusinessStatus.SUCCESS.ordinal() : BusinessStatus.FAIL.ordinal()); + if (!success) { + operLog.setErrorMsg(StringUtils.substring(errorMsg, 0, 2000)); + } + operLogService.insertOperlog(operLog); + } catch (Exception e) { + logger.error("记录sys_oper_log失败, siteId={}, deviceId={}, action={}", siteId, deviceId, action, e); + } + } + + private ProtectionConstraintVo getProtectionConstraint(String siteId) { + ProtectionConstraintVo constraint = redisCache.getCacheObject(RedisKeyConstants.PROTECTION_CONSTRAINT + siteId); + if (constraint == null) { + return ProtectionConstraintVo.empty(); + } + if (constraint.getPowerLimitRatio() == null) { + constraint.setPowerLimitRatio(DEFAULT_PROTECTION_RATIO); + } + if (constraint.getAllowCharge() == null) { + constraint.setAllowCharge(true); + } + if (constraint.getAllowDischarge() == null) { + constraint.setAllowDischarge(true); + } + if (constraint.getForceStandby() == null) { + constraint.setForceStandby(false); + } + if (constraint.getForceStop() == null) { + constraint.setForceStop(false); + } + if (constraint.getLevel() == null) { + constraint.setLevel(0); + } + return constraint; + } + + private StrategyCommandDecision applyProtectionConstraint(BigDecimal targetPower, + ChargeStatus targetStatus, + EmsStrategyRuntimeConfig runtimeConfig, + ProtectionConstraintVo constraint) { + if (constraint == null || nullSafeInt(constraint.getLevel()) <= 0) { + return new StrategyCommandDecision(targetStatus, safePower(targetPower)); + } + if (Boolean.TRUE.equals(constraint.getForceStop()) || Boolean.TRUE.equals(constraint.getForceStandby())) { + return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO); + } + if (ChargeStatus.CHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowCharge())) { + return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO); + } + if (ChargeStatus.DISCHARGING.equals(targetStatus) && Boolean.FALSE.equals(constraint.getAllowDischarge())) { + return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO); + } + BigDecimal ratio = getPowerLimitRatio(constraint, runtimeConfig); + BigDecimal finalPower = safePower(targetPower).multiply(ratio).setScale(POWER_SCALE, RoundingMode.HALF_UP); + if (finalPower.compareTo(BigDecimal.ZERO) <= 0) { + return new StrategyCommandDecision(ChargeStatus.STANDBY, BigDecimal.ZERO); + } + return new StrategyCommandDecision(targetStatus, finalPower); + } + + private BigDecimal getPowerLimitRatio(ProtectionConstraintVo constraint, EmsStrategyRuntimeConfig runtimeConfig) { + BigDecimal ratio = constraint.getPowerLimitRatio(); + if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) { + ratio = DEFAULT_PROTECTION_RATIO; + } + if (nullSafeInt(constraint.getLevel()) == 1 && DEFAULT_PROTECTION_RATIO.compareTo(ratio) == 0) { + BigDecimal deratePercent = runtimeConfig.getProtectL1DeratePercent(); + if (deratePercent == null || deratePercent.compareTo(BigDecimal.ZERO) < 0 || deratePercent.compareTo(new BigDecimal("100")) > 0) { + deratePercent = DEFAULT_PROTECT_L1_DERATE_PERCENT; + } + ratio = deratePercent.divide(new BigDecimal("100"), POWER_SCALE, RoundingMode.HALF_UP); + } + return ratio; + } + + private BigDecimal safePower(BigDecimal power) { + if (power == null || power.compareTo(BigDecimal.ZERO) < 0) { + return BigDecimal.ZERO; + } + return power; + } + + private int nullSafeInt(Integer value) { + return value == null ? 0 : value; + } + + private static class StrategyCommandDecision { + private final ChargeStatus chargeStatus; + private final BigDecimal power; + + private StrategyCommandDecision(ChargeStatus chargeStatus, BigDecimal power) { + this.chargeStatus = chargeStatus; + this.power = power; + } + + public ChargeStatus getChargeStatus() { + return chargeStatus; + } + + public BigDecimal getPower() { + return power; + } + } + // 判断当前时间是否在时间范围内 private static boolean isTimeInRange(LocalTime now, Date startTime, Date endTime) { ZoneId zoneId = ZoneId.of("Asia/Shanghai"); @@ -461,13 +689,17 @@ public class StrategyPoller { LocalTime endLocalTime = endTime.toInstant() .atZone(zoneId) .toLocalTime(); - return now.equals(startLocalTime) || (now.isAfter(startLocalTime) && now.isBefore(endLocalTime)); + // 支持跨天时段,如23:00-01:00;边界采用闭区间 + if (!startLocalTime.isAfter(endLocalTime)) { + return !now.isBefore(startLocalTime) && !now.isAfter(endLocalTime); + } + return !now.isBefore(startLocalTime) || !now.isAfter(endLocalTime); } // 判断SOC上限和下限 - private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp) { - BigDecimal socDown = SOC_DOWN; - BigDecimal socUp = SOC_UP; + private boolean isSocInRange(EmsStrategyTemp emsStrategyTemp, EmsStrategyRuntimeConfig runtimeConfig) { + BigDecimal socDown = runtimeConfig.getSocDown(); + BigDecimal socUp = runtimeConfig.getSocUp(); if (SocLimit.ON.getCode().equals(emsStrategyTemp.getSdcLimit())) { socDown = emsStrategyTemp.getSdcDown(); socUp = emsStrategyTemp.getSdcUp(); @@ -491,4 +723,54 @@ public class StrategyPoller { return true; } + private EmsStrategyRuntimeConfig getRuntimeConfig(String siteId) { + EmsStrategyRuntimeConfig config = runtimeConfigMapper.selectBySiteId(siteId); + if (config == null) { + config = new EmsStrategyRuntimeConfig(); + config.setSiteId(siteId); + } + if (config.getSocDown() == null) { + config.setSocDown(DEFAULT_SOC_DOWN); + } + if (config.getSocUp() == null) { + config.setSocUp(DEFAULT_SOC_UP); + } + if (config.getAntiReverseThreshold() == null) { + config.setAntiReverseThreshold(DEFAULT_ANTI_REVERSE_THRESHOLD); + } + if (config.getAntiReverseRangePercent() == null) { + config.setAntiReverseRangePercent(DEFAULT_ANTI_REVERSE_RANGE_PERCENT); + } + if (config.getAntiReverseUp() == null) { + config.setAntiReverseUp(DEFAULT_ANTI_REVERSE_UP); + } + if (config.getAntiReversePowerDownPercent() == null) { + config.setAntiReversePowerDownPercent(DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT); + } + if (config.getAntiReverseHardStopThreshold() == null) { + config.setAntiReverseHardStopThreshold(DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD); + } + if (config.getPowerSetMultiplier() == null || config.getPowerSetMultiplier().compareTo(BigDecimal.ZERO) <= 0) { + config.setPowerSetMultiplier(DEFAULT_POWER_SET_MULTIPLIER); + } + if (config.getProtectInterveneEnable() == null) { + config.setProtectInterveneEnable(DEFAULT_PROTECT_INTERVENE_ENABLE); + } + if (config.getProtectL1DeratePercent() == null + || config.getProtectL1DeratePercent().compareTo(BigDecimal.ZERO) < 0 + || config.getProtectL1DeratePercent().compareTo(new BigDecimal("100")) > 0) { + config.setProtectL1DeratePercent(DEFAULT_PROTECT_L1_DERATE_PERCENT); + } + if (config.getProtectRecoveryStableSeconds() == null || config.getProtectRecoveryStableSeconds() < 0) { + config.setProtectRecoveryStableSeconds(DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS); + } + if (config.getProtectL3LatchEnable() == null) { + config.setProtectL3LatchEnable(DEFAULT_PROTECT_L3_LATCH_ENABLE); + } + if (StringUtils.isEmpty(config.getProtectConflictPolicy())) { + config.setProtectConflictPolicy(DEFAULT_PROTECT_CONFLICT_POLICY); + } + return config; + } + } diff --git a/ems-system/pom.xml b/ems-system/pom.xml index 7f2b1e1..305a0f8 100644 --- a/ems-system/pom.xml +++ b/ems-system/pom.xml @@ -27,6 +27,12 @@ jaxb-runtime + + org.influxdb + influxdb-java + 2.24 + + - \ No newline at end of file + diff --git a/ems-system/src/main/java/com/xzzn/ems/config/WeatherApiProperties.java b/ems-system/src/main/java/com/xzzn/ems/config/WeatherApiProperties.java new file mode 100644 index 0000000..6d0a79c --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/config/WeatherApiProperties.java @@ -0,0 +1,61 @@ +package com.xzzn.ems.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "weather.api") +public class WeatherApiProperties { + + /** + * 是否启用天气同步 + */ + private boolean enabled = false; + + /** + * 天气接口基础地址 + */ + private String baseUrl = "https://archive-api.open-meteo.com/v1/archive"; + + /** + * 可选接口鉴权字段 + */ + private String apiKey; + + /** + * 时区参数 + */ + private String timezone = "Asia/Shanghai"; + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } + + public String getApiKey() { + return apiKey; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsDailyChargeData.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsDailyChargeData.java index 05e5898..bea8886 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsDailyChargeData.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsDailyChargeData.java @@ -25,10 +25,6 @@ public class EmsDailyChargeData extends BaseEntity @Excel(name = "站点id") private String siteId; - /** 设备唯一标识符 */ - @Excel(name = "设备唯一标识符") - private String deviceId; - /** 数据日期:yyyy-MM-dd */ @JsonFormat(pattern = "yyyy-MM-dd") @Excel(name = "数据日期:yyyy-MM-dd", width = 30, dateFormat = "yyyy-MM-dd") @@ -50,6 +46,14 @@ public class EmsDailyChargeData extends BaseEntity @Excel(name = "当日放电量") private BigDecimal dischargeData; + /** 总收入 */ + @Excel(name = "总收入") + private BigDecimal totalRevenue; + + /** 当日实时收入 */ + @Excel(name = "当日实时收入") + private BigDecimal dayRevenue; + public void setId(Long id) { this.id = id; @@ -70,16 +74,6 @@ public class EmsDailyChargeData extends BaseEntity return siteId; } - public void setDeviceId(String deviceId) - { - this.deviceId = deviceId; - } - - public String getDeviceId() - { - return deviceId; - } - public void setDateTime(Date dateTime) { this.dateTime = dateTime; @@ -130,17 +124,38 @@ public class EmsDailyChargeData extends BaseEntity return dischargeData; } + public void setTotalRevenue(BigDecimal totalRevenue) + { + this.totalRevenue = totalRevenue; + } + + public BigDecimal getTotalRevenue() + { + return totalRevenue; + } + + public void setDayRevenue(BigDecimal dayRevenue) + { + this.dayRevenue = dayRevenue; + } + + public BigDecimal getDayRevenue() + { + return dayRevenue; + } + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("id", getId()) .append("siteId", getSiteId()) - .append("deviceId", getDeviceId()) .append("dateTime", getDateTime()) .append("totalChargeData", getTotalChargeData()) .append("totalDischargeData", getTotalDischargeData()) .append("chargeData", getChargeData()) .append("dischargeData", getDischargeData()) + .append("totalRevenue", getTotalRevenue()) + .append("dayRevenue", getDayRevenue()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsDailyEnergyData.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsDailyEnergyData.java index 91f1630..cf3d897 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsDailyEnergyData.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsDailyEnergyData.java @@ -30,13 +30,9 @@ public class EmsDailyEnergyData extends BaseEntity @Excel(name = "数据日期:yyyy-MM-dd", width = 30, dateFormat = "yyyy-MM-dd") private Date dataDate; - /** 总收入 */ - @Excel(name = "总收入") - private BigDecimal totalRevenue; - - /** 当日实时收入 */ - @Excel(name = "当日实时收入") - private BigDecimal dayRevenue; + /** 数据小时(0-23) */ + @Excel(name = "数据小时(0-23)") + private Integer dataHour; /** 尖峰时段充电差值 */ @Excel(name = "尖峰时段充电差值") @@ -71,6 +67,7 @@ public class EmsDailyEnergyData extends BaseEntity private BigDecimal valleyDischargeDiff; /** 差值计算时间(如2025-10-10 23:59:00) */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @Excel(name = "差值计算时间", readConverterExp = "如=2025-10-10,2=3:59:00") private Date calcTime; @@ -104,24 +101,14 @@ public class EmsDailyEnergyData extends BaseEntity return dataDate; } - public void setTotalRevenue(BigDecimal totalRevenue) + public void setDataHour(Integer dataHour) { - this.totalRevenue = totalRevenue; + this.dataHour = dataHour; } - public BigDecimal getTotalRevenue() + public Integer getDataHour() { - return totalRevenue; - } - - public void setDayRevenue(BigDecimal dayRevenue) - { - this.dayRevenue = dayRevenue; - } - - public BigDecimal getDayRevenue() - { - return dayRevenue; + return dataHour; } public void setPeakChargeDiff(BigDecimal peakChargeDiff) @@ -220,8 +207,7 @@ public class EmsDailyEnergyData extends BaseEntity .append("id", getId()) .append("siteId", getSiteId()) .append("dataDate", getDataDate()) - .append("totalRevenue", getTotalRevenue()) - .append("dayRevenue", getDayRevenue()) + .append("dataHour", getDataHour()) .append("peakChargeDiff", getPeakChargeDiff()) .append("peakDischargeDiff", getPeakDischargeDiff()) .append("highChargeDiff", getHighChargeDiff()) diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsSetting.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsSetting.java index 2327f61..7b71d6a 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsSetting.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPcsSetting.java @@ -49,6 +49,10 @@ public class EmsPcsSetting extends BaseEntity @Excel(name = "关机目标功率") private BigDecimal stopPower; + /** 目标功率倍率 */ + @Excel(name = "目标功率倍率") + private BigDecimal powerMultiplier; + /** 电池簇数 */ @Excel(name = "电池簇数") private Integer clusterNum; @@ -135,6 +139,16 @@ public class EmsPcsSetting extends BaseEntity return stopPower; } + public BigDecimal getPowerMultiplier() + { + return powerMultiplier; + } + + public void setPowerMultiplier(BigDecimal powerMultiplier) + { + this.powerMultiplier = powerMultiplier; + } + public void setClusterNum(Integer clusterNum) { this.clusterNum = clusterNum; @@ -165,6 +179,7 @@ public class EmsPcsSetting extends BaseEntity .append("stopCommand", getStopCommand()) .append("startPower", getStartPower()) .append("stopPower", getStopPower()) + .append("powerMultiplier", getPowerMultiplier()) .append("clusterNum", getClusterNum()) .append("clusterPointAddress", getClusterPointAddress()) .append("createBy", getCreateBy()) diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointCalcConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointCalcConfig.java new file mode 100644 index 0000000..7c628bd --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointCalcConfig.java @@ -0,0 +1,128 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.annotation.Excel; +import com.xzzn.common.core.domain.BaseEntity; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class EmsPointCalcConfig extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + + @Excel(name = "点位ID") + private String pointId; + + @Excel(name = "站点ID") + private String siteId; + + @Excel(name = "设备类型") + private String deviceCategory; + + @Excel(name = "点位名称") + private String pointName; + + @Excel(name = "数据键") + private String dataKey; + + @Excel(name = "点位描述") + private String pointDesc; + + @Excel(name = "单位") + private String dataUnit; + + @Excel(name = "计算表达式") + private String calcExpression; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + public String getDeviceCategory() { + return deviceCategory; + } + + public void setDeviceCategory(String deviceCategory) { + this.deviceCategory = deviceCategory; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } + + public String getPointDesc() { + return pointDesc; + } + + public void setPointDesc(String pointDesc) { + this.pointDesc = pointDesc; + } + + public String getDataUnit() { + return dataUnit; + } + + public void setDataUnit(String dataUnit) { + this.dataUnit = dataUnit; + } + + public String getCalcExpression() { + return calcExpression; + } + + public void setCalcExpression(String calcExpression) { + this.calcExpression = calcExpression; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("pointId", getPointId()) + .append("siteId", getSiteId()) + .append("deviceCategory", getDeviceCategory()) + .append("pointName", getPointName()) + .append("dataKey", getDataKey()) + .append("pointDesc", getPointDesc()) + .append("dataUnit", getDataUnit()) + .append("calcExpression", getCalcExpression()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java new file mode 100644 index 0000000..2f2040a --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsPointConfig.java @@ -0,0 +1,301 @@ +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_point_config + */ +public class EmsPointConfig 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 = "设备ID") + private String deviceId; + + @Excel(name = "点位名称") + private String pointName; + + @Excel(name = "数据键") + private String dataKey; + + @Excel(name = "点位描述") + private String pointDesc; + + @Excel(name = "寄存器地址") + private String registerAddress; + + @Excel(name = "单位") + private String dataUnit; + + @Excel(name = "A系数") + private BigDecimal dataA; + + @Excel(name = "K系数") + private BigDecimal dataK; + + @Excel(name = "B系数") + private BigDecimal dataB; + + @Excel(name = "位偏移") + private Integer dataBit; + + @Excel(name = "是否报警点位", readConverterExp = "0=否,1=是") + private Integer isAlarm; + + @Excel(name = "点位类型") + private String pointType; + + @Excel(name = "计算表达式") + private String calcExpression; + + @Excel(name = "是否启用采集", readConverterExp = "0=否,1=是") + private Integer collectEnabled; + + @Excel(name = "采集来源") + private String collectSource; + + @Excel(name = "Modbus寄存器类型") + private String modbusRegisterType; + + @Excel(name = "Modbus数据类型") + private String modbusDataType; + + @Excel(name = "Modbus读取顺序") + private Integer modbusReadOrder; + + @Excel(name = "Modbus分组") + private String modbusGroup; + + 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 getDeviceCategory() { + return deviceCategory; + } + + public void setDeviceCategory(String deviceCategory) { + this.deviceCategory = deviceCategory; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + 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 getRegisterAddress() { + return registerAddress; + } + + public void setRegisterAddress(String registerAddress) { + this.registerAddress = registerAddress; + } + + public String getDataUnit() { + return dataUnit; + } + + public void setDataUnit(String dataUnit) { + this.dataUnit = dataUnit; + } + + public BigDecimal getDataA() { + return dataA; + } + + public void setDataA(BigDecimal dataA) { + this.dataA = dataA; + } + + public BigDecimal getDataK() { + return dataK; + } + + public void setDataK(BigDecimal dataK) { + this.dataK = dataK; + } + + public BigDecimal getDataB() { + return dataB; + } + + public void setDataB(BigDecimal dataB) { + this.dataB = dataB; + } + + public Integer getDataBit() { + return dataBit; + } + + public void setDataBit(Integer dataBit) { + this.dataBit = dataBit; + } + + public Integer getIsAlarm() { + return isAlarm; + } + + public void setIsAlarm(Integer isAlarm) { + this.isAlarm = isAlarm; + } + + public String getPointType() { + return pointType; + } + + public void setPointType(String pointType) { + this.pointType = pointType; + } + + public String getCalcExpression() { + return calcExpression; + } + + public void setCalcExpression(String calcExpression) { + this.calcExpression = calcExpression; + } + + public Integer getCollectEnabled() { + return collectEnabled; + } + + public void setCollectEnabled(Integer collectEnabled) { + this.collectEnabled = collectEnabled; + } + + public String getCollectSource() { + return collectSource; + } + + public void setCollectSource(String collectSource) { + this.collectSource = collectSource; + } + + public String getModbusRegisterType() { + return modbusRegisterType; + } + + public void setModbusRegisterType(String modbusRegisterType) { + this.modbusRegisterType = modbusRegisterType; + } + + public String getModbusDataType() { + return modbusDataType; + } + + public void setModbusDataType(String modbusDataType) { + this.modbusDataType = modbusDataType; + } + + public Integer getModbusReadOrder() { + return modbusReadOrder; + } + + public void setModbusReadOrder(Integer modbusReadOrder) { + this.modbusReadOrder = modbusReadOrder; + } + + public String getModbusGroup() { + return modbusGroup; + } + + public void setModbusGroup(String modbusGroup) { + this.modbusGroup = modbusGroup; + } + + @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("deviceId", getDeviceId()) + .append("pointName", getPointName()) + .append("dataKey", getDataKey()) + .append("pointDesc", getPointDesc()) + .append("registerAddress", getRegisterAddress()) + .append("dataUnit", getDataUnit()) + .append("dataA", getDataA()) + .append("dataK", getDataK()) + .append("dataB", getDataB()) + .append("dataBit", getDataBit()) + .append("isAlarm", getIsAlarm()) + .append("pointType", getPointType()) + .append("calcExpression", getCalcExpression()) + .append("collectEnabled", getCollectEnabled()) + .append("collectSource", getCollectSource()) + .append("modbusRegisterType", getModbusRegisterType()) + .append("modbusDataType", getModbusDataType()) + .append("modbusReadOrder", getModbusReadOrder()) + .append("modbusGroup", getModbusGroup()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorData.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorData.java new file mode 100644 index 0000000..684b117 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorData.java @@ -0,0 +1,58 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.core.domain.BaseEntity; + +import java.util.Date; + +/** + * 单站监控展示数据对象(按分组落到 ems_site_monitor_data_home/sbjk/tjbb) + */ +public class EmsSiteMonitorData extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + private String siteId; + private String fieldCode; + private String fieldValue; + private Date valueTime; + + 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 String getFieldCode() { + return fieldCode; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public Date getValueTime() { + return valueTime; + } + + public void setValueTime(Date valueTime) { + this.valueTime = valueTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorItem.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorItem.java new file mode 100644 index 0000000..84c8857 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorItem.java @@ -0,0 +1,101 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.core.domain.BaseEntity; + +/** + * 单站监控配置项定义对象 ems_site_monitor_item + */ +public class EmsSiteMonitorItem extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + private String moduleCode; + private String moduleName; + private String menuCode; + private String menuName; + private String sectionName; + private String fieldCode; + private String fieldName; + private Integer sortNo; + private Integer status; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getModuleCode() { + return moduleCode; + } + + public void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode; + } + + public String getModuleName() { + return moduleName; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public String getMenuCode() { + return menuCode; + } + + public void setMenuCode(String menuCode) { + this.menuCode = menuCode; + } + + public String getMenuName() { + return menuName; + } + + public void setMenuName(String menuName) { + this.menuName = menuName; + } + + public String getSectionName() { + return sectionName; + } + + public void setSectionName(String sectionName) { + this.sectionName = sectionName; + } + + public String getFieldCode() { + return fieldCode; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public Integer getSortNo() { + return sortNo; + } + + public void setSortNo(Integer sortNo) { + this.sortNo = sortNo; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java new file mode 100644 index 0000000..4b86b6b --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteMonitorPointMatch.java @@ -0,0 +1,74 @@ +package com.xzzn.ems.domain; + +import com.xzzn.common.core.domain.BaseEntity; + +/** + * 单站监控字段点位映射对象 ems_site_monitor_point_match + */ +public class EmsSiteMonitorPointMatch extends BaseEntity { + private static final long serialVersionUID = 1L; + + private Long id; + private String siteId; + private String fieldCode; + private String deviceId; + private String dataPoint; + private String fixedDataPoint; + private Integer useFixedDisplay; + + 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 String getFieldCode() { + return fieldCode; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getDataPoint() { + return dataPoint; + } + + public void setDataPoint(String dataPoint) { + this.dataPoint = dataPoint; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getFixedDataPoint() { + return fixedDataPoint; + } + + public void setFixedDataPoint(String fixedDataPoint) { + this.fixedDataPoint = fixedDataPoint; + } + + public Integer getUseFixedDisplay() { + return useFixedDisplay; + } + + public void setUseFixedDisplay(Integer useFixedDisplay) { + this.useFixedDisplay = useFixedDisplay; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteSetting.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteSetting.java index 9ef28ee..189b531 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteSetting.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsSiteSetting.java @@ -25,6 +25,10 @@ public class EmsSiteSetting extends BaseEntity @Excel(name = "站点名称") private String siteName; + /** 站点简称 */ + @Excel(name = "站点简称") + private String siteShortName; + /** 站点地址 */ @Excel(name = "站点地址") private String siteAddress; @@ -54,6 +58,9 @@ public class EmsSiteSetting extends BaseEntity @Excel(name = "站点id") private String siteId; + /** 授权状态:true-已授权,false-未授权 */ + private Boolean authorized; + public void setId(Long id) { this.id = id; @@ -74,6 +81,16 @@ public class EmsSiteSetting extends BaseEntity return siteName; } + public void setSiteShortName(String siteShortName) + { + this.siteShortName = siteShortName; + } + + public String getSiteShortName() + { + return siteShortName; + } + public void setSiteAddress(String siteAddress) { this.siteAddress = siteAddress; @@ -144,11 +161,22 @@ public class EmsSiteSetting extends BaseEntity return siteId; } + public void setAuthorized(Boolean authorized) + { + this.authorized = authorized; + } + + public Boolean getAuthorized() + { + return authorized; + } + @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("id", getId()) .append("siteName", getSiteName()) + .append("siteShortName", getSiteShortName()) .append("siteAddress", getSiteAddress()) .append("runningTime", getRunningTime()) .append("latitude", getLatitude()) @@ -161,6 +189,7 @@ public class EmsSiteSetting extends BaseEntity .append("createTime", getCreateTime()) .append("updateTime", getUpdateTime()) .append("siteId", getSiteId()) + .append("authorized", getAuthorized()) .toString(); } } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyRuntimeConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyRuntimeConfig.java new file mode 100644 index 0000000..1391e26 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyRuntimeConfig.java @@ -0,0 +1,221 @@ +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; + /** 设定功率倍率 */ + @Excel(name = "设定功率倍率") + private BigDecimal powerSetMultiplier; + + /** 保护介入开关(1-启用,0-禁用) */ + @Excel(name = "保护介入开关") + private Integer protectInterveneEnable; + + /** 一级保护降额比例(%) */ + @Excel(name = "一级保护降额比例(%)") + private BigDecimal protectL1DeratePercent; + + /** 保护释放稳定时长(秒) */ + @Excel(name = "保护释放稳定时长(秒)") + private Integer protectRecoveryStableSeconds; + + /** 三级保护锁存开关(1-启用,0-禁用) */ + @Excel(name = "三级保护锁存开关") + private Integer protectL3LatchEnable; + + /** 保护冲突策略 */ + @Excel(name = "保护冲突策略") + private String protectConflictPolicy; + + public Long getId() { + 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; + } + + public BigDecimal getPowerSetMultiplier() { + return powerSetMultiplier; + } + + public void setPowerSetMultiplier(BigDecimal powerSetMultiplier) { + this.powerSetMultiplier = powerSetMultiplier; + } + + public Integer getProtectInterveneEnable() { + return protectInterveneEnable; + } + + public void setProtectInterveneEnable(Integer protectInterveneEnable) { + this.protectInterveneEnable = protectInterveneEnable; + } + + public BigDecimal getProtectL1DeratePercent() { + return protectL1DeratePercent; + } + + public void setProtectL1DeratePercent(BigDecimal protectL1DeratePercent) { + this.protectL1DeratePercent = protectL1DeratePercent; + } + + public Integer getProtectRecoveryStableSeconds() { + return protectRecoveryStableSeconds; + } + + public void setProtectRecoveryStableSeconds(Integer protectRecoveryStableSeconds) { + this.protectRecoveryStableSeconds = protectRecoveryStableSeconds; + } + + public Integer getProtectL3LatchEnable() { + return protectL3LatchEnable; + } + + public void setProtectL3LatchEnable(Integer protectL3LatchEnable) { + this.protectL3LatchEnable = protectL3LatchEnable; + } + + public String getProtectConflictPolicy() { + return protectConflictPolicy; + } + + public void setProtectConflictPolicy(String protectConflictPolicy) { + this.protectConflictPolicy = protectConflictPolicy; + } + + @Override + 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("powerSetMultiplier", getPowerSetMultiplier()) + .append("protectInterveneEnable", getProtectInterveneEnable()) + .append("protectL1DeratePercent", getProtectL1DeratePercent()) + .append("protectRecoveryStableSeconds", getProtectRecoveryStableSeconds()) + .append("protectL3LatchEnable", getProtectL3LatchEnable()) + .append("protectConflictPolicy", getProtectConflictPolicy()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyTempTimeConfig.java b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyTempTimeConfig.java index 1cf9f9f..746d511 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyTempTimeConfig.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/EmsStrategyTempTimeConfig.java @@ -35,6 +35,14 @@ public class EmsStrategyTempTimeConfig extends BaseEntity @Excel(name = "充放功率 (kW)") private BigDecimal chargeDischargePower; + /** SDC下限 (%) */ + @Excel(name = "SDC下限 (%)") + private BigDecimal sdcDown; + + /** SDC上限 (%) */ + @Excel(name = "SDC上限 (%)") + private BigDecimal sdcUp; + /** 充电状态,如“1-充电”、“2-待机” */ @Excel(name = "充电状态,如“1-充电”、“2-待机”") private String chargeStatus; @@ -83,6 +91,26 @@ public class EmsStrategyTempTimeConfig extends BaseEntity return chargeDischargePower; } + public void setSdcDown(BigDecimal sdcDown) + { + this.sdcDown = sdcDown; + } + + public BigDecimal getSdcDown() + { + return sdcDown; + } + + public void setSdcUp(BigDecimal sdcUp) + { + this.sdcUp = sdcUp; + } + + public BigDecimal getSdcUp() + { + return sdcUp; + } + public void setChargeStatus(String chargeStatus) { this.chargeStatus = chargeStatus; @@ -110,6 +138,8 @@ public class EmsStrategyTempTimeConfig extends BaseEntity .append("startTime", getStartTime()) .append("endTime", getEndTime()) .append("chargeDischargePower", getChargeDischargePower()) + .append("sdcDown", getSdcDown()) + .append("sdcUp", getSdcUp()) .append("chargeStatus", getChargeStatus()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/AmmeterRevenueStatisListVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/AmmeterRevenueStatisListVo.java index c0acd13..4f9dd7e 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/AmmeterRevenueStatisListVo.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/AmmeterRevenueStatisListVo.java @@ -11,6 +11,15 @@ public class AmmeterRevenueStatisListVo { /** 类别 */ private String dataTime; + /** 是否工作日:1-工作日 0-节假日 */ + private Integer isWorkday; + + /** 日期类型 */ + private String dayType; + + /** 天气情况 */ + private String weatherDesc; + /** 组合有功-总 */ private BigDecimal activeTotalPrice = BigDecimal.ZERO; @@ -52,6 +61,30 @@ public class AmmeterRevenueStatisListVo { this.dataTime = dataTime; } + public Integer getIsWorkday() { + return isWorkday; + } + + public void setIsWorkday(Integer isWorkday) { + this.isWorkday = isWorkday; + } + + public String getDayType() { + return dayType; + } + + public void setDayType(String dayType) { + this.dayType = dayType; + } + + public String getWeatherDesc() { + return weatherDesc; + } + + public void setWeatherDesc(String weatherDesc) { + this.weatherDesc = weatherDesc; + } + public BigDecimal getActiveTotalPrice() { return activeTotalPrice; } 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/DevicePointDataList.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/DevicePointDataList.java index 3d9d2f0..29bcb25 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/DevicePointDataList.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/DevicePointDataList.java @@ -24,11 +24,25 @@ public class DevicePointDataList private BigDecimal avgValue; // 差值(max - min) private BigDecimal diffValue; + // 第一四分位数 + private BigDecimal q1; + // 中位数 + private BigDecimal median; + // 第三四分位数 + private BigDecimal q3; public DevicePointDataList(String deviceId, List pointValueList,String parentDeviceId, BigDecimal maxValue, BigDecimal minValue, BigDecimal avgValue, BigDecimal diffValue, String maxDate, String minDate) { + this(deviceId, pointValueList, parentDeviceId, maxValue, minValue, avgValue, diffValue, maxDate, minDate, null, null, null); + } + + public DevicePointDataList(String deviceId, List pointValueList,String parentDeviceId, + BigDecimal maxValue, BigDecimal minValue, + BigDecimal avgValue, BigDecimal diffValue, + String maxDate, String minDate, + BigDecimal q1, BigDecimal median, BigDecimal q3) { this.deviceId = deviceId; this.pointValueList = pointValueList; this.parentDeviceId = parentDeviceId; @@ -38,6 +52,9 @@ public class DevicePointDataList this.diffValue = diffValue; this.maxDate = maxDate; this.minDate = minDate; + this.q1 = q1; + this.median = median; + this.q3 = q3; } public DevicePointDataList(String deviceId, String parentDeviceId, List pointValueList) { @@ -121,4 +138,28 @@ public class DevicePointDataList public void setDiffValue(BigDecimal diffValue) { this.diffValue = diffValue; } + + public BigDecimal getQ1() { + return q1; + } + + public void setQ1(BigDecimal q1) { + this.q1 = q1; + } + + public BigDecimal getMedian() { + return median; + } + + public void setMedian(BigDecimal median) { + this.median = median; + } + + public BigDecimal getQ3() { + return q3; + } + + public void setQ3(BigDecimal q3) { + this.q3 = q3; + } } diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryPointOptionVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryPointOptionVo.java new file mode 100644 index 0000000..87abe84 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/GeneralQueryPointOptionVo.java @@ -0,0 +1,40 @@ +package com.xzzn.ems.domain.vo; + +public class GeneralQueryPointOptionVo { + private String pointId; + private String pointName; + private String dataKey; + private String pointDesc; + + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + + public String getPointName() { + return pointName; + } + + public void setPointName(String pointName) { + this.pointName = pointName; + } + + 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; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/ImportPointTemplateRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ImportPointTemplateRequest.java new file mode 100644 index 0000000..1ca6a31 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/ImportPointTemplateRequest.java @@ -0,0 +1,32 @@ +package com.xzzn.ems.domain.vo; + +import javax.validation.constraints.NotBlank; + +/** + * 按站点导入点位模板请求参数 + */ +public class ImportPointTemplateRequest { + + /** 目标站点ID */ + @NotBlank(message = "站点ID不能为空") + private String siteId; + + /** 是否覆盖目标站点已有点位配置 */ + private Boolean overwrite; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public Boolean getOverwrite() { + return overwrite; + } + + public void setOverwrite(Boolean overwrite) { + this.overwrite = overwrite; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java new file mode 100644 index 0000000..fc15700 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveRequest.java @@ -0,0 +1,76 @@ +package com.xzzn.ems.domain.vo; + +public class PointConfigCurveRequest { + private String siteId; + private String deviceId; + private String pointId; + private String dataKey; + private String pointType; + private String rangeType; + private String startTime; + private String endTime; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } + + public String getPointType() { + return pointType; + } + + public void setPointType(String pointType) { + this.pointType = pointType; + } + + public String getRangeType() { + return rangeType; + } + + public void setRangeType(String rangeType) { + this.rangeType = rangeType; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveValueVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveValueVo.java new file mode 100644 index 0000000..0ba51db --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigCurveValueVo.java @@ -0,0 +1,24 @@ +package com.xzzn.ems.domain.vo; + +import java.util.Date; + +public class PointConfigCurveValueVo { + private Date dataTime; + private Object pointValue; + + public Date getDataTime() { + return dataTime; + } + + public void setDataTime(Date dataTime) { + this.dataTime = dataTime; + } + + public Object getPointValue() { + return pointValue; + } + + public void setPointValue(Object pointValue) { + this.pointValue = pointValue; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigGenerateRecentRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigGenerateRecentRequest.java new file mode 100644 index 0000000..1bf20d1 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigGenerateRecentRequest.java @@ -0,0 +1,38 @@ +package com.xzzn.ems.domain.vo; + +import javax.validation.constraints.NotBlank; + +public class PointConfigGenerateRecentRequest { + + @NotBlank(message = "站点ID不能为空") + private String siteId; + + @NotBlank(message = "点位ID不能为空") + private String pointId; + + private String deviceId; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueItemVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueItemVo.java new file mode 100644 index 0000000..af5fded --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueItemVo.java @@ -0,0 +1,40 @@ +package com.xzzn.ems.domain.vo; + +public class PointConfigLatestValueItemVo { + private String siteId; + private String pointId; + private String deviceId; + private String dataKey; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueRequest.java new file mode 100644 index 0000000..37eb506 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueRequest.java @@ -0,0 +1,15 @@ +package com.xzzn.ems.domain.vo; + +import java.util.List; + +public class PointConfigLatestValueRequest { + private List points; + + public List getPoints() { + return points; + } + + public void setPoints(List points) { + this.points = points; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueVo.java new file mode 100644 index 0000000..af322c3 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointConfigLatestValueVo.java @@ -0,0 +1,60 @@ +package com.xzzn.ems.domain.vo; + +import java.util.Date; + +public class PointConfigLatestValueVo { + private String siteId; + private String pointId; + private String deviceId; + private String dataKey; + private Object pointValue; + private Date dataTime; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDataKey() { + return dataKey; + } + + public void setDataKey(String dataKey) { + this.dataKey = dataKey; + } + + public Object getPointValue() { + return pointValue; + } + + public void setPointValue(Object pointValue) { + this.pointValue = pointValue; + } + + public Date getDataTime() { + return dataTime; + } + + public void setDataTime(Date dataTime) { + this.dataTime = dataTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointNameRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointNameRequest.java index 8552c37..c1f13d5 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointNameRequest.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/PointNameRequest.java @@ -14,6 +14,9 @@ public class PointNameRequest { private String deviceCategory; private String pointName; + private List pointNames; + private String pointId; + private List pointIds; /** 数据分组 1-分钟 2-小时 3-天 */ private int dataUnit; @@ -50,6 +53,30 @@ public class PointNameRequest { this.pointName = pointName; } + public List getPointNames() { + return pointNames; + } + + public void setPointNames(List pointNames) { + this.pointNames = pointNames; + } + + public String getPointId() { + return pointId; + } + + public void setPointId(String pointId) { + this.pointId = pointId; + } + + public List getPointIds() { + return pointIds; + } + + public void setPointIds(List pointIds) { + this.pointIds = pointIds; + } + public int getDataUnit() { return dataUnit; } 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/domain/vo/RunningGraphRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/RunningGraphRequest.java index a144147..e90f6b5 100644 --- a/ems-system/src/main/java/com/xzzn/ems/domain/vo/RunningGraphRequest.java +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/RunningGraphRequest.java @@ -1,6 +1,7 @@ package com.xzzn.ems.domain.vo; import com.fasterxml.jackson.annotation.JsonFormat; +import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; @@ -10,10 +11,12 @@ import java.util.Date; public class RunningGraphRequest { /** 开始时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd") private Date startDate; /** 结束时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd") @JsonFormat(pattern = "yyyy-MM-dd") private Date endDate; diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveItemVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveItemVo.java new file mode 100644 index 0000000..b71d607 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveItemVo.java @@ -0,0 +1,33 @@ +package com.xzzn.ems.domain.vo; + +import java.util.Date; + +public class SiteMonitorDataSaveItemVo { + private String fieldCode; + private String fieldValue; + private Date valueTime; + + public String getFieldCode() { + return fieldCode; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public Date getValueTime() { + return valueTime; + } + + public void setValueTime(Date valueTime) { + this.valueTime = valueTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveRequest.java new file mode 100644 index 0000000..252b4ed --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorDataSaveRequest.java @@ -0,0 +1,24 @@ +package com.xzzn.ems.domain.vo; + +import java.util.List; + +public class SiteMonitorDataSaveRequest { + private String siteId; + private List items; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectDisplayVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectDisplayVo.java new file mode 100644 index 0000000..d1dd6a1 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectDisplayVo.java @@ -0,0 +1,27 @@ +package com.xzzn.ems.domain.vo; + +import java.util.Date; + +/** + * 单站监控展示数据(配置项 + 当前值) + */ +public class SiteMonitorProjectDisplayVo extends SiteMonitorProjectPointMappingVo { + private String fieldValue; + private Date valueTime; + + public String getFieldValue() { + return fieldValue; + } + + public void setFieldValue(String fieldValue) { + this.fieldValue = fieldValue; + } + + public Date getValueTime() { + return valueTime; + } + + public void setValueTime(Date valueTime) { + this.valueTime = valueTime; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java new file mode 100644 index 0000000..4dc6369 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingSaveRequest.java @@ -0,0 +1,36 @@ +package com.xzzn.ems.domain.vo; + +import java.util.List; + +public class SiteMonitorProjectPointMappingSaveRequest { + + private String siteId; + + private List mappings; + + private List deletedFieldCodes; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public List getMappings() { + return mappings; + } + + public void setMappings(List mappings) { + this.mappings = mappings; + } + + public List getDeletedFieldCodes() { + return deletedFieldCodes; + } + + public void setDeletedFieldCodes(List deletedFieldCodes) { + this.deletedFieldCodes = deletedFieldCodes; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java new file mode 100644 index 0000000..8594ebc --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/SiteMonitorProjectPointMappingVo.java @@ -0,0 +1,124 @@ +package com.xzzn.ems.domain.vo; + +public class SiteMonitorProjectPointMappingVo { + + private String moduleCode; + + private String moduleName; + + private String menuCode; + + private String menuName; + + private String sectionName; + + private String fieldCode; + + private String fieldName; + + private String deviceId; + + private String deviceName; + + private String dataPoint; + + private String fixedDataPoint; + + private Integer useFixedDisplay; + + public String getModuleCode() { + return moduleCode; + } + + public void setModuleCode(String moduleCode) { + this.moduleCode = moduleCode; + } + + public String getModuleName() { + return moduleName; + } + + public void setModuleName(String moduleName) { + this.moduleName = moduleName; + } + + public String getFieldCode() { + return fieldCode; + } + + public String getMenuCode() { + return menuCode; + } + + public void setMenuCode(String menuCode) { + this.menuCode = menuCode; + } + + public String getMenuName() { + return menuName; + } + + public void setMenuName(String menuName) { + this.menuName = menuName; + } + + public String getSectionName() { + return sectionName; + } + + public void setSectionName(String sectionName) { + this.sectionName = sectionName; + } + + public void setFieldCode(String fieldCode) { + this.fieldCode = fieldCode; + } + + public String getFieldName() { + return fieldName; + } + + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getDataPoint() { + return dataPoint; + } + + public void setDataPoint(String dataPoint) { + this.dataPoint = dataPoint; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getFixedDataPoint() { + return fixedDataPoint; + } + + public void setFixedDataPoint(String fixedDataPoint) { + this.fixedDataPoint = fixedDataPoint; + } + + public Integer getUseFixedDisplay() { + return useFixedDisplay; + } + + public void setUseFixedDisplay(Integer useFixedDisplay) { + this.useFixedDisplay = useFixedDisplay; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/WeatherSyncResultVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/WeatherSyncResultVo.java new file mode 100644 index 0000000..9b1da54 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/WeatherSyncResultVo.java @@ -0,0 +1,76 @@ +package com.xzzn.ems.domain.vo; + +public class WeatherSyncResultVo { + private String siteId; + private String startTime; + private String endTime; + private int totalDays; + private int successDays; + private int insertedDays; + private int updatedDays; + private String message; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public String getEndTime() { + return endTime; + } + + public void setEndTime(String endTime) { + this.endTime = endTime; + } + + public int getTotalDays() { + return totalDays; + } + + public void setTotalDays(int totalDays) { + this.totalDays = totalDays; + } + + public int getSuccessDays() { + return successDays; + } + + public void setSuccessDays(int successDays) { + this.successDays = successDays; + } + + public int getInsertedDays() { + return insertedDays; + } + + public void setInsertedDays(int insertedDays) { + this.insertedDays = insertedDays; + } + + public int getUpdatedDays() { + return updatedDays; + } + + public void setUpdatedDays(int updatedDays) { + this.updatedDays = updatedDays; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/WorkStatusEnumMappingSaveRequest.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/WorkStatusEnumMappingSaveRequest.java new file mode 100644 index 0000000..da7513a --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/WorkStatusEnumMappingSaveRequest.java @@ -0,0 +1,26 @@ +package com.xzzn.ems.domain.vo; + +import java.util.List; + +public class WorkStatusEnumMappingSaveRequest { + + private String siteId; + + private List mappings; + + public String getSiteId() { + return siteId; + } + + public void setSiteId(String siteId) { + this.siteId = siteId; + } + + public List getMappings() { + return mappings; + } + + public void setMappings(List mappings) { + this.mappings = mappings; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/domain/vo/WorkStatusEnumMappingVo.java b/ems-system/src/main/java/com/xzzn/ems/domain/vo/WorkStatusEnumMappingVo.java new file mode 100644 index 0000000..4ebc081 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/domain/vo/WorkStatusEnumMappingVo.java @@ -0,0 +1,74 @@ +package com.xzzn.ems.domain.vo; + +public class WorkStatusEnumMappingVo { + + private String deviceCategory; + + private String matchField; + + private String matchFieldName; + + private String enumCode; + + private String enumName; + + private String enumDesc; + + private String dataEnumCode; + + public String getEnumCode() { + return enumCode; + } + + public void setEnumCode(String enumCode) { + this.enumCode = enumCode; + } + + public String getEnumName() { + return enumName; + } + + public void setEnumName(String enumName) { + this.enumName = enumName; + } + + public String getEnumDesc() { + return enumDesc; + } + + public void setEnumDesc(String enumDesc) { + this.enumDesc = enumDesc; + } + + public String getDataEnumCode() { + return dataEnumCode; + } + + public void setDataEnumCode(String dataEnumCode) { + this.dataEnumCode = dataEnumCode; + } + + public String getDeviceCategory() { + return deviceCategory; + } + + public void setDeviceCategory(String deviceCategory) { + this.deviceCategory = deviceCategory; + } + + public String getMatchField() { + return matchField; + } + + public void setMatchField(String matchField) { + this.matchField = matchField; + } + + public String getMatchFieldName() { + return matchFieldName; + } + + public void setMatchFieldName(String matchFieldName) { + this.matchFieldName = matchFieldName; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsDailyChargeDataMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsDailyChargeDataMapper.java index 27c3864..44372b2 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsDailyChargeDataMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsDailyChargeDataMapper.java @@ -69,6 +69,17 @@ public interface EmsDailyChargeDataMapper // 插入或更新站点每日充放电数据 public void insertOrUpdateData(EmsDailyChargeData emsDailyChargeData); + // 按站点+日期(天)查询当日已存在记录 + public EmsDailyChargeData selectBySiteIdAndDateTime(@Param("siteId") String siteId, + @Param("dateTime") Date dateTime); + + // 按站点+日期(天)更新收入字段 + public int updateRevenueBySiteAndDate(@Param("siteId") String siteId, + @Param("dateTime") Date dateTime, + @Param("totalRevenue") BigDecimal totalRevenue, + @Param("dayRevenue") BigDecimal dayRevenue, + @Param("updateBy") String updateBy); + // 获取所有站点总充总放 public Map getAllSiteChargeData(@Param("nowData")String nowData, @Param("siteId")String siteId); diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsDailyEnergyDataMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsDailyEnergyDataMapper.java index 536f73d..ff7c2eb 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsDailyEnergyDataMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsDailyEnergyDataMapper.java @@ -68,6 +68,12 @@ public interface EmsDailyEnergyDataMapper // 获取站点某日电表数据 public EmsDailyEnergyData getDataByDate(@Param("siteId")String siteId,@Param("today") String today); + + // 按站点+日期+小时查询当小时已存在记录 + public EmsDailyEnergyData selectBySiteIdAndDateHour(@Param("siteId") String siteId, + @Param("dataDate") java.util.Date dataDate, + @Param("dataHour") Integer dataHour); + // 插入或更新每日尖峰平谷差值 public void insertOrUpdateData(EmsDailyEnergyData energyData); // 电表报表 diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointCalcConfigMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointCalcConfigMapper.java new file mode 100644 index 0000000..edaef0a --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointCalcConfigMapper.java @@ -0,0 +1,24 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsPointCalcConfig; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface EmsPointCalcConfigMapper { + EmsPointCalcConfig selectEmsPointCalcConfigById(Long id); + + List selectEmsPointCalcConfigList(EmsPointCalcConfig emsPointCalcConfig); + + int insertEmsPointCalcConfig(EmsPointCalcConfig emsPointCalcConfig); + + int updateEmsPointCalcConfig(EmsPointCalcConfig emsPointCalcConfig); + + int deleteEmsPointCalcConfigById(Long id); + + int deleteEmsPointCalcConfigByIds(Long[] ids); + + int countBySiteId(@Param("siteId") String siteId); + + int deleteBySiteId(@Param("siteId") String siteId); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java new file mode 100644 index 0000000..2eb97d5 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointConfigMapper.java @@ -0,0 +1,54 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.vo.GeneralQueryPointOptionVo; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface EmsPointConfigMapper { + EmsPointConfig selectEmsPointConfigById(Long id); + + List selectEmsPointConfigList(EmsPointConfig emsPointConfig); + + int insertEmsPointConfig(EmsPointConfig emsPointConfig); + + int insertBatchEmsPointConfig(@Param("list") List list); + + int updateEmsPointConfig(EmsPointConfig emsPointConfig); + + int updateEmsPointConfigForImport(EmsPointConfig emsPointConfig); + + int deleteEmsPointConfigById(Long id); + + int deleteEmsPointConfigByIds(Long[] ids); + + int countBySiteId(@Param("siteId") String siteId); + + int deleteBySiteId(@Param("siteId") String siteId); + + int copyTemplateToSite(@Param("templateSiteId") String templateSiteId, + @Param("targetSiteId") String targetSiteId, + @Param("operName") String operName); + + String getRegisterAddress(@Param("siteId") String siteId, + @Param("deviceCategory") String deviceCategory, + @Param("deviceId") String deviceId, + @Param("dataKey") String dataKey); + + List getPointNameList(@Param("siteIds") List siteIds, + @Param("deviceCategory") String deviceCategory, + @Param("deviceId") String deviceId, + @Param("pointName") String pointName); + + List getConfigListForGeneralQuery(@Param("siteIds") List siteIds, + @Param("deviceCategory") String deviceCategory, + @Param("pointIds") List pointIds, + @Param("pointNames") List pointNames, + @Param("deviceIds") List deviceIds); + + List selectBySiteIdAndPointIds(@Param("siteId") String siteId, + @Param("pointIds") List pointIds); + + List selectModbusCollectPointConfigs(@Param("siteId") String siteId); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointEnumMatchMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointEnumMatchMapper.java index f517c59..6739130 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointEnumMatchMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointEnumMatchMapper.java @@ -67,4 +67,16 @@ public interface EmsPointEnumMatchMapper public List selectList(@Param("siteId") String siteId, @Param("deviceCategory") String deviceCategory, @Param("matchField") String matchField); + + int countBySiteId(@Param("siteId") String siteId); + + int deleteBySiteId(@Param("siteId") String siteId); + + int copyTemplateToSite(@Param("templateSiteId") String templateSiteId, + @Param("targetSiteId") String targetSiteId, + @Param("operName") String operName); + + int deleteByScope(@Param("siteId") String siteId, + @Param("deviceCategory") String deviceCategory, + @Param("matchField") String matchField); } diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java index ee94d2d..2854a61 100644 --- a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsPointMatchMapper.java @@ -173,4 +173,14 @@ public interface EmsPointMatchMapper int getDevicePointAlarmNum(@Param("siteId") String siteId, @Param("deviceId") String deviceId, @Param("deviceCategory") String deviceCategory); List selectDeviceStatusPoint(@Param("request") DeviceUpdateRequest request); + + int countBySiteId(@Param("siteId") String siteId); + + int deleteBySiteId(@Param("siteId") String siteId); + + int copyTemplateToSite(@Param("templateSiteId") String templateSiteId, + @Param("targetSiteId") String targetSiteId, + @Param("operName") String operName); + + List selectBySiteId(@Param("siteId") String siteId); } diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorDataMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorDataMapper.java new file mode 100644 index 0000000..0759dfa --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorDataMapper.java @@ -0,0 +1,23 @@ +package com.xzzn.ems.mapper; + +import org.apache.ibatis.annotations.Param; + +/** + * 单站监控展示数据 Mapper + */ +public interface EmsSiteMonitorDataMapper { + String selectHistoryJsonByMinute(@Param("tableName") String tableName, + @Param("siteId") String siteId, + @Param("statisMinute") java.util.Date statisMinute); + + int upsertHistoryJsonByMinute(@Param("tableName") String tableName, + @Param("siteId") String siteId, + @Param("statisMinute") java.util.Date statisMinute, + @Param("dataJson") String dataJson, + @Param("hotSoc") String hotSoc, + @Param("hotTotalActivePower") String hotTotalActivePower, + @Param("hotTotalReactivePower") String hotTotalReactivePower, + @Param("hotDayChargedCap") String hotDayChargedCap, + @Param("hotDayDisChargedCap") String hotDayDisChargedCap, + @Param("operName") String operName); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorItemMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorItemMapper.java new file mode 100644 index 0000000..16b18c3 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorItemMapper.java @@ -0,0 +1,12 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsSiteMonitorItem; + +import java.util.List; + +/** + * 单站监控配置项定义 Mapper + */ +public interface EmsSiteMonitorItemMapper { + List selectEnabledList(); +} 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 new file mode 100644 index 0000000..b9fa099 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteMonitorPointMatchMapper.java @@ -0,0 +1,21 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsSiteMonitorPointMatch; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 单站监控字段点位映射 Mapper + */ +public interface EmsSiteMonitorPointMatchMapper { + List selectDistinctSiteIds(); + + List selectBySiteId(@Param("siteId") String siteId); + + 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/mapper/EmsSiteWeatherDayMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteWeatherDayMapper.java new file mode 100644 index 0000000..d7f16bb --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsSiteWeatherDayMapper.java @@ -0,0 +1,19 @@ +package com.xzzn.ems.mapper; + +import org.apache.ibatis.annotations.Param; + +public interface EmsSiteWeatherDayMapper { + + int updateWeatherDesc(@Param("siteId") String siteId, + @Param("calendarDate") String calendarDate, + @Param("weatherDesc") String weatherDesc, + @Param("weatherCode") Integer weatherCode); + + int selectCountBySiteAndDate(@Param("siteId") String siteId, @Param("calendarDate") String calendarDate); + + int insertSiteWeatherDay(@Param("siteId") String siteId, + @Param("calendarDate") String calendarDate, + @Param("weatherDesc") String weatherDesc, + @Param("weatherCode") Integer weatherCode, + @Param("source") String source); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyRuntimeConfigMapper.java b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyRuntimeConfigMapper.java new file mode 100644 index 0000000..41fc0f6 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/mapper/EmsStrategyRuntimeConfigMapper.java @@ -0,0 +1,36 @@ +package com.xzzn.ems.mapper; + +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; + +/** + * 策略运行参数配置Mapper接口 + * + * @author xzzn + * @date 2026-02-13 + */ +public interface EmsStrategyRuntimeConfigMapper { + + /** + * 根据站点ID查询参数配置 + * + * @param siteId 站点ID + * @return 参数配置 + */ + EmsStrategyRuntimeConfig selectBySiteId(String siteId); + + /** + * 新增参数配置 + * + * @param config 参数配置 + * @return 结果 + */ + int insert(EmsStrategyRuntimeConfig config); + + /** + * 按站点ID更新参数配置 + * + * @param config 参数配置 + * @return 结果 + */ + int updateBySiteId(EmsStrategyRuntimeConfig config); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IDDSDataProcessService.java b/ems-system/src/main/java/com/xzzn/ems/service/IDDSDataProcessService.java deleted file mode 100644 index c1ab276..0000000 --- a/ems-system/src/main/java/com/xzzn/ems/service/IDDSDataProcessService.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.xzzn.ems.service; - -public interface IDDSDataProcessService { - - public void handleDdsData(String message); - -} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsAlarmRecordsService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsAlarmRecordsService.java index ba7d36f..c44d15b 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/IEmsAlarmRecordsService.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsAlarmRecordsService.java @@ -77,6 +77,15 @@ public interface IEmsAlarmRecordsService * @return */ public String createTicketNo(Long id, Long userId); + + /** + * 关闭告警并设置为已处理 + * + * @param id 告警ID + * @param userId 用户ID + * @return 处理结果 + */ + public String closeAlarm(Long id, Long userId); // 订阅失败-增加告警 public void addSubFailedAlarmRecord(String topic); // 订阅成功-处理告警 diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsDailyChargeDataService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsDailyChargeDataService.java new file mode 100644 index 0000000..4c7199f --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsDailyChargeDataService.java @@ -0,0 +1,21 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.EmsDailyChargeData; + +import java.util.List; + +/** + * 站点每日充放电数据Service接口 + */ +public interface IEmsDailyChargeDataService { + + List selectEmsDailyChargeDataList(EmsDailyChargeData emsDailyChargeData); + + EmsDailyChargeData selectEmsDailyChargeDataById(Long id); + + int insertEmsDailyChargeData(EmsDailyChargeData emsDailyChargeData, String operName); + + int updateEmsDailyChargeData(EmsDailyChargeData emsDailyChargeData, String operName); + + int deleteEmsDailyChargeDataByIds(Long[] ids); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsDailyEnergyDataService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsDailyEnergyDataService.java new file mode 100644 index 0000000..7b78ded --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsDailyEnergyDataService.java @@ -0,0 +1,21 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.EmsDailyEnergyData; + +import java.util.List; + +/** + * 站点每日收益数据Service接口 + */ +public interface IEmsDailyEnergyDataService { + + List selectEmsDailyEnergyDataList(EmsDailyEnergyData emsDailyEnergyData); + + EmsDailyEnergyData selectEmsDailyEnergyDataById(Long id); + + int insertEmsDailyEnergyData(EmsDailyEnergyData emsDailyEnergyData, String operName); + + int updateEmsDailyEnergyData(EmsDailyEnergyData emsDailyEnergyData, String operName); + + int deleteEmsDailyEnergyDataByIds(Long[] ids); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsDeviceSettingService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsDeviceSettingService.java index 554bdee..72d8f5d 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/IEmsDeviceSettingService.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsDeviceSettingService.java @@ -6,7 +6,13 @@ import com.xzzn.ems.domain.vo.DeviceUpdateRequest; import com.xzzn.ems.domain.vo.DevicesSettingVo; import com.xzzn.ems.domain.vo.PointDataRequest; import com.xzzn.ems.domain.vo.PointQueryResponse; +import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest; +import com.xzzn.ems.domain.vo.SiteMonitorProjectDisplayVo; +import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest; +import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingVo; +import com.xzzn.ems.domain.vo.WorkStatusEnumMappingVo; +import java.util.Date; import java.util.List; import java.util.Map; @@ -36,4 +42,18 @@ public interface IEmsDeviceSettingService public List> getDeviceListBySiteAndCategory(String siteId, String deviceCategory); public boolean updateDeviceStatus(DeviceUpdateRequest request); + + public List getSiteMonitorProjectPointMapping(String siteId); + + public int saveSiteMonitorProjectPointMapping(SiteMonitorProjectPointMappingSaveRequest request, String operName); + + public List getSiteWorkStatusEnumMappings(String siteId); + + public int saveSiteWorkStatusEnumMappings(String siteId, List mappings, String operName); + + public List getSiteMonitorProjectDisplay(String siteId); + + public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName); + + public int syncSiteMonitorDataByMqtt(String siteId, String deviceId, String jsonData, Date valueTime); } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointCalcConfigService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointCalcConfigService.java new file mode 100644 index 0000000..0637ff6 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointCalcConfigService.java @@ -0,0 +1,19 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.EmsPointCalcConfig; + +import java.util.List; + +public interface IEmsPointCalcConfigService { + List selectPointCalcConfigList(EmsPointCalcConfig pointCalcConfig); + + EmsPointCalcConfig selectPointCalcConfigById(Long id); + + int insertPointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName); + + int updatePointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName); + + int deletePointCalcConfigByIds(Long[] ids); + + int deleteBySiteId(String siteId); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointConfigService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointConfigService.java new file mode 100644 index 0000000..4cad426 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointConfigService.java @@ -0,0 +1,36 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveValueVo; +import com.xzzn.ems.domain.vo.PointConfigGenerateRecentRequest; +import com.xzzn.ems.domain.vo.PointConfigLatestValueRequest; +import com.xzzn.ems.domain.vo.PointConfigLatestValueVo; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface IEmsPointConfigService { + List selectPointConfigList(EmsPointConfig pointConfig); + + EmsPointConfig selectPointConfigById(Long id); + + int insertPointConfig(EmsPointConfig pointConfig, String operName); + + int updatePointConfig(EmsPointConfig pointConfig, String operName); + + int deletePointConfigByIds(Long[] ids); + + String importTemplateBySite(ImportPointTemplateRequest request, String operName); + + String importCsvBySite(String siteId, Boolean overwrite, MultipartFile file, String operName); + + String getRegisterAddress(String siteId, String deviceCategory, String deviceId, String dataKey); + + List getLatestValues(PointConfigLatestValueRequest request); + + List getCurveData(PointConfigCurveRequest request); + + String generateRecent7DaysData(PointConfigGenerateRecentRequest request); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointMatchService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointMatchService.java index 121ec50..394ceb5 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointMatchService.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsPointMatchService.java @@ -6,6 +6,7 @@ import com.xzzn.ems.domain.EmsPointMatch; import com.xzzn.ems.domain.vo.DevicePointMatchExportVo; import com.xzzn.ems.domain.vo.DevicePointMatchVo; import com.xzzn.ems.domain.vo.ImportPointDataRequest; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; /** * 点位匹配Service接口 @@ -40,4 +41,52 @@ public interface IEmsPointMatchService * @throws Exception */ public List importDataByDevice(ImportPointDataRequest request, String operName); + + /** + * 按站点导入模板点位数据 + * @param request 请求参数 + * @param operName 操作人 + * @return 导入结果信息 + */ + public String importTemplateBySite(ImportPointTemplateRequest request, String operName); + + /** + * 查询点位配置列表 + * + * @param emsPointMatch 点位配置 + * @return 点位配置列表 + */ + public List selectPointMatchConfigList(EmsPointMatch emsPointMatch); + + /** + * 查询点位配置详情 + * + * @param id 主键ID + * @return 点位配置 + */ + public EmsPointMatch selectPointMatchById(Long id); + + /** + * 新增点位配置 + * + * @param emsPointMatch 点位配置 + * @return 结果 + */ + public int insertPointMatch(EmsPointMatch emsPointMatch); + + /** + * 修改点位配置 + * + * @param emsPointMatch 点位配置 + * @return 结果 + */ + public int updatePointMatch(EmsPointMatch emsPointMatch); + + /** + * 删除点位配置 + * + * @param ids 主键集合 + * @return 结果 + */ + public int deletePointMatchByIds(Long[] ids); } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsSiteService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsSiteService.java index 298266f..df32981 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/IEmsSiteService.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsSiteService.java @@ -32,4 +32,8 @@ public interface IEmsSiteService public List> getAllPcsInfo(String siteId); public List> getParentCategoryDeviceId(String siteId, String deviceCategory); + + int addSite(EmsSiteSetting emsSiteSetting); + + int updateSite(EmsSiteSetting emsSiteSetting); } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsStrategyRuntimeConfigService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsStrategyRuntimeConfigService.java new file mode 100644 index 0000000..5d31fae --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsStrategyRuntimeConfigService.java @@ -0,0 +1,28 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.EmsStrategyRuntimeConfig; + +/** + * 策略运行参数配置Service接口 + * + * @author xzzn + * @date 2026-02-13 + */ +public interface IEmsStrategyRuntimeConfigService { + + /** + * 按站点ID获取参数配置(不存在时返回默认值) + * + * @param siteId 站点ID + * @return 参数配置 + */ + EmsStrategyRuntimeConfig getBySiteId(String siteId); + + /** + * 保存参数配置(按siteId新增或更新) + * + * @param config 参数配置 + * @return 结果 + */ + int saveBySiteId(EmsStrategyRuntimeConfig config); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IEmsWeatherSyncService.java b/ems-system/src/main/java/com/xzzn/ems/service/IEmsWeatherSyncService.java new file mode 100644 index 0000000..0a70e6b --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/IEmsWeatherSyncService.java @@ -0,0 +1,8 @@ +package com.xzzn.ems.service; + +import com.xzzn.ems.domain.vo.StatisAmmeterDateRequest; +import com.xzzn.ems.domain.vo.WeatherSyncResultVo; + +public interface IEmsWeatherSyncService { + WeatherSyncResultVo syncWeatherByDateRange(StatisAmmeterDateRequest requestVo); +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IFXXDataProcessService.java b/ems-system/src/main/java/com/xzzn/ems/service/IFXXDataProcessService.java deleted file mode 100644 index c9610c7..0000000 --- a/ems-system/src/main/java/com/xzzn/ems/service/IFXXDataProcessService.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.xzzn.ems.service; - -public interface IFXXDataProcessService { - - public void handleFxData(String message); -} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/IGeneralQueryService.java b/ems-system/src/main/java/com/xzzn/ems/service/IGeneralQueryService.java index 325993f..c245f3d 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/IGeneralQueryService.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/IGeneralQueryService.java @@ -13,7 +13,7 @@ public interface IGeneralQueryService { // 模糊查询获取点位名称List - public List getPointNameList(PointNameRequest request); + public List getPointNameList(PointNameRequest request); // 根据条件获取点位数据变化 public List getPointValueList(PointNameRequest request); diff --git a/ems-system/src/main/java/com/xzzn/ems/service/ISingleSiteService.java b/ems-system/src/main/java/com/xzzn/ems/service/ISingleSiteService.java index 926d1b7..1ea2971 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/ISingleSiteService.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/ISingleSiteService.java @@ -13,6 +13,8 @@ public interface ISingleSiteService public SiteMonitorHomeVo getSiteMonitorDataVo(String siteId); + public SiteMonitorHomeVo getSiteMonitorTotalDataVo(String siteId); + public SiteMonitorRunningHeadInfoVo getSiteRunningHeadInfo(String siteId); diff --git a/ems-system/src/main/java/com/xzzn/ems/service/InfluxPointDataWriter.java b/ems-system/src/main/java/com/xzzn/ems/service/InfluxPointDataWriter.java new file mode 100644 index 0000000..4aed2c3 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/InfluxPointDataWriter.java @@ -0,0 +1,697 @@ +package com.xzzn.ems.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.net.HttpURLConnection; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.HashMap; +import java.util.Map; + +@Component +public class InfluxPointDataWriter { + + private static final Logger log = LoggerFactory.getLogger(InfluxPointDataWriter.class); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @Value("${influxdb.enabled:true}") + private boolean enabled; + + @Value("${influxdb.url:}") + private String url; + + @Value("${influxdb.username:}") + private String username; + + @Value("${influxdb.password:}") + private String password; + + @Value("${influxdb.api-token:}") + private String apiToken; + + @Value("${influxdb.database:ems_point_data}") + private String database; + + @Value("${influxdb.retention-policy:autogen}") + private String retentionPolicy; + + @Value("${influxdb.measurement:mqtt_point_data}") + private String measurement; + + @Value("${influxdb.write-method:POST}") + private String writeMethod; + + @Value("${influxdb.read-method:GET}") + private String readMethod; + + @Value("${influxdb.write-path:/write}") + private String writePath; + + @Value("${influxdb.query-path:/query}") + private String queryPath; + + @Value("${influxdb.org:}") + private String org; + + @Value("${influxdb.bucket:}") + private String bucket; + + @PostConstruct + public void init() { + if (!enabled) { + log.info("InfluxDB 写入已禁用"); + return; + } + if (url == null || url.trim().isEmpty()) { + log.warn("InfluxDB URL 未配置,跳过初始化"); + return; + } + log.info("InfluxDB 已启用 HTTP 接入, url: {}, database: {}", url, database); + } + + public void writeBatch(List payloads) { + if (!enabled || payloads == null || payloads.isEmpty()) { + return; + } + try { + StringBuilder body = new StringBuilder(); + for (PointWritePayload payload : payloads) { + if (payload == null || payload.getPointValue() == null) { + continue; + } + long time = payload.getDataTime() == null ? System.currentTimeMillis() : payload.getDataTime().getTime(); + body.append(measurement) + .append(",site_id=").append(escapeLineTag(payload.getSiteId())) + .append(",device_id=").append(escapeLineTag(payload.getDeviceId())) + .append(",point_key=").append(escapeLineTag(payload.getPointKey())) + .append(" value=").append(payload.getPointValue().toPlainString()) + .append(" ").append(time) + .append("\n"); + } + if (body.length() == 0) { + return; + } + String writeUrl = buildWriteUrl(); + if (isBlank(writeUrl)) { + log.warn("写入 InfluxDB 失败:v2 写入地址未构建成功,请检查 influxdb.org / influxdb.bucket 配置"); + return; + } + HttpResult result = executeRequest(methodOrDefault(writeMethod, "POST"), writeUrl, body.toString()); + if (result.code < 200 || result.code >= 300) { + if (result.code == 404 && isV2WritePath() && isOrgOrBucketMissing(result.body)) { + if (ensureV2OrgAndBucket()) { + HttpResult retryResult = executeRequest(methodOrDefault(writeMethod, "POST"), writeUrl, body.toString()); + if (retryResult.code >= 200 && retryResult.code < 300) { + log.info("InfluxDB org/bucket 自动创建成功,写入已恢复"); + return; + } + log.warn("InfluxDB 重试写入失败,HTTP状态码: {}, url: {}, body: {}", retryResult.code, writeUrl, safeLog(retryResult.body)); + return; + } + } + log.warn("写入 InfluxDB 失败,HTTP状态码: {}, url: {}, body: {}", result.code, writeUrl, safeLog(result.body)); + } + } catch (Exception e) { + log.warn("写入 InfluxDB 失败: {}", e.getMessage()); + } + } + + public List queryCurveData(String siteId, String deviceId, String pointKey, Date startTime, Date endTime) { + if (!enabled) { + return Collections.emptyList(); + } + if (isBlank(siteId) || isBlank(deviceId) || isBlank(pointKey) || startTime == null || endTime == null) { + return Collections.emptyList(); + } + + String normalizedSiteId = siteId.trim(); + String normalizedDeviceId = deviceId.trim(); + String normalizedPointKey = pointKey.trim(); + + String influxQl = String.format( + "SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"device_id\" = '%s' AND \"point_key\" = '%s' " + + "AND time >= %dms AND time <= %dms ORDER BY time ASC", + measurement, + escapeTagValue(normalizedSiteId), + escapeTagValue(normalizedDeviceId), + escapeTagValue(normalizedPointKey), + startTime.getTime(), + endTime.getTime() + ); + + try { + String queryUrl = buildQueryUrl(influxQl); + List values = parseInfluxQlResponse(executeRequestWithResponse(methodOrDefault(readMethod, "GET"), queryUrl)); + if (!values.isEmpty()) { + return values; + } + + // 兼容 dataKey 大小写差异,避免点位 key 字母大小写不一致导致查不到曲线 + String regexQuery = String.format( + "SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"device_id\" = '%s' AND \"point_key\" =~ /(?i)^%s$/ " + + "AND time >= %dms AND time <= %dms ORDER BY time ASC", + measurement, + escapeTagValue(normalizedSiteId), + escapeTagValue(normalizedDeviceId), + escapeRegex(normalizedPointKey), + startTime.getTime(), + endTime.getTime() + ); + values = parseInfluxQlResponse(executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(regexQuery))); + return values; + } catch (Exception e) { + log.warn("查询 InfluxDB 曲线失败: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + public List queryCurveDataByPointKey(String siteId, String pointKey, Date startTime, Date endTime) { + if (!enabled) { + return Collections.emptyList(); + } + if (isBlank(siteId) || isBlank(pointKey) || startTime == null || endTime == null) { + return Collections.emptyList(); + } + + String normalizedSiteId = siteId.trim(); + String normalizedPointKey = pointKey.trim(); + + String influxQl = String.format( + "SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" = '%s' " + + "AND time >= %dms AND time <= %dms ORDER BY time ASC", + measurement, + escapeTagValue(normalizedSiteId), + escapeTagValue(normalizedPointKey), + startTime.getTime(), + endTime.getTime() + ); + + try { + String queryUrl = buildQueryUrl(influxQl); + return parseInfluxQlResponse(executeRequestWithResponse(methodOrDefault(readMethod, "GET"), queryUrl)); + } catch (Exception e) { + log.warn("按 pointKey 查询 InfluxDB 曲线失败: {}", e.getMessage()); + return Collections.emptyList(); + } + } + + + public PointValue queryLatestPointValueByPointKey(String siteId, String pointKey, Date startTime, Date endTime) { + if (!enabled) { + return null; + } + if (isBlank(siteId) || isBlank(pointKey) || startTime == null || endTime == null) { + return null; + } + + String normalizedSiteId = siteId.trim(); + String normalizedPointKey = pointKey.trim(); + + String influxQl = String.format( + "SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" = '%s' " + + "AND time >= %dms AND time <= %dms ORDER BY time DESC LIMIT 1", + measurement, + escapeTagValue(normalizedSiteId), + escapeTagValue(normalizedPointKey), + startTime.getTime(), + endTime.getTime() + ); + + try { + List values = parseInfluxQlResponse( + executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(influxQl)) + ); + if (!values.isEmpty()) { + return values.get(0); + } + + String regexQuery = String.format( + "SELECT \"value\" FROM \"%s\" WHERE \"site_id\" = '%s' AND \"point_key\" =~ /(?i)^%s$/ " + + "AND time >= %dms AND time <= %dms ORDER BY time DESC LIMIT 1", + measurement, + escapeTagValue(normalizedSiteId), + escapeRegex(normalizedPointKey), + startTime.getTime(), + endTime.getTime() + ); + values = parseInfluxQlResponse( + executeRequestWithResponse(methodOrDefault(readMethod, "GET"), buildQueryUrl(regexQuery)) + ); + return values.isEmpty() ? null : values.get(0); + } catch (Exception e) { + log.warn("按 pointKey 查询 InfluxDB 最新值失败: {}", e.getMessage()); + return null; + } + } + + private String buildWriteUrl() { + if (isV2WritePath()) { + return buildV2WriteUrl(); + } + StringBuilder sb = new StringBuilder(trimTrailingSlash(url)); + sb.append(normalizePath(writePath)).append("?db=").append(urlEncode(database)); + if (!isBlank(retentionPolicy)) { + sb.append("&rp=").append(urlEncode(retentionPolicy)); + } + sb.append("&precision=ms"); + return sb.toString(); + } + + private String buildQueryUrl(String influxQl) { + String queryDb = database; + if (isV2WritePath() && !isBlank(bucket)) { + queryDb = bucket; + } + StringBuilder queryUrl = new StringBuilder(trimTrailingSlash(url)) + .append(normalizePath(queryPath)) + .append("?db=").append(urlEncode(queryDb)) + .append("&epoch=ms&q=").append(urlEncode(influxQl)); + if (!isBlank(retentionPolicy)) { + queryUrl.append("&rp=").append(urlEncode(retentionPolicy)); + } + return queryUrl.toString(); + } + + private String buildV2WriteUrl() { + String currentBucket = isBlank(bucket) ? database : bucket; + if (isBlank(org) || isBlank(currentBucket)) { + return null; + } + return trimTrailingSlash(url) + + "/api/v2/write?org=" + urlEncode(org) + + "&bucket=" + urlEncode(currentBucket) + + "&precision=ms"; + } + + private HttpResult executeRequest(String method, String requestUrl, String body) throws Exception { + HttpURLConnection connection = openConnection(method, requestUrl, "text/plain; charset=UTF-8"); + try { + if (body != null) { + connection.setDoOutput(true); + try (OutputStream os = connection.getOutputStream()) { + os.write(body.getBytes(StandardCharsets.UTF_8)); + } + } + int code = connection.getResponseCode(); + InputStream is = (code >= 200 && code < 300) ? connection.getInputStream() : connection.getErrorStream(); + return new HttpResult(code, readStream(is)); + } finally { + connection.disconnect(); + } + } + + private String executeRequestWithResponse(String method, String requestUrl) throws Exception { + HttpURLConnection connection = openConnection(method, requestUrl, "text/plain; charset=UTF-8"); + try { + int code = connection.getResponseCode(); + InputStream is = (code >= 200 && code < 300) ? connection.getInputStream() : connection.getErrorStream(); + return readStream(is); + } finally { + connection.disconnect(); + } + } + + private String readStream(InputStream is) throws Exception { + if (is == null) { + return ""; + } + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } + } + + private HttpResult executeJsonRequest(String method, String requestUrl, String jsonBody) throws Exception { + HttpURLConnection connection = openConnection(method, requestUrl, "application/json; charset=UTF-8"); + try { + if (jsonBody != null) { + connection.setDoOutput(true); + try (OutputStream os = connection.getOutputStream()) { + os.write(jsonBody.getBytes(StandardCharsets.UTF_8)); + } + } + int code = connection.getResponseCode(); + InputStream is = (code >= 200 && code < 300) ? connection.getInputStream() : connection.getErrorStream(); + return new HttpResult(code, readStream(is)); + } finally { + connection.disconnect(); + } + } + + private HttpURLConnection openConnection(String method, String requestUrl, String contentType) throws Exception { + HttpURLConnection connection = (HttpURLConnection) new java.net.URL(requestUrl).openConnection(); + connection.setRequestMethod(method); + connection.setConnectTimeout(5000); + connection.setReadTimeout(8000); + connection.setRequestProperty("Accept", "application/json"); + connection.setRequestProperty("Content-Type", contentType); + if (!isBlank(apiToken)) { + connection.setRequestProperty("Authorization", "Token " + apiToken.trim()); + } else if (!isBlank(username) || !isBlank(password)) { + String basic = java.util.Base64.getEncoder() + .encodeToString((safe(username) + ":" + safe(password)).getBytes(StandardCharsets.UTF_8)); + connection.setRequestProperty("Authorization", "Basic " + basic); + } + return connection; + } + + private String escapeLineTag(String value) { + if (value == null) { + return ""; + } + return value.replace("\\", "\\\\") + .replace(",", "\\,") + .replace(" ", "\\ ") + .replace("=", "\\="); + } + + private String trimTrailingSlash(String v) { + if (v == null) { + return ""; + } + String result = v.trim(); + while (result.endsWith("/")) { + result = result.substring(0, result.length() - 1); + } + return result; + } + + private String urlEncode(String value) { + try { + return URLEncoder.encode(safe(value), StandardCharsets.UTF_8.name()); + } catch (Exception e) { + return safe(value); + } + } + + private String safe(String value) { + return value == null ? "" : value; + } + + private boolean isV2WritePath() { + return "/api/v2/write".equals(normalizePath(writePath)); + } + + private boolean isOrgOrBucketMissing(String responseBody) { + if (isBlank(responseBody)) { + return false; + } + String lower = responseBody.toLowerCase(); + return (lower.contains("organization") && lower.contains("not found")) + || (lower.contains("bucket") && lower.contains("not found")); + } + + private boolean ensureV2OrgAndBucket() { + try { + if (isBlank(org)) { + log.warn("InfluxDB 自动创建 organization 失败:org 配置为空"); + return false; + } + String currentBucket = isBlank(bucket) ? database : bucket; + String orgId = queryOrgId(org); + if (isBlank(orgId)) { + orgId = createOrg(org); + } + if (isBlank(orgId)) { + log.warn("InfluxDB 自动创建 organization 失败,org={}", org); + return false; + } + + if (!bucketExists(org, currentBucket)) { + if (!createBucket(orgId, currentBucket)) { + log.warn("InfluxDB 自动创建 bucket 失败,org={}, bucket={}", org, currentBucket); + return false; + } + } + return true; + } catch (Exception ex) { + log.warn("InfluxDB 自动创建 org/bucket 异常: {}", ex.getMessage()); + return false; + } + } + + private String queryOrgId(String orgName) throws Exception { + String requestUrl = trimTrailingSlash(url) + "/api/v2/orgs?org=" + urlEncode(orgName); + HttpResult result = executeJsonRequest("GET", requestUrl, null); + if (result.code < 200 || result.code >= 300 || isBlank(result.body)) { + log.warn("查询 organization 失败,status={}, org={}, body={}", result.code, orgName, safeLog(result.body)); + return null; + } + JsonNode root = OBJECT_MAPPER.readTree(result.body); + JsonNode orgs = root.path("orgs"); + if (orgs.isArray() && orgs.size() > 0) { + JsonNode first = orgs.get(0); + if (first != null) { + String id = first.path("id").asText(null); + if (!isBlank(id)) { + return id; + } + } + } + return null; + } + + private String createOrg(String orgName) throws Exception { + String requestUrl = trimTrailingSlash(url) + "/api/v2/orgs"; + Map payload = new HashMap<>(); + payload.put("name", orgName); + String body = OBJECT_MAPPER.writeValueAsString(payload); + HttpResult result = executeJsonRequest("POST", requestUrl, body); + if ((result.code < 200 || result.code >= 300) || isBlank(result.body)) { + log.warn("创建 organization 失败,status={}, org={}, body={}", result.code, orgName, safeLog(result.body)); + return null; + } + JsonNode root = OBJECT_MAPPER.readTree(result.body); + String id = root.path("id").asText(null); + return isBlank(id) ? null : id; + } + + private boolean bucketExists(String orgName, String bucketName) throws Exception { + String requestUrl = trimTrailingSlash(url) + + "/api/v2/buckets?name=" + urlEncode(bucketName) + + "&org=" + urlEncode(orgName); + HttpResult result = executeJsonRequest("GET", requestUrl, null); + if (result.code < 200 || result.code >= 300 || isBlank(result.body)) { + log.warn("查询 bucket 失败,status={}, org={}, bucket={}, body={}", result.code, orgName, bucketName, safeLog(result.body)); + return false; + } + JsonNode root = OBJECT_MAPPER.readTree(result.body); + JsonNode buckets = root.path("buckets"); + return buckets.isArray() && buckets.size() > 0; + } + + private boolean createBucket(String orgId, String bucketName) throws Exception { + String requestUrl = trimTrailingSlash(url) + "/api/v2/buckets"; + Map payload = new HashMap<>(); + payload.put("orgID", orgId); + payload.put("name", bucketName); + String body = OBJECT_MAPPER.writeValueAsString(payload); + HttpResult result = executeJsonRequest("POST", requestUrl, body); + if (result.code < 200 || result.code >= 300) { + log.warn("创建 bucket 失败,status={}, orgId={}, bucket={}, body={}", result.code, orgId, bucketName, safeLog(result.body)); + } + return result.code >= 200 && result.code < 300; + } + + private String methodOrDefault(String method, String defaultMethod) { + return isBlank(method) ? defaultMethod : method.trim().toUpperCase(); + } + + private String normalizePath(String path) { + if (isBlank(path)) { + return "/"; + } + String p = path.trim(); + return p.startsWith("/") ? p : "/" + p; + } + + private String safeLog(String body) { + if (body == null) { + return ""; + } + return body.length() > 200 ? body.substring(0, 200) : body; + } + + private Date parseInfluxTime(Object timeObject) { + if (timeObject == null) { + return null; + } + if (timeObject instanceof Number) { + return new Date(((Number) timeObject).longValue()); + } + try { + return Date.from(Instant.parse(String.valueOf(timeObject))); + } catch (Exception e) { + return null; + } + } + + private BigDecimal toBigDecimal(Object valueObject) { + if (valueObject == null) { + return null; + } + if (valueObject instanceof BigDecimal) { + return (BigDecimal) valueObject; + } + if (valueObject instanceof Number) { + return new BigDecimal(valueObject.toString()); + } + try { + return new BigDecimal(String.valueOf(valueObject)); + } catch (Exception e) { + return null; + } + } + + private String escapeTagValue(String value) { + return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'"); + } + + + private String escapeRegex(String value) { + if (value == null) { + return ""; + } + return value.replace("\\", "\\\\") + .replace("/", "\\/") + .replace(".", "\\.") + .replace("(", "\\(") + .replace(")", "\\)") + .replace("[", "\\[") + .replace("]", "\\]") + .replace("{", "\\{") + .replace("}", "\\}") + .replace("^", "\\^") + .replace("$", "\\$") + .replace("*", "\\*") + .replace("+", "\\+") + .replace("?", "\\?") + .replace("|", "\\|"); + } + + private List parseInfluxQlResponse(String response) throws Exception { + if (isBlank(response)) { + return Collections.emptyList(); + } + List values = new ArrayList<>(); + JsonNode root = OBJECT_MAPPER.readTree(response); + JsonNode resultsNode = root.path("results"); + if (!resultsNode.isArray()) { + return values; + } + + for (JsonNode result : resultsNode) { + JsonNode seriesArray = result.path("series"); + if (!seriesArray.isArray()) { + continue; + } + for (JsonNode series : seriesArray) { + JsonNode rows = series.path("values"); + if (!rows.isArray()) { + continue; + } + for (JsonNode row : rows) { + if (!row.isArray() || row.size() < 2) { + continue; + } + Date dataTime = parseInfluxTime(row.get(0).isNumber() ? row.get(0).asLong() : row.get(0).asText()); + BigDecimal pointValue = toBigDecimal(row.get(1).isNumber() ? row.get(1).asText() : row.get(1).asText(null)); + if (dataTime == null || pointValue == null) { + continue; + } + values.add(new PointValue(dataTime, pointValue)); + } + } + } + return values; + } + + + private boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + public static class PointWritePayload { + private final String siteId; + private final String deviceId; + private final String pointKey; + private final BigDecimal pointValue; + private final Date dataTime; + + public PointWritePayload(String siteId, String deviceId, String pointKey, BigDecimal pointValue, Date dataTime) { + this.siteId = siteId; + this.deviceId = deviceId; + this.pointKey = pointKey; + this.pointValue = pointValue; + this.dataTime = dataTime; + } + + public String getSiteId() { + return siteId; + } + + public String getDeviceId() { + return deviceId; + } + + public String getPointKey() { + return pointKey; + } + + public BigDecimal getPointValue() { + return pointValue; + } + + public Date getDataTime() { + return dataTime; + } + } + + public static class PointValue { + private final Date dataTime; + private final BigDecimal pointValue; + + public PointValue(Date dataTime, BigDecimal pointValue) { + this.dataTime = dataTime; + this.pointValue = pointValue; + } + + public Date getDataTime() { + return dataTime; + } + + public BigDecimal getPointValue() { + return pointValue; + } + } + + private static class HttpResult { + private final int code; + private final String body; + + private HttpResult(int code, String body) { + this.code = code; + this.body = body; + } + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/DDSDataProcessServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/DDSDataProcessServiceImpl.java deleted file mode 100644 index ba48224..0000000 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/DDSDataProcessServiceImpl.java +++ /dev/null @@ -1,1228 +0,0 @@ -package com.xzzn.ems.service.impl; - -import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONObject; -import com.alibaba.fastjson2.TypeReference; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.xzzn.common.constant.Constants; -import com.xzzn.common.constant.RedisKeyConstants; -import com.xzzn.common.core.redis.RedisCache; -import com.xzzn.common.enums.BranchStatus; -import com.xzzn.common.enums.ChargeStatus; -import com.xzzn.common.enums.CommunicationStatus; -import com.xzzn.common.enums.ControlModeStatus; -import com.xzzn.common.enums.DeviceRunningStatus; -import com.xzzn.common.enums.GridStatus; -import com.xzzn.common.enums.SwitchStatus; -import com.xzzn.common.enums.WorkStatus; -import com.xzzn.common.utils.DateUtils; -import com.xzzn.common.utils.StringUtils; -import com.xzzn.ems.domain.EmsAmmeterData; -import com.xzzn.ems.domain.EmsBatteryCluster; -import com.xzzn.ems.domain.EmsBatteryData; -import com.xzzn.ems.domain.EmsBatteryDataDailyLatest; -import com.xzzn.ems.domain.EmsBatteryDataMinutes; -import com.xzzn.ems.domain.EmsBatteryGroup; -import com.xzzn.ems.domain.EmsBatteryStack; -import com.xzzn.ems.domain.EmsDailyChargeData; -import com.xzzn.ems.domain.EmsDailyEnergyData; -import com.xzzn.ems.domain.EmsDevicesSetting; -import com.xzzn.ems.domain.EmsDhData; -import com.xzzn.ems.domain.EmsPcsBranchData; -import com.xzzn.ems.domain.EmsPcsData; -import com.xzzn.ems.domain.EmsXfData; -import com.xzzn.ems.domain.vo.EnergyPriceVo; -import com.xzzn.ems.mapper.EmsAmmeterDataMapper; -import com.xzzn.ems.mapper.EmsBatteryClusterMapper; -import com.xzzn.ems.mapper.EmsBatteryDataMapper; -import com.xzzn.ems.mapper.EmsBatteryDataMinutesMapper; -import com.xzzn.ems.mapper.EmsBatteryGroupMapper; -import com.xzzn.ems.mapper.EmsBatteryStackMapper; -import com.xzzn.ems.mapper.EmsDailyChargeDataMapper; -import com.xzzn.ems.mapper.EmsDailyEnergyDataMapper; -import com.xzzn.ems.mapper.EmsDevicesSettingMapper; -import com.xzzn.ems.mapper.EmsDhDataMapper; -import com.xzzn.ems.mapper.EmsPcsBranchDataMapper; -import com.xzzn.ems.mapper.EmsPcsDataMapper; -import com.xzzn.ems.mapper.EmsXfDataMapper; -import com.xzzn.ems.service.IDDSDataProcessService; -import com.xzzn.ems.service.IEmsAlarmRecordsService; -import com.xzzn.ems.service.IEmsDeviceSettingService; -import com.xzzn.ems.utils.AbstractBatteryDataProcessor; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; - -@Service -public class DDSDataProcessServiceImpl extends AbstractBatteryDataProcessor implements IDDSDataProcessService { - private static final Log log = LogFactory.getLog(DDSDataProcessServiceImpl.class); - private static final String SITE_ID = "021_DDS_01"; - // 正则表达式匹配BMS设备编号和属性 - private static final Pattern PATTERN = Pattern.compile("(BMSD\\d{2})(ZT|SOC|SOH|DL|DY|BDSC)"); - // 匹配DTDC+数字格式的正则(提取序号) - private static final Pattern DTDC_PATTERN = Pattern.compile("DTDC(\\d+)([A-Za-z]*)"); - - @Autowired - private EmsBatteryClusterMapper emsBatteryClusterMapper; - - @Autowired - private EmsBatteryStackMapper emsBatteryStackMapper; - - @Autowired - private EmsBatteryDataMapper emsBatteryDataMapper; - - @Autowired - private EmsPcsDataMapper emsPcsDataMapper; - - @Autowired - private EmsPcsBranchDataMapper emsPcsBranchDataMapper; - - @Autowired - private RedisCache redisCache; - - @Autowired - private EmsDevicesSettingMapper emsDevicesSettingMapper; - - @Autowired - private EmsAmmeterDataMapper emsAmmeterDataMapper; - @Autowired - private EmsBatteryDailyLatestServiceImpl emsBatteryDailyLatestServiceImpl; - @Autowired - private EmsDhDataMapper emsDhDataMapper; - @Autowired - private EmsBatteryGroupMapper emsBatteryGroupMapper; - @Autowired - private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper; - @Autowired - private EmsDailyChargeDataMapper emsDailyChargeDataMapper; - @Autowired - private IEmsAlarmRecordsService iEmsAlarmRecordsService; - @Autowired - private EmsDailyEnergyDataMapper emsDailyEnergyDataMapper; - @Autowired - private IEmsDeviceSettingService iEmsDeviceSettingService; - @Autowired - private EmsXfDataMapper emsXfDataMapper; - - public DDSDataProcessServiceImpl(ObjectMapper objectMapper) { - super(objectMapper); - } - - @Override - public void handleDdsData(String message) { - JSONObject obj = JSONObject.parseObject(message); - - String deviceId = obj.get("Device").toString(); - String jsonData = obj.get("Data").toString(); - Long timestamp = Long.valueOf(obj.get("timestamp").toString()); - Date dataUpdateTime = DateUtils.convertUpdateTime(timestamp); - - log.info("deviceId:" + deviceId); - boolean isEmpty = checkJsonDataEmpty(jsonData); - if (isEmpty) { - // 添加设备告警 - iEmsAlarmRecordsService.addEmptyDataAlarmRecord(SITE_ID,deviceId); - return; - } - - // 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据 - redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + SITE_ID + "_" + deviceId, obj); - // 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据 - redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + SITE_ID + "_" + deviceId, obj, 2, TimeUnit.MINUTES); - - // 处理相关数据 - if (deviceId.contains("BMSD")) { - batteryStackDataProcess(deviceId, jsonData); - batteryGroupDataProcess(deviceId, jsonData); - batteryDataProcess(deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains("PCS")) { - pcsDataProcess(deviceId, jsonData); - pcsBranchDataProcess(deviceId, jsonData); - batteryClusterDataProcess(deviceId, jsonData); - } else if (deviceId.contains("LOAD")) { - loadDataProcess(deviceId, jsonData); - } else if (deviceId.contains("METEGF")) { - meteGFDataProcess(deviceId, jsonData); - } else if (deviceId.equals("METE")) { - meteDataProcess(deviceId, jsonData); - } else if (deviceId.contains("METE0")) { - meteBranchDataProcess(deviceId, jsonData); - } else if (deviceId.contains("XF")) { - meteXFProcess(deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains("DH")) { - dhDataProcess(deviceId, jsonData); - } - } - - private void dhDataProcess(String deviceId, String dataJson) { - //动环 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - //DH - EmsDhData dhData = new EmsDhData(); - - dhData.setHumidity(StringUtils.getBigDecimal(obj.get("SD"))); - dhData.setTemperature(StringUtils.getBigDecimal(obj.get("WD"))); - - dhData.setCreateBy("system"); - dhData.setCreateTime(DateUtils.getNowDate()); - dhData.setUpdateBy("system"); - dhData.setUpdateTime(DateUtils.getNowDate()); - dhData.setSiteId(SITE_ID); - dhData.setDeviceId(deviceId); - emsDhDataMapper.insertEmsDhData(dhData); - - redisCache.setCacheObject(RedisKeyConstants.DH + SITE_ID + "_" +deviceId, dhData); - } - - private void meteXFProcess(String deviceId, String dataJson, Date dataUpdateTime) { - //消防 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - // 暂时只更新设备表的设备状态 ZDYBYDCZT-主电源备用电池状态 - // 数据存表 - EmsXfData xfData = new EmsXfData(); - xfData.setDataTimestamp(dataUpdateTime); - xfData.setDcfzt(StringUtils.getBigDecimal(obj.get("YLKGDCFZT"))); - xfData.setDczt(StringUtils.getBigDecimal(obj.get("ZDYBYDCZT"))); - xfData.setPszt(StringUtils.getBigDecimal(obj.get("QDQTPSZT"))); - xfData.setYszt(StringUtils.getBigDecimal(obj.get("SZDYSZT"))); - - xfData.setCreateBy("system"); - xfData.setCreateTime(DateUtils.getNowDate()); - xfData.setUpdateBy("system"); - xfData.setUpdateTime(DateUtils.getNowDate()); - xfData.setSiteId(SITE_ID); - xfData.setDeviceId(deviceId); - emsXfDataMapper.insertEmsXfData(xfData); - - redisCache.setCacheObject(RedisKeyConstants.XF + SITE_ID + "_" +deviceId, xfData); - // 状态枚举还没有提供 - EmsDevicesSetting emsDevicesSetting = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, SITE_ID); - emsDevicesSetting.setCommunicationStatus(StringUtils.getString(obj.get("ZDYBYDCZT"))); - emsDevicesSetting.setUpdatedAt(DateUtils.getNowDate()); - emsDevicesSettingMapper.updateEmsDevicesSetting(emsDevicesSetting); - } - - private void batteryStackDataProcess(String deviceId, String dataJson) { - - //电池堆 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - // 将原始数据存一下方便后面簇数据使用 - redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_BMSD + SITE_ID + "_" + deviceId, obj); - //BMS 电池堆 - EmsBatteryStack dataStack = new EmsBatteryStack(); - - // 其他非 BigDecimal 字段 - dataStack.setWorkStatus(WorkStatus.NORMAL.getCode()); // 或其他默认值 - dataStack.setPcsCommunicationStatus(CommunicationStatus.OK.getCode()); - dataStack.setEmsCommunicationStatus(CommunicationStatus.OK.getCode()); - - // 电压极值信息 - dataStack.setMaxCellVoltage(StringUtils.getBigDecimal(obj.get("DTZDDY"))); - dataStack.setMaxVoltageGroupId(StringUtils.getLong(obj.get("DTZDDYXH"))); - dataStack.setMinCellVoltage(StringUtils.getBigDecimal(obj.get("DTZXDY"))); - dataStack.setMinVoltageGroupId(StringUtils.getLong(obj.get("DTZXDYXH"))); - - // 温度极值信息 - dataStack.setMaxCellTemp(StringUtils.getBigDecimal(obj.get("DTZGWD"))); - dataStack.setMaxTempGroupId(StringUtils.getLong(obj.get("DTZGWDXH"))); - dataStack.setMinCellTemp(StringUtils.getBigDecimal(obj.get("DTZDWD"))); - dataStack.setMinTempGroupId(StringUtils.getLong(obj.get("DTZDWDXH"))); - - // 干节点信号状态 - dataStack.setBatteryNumber(StringUtils.getLong(obj.get("DTGS"))); - dataStack.setBatteryAvgVoltage(StringUtils.getBigDecimal(obj.get("DTPJDY"))); - dataStack.setBatteryDifferentPressure(StringUtils.getBigDecimal(obj.get("DTDCYC"))); - dataStack.setAvgTemperature(StringUtils.getBigDecimal(obj.get("DTPJWD"))); - dataStack.setBatteryDifferentTemperature(StringUtils.getBigDecimal(obj.get("DTDCWC"))); - dataStack.setMaxInternalResistance(StringUtils.getBigDecimal(obj.get("DTZDNZ"))); - dataStack.setMinInternalResistance(StringUtils.getBigDecimal(obj.get("DTZXNZ"))); - dataStack.setAvgInternalResistance(StringUtils.getBigDecimal(obj.get("DTPJNZ"))); - dataStack.setBatteryDefferentResistance(StringUtils.getBigDecimal(obj.get("DTDCNZC"))); - dataStack.setMaxResistanceCellId(StringUtils.getLong(obj.get("DTZDNZXH"))); - dataStack.setMinResistanceCellId(StringUtils.getLong(obj.get("DTZXNZXH"))); - dataStack.setEnvironmentTemperature(StringUtils.getBigDecimal(obj.get("HJWD"))); - dataStack.setEnvironmentHumidity(StringUtils.getBigDecimal(obj.get("HJSD"))); - dataStack.setCircuitBreakerStatus(StringUtils.getString(obj.get("LDQZT"))); - - // 页面字段临时填充 - dataStack.setStackVoltage(StringUtils.getBigDecimal(obj.get("BMSD01DY"))); - dataStack.setStackCurrent(StringUtils.getBigDecimal(obj.get("BMSD01DL"))); - dataStack.setStackSoc(StringUtils.getBigDecimal(obj.get("BMSD01SOC"))); - dataStack.setStackSoh(StringUtils.getBigDecimal(obj.get("BMSD01SOH"))); - dataStack.setOperatingTemp(StringUtils.getBigDecimal(obj.get("HJWD"))); - - dataStack.setCreateBy("system"); - dataStack.setCreateTime(DateUtils.getNowDate()); - dataStack.setUpdateBy("system"); - dataStack.setUpdateTime(DateUtils.getNowDate()); - dataStack.setSiteId(SITE_ID); - dataStack.setDeviceId(deviceId); - - emsBatteryStackMapper.insertEmsBatteryStack(dataStack); - - redisCache.setCacheObject(RedisKeyConstants.STACK + SITE_ID + "_" +deviceId, dataStack); - } - - private void batteryGroupDataProcess(String deviceId, String jsonData) { - //电池组 - Map obj = JSON.parseObject(jsonData, new TypeReference>() { - }); - - Map groupMap = new HashMap<>(); - for (Map.Entry entry : obj.entrySet()) { - String key = entry.getKey(); - // 跳过空键 - if (key == null || key.trim().isEmpty()) { - continue; - } - Matcher matcher = PATTERN.matcher(key); - if (matcher.matches()) { - String groupDeviceId = matcher.group(1); - String property = matcher.group(2); - EmsBatteryGroup dataGroup = groupMap.getOrDefault(groupDeviceId, new EmsBatteryGroup()); - dataGroup.setDeviceId(groupDeviceId); - dataGroup.setSiteId(SITE_ID); - dataGroup.setCreateBy("system"); - dataGroup.setCreateTime(DateUtils.getNowDate()); - dataGroup.setUpdateBy("system"); - dataGroup.setUpdateTime(DateUtils.getNowDate()); - - setPropertyValue(dataGroup, property, entry.getValue()); - groupMap.put(groupDeviceId, dataGroup); - } - } - - // 批量插入数据库 - if (!CollectionUtils.isEmpty(groupMap)) { - List batteryGroupList = new ArrayList<>(groupMap.values()); - emsBatteryGroupMapper.batchInsertGroupData(batteryGroupList); - redisCache.setCacheObject(RedisKeyConstants.GROUP + SITE_ID + "_" +deviceId, batteryGroupList); - } - - } - //根据属性名设置对应的值 - private static void setPropertyValue(EmsBatteryGroup groupData, String property, Object value) { - BigDecimal numberValue = null; - if (value instanceof Number) { - numberValue = new BigDecimal(value.toString()); - } - - switch (property) { - case "ZT": - if (numberValue != null) { - groupData.setStatus(numberValue.toString()); - } - break; - case "SOC": - groupData.setSoc(numberValue); - break; - case "SOH": - groupData.setSoh(numberValue); - break; - case "DL": - groupData.setCurrent(numberValue); - break; - case "DY": - groupData.setVoltage(numberValue); - break; - case "BDSC": - groupData.setEstimatedBackupDuration(numberValue); - break; - } - } - private void batteryDataProcess(String deviceId, String dataJson, Date dataUpdateTime) { - //电池组 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - List dataList = new ArrayList<>(); - - // 前一个小时 - LocalDateTime oneHourAgo = LocalDateTime.now().minus(1, ChronoUnit.HOURS); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - String oneHourAgoStr = oneHourAgo.format(formatter); - - Map dataMap = new HashMap<>(); - Map dailyMap = new HashMap<>(); - Map minutesMap = new HashMap<>(); - - String clusterId = getClusterDeviceIdByParentDeviceId(deviceId); - for (Map.Entry entry : obj.entrySet()) { - String key = entry.getKey(); - - if (key.startsWith("DTDC")) { - Matcher matcher = DTDC_PATTERN.matcher(key); - if (matcher.matches()) { - String batteryCellId = matcher.group(1); - String property = matcher.group(2); - - EmsBatteryData data = dataMap.getOrDefault(batteryCellId, new EmsBatteryData()); - if (StringUtils.isNotEmpty(batteryCellId)) { - data.setDataTimestamp(dataUpdateTime); - data.setBatteryPack(deviceId); - data.setBatteryCluster(clusterId); - data.setClusterDeviceId(clusterId); - data.setBatteryCellId(batteryCellId); - data.setSiteId(SITE_ID); - data.setDeviceId(batteryCellId); - data.setCreateBy("system"); - data.setCreateTime(DateUtils.getNowDate()); - data.setUpdateBy("system"); - data.setUpdateTime(DateUtils.getNowDate()); - } - // 根据后缀设置对应属性 - setDTDCPropertyValue(data, property, entry.getValue()); - dataMap.put(batteryCellId, data); - - // 每日最新数据:按batteryCellId去重 - EmsBatteryDataDailyLatest daily = dailyMap.getOrDefault(batteryCellId, new EmsBatteryDataDailyLatest()); - BeanUtils.copyProperties(data, daily); - daily.setDateDay(DateUtils.getNowDate()); - dailyMap.put(batteryCellId, daily); - - // 分钟级的表,上报的数据直接存入,数据上限是 1 个小时 - EmsBatteryDataMinutes minutes = minutesMap.getOrDefault(batteryCellId, new EmsBatteryDataMinutes()); - BeanUtils.copyProperties(data, minutes); - minutesMap.put(batteryCellId, minutes); - } - } - } - if (!CollectionUtils.isEmpty(dataMap)) { - dataList = new ArrayList<>(dataMap.values()); - emsBatteryDataMapper.insertEmsBatteryDataList(new ArrayList<>(dataList)); - - redisCache.deleteList(RedisKeyConstants.BATTERY + SITE_ID + "_" + clusterId); - redisCache.setCacheList(RedisKeyConstants.BATTERY + SITE_ID + "_" + clusterId , dataList); - } - // 批量处理每日最新数据 - List dailyList = new ArrayList<>(dailyMap.values()); - if (!dailyList.isEmpty()) { - dailyList = new ArrayList<>(dailyMap.values()); - emsBatteryDailyLatestServiceImpl.batchProcessBatteryData(dailyList); - } - - // 实时插入每分钟数据 - List minutesList = new ArrayList<>(minutesMap.values()); - if (!minutesList.isEmpty()) { - emsBatteryDataMinutesMapper.insertMinutesBatteryDataList(minutesList); - } - // 清理分钟级表里一小时前数据 - emsBatteryDataMinutesMapper.deleteByTimeBeforeOneHour(oneHourAgoStr); - - // 分片处理时级,天级,月级数据 - if (dataList.size() > 0) { - super.processBatch(dataList); - } - } - - private String getClusterDeviceIdByParentDeviceId(String deviceId) { - String clusterId = "BMSC01"; - Map> map = redisCache.getCacheObject(RedisKeyConstants.INIT_DEVICE_INFO); - if (map == null || map.isEmpty()) { - map = iEmsDeviceSettingService.initDeviceInfo(); - } - // 不为空,则查找子类的簇id - if (map != null && !map.isEmpty()) { - List list = map.get(SITE_ID); - if (list != null && list.size() > 0) { - for (EmsDevicesSetting emsDevicesSetting : list) { - if (deviceId.equals(emsDevicesSetting.getParentId())) { - clusterId = emsDevicesSetting.getDeviceId(); - } - } - } - } - return clusterId; - } - - private void setDTDCPropertyValue(EmsBatteryData data, String property, Object value) { - BigDecimal numberValue = null; - if (value instanceof Number) { - numberValue = new BigDecimal(value.toString()); - } - switch (property) { - case "DY": - data.setVoltage(numberValue); - break; - case "WD": - data.setTemperature(numberValue); - break; - case "NZ": - data.setInterResistance(numberValue); - break; - } - } - - private void batteryClusterDataProcess(String deviceId, String dataJson) { - - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - EmsDevicesSetting joken = new EmsDevicesSetting(); - - - //BMSC 电池簇 - EmsBatteryCluster data = new EmsBatteryCluster(); - // 其他非 BigDecimal 字段 - data.setWorkStatus(WorkStatus.NORMAL.getCode()); // 或其他默认值 - data.setPcsCommunicationStatus(CommunicationStatus.OK.getCode()); - data.setEmsCommunicationStatus(CommunicationStatus.OK.getCode()); - data.setCreateBy("system"); - data.setCreateTime(DateUtils.getNowDate()); - data.setUpdateBy("system"); - data.setUpdateTime(DateUtils.getNowDate()); - data.setSiteId(SITE_ID); - // BMSC02 电流电压功率 - deviceId = "BMSC02"; - String stackDeviceId = getDeviceParent(deviceId); - if (StringUtils.isNotBlank(stackDeviceId)) { - data.setStackDeviceId(stackDeviceId); - } else { - data.setStackDeviceId("1"); - } - // 获取redis获取最新的BMSD数据 - Map stackObj = redisCache.getCacheObject(RedisKeyConstants.ORIGINAL_BMSD +SITE_ID+"_"+stackDeviceId); - data.setDeviceId(deviceId); - data.setClusterVoltage(StringUtils.getBigDecimal(obj.get("BMSC02ZLDY"))); - data.setClusterCurrent(StringUtils.getBigDecimal(obj.get("BMSC02ZLDL"))); - data.setMaxAllowedChargePower(StringUtils.getBigDecimal(obj.get("BMSC02ZLSCGL"))); - // 取堆里面数据 - data.setSoh(StringUtils.getBigDecimal(stackObj.get("BMSD02SOH"))); - data.setCurrentSoc(StringUtils.getBigDecimal(stackObj.get("BMSD02SOC"))); - data.setBatteryPackVoltage(StringUtils.getBigDecimal(stackObj.get("BMSD02DY"))); - data.setBatteryPackCurrent(StringUtils.getBigDecimal(stackObj.get("BMSD02DL"))); - data.setBatteryPackSoc(StringUtils.getBigDecimal(stackObj.get("BMSD02SOC"))); - data.setBatteryPackSoh(StringUtils.getBigDecimal(stackObj.get("BMSD02SOH"))); - data.setAvgCellTemp(StringUtils.getBigDecimal(stackObj.get("DTPJWD"))); - data.setMaxCellVoltage(StringUtils.getBigDecimal(stackObj.get("DTZDDY"))); - data.setMaxCellVoltageId(StringUtils.getString(stackObj.get("DTZDDYXH"))); - data.setMinCellVoltage(StringUtils.getBigDecimal(stackObj.get("DTZXDY"))); - data.setMinCellVoltageId(StringUtils.getString(stackObj.get("DTZXDYXH"))); - data.setMaxCellTemp(StringUtils.getBigDecimal(stackObj.get("DTZGWD"))); - data.setMaxCellTempId(StringUtils.getString(stackObj.get("DTZGWDXH"))); - data.setMinCellTemp(StringUtils.getBigDecimal(stackObj.get("DTZDWD"))); - data.setMinCellTempId(StringUtils.getString(stackObj.get("DTZDWDXH"))); - - emsBatteryClusterMapper.insertEmsBatteryCluster(data); - redisCache.setCacheObject(RedisKeyConstants.CLUSTER + SITE_ID + "_" +deviceId, data); - - // BMSC01 电流电压功率 - deviceId = "BMSC01"; - stackDeviceId = getDeviceParent(deviceId); - if (StringUtils.isNotBlank(stackDeviceId)) { - data.setStackDeviceId(stackDeviceId); - } else { - data.setStackDeviceId("1"); - } - // 获取redis获取最新的BMSD数据 - stackObj = redisCache.getCacheObject(RedisKeyConstants.ORIGINAL_BMSD +SITE_ID+"_"+stackDeviceId); - data.setDeviceId(deviceId); - data.setClusterVoltage(StringUtils.getBigDecimal(obj.get("BMSC01ZLDY"))); - data.setClusterCurrent(StringUtils.getBigDecimal(obj.get("BMSC01ZLDL"))); - data.setMaxAllowedChargePower(StringUtils.getBigDecimal(obj.get("BMSC01ZLSCGL"))); - // 取堆里面数据 - data.setSoh(StringUtils.getBigDecimal(stackObj.get("BMSD01SOH"))); - data.setCurrentSoc(StringUtils.getBigDecimal(stackObj.get("BMSD01SOC"))); - data.setBatteryPackVoltage(StringUtils.getBigDecimal(stackObj.get("BMSD01DY"))); - data.setBatteryPackCurrent(StringUtils.getBigDecimal(stackObj.get("BMSD01DL"))); - data.setBatteryPackSoc(StringUtils.getBigDecimal(stackObj.get("BMSD01SOC"))); - data.setBatteryPackSoh(StringUtils.getBigDecimal(stackObj.get("BMSD01SOH"))); - data.setAvgCellTemp(StringUtils.getBigDecimal(stackObj.get("DTPJWD"))); - data.setMaxCellVoltage(StringUtils.getBigDecimal(stackObj.get("DTZDDY"))); - data.setMaxCellVoltageId(StringUtils.getString(stackObj.get("DTZDDYXH"))); - data.setMinCellVoltage(StringUtils.getBigDecimal(stackObj.get("DTZXDY"))); - data.setMinCellVoltageId(StringUtils.getString(stackObj.get("DTZXDYXH"))); - data.setMaxCellTemp(StringUtils.getBigDecimal(stackObj.get("DTZGWD"))); - data.setMaxCellTempId(StringUtils.getString(stackObj.get("DTZGWDXH"))); - data.setMinCellTemp(StringUtils.getBigDecimal(stackObj.get("DTZDWD"))); - data.setMinCellTempId(StringUtils.getString(stackObj.get("DTZDWDXH"))); - - emsBatteryClusterMapper.insertEmsBatteryCluster(data); - redisCache.setCacheObject(RedisKeyConstants.CLUSTER + SITE_ID + "_" +deviceId, data); - } - - private String getDeviceParent(String deviceId) { - Map> map = redisCache.getCacheObject(RedisKeyConstants.INIT_DEVICE_INFO); - if (map == null || map.isEmpty()) { - map = iEmsDeviceSettingService.initDeviceInfo(); - } - // 不为空,则查找父类 - String stackDeviceId = "1"; - if (map != null && !map.isEmpty()) { - List list = map.get(SITE_ID); - if (list == null || list.isEmpty()) { - EmsDevicesSetting deviceInfo = new EmsDevicesSetting(); - deviceInfo.setDeviceId(deviceId); - deviceInfo.setSiteId(SITE_ID); - list = emsDevicesSettingMapper.selectEmsDevicesSettingList(deviceInfo); - if (list == null || list.isEmpty()) { - return stackDeviceId; - } - } - for (EmsDevicesSetting emsDevicesSetting : list) { - if (deviceId.equals(emsDevicesSetting.getDeviceId())) { - stackDeviceId = emsDevicesSetting.getParentId(); - } - } - } - return stackDeviceId; - } - - private void meteBranchDataProcess(String deviceId, String dataJson) { - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - //pcs电表数据 - EmsAmmeterData data = new EmsAmmeterData(); - // 更新时间 - data.setDataUpdateTime(new Date()); - - // 当月有功电能 - data.setCurrentForwardActiveTotal(StringUtils.getBigDecimal(obj.get("DQYZZXYGDN"))); - data.setCurrentForwardActivePeak(StringUtils.getBigDecimal(obj.get("DQYJZXYGDN"))); - data.setCurrentForwardActiveHigh(StringUtils.getBigDecimal(obj.get("DQYFZXYGDN"))); - data.setCurrentForwardActiveFlat(StringUtils.getBigDecimal(obj.get("DQYPZXYGDN"))); - data.setCurrentForwardActiveValley(StringUtils.getBigDecimal(obj.get("DQYGZXYGDN"))); - data.setCurrentReverseActiveTotal(StringUtils.getBigDecimal(obj.get("DQYZFXYGDN"))); - data.setCurrentReverseActivePeak(StringUtils.getBigDecimal(obj.get("DQYJFXYGDN"))); - data.setCurrentReverseActiveHigh(StringUtils.getBigDecimal(obj.get("DQYFFXYGDN"))); - data.setCurrentReverseActiveFlat(StringUtils.getBigDecimal(obj.get("DQYPFXYGDN"))); - data.setCurrentReverseActiveValley(StringUtils.getBigDecimal(obj.get("DQYGFXYGDN"))); - - // 总有功电能 - data.setTotalForwardActiveTwo(StringUtils.getBigDecimal(obj.get("ZZXYGDN2"))); - data.setTotalPeakForwardActive(StringUtils.getBigDecimal(obj.get("ZJZXYGDN"))); - data.setTotalHighForwardActive(StringUtils.getBigDecimal(obj.get("ZFZXYGDN"))); - data.setTotalFlatForwardActive(StringUtils.getBigDecimal(obj.get("ZPZXYGDN"))); - data.setTotalValleyForwardActive(StringUtils.getBigDecimal(obj.get("ZGZXYGDN"))); - data.setTotalReverseActiveTwo(StringUtils.getBigDecimal(obj.get("ZFXYGDN2"))); - data.setTotalPeakReverseActive(StringUtils.getBigDecimal(obj.get("ZJFXYGDN"))); - data.setTotalHighReverseActive(StringUtils.getBigDecimal(obj.get("ZFFXYGDN"))); - data.setTotalFlatReverseActive(StringUtils.getBigDecimal(obj.get("ZPFXYGDN"))); - data.setTotalValleyReverseActive(StringUtils.getBigDecimal(obj.get("ZGFXYGDN"))); - - // 其他字段 - data.setVoltagePercent(StringUtils.getBigDecimal(obj.get("DYBB"))); - data.setDisconnectDetectionIndication(StringUtils.getString(obj.get("DXJCZS"))); - data.setRatedPrimaryCurrentValue(StringUtils.getBigDecimal(obj.get("EDYCDLZ"))); - data.setSwitchInputOutputStatus(StringUtils.getString(obj.get("KGLSRSCZT"))); - data.setAlarmStatus(StringUtils.getString(obj.get("BJZT"))); - data.setCurrentTotalVoltagePercent(StringUtils.getBigDecimal(obj.get("DQZDYBFB"))); - data.setVoltageContentPercent(StringUtils.getBigDecimal(obj.get("DYZLHLBFB"))); - data.setVoltageAcPercent(StringUtils.getBigDecimal(obj.get("DYJLHLBFB"))); - data.setCurrentTotalCurrentPercent(StringUtils.getBigDecimal(obj.get("DQZDLBFB"))); - data.setCurrentContentPercent(StringUtils.getBigDecimal(obj.get("DLZLHLBFB"))); - data.setCurrentAcPercent(StringUtils.getBigDecimal(obj.get("DLJLHLBFB"))); - data.setCurrentTotalPowerPercent(StringUtils.getBigDecimal(obj.get("DQZGLBFB"))); - data.setPowerContentPercent(StringUtils.getBigDecimal(obj.get("GLZLHLBFB"))); - data.setPowerAcPercent(StringUtils.getBigDecimal(obj.get("GLJLHLBFB"))); - data.setDataSettingYearMonth(StringUtils.getString(obj.get("RQSJSZNY"))); - data.setDataSettingDayHour(StringUtils.getString(obj.get("RQSJSZRS"))); - data.setDataSettingMinutesSeconds(StringUtils.getString(obj.get("RQSJSZFM"))); - data.setCurrentRate(StringUtils.getBigDecimal(obj.get("DQCBRFL"))); - data.setVersionNumber(StringUtils.getString(obj.get("RJBBH"))); - data.setVoltage(StringUtils.getBigDecimal(obj.get("DY"))); - data.setCurrent(StringUtils.getBigDecimal(obj.get("DL"))); - data.setPower(StringUtils.getBigDecimal(obj.get("GL"))); - data.setInternalTemp(StringUtils.getBigDecimal(obj.get("NBWD"))); - data.setTotalForwardActiveOne(StringUtils.getBigDecimal(obj.get("ZZXYGDN1"))); - data.setTotalReverseActiveOne(StringUtils.getBigDecimal(obj.get("ZFXYGDN1"))); - - data.setCreateBy("system"); - data.setCreateTime(DateUtils.getNowDate()); - data.setUpdateBy("system"); - data.setUpdateTime(DateUtils.getNowDate()); - data.setSiteId(SITE_ID); - data.setDeviceId(deviceId); - emsAmmeterDataMapper.insertEmsAmmeterData(data); - redisCache.setCacheObject(RedisKeyConstants.AMMETER + SITE_ID + "_" +deviceId, data); - } - - private void pcsDataProcess(String deviceId, String dataJson) { - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - //pcs - EmsPcsData pcsData = new EmsPcsData(); - // 时间与状态类字段 - pcsData.setDataUpdateTime(new Date()); - pcsData.setWorkStatus(WorkStatus.NORMAL.getCode()); - pcsData.setGridStatus(GridStatus.GRID.getCode()); - pcsData.setDeviceStatus(DeviceRunningStatus.ONLINE.getCode()); - pcsData.setControlMode(ControlModeStatus.REMOTE.getCode()); - - // 电流 - pcsData.setaPhaseCurrent(StringUtils.getBigDecimal(obj.get("PCSJLDLIA"))); - pcsData.setbPhaseCurrent(StringUtils.getBigDecimal(obj.get("PCSJLDLIB"))); - pcsData.setcPhaseCurrent(StringUtils.getBigDecimal(obj.get("PCSJLDLIC"))); - - // 功率与能量类字段 - pcsData.setDcPower(StringUtils.getBigDecimal(obj.get("ZLZGL"))); - pcsData.setDcVoltage(StringUtils.getBigDecimal(obj.get("ZLDY"))); - pcsData.setDcCurrent(StringUtils.getBigDecimal(obj.get("ZLDL"))); - pcsData.setTotalActivePower(StringUtils.getBigDecimal(obj.get("JLYGGL"))); - pcsData.setTotalReactivePower(StringUtils.getBigDecimal(obj.get("JLWGGL"))); - pcsData.setTotalAcChargeEnergy(StringUtils.getBigDecimal(obj.get("CDDN"))); - pcsData.setTotalAcDischargeEnergy(StringUtils.getBigDecimal(obj.get("FDDN"))); - - // 电网频率 - pcsData.setDwFrequency(StringUtils.getBigDecimal(obj.get("DWPL"))); - - // 状态指示类 - pcsData.setBranchStatus(BranchStatus.NORMAL.getCode()); - pcsData.setDischargeStatus(ChargeStatus.CHARGING.getCode()); - String acSwitchStatus = StringUtils.getString(obj.get("JLKGZT")); - pcsData.setAcSwitchStatus(SwitchStatus.CLOSED.getCode()); - String dcSwitchStatus = StringUtils.getString(obj.get("ZLKGZT")); - pcsData.setDcSwitchStatus(SwitchStatus.CLOSED.getCode()); - String controlMode = StringUtils.getString(obj.get("YCTT")); - pcsData.setRemoteControlStatus(ControlModeStatus.REMOTE.getCode()); - - // 电流参数 - pcsData.setSysUCurrent(StringUtils.getBigDecimal(obj.get("XTSCUXDL"))); - pcsData.setSysVCurrent(StringUtils.getBigDecimal(obj.get("XTSCVXDL"))); - pcsData.setSysWCurrent(StringUtils.getBigDecimal(obj.get("XTSCWXDL"))); - - // 页面字段填充 - pcsData.setAcFrequency(StringUtils.getBigDecimal(obj.get("DWPL"))); - pcsData.setTotalApparentPower(StringUtils.getBigDecimal(obj.get("ZLZGL"))); - pcsData.setTotalPowerFactor(new BigDecimal(0)); - - // 模块温度 - pcsData.setModule1Temp(StringUtils.getBigDecimal(obj.get("IGBYZGWD1"))); - pcsData.setModule2Temp(StringUtils.getBigDecimal(obj.get("IGBYZGWD2"))); - pcsData.setModule3Temp(StringUtils.getBigDecimal(obj.get("IGBYZGWD3"))); - pcsData.setModule4Temp(StringUtils.getBigDecimal(obj.get("IGBYZGWD4"))); - - // 系统管理字段 - pcsData.setCreateBy("system"); - pcsData.setCreateTime(DateUtils.getNowDate()); - pcsData.setUpdateBy("system"); - pcsData.setUpdateTime(DateUtils.getNowDate()); - pcsData.setSiteId(SITE_ID); - pcsData.setDeviceId(deviceId); - pcsData.setDateMonth(DateUtils.getNowMonthLong()); - pcsData.setDateDay(DateUtils.getNowDayLong()); - - emsPcsDataMapper.insertEmsPcsData(pcsData); - redisCache.setCacheObject(RedisKeyConstants.PCS + SITE_ID + "_" +deviceId, pcsData); - - } - - private void pcsBranchDataProcess(String deviceId, String dataJson) { - - Map records = JSON.parseObject(dataJson, new TypeReference>() { - }); - List list = new ArrayList<>(); - - EmsPcsBranchData data = new EmsPcsBranchData(); - data.setDeviceId(deviceId); - data.setSiteId(SITE_ID); - data.setGridStatus(GridStatus.GRID.getCode()); - - data.setGridUVoltage(StringUtils.getBigDecimal(records.get("DWXDYUAB"))); - data.setGridVVoltage(StringUtils.getBigDecimal(records.get("DWXDYUBC"))); - data.setGridWVoltage(StringUtils.getBigDecimal(records.get("DWXDYUCA"))); - data.setOutputUCurrent(StringUtils.getBigDecimal(records.get("PCSJLDLIA"))); - data.setOutputVCurrent(StringUtils.getBigDecimal(records.get("PCSJLDLIB"))); - data.setOutputWCurrent(StringUtils.getBigDecimal(records.get("PCSJLDLIC"))); - - // 页面字段填充 - data.setDcPower(StringUtils.getBigDecimal(records.get("ZLZGL"))); - data.setDcVoltage(StringUtils.getBigDecimal(records.get("ZLDY"))); - data.setDcCurrent(StringUtils.getBigDecimal(records.get("ZLDL"))); - - data.setBranchId("DY1"); - data.setCreateBy("system"); - data.setCreateTime(DateUtils.getNowDate()); - data.setUpdateBy("system"); - data.setUpdateTime(DateUtils.getNowDate()); - list.add(data); - emsPcsBranchDataMapper.insertPcsBranchDataList(list); - redisCache.setCacheObject(RedisKeyConstants.BRANCH + SITE_ID + "_" +deviceId, list); - } - - private void meteGFDataProcess(String deviceId, String dataJson) { - - //总表 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - EmsAmmeterData dataGF = new EmsAmmeterData(); - // 更新时间 - dataGF.setDataUpdateTime(new Date()); - - // 电压+电流 - dds字段 - dataGF.setPhaseAVoltage(StringUtils.getBigDecimal(obj.get("AXDY"))); - dataGF.setPhaseBVoltage(StringUtils.getBigDecimal(obj.get("BXDY"))); - dataGF.setPhaseCVoltage(StringUtils.getBigDecimal(obj.get("CXDY"))); - dataGF.setPhaseACurrent(StringUtils.getBigDecimal(obj.get("AXDL"))); - dataGF.setPhaseBCurrent(StringUtils.getBigDecimal(obj.get("BXDL"))); - dataGF.setPhaseCCurrent(StringUtils.getBigDecimal(obj.get("CXDL"))); - - dataGF.setAbLineVoltage(StringUtils.getBigDecimal(obj.get("ABXDY"))); - dataGF.setCbLineVoltage(StringUtils.getBigDecimal(obj.get("BCXDY"))); - dataGF.setAcLineVoltage(StringUtils.getBigDecimal(obj.get("CAXDY"))); - - // 频率 - dataGF.setFrequency(StringUtils.getBigDecimal(obj.get("PL"))); - - // 功率 有功+总+无功+无总+视在 - dataGF.setPhaseAActivePower(StringUtils.getBigDecimal(obj.get("AXYGGL"))); - dataGF.setPhaseBActivePower(StringUtils.getBigDecimal(obj.get("BXYGGL"))); - dataGF.setPhaseCActivePower(StringUtils.getBigDecimal(obj.get("CXYGGL"))); - dataGF.setTotalActivePower(StringUtils.getBigDecimal(obj.get("ZYGGL"))); - dataGF.setPhaseAReactivePower(StringUtils.getBigDecimal(obj.get("AXWGGL"))); - dataGF.setPhaseBReactivePower(StringUtils.getBigDecimal(obj.get("BXWGGL"))); - dataGF.setPhaseCReactivePower(StringUtils.getBigDecimal(obj.get("CXWGGL"))); - dataGF.setTotalReactivePower(StringUtils.getBigDecimal(obj.get("ZWGGL"))); - dataGF.setPhaseAApparentPower(StringUtils.getBigDecimal(obj.get("AXSZGL"))); - dataGF.setPhaseBApparentPower(StringUtils.getBigDecimal(obj.get("BXSZGL"))); - dataGF.setPhaseCApparentPower(StringUtils.getBigDecimal(obj.get("CXSZGL"))); - dataGF.setTotalApparentPower(StringUtils.getBigDecimal(obj.get("ZSZGL"))); - - // 功率因数 - dataGF.setPhaseAPowerFactor(StringUtils.getBigDecimal(obj.get("AXGLYS"))); - dataGF.setPhaseBPowerFactor(StringUtils.getBigDecimal(obj.get("BXGLYS"))); - dataGF.setPhaseCPowerFactor(StringUtils.getBigDecimal(obj.get("CXGLYS"))); - dataGF.setTotalPowerFactor(StringUtils.getBigDecimal(obj.get("ZGLYS"))); - - // 其他字段 - dataGF.setPositiveReactiveEnergyEqPlus(StringUtils.getBigDecimal(obj.get("WGDN"))); - dataGF.setPositiveActiveEnergyEpPlus(StringUtils.getBigDecimal(obj.get("YGDN"))); - dataGF.setCurrentPercent(StringUtils.getBigDecimal(obj.get("DLBB"))); - dataGF.setVoltagePercent(StringUtils.getBigDecimal(obj.get("DYBB"))); - dataGF.setAvgCurrent(StringUtils.getBigDecimal(obj.get("PJDL"))); - - dataGF.setCreateBy("system"); - dataGF.setCreateTime(DateUtils.getNowDate()); - dataGF.setUpdateBy("system"); - dataGF.setUpdateTime(DateUtils.getNowDate()); - dataGF.setSiteId(SITE_ID); - dataGF.setDeviceId(deviceId); - - emsAmmeterDataMapper.insertEmsAmmeterData(dataGF); - - redisCache.setCacheObject(RedisKeyConstants.AMMETER + SITE_ID + "_" +deviceId, dataGF); - } - - private void loadDataProcess(String deviceId, String dataJson) { - - //总表 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - EmsAmmeterData dataLoad = new EmsAmmeterData(); - // 更新时间 - dataLoad.setDataUpdateTime(new Date()); - - // 电压+电流 - dds字段 - dataLoad.setPhaseAVoltage(StringUtils.getBigDecimal(obj.get("XDYUA"))); - dataLoad.setPhaseBVoltage(StringUtils.getBigDecimal(obj.get("XDYUB"))); - dataLoad.setPhaseCVoltage(StringUtils.getBigDecimal(obj.get("XDYUC"))); - dataLoad.setPhaseACurrent(StringUtils.getBigDecimal(obj.get("XDLIA"))); - dataLoad.setPhaseBCurrent(StringUtils.getBigDecimal(obj.get("XDLIB"))); - dataLoad.setPhaseCCurrent(StringUtils.getBigDecimal(obj.get("XDLIC"))); - - dataLoad.setAbLineVoltage(StringUtils.getBigDecimal(obj.get("XDYUAB"))); - dataLoad.setCbLineVoltage(StringUtils.getBigDecimal(obj.get("XDYUBC"))); - dataLoad.setAcLineVoltage(StringUtils.getBigDecimal(obj.get("XDYUCA"))); - - // 频率 - dataLoad.setFrequency(StringUtils.getBigDecimal(obj.get("PL"))); - - // 功率 有功+总+无功+无总+视在 - dataLoad.setPhaseAActivePower(StringUtils.getBigDecimal(obj.get("AXYGGL"))); - dataLoad.setPhaseBActivePower(StringUtils.getBigDecimal(obj.get("BXYGGL"))); - dataLoad.setPhaseCActivePower(StringUtils.getBigDecimal(obj.get("CXYGGL"))); - dataLoad.setTotalActivePower(StringUtils.getBigDecimal(obj.get("YGZGL"))); - dataLoad.setPhaseAReactivePower(StringUtils.getBigDecimal(obj.get("AXWGGL"))); - dataLoad.setPhaseBReactivePower(StringUtils.getBigDecimal(obj.get("BXWGGL"))); - dataLoad.setPhaseCReactivePower(StringUtils.getBigDecimal(obj.get("CXWGGL"))); - dataLoad.setTotalReactivePower(StringUtils.getBigDecimal(obj.get("WGZGL"))); - dataLoad.setPhaseAApparentPower(StringUtils.getBigDecimal(obj.get("AXSZGL"))); - dataLoad.setPhaseBApparentPower(StringUtils.getBigDecimal(obj.get("BXSZGL"))); - dataLoad.setPhaseCApparentPower(StringUtils.getBigDecimal(obj.get("CXSZGL"))); - dataLoad.setTotalApparentPower(StringUtils.getBigDecimal(obj.get("ZSZGL"))); - - // 功率因数 - dataLoad.setPhaseAPowerFactor(StringUtils.getBigDecimal(obj.get("AXGLYS"))); - dataLoad.setPhaseBPowerFactor(StringUtils.getBigDecimal(obj.get("BXGLYS"))); - dataLoad.setPhaseCPowerFactor(StringUtils.getBigDecimal(obj.get("CXGLYS"))); - dataLoad.setTotalPowerFactor(StringUtils.getBigDecimal(obj.get("ZGLYS"))); - - // 电度 - dataLoad.setSecondaryReverseReactiveEnergy(StringUtils.getBigDecimal(obj.get("GXWGDDECC"))); - dataLoad.setSecondaryNegativeActiveEnergy(StringUtils.getBigDecimal(obj.get("SFTGDDECC"))); - dataLoad.setSecondaryPositiveReactiveEnergy(StringUtils.getBigDecimal(obj.get("RXWGDDECC"))); - dataLoad.setSecondaryPositiveActiveEnergy(StringUtils.getBigDecimal(obj.get("XSYGDDECC"))); - dataLoad.setReverseReactiveEnergyEqMinus(StringUtils.getBigDecimal(obj.get("RXWGDDYCC"))); - dataLoad.setReverseActiveEnergyEpMinus(StringUtils.getBigDecimal(obj.get("SFYGDDYCC"))); - dataLoad.setPositiveReactiveEnergyEqPlus(StringUtils.getBigDecimal(obj.get("GXWGDDYCC"))); - dataLoad.setPositiveActiveEnergyEpPlus(StringUtils.getBigDecimal(obj.get("XSYGDDYCC"))); - - // 正向反向有功无功电能 - dataLoad.setCurrentForwardActiveTotal(StringUtils.getBigDecimal(obj.get("XSYGDDYCC"))); - dataLoad.setCurrentReverseActiveTotal(StringUtils.getBigDecimal(obj.get("SFYGDDYCC"))); - dataLoad.setCurrentForwardReactiveTotal(StringUtils.getBigDecimal(obj.get("GXWGDDYCC"))); - dataLoad.setCurrentReverseReactiveTotal(StringUtils.getBigDecimal(obj.get("RXWGDDYCC"))); - - dataLoad.setCreateBy("system"); - dataLoad.setCreateTime(DateUtils.getNowDate()); - dataLoad.setUpdateBy("system"); - dataLoad.setUpdateTime(DateUtils.getNowDate()); - dataLoad.setSiteId(SITE_ID); - dataLoad.setDeviceId(deviceId); - - emsAmmeterDataMapper.insertEmsAmmeterData(dataLoad); - - redisCache.setCacheObject(RedisKeyConstants.AMMETER + SITE_ID + "_" +deviceId, dataLoad); - } - - private void meteDataProcess(String deviceId, String dataJson) { - - //总表 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - EmsAmmeterData dataMete = new EmsAmmeterData(); - // 更新时间 - dataMete.setDataUpdateTime(new Date()); - - // 电压+电流 - dataMete.setPhaseAVoltage(StringUtils.getBigDecimal(obj.get("AXDY"))); - dataMete.setPhaseBVoltage(StringUtils.getBigDecimal(obj.get("BXDY"))); - dataMete.setPhaseCVoltage(StringUtils.getBigDecimal(obj.get("CXDY"))); - dataMete.setPhaseACurrent(StringUtils.getBigDecimal(obj.get("AXDL"))); - dataMete.setPhaseBCurrent(StringUtils.getBigDecimal(obj.get("BXDL"))); - dataMete.setPhaseCCurrent(StringUtils.getBigDecimal(obj.get("CXDL"))); - - dataMete.setAbLineVoltage(StringUtils.getBigDecimal(obj.get("ABXDY"))); - dataMete.setCbLineVoltage(StringUtils.getBigDecimal(obj.get("BCXDY"))); - dataMete.setAcLineVoltage(StringUtils.getBigDecimal(obj.get("CAXDY"))); - - // 频率 - dataMete.setFrequency(StringUtils.getBigDecimal(obj.get("DWPL"))); - - // 功率 - dataMete.setPhaseAActivePower(StringUtils.getBigDecimal(obj.get("AXYGGL"))); - dataMete.setPhaseBActivePower(StringUtils.getBigDecimal(obj.get("BXYGGL"))); - dataMete.setPhaseCActivePower(StringUtils.getBigDecimal(obj.get("CXYGGL"))); - dataMete.setTotalActivePower(StringUtils.getBigDecimal(obj.get("ZYGGL"))); - dataMete.setPhaseAReactivePower(StringUtils.getBigDecimal(obj.get("AXWGGL"))); - dataMete.setPhaseBReactivePower(StringUtils.getBigDecimal(obj.get("BXWGGL"))); - dataMete.setPhaseCReactivePower(StringUtils.getBigDecimal(obj.get("CXWGGL"))); - dataMete.setTotalReactivePower(StringUtils.getBigDecimal(obj.get("ZWGGL"))); - dataMete.setPhaseAApparentPower(StringUtils.getBigDecimal(obj.get("AXSZGL"))); - dataMete.setPhaseBApparentPower(StringUtils.getBigDecimal(obj.get("BXSZGL"))); - dataMete.setPhaseCApparentPower(StringUtils.getBigDecimal(obj.get("CXSZGL"))); - dataMete.setTotalApparentPower(StringUtils.getBigDecimal(obj.get("ZSZGL"))); - dataMete.setPhaseAPowerFactor(StringUtils.getBigDecimal(obj.get("AXGLYS"))); - dataMete.setPhaseBPowerFactor(StringUtils.getBigDecimal(obj.get("BXGLYS"))); - dataMete.setPhaseCPowerFactor(StringUtils.getBigDecimal(obj.get("CXGLYS"))); - dataMete.setTotalPowerFactor(StringUtils.getBigDecimal(obj.get("ZGLYS"))); - - // 电能设置-组合有功 - dataMete.setCurrentCombActiveTotal(StringUtils.getBigDecimal(obj.get("DQZHYGZDN"))); - dataMete.setCurrentCombActivePeak(StringUtils.getBigDecimal(obj.get("DQZHYGJDN"))); - dataMete.setCurrentCombActiveHigh(StringUtils.getBigDecimal(obj.get("DQZHYGFDN"))); - dataMete.setCurrentCombActiveFlat(StringUtils.getBigDecimal(obj.get("DQZHYGPDN"))); - dataMete.setCurrentCombActiveValley(StringUtils.getBigDecimal(obj.get("DQZHYGGDN"))); - // 电能设置-正向有功 - dataMete.setCurrentForwardActiveTotal(StringUtils.getBigDecimal(obj.get("DQZXZYGDN"))); - dataMete.setCurrentForwardActivePeak(StringUtils.getBigDecimal(obj.get("DQZXYGJDN"))); - dataMete.setCurrentForwardActiveHigh(StringUtils.getBigDecimal(obj.get("DQZXYGFDN"))); - dataMete.setCurrentForwardActiveFlat(StringUtils.getBigDecimal(obj.get("DQZXYGPDN"))); - dataMete.setCurrentForwardActiveValley(StringUtils.getBigDecimal(obj.get("DQZXYGGDN"))); - // 电能设置-反向有功 - dataMete.setCurrentReverseActiveTotal(StringUtils.getBigDecimal(obj.get("DQFXZYGDN"))); - dataMete.setCurrentReverseActivePeak(StringUtils.getBigDecimal(obj.get("DQFXYGJDN"))); - dataMete.setCurrentReverseActiveHigh(StringUtils.getBigDecimal(obj.get("DQFXYGFDN"))); - dataMete.setCurrentReverseActiveFlat(StringUtils.getBigDecimal(obj.get("DQFXYGPDN"))); - dataMete.setCurrentReverseActiveValley(StringUtils.getBigDecimal(obj.get("DQFXYGGDN"))); - // 电能设置-组合无功 - dataMete.setCurrentCombReactiveTotal(StringUtils.getBigDecimal(obj.get("DQZHWGZDN"))); - dataMete.setCurrentCombReactivePeak(StringUtils.getBigDecimal(obj.get("DQZHWGJDN"))); - dataMete.setCurrentCombReactiveHigh(StringUtils.getBigDecimal(obj.get("DQZHWGFDN"))); - dataMete.setCurrentCombReactiveFlat(StringUtils.getBigDecimal(obj.get("DQZHWGPDN"))); - dataMete.setCurrentCombReactiveValley(StringUtils.getBigDecimal(obj.get("DQZHWGGDN"))); - // 电能设置-正向无功 - dataMete.setCurrentForwardReactiveTotal(StringUtils.getBigDecimal(obj.get("DQZXZWGDN"))); - dataMete.setCurrentForwardReactivePeak(StringUtils.getBigDecimal(obj.get("DQZXWGJDN"))); - dataMete.setCurrentForwardReactiveHigh(StringUtils.getBigDecimal(obj.get("DQZXWGFDN"))); - dataMete.setCurrentForwardReactiveFlat(StringUtils.getBigDecimal(obj.get("DQZXWGPDN"))); - dataMete.setCurrentForwardReactiveValley(StringUtils.getBigDecimal(obj.get("DQZXWGGDN"))); - // 电能设置-反向无功 - dataMete.setCurrentReverseReactiveTotal(StringUtils.getBigDecimal(obj.get("DQFXZWGDN"))); - dataMete.setCurrentReverseReactivePeak(StringUtils.getBigDecimal(obj.get("DQFXWGJDN"))); - dataMete.setCurrentReverseReactiveHigh(StringUtils.getBigDecimal(obj.get("DQFXWGFDN"))); - dataMete.setCurrentReverseReactiveFlat(StringUtils.getBigDecimal(obj.get("DQFXWGPDN"))); - dataMete.setCurrentReverseReactiveValley(StringUtils.getBigDecimal(obj.get("DQFXWGGDN"))); - // abc相 - dataMete.setaForwardActiveEnergy(StringUtils.getBigDecimal(obj.get("AXZXYGDN"))); - dataMete.setbForwardActiveEnergy(StringUtils.getBigDecimal(obj.get("BXZXYGDN"))); - dataMete.setcForwardActiveEnergy(StringUtils.getBigDecimal(obj.get("CXZXYGDN"))); - - // 变比 - dataMete.setCurrentPercent(StringUtils.getBigDecimal(obj.get("DLBBCT"))); - dataMete.setVoltagePercent(StringUtils.getBigDecimal(obj.get("DYBBPT"))); - - // 需量 - dataMete.setForwardAcMaxDemand(StringUtils.getBigDecimal(obj.get("ZXYGZDXL"))); - dataMete.setReverseAcMaxDemand(StringUtils.getBigDecimal(obj.get("FXYGZDXL"))); - dataMete.setDailyForwardMaxDemand(StringUtils.getBigDecimal(obj.get("DRZXYGZDXL"))); - dataMete.setForwardReactiveMaxDemand(StringUtils.getBigDecimal(obj.get("ZXWGZDXL"))); - dataMete.setReverseReactiveMaxDemand(StringUtils.getBigDecimal(obj.get("FXWGZDXL"))); - dataMete.setDailyReverseAcMaxDemand(StringUtils.getBigDecimal(obj.get("DRFXYGZDXL"))); - dataMete.setDailyForwardReacMaxDemand(StringUtils.getBigDecimal(obj.get("DRZXWGZDXL"))); - dataMete.setDailyReverseReacMaxDemand(StringUtils.getBigDecimal(obj.get("DRFXWGZDXL"))); - - dataMete.setPreDayForwardAcMaxDemand(StringUtils.getBigDecimal(obj.get("S1RZXYGZDXL"))); - dataMete.setPreDayReverseAcMaxDemand(StringUtils.getBigDecimal(obj.get("S1RFXYGZDXL"))); - dataMete.setPreDayForwardReacMaxDemand(StringUtils.getBigDecimal(obj.get("S1RZXWGZDX"))); - dataMete.setPreDayReverseReacMaxDemand(StringUtils.getBigDecimal(obj.get("S1RFXWGZDX"))); - dataMete.setPre2dForwardAcMaxDemand(StringUtils.getBigDecimal(obj.get("S2RZXYGZDXL"))); - dataMete.setPre2dReverseAcMaxDemand(StringUtils.getBigDecimal(obj.get("S2RFXYGZDXL"))); - dataMete.setPre2dForwardReacMaxDemand(StringUtils.getBigDecimal(obj.get("S2RZXWGZDXL"))); - dataMete.setPre2dReverseReacMaxDemand(StringUtils.getBigDecimal(obj.get("S2RFXWGZDXL"))); - - dataMete.setCurrentForwardAcDemand(StringUtils.getBigDecimal(obj.get("DQZXYGXL"))); - dataMete.setCurrentReverseAcDemand(StringUtils.getBigDecimal(obj.get("DQFXYGXL"))); - dataMete.setCurrentForwardReacDemand(StringUtils.getBigDecimal(obj.get("DQZXWGXL"))); - dataMete.setCurrentReverseReacDemand(StringUtils.getBigDecimal(obj.get("DQFXWGXL"))); - - // 其他字段 - dataMete.setDidoStatus(StringUtils.getString(obj.get("DIDOZTSYZT"))); - dataMete.setRunningStatus(StringUtils.getString(obj.get("YXZT"))); - dataMete.setZeroSeqCurrent(StringUtils.getBigDecimal(obj.get("LXDL"))); - dataMete.setVoltageUnbalanceDegree(StringUtils.getBigDecimal(obj.get("DYBPHD"))); - dataMete.setCurrentUnbalanceDegree(StringUtils.getBigDecimal(obj.get("DLBPHD"))); - - // 储能功率 - dataMete.setSecondaryTotalActivePower(StringUtils.getBigDecimal(obj.get("ZYGGL"))); - - dataMete.setCreateBy("system"); - dataMete.setCreateTime(DateUtils.getNowDate()); - dataMete.setUpdateBy("system"); - dataMete.setUpdateTime(DateUtils.getNowDate()); - dataMete.setSiteId(SITE_ID); - dataMete.setDeviceId(deviceId); - - emsAmmeterDataMapper.insertEmsAmmeterData(dataMete); - - redisCache.setCacheObject(RedisKeyConstants.AMMETER + SITE_ID + "_" +deviceId, dataMete); - - // 处理每日充放电数据 - dealDDSDailyChargeDate(obj,deviceId); - } - - private EmsDailyEnergyData initEnergyData() { - EmsDailyEnergyData energyData = new EmsDailyEnergyData(); - energyData.setSiteId(SITE_ID); - energyData.setDataDate(DateUtils.getNowDate()); - energyData.setCreateBy("system"); - energyData.setCreateTime(DateUtils.getNowDate()); - energyData.setUpdateBy("system"); - energyData.setUpdateTime(DateUtils.getNowDate()); - return energyData; - } - - private void dealDDSDailyChargeDate(Map obj, String deviceId) { - // 初始化今日充放电 - BigDecimal dailyDisChargeDate = new BigDecimal(0); - BigDecimal dailyChargeDate = new BigDecimal(0); - - BigDecimal nowTotalDisChargeDate = StringUtils.getBigDecimal(obj.get("DQFXZYGDN")); - BigDecimal nowTotalChargeDate = StringUtils.getBigDecimal(obj.get("DQZXZYGDN")); - // 初始化当日数据-总的 - EmsDailyChargeData emsDailyChargeData = initDailyChargeData(deviceId,nowTotalChargeDate,nowTotalDisChargeDate); - // 获取redis存放昨日最晚数据 - String yestDate = DateUtils.getYesterdayDate(); - String yestDateRedisKey = RedisKeyConstants.AMMETER + SITE_ID + "_" + deviceId + "_" + yestDate; - EmsAmmeterData yestData = redisCache.getCacheObject(yestDateRedisKey); - if (yestData == null) { - // redis没有这查电表总数据表取截止到昨日最新第一条数据 - yestData = emsAmmeterDataMapper.getYestLatestDate(SITE_ID,deviceId,yestDate); - // 数据存redis-有效期1天 - redisCache.setCacheObject(yestDateRedisKey, yestData , Constants.DATE_VALID_TIME, TimeUnit.DAYS); - } - if (yestData != null) { - // 今日总数据-昨日总数据=今日充放电 - BigDecimal yestTotalDisChargeDate = yestData.getCurrentReverseActiveTotal(); - BigDecimal yestTotalChargeDate = yestData.getCurrentForwardActiveTotal(); - - dailyChargeDate = nowTotalChargeDate.subtract(yestTotalChargeDate); - dailyDisChargeDate = nowTotalDisChargeDate.subtract(yestTotalDisChargeDate); - emsDailyChargeData.setChargeData(dailyChargeDate); - emsDailyChargeData.setDischargeData(dailyDisChargeDate); - } - - // 插入或更新每日充放电数据表 - emsDailyChargeDataMapper.insertOrUpdateData(emsDailyChargeData); - - // 初始化数据-尖峰平谷 - EmsDailyEnergyData energyData = initEnergyData(); - // 计算尖峰平谷差值,更新表 - calcEnergyDiffAndRevenue(energyData,obj,yestData); - energyData.setCalcTime(DateUtils.getNowDate()); - // 插入或更新电表每日差值数据表 - emsDailyEnergyDataMapper.insertOrUpdateData(energyData); - } - - private void calcEnergyDiffAndRevenue(EmsDailyEnergyData energyData, Map obj, EmsAmmeterData yestData) { - - // 获取当月电价 - String key = RedisKeyConstants.ENERGY_PRICE_TIME + SITE_ID + "_" + LocalDate.now().getYear() + LocalDate.now().getMonthValue(); - EnergyPriceVo priceVo = redisCache.getCacheObject(key); - - // 计算尖峰平谷差值 - // 正反向-尖 - BigDecimal peakChargeDiff = StringUtils.getBigDecimal(obj.get("DQZXYGJDN")) - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActivePeak()); - energyData.setPeakChargeDiff(peakChargeDiff); - BigDecimal peakDischargeDiff = StringUtils.getBigDecimal(obj.get("DQFXYGJDN")) - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActivePeak()); - energyData.setPeakDischargeDiff(peakDischargeDiff); - // 正反向-峰 - BigDecimal highChargeDiff = StringUtils.getBigDecimal(obj.get("DQZXYGFDN")) - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveHigh()); - energyData.setHighChargeDiff(highChargeDiff); - BigDecimal highDischargeDiff = StringUtils.getBigDecimal(obj.get("DQFXYGFDN")) - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveHigh()); - energyData.setHighDischargeDiff(highDischargeDiff); - // 正反向-平 - BigDecimal flatChargeDiff = StringUtils.getBigDecimal(obj.get("DQZXYGPDN")) - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveFlat()); - energyData.setFlatChargeDiff(flatChargeDiff); - BigDecimal flatDisChargeDiff = StringUtils.getBigDecimal(obj.get("DQFXYGPDN")) - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveFlat()); - energyData.setFlatDischargeDiff(flatDisChargeDiff); - // 正反向-谷 - BigDecimal valleyChargeDiff = StringUtils.getBigDecimal(obj.get("DQZXYGGDN")) - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveValley()); - energyData.setValleyChargeDiff(valleyChargeDiff); - BigDecimal valleyDisChargeDiff = StringUtils.getBigDecimal(obj.get("DQFXYGGDN")) - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveValley()); - energyData.setValleyDischargeDiff(valleyDisChargeDiff); - - - BigDecimal totalRevenue = getYestLastData(SITE_ID); - BigDecimal dayRevenue = BigDecimal.ZERO; - BigDecimal price = BigDecimal.ZERO; - // 计算当日收益,尖峰平谷收益累加,(放电量-充电量)*电价 - if (priceVo != null) { - price = priceVo.getPeak() == null ? BigDecimal.ZERO : priceVo.getPeak(); - BigDecimal peakRevenue = peakDischargeDiff.subtract(peakChargeDiff).multiply(price); - dayRevenue = dayRevenue.add(peakRevenue); - price = priceVo.getHigh() == null ? BigDecimal.ZERO : priceVo.getHigh(); - BigDecimal highRevenue = highDischargeDiff.subtract(highChargeDiff).multiply(price); - dayRevenue = dayRevenue.add(highRevenue); - price = priceVo.getFlat() == null ? BigDecimal.ZERO : priceVo.getFlat(); - BigDecimal flatRevenue = flatDisChargeDiff.subtract(flatChargeDiff).multiply(price); - dayRevenue = dayRevenue.add(flatRevenue); - price = priceVo.getValley() == null ? BigDecimal.ZERO : priceVo.getValley(); - BigDecimal valleyRevenue = valleyDisChargeDiff.subtract(valleyChargeDiff).multiply(price); - dayRevenue = dayRevenue.add(valleyRevenue); - energyData.setDayRevenue(dayRevenue); - } - // 总收益 = 昨日总收益+今日实时收益 - totalRevenue = totalRevenue.add(dayRevenue); - energyData.setTotalRevenue(totalRevenue); - } - - private BigDecimal getYestLastData(String siteId) { - // dds存的是累计到昨日总收益 - String yestDate = DateUtils.getYesterdayDayString(); - String redisKey = RedisKeyConstants.DDS_TOTAL_REVENUE + siteId + "_" + yestDate; - BigDecimal yestLastTotalRevenue = redisCache.getCacheObject(redisKey); - if (yestLastTotalRevenue == null) { - yestLastTotalRevenue = emsDailyEnergyDataMapper.getLastTotalRevenue(siteId); - if (yestLastTotalRevenue == null) { - yestLastTotalRevenue = BigDecimal.ZERO; - } - redisCache.setCacheObject(redisKey, yestLastTotalRevenue, 1 , TimeUnit.DAYS); - } - return yestLastTotalRevenue; - } - - private EmsDailyChargeData initDailyChargeData(String deviceId, BigDecimal nowTotalChargeDate, - BigDecimal nowTotalDisChargeDate) { - EmsDailyChargeData emsDailyChargeData = new EmsDailyChargeData(); - emsDailyChargeData.setSiteId(SITE_ID); - emsDailyChargeData.setDeviceId(deviceId); - emsDailyChargeData.setDateTime(DateUtils.getNowDate()); - emsDailyChargeData.setTotalChargeData(nowTotalChargeDate); - emsDailyChargeData.setTotalDischargeData(nowTotalDisChargeDate); - emsDailyChargeData.setCreateBy("system"); - emsDailyChargeData.setCreateTime(DateUtils.getNowDate()); - emsDailyChargeData.setUpdateBy("system"); - emsDailyChargeData.setUpdateTime(DateUtils.getNowDate()); - return emsDailyChargeData; - } - - // 数据分组处理 - private static Map> processData(Map rawData) { - Map> records = new HashMap<>(); - - for (Map.Entry entry : rawData.entrySet()) { - String key = entry.getKey(); - // 提取记录ID(最后3位) - String recordId = key.substring(key.length() - 3); - try { - Long.parseLong(recordId); - } catch (Exception e) { - continue; - } - - // 提取字段类型(前缀) - String fieldType = key.substring(0, key.length() - 3); - - // 初始化记录 - records.putIfAbsent(recordId, new HashMap<>()); - // 存入字段值 - records.get(recordId).put(fieldType, entry.getValue()); - } - return records; - } - - // 空数据不处理 - private boolean checkJsonDataEmpty(String jsonData) { - boolean flag = false; - try { - if (StringUtils.isEmpty(jsonData)) { - flag = true; - } - JsonNode jsonNode = objectMapper.readTree(jsonData); - // 判断是否为空对象({}) - if (jsonNode.isObject() && jsonNode.isEmpty()) { - flag = true ; - } - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - return flag; - } - -} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java index fcb4274..00e346d 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/DeviceDataProcessServiceImpl.java @@ -5,7 +5,6 @@ import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONException; import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.TypeReference; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.xzzn.common.constant.Constants; @@ -28,6 +27,7 @@ import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.MapUtils; import com.xzzn.common.utils.StringUtils; import com.xzzn.ems.domain.*; +import com.xzzn.ems.domain.vo.AmmeterRevenueStatisListVo; import com.xzzn.ems.domain.vo.EnergyPriceTimeRange; import com.xzzn.ems.domain.vo.EnergyPriceVo; import com.xzzn.ems.enums.DeviceMatchTable; @@ -48,36 +48,56 @@ import com.xzzn.ems.mapper.EmsEmsDataMapper; import com.xzzn.ems.mapper.EmsPcsAlarmDataMapper; import com.xzzn.ems.mapper.EmsPcsBranchDataMapper; import com.xzzn.ems.mapper.EmsPcsDataMapper; +import com.xzzn.ems.mapper.EmsPointConfigMapper; +import com.xzzn.ems.mapper.EmsSiteMonitorPointMatchMapper; import com.xzzn.ems.mapper.EmsStackAlarmDataMapper; import com.xzzn.ems.mapper.EmsXfDataMapper; import com.xzzn.ems.service.IDeviceDataProcessService; import com.xzzn.ems.service.IEmsAlarmRecordsService; import com.xzzn.ems.service.IEmsDeviceSettingService; import com.xzzn.ems.service.IEmsEnergyPriceConfigService; +import com.xzzn.ems.service.InfluxPointDataWriter; import com.xzzn.ems.utils.AbstractBatteryDataProcessor; import com.xzzn.ems.utils.DevicePointMatchDataProcessor; import java.lang.reflect.Field; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.YearMonth; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.Deque; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.Collections; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; + import org.apache.commons.collections4.CollectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -92,7 +112,60 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i private static final Pattern PATTERN = Pattern.compile("(BMSD\\d{2})(ZT|SOC|SOH|DL|DY|BDSC)"); // 匹配DTDC+数字格式的正则(提取序号) private static final Pattern DTDC_PATTERN = Pattern.compile("DTDC(\\d+)([A-Za-z]*)"); - private final Map topicEmptyCounts = new ConcurrentHashMap<>(); + private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); + private static final Pattern VARIABLE_SAFE_PATTERN = Pattern.compile("[^A-Za-z0-9_]"); + private static final Pattern SIMPLE_CALC_EXPRESSION_PATTERN = Pattern.compile("^[0-9A-Za-z_+\\-*/().,?:<>=!&|\\s]+$"); + private static final Pattern AUTO_DAY_DIFF_FUNCTION_PATTERN = Pattern.compile("(?i)DAY_DIFF\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"); + private static final Pattern AUTO_MONTH_DIFF_FUNCTION_PATTERN = Pattern.compile("(?i)MONTH_DIFF\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"); + private static final Pattern AUTO_HOUR_DIFF_FUNCTION_PATTERN = Pattern.compile("(?i)HOUR_DIFF\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"); + private static final int POINT_QUEUE_CAPACITY = 100000; + private static final int POINT_FLUSH_BATCH_SIZE = 2000; + private static final int POINT_FLUSH_MAX_DRAIN_PER_RUN = 20000; + private static final int POINT_ENQUEUE_RETRY_TIMES = 3; + private static final long POINT_ENQUEUE_RETRY_WAIT_MS = 10; + private static final long POINT_FLUSH_INTERVAL_MS = 100; + private static final String SITE_LEVEL_CALC_DEVICE_ID = "SITE_CALC"; + private static final int MONITOR_POINT_MATCH_REDIS_TTL_SECONDS = 300; + private static final String DELETED_FIELD_MARK = "__DELETED__"; + private static final String AUTO_DAY_DIFF_CONTEXT_PREFIX = "__AUTO_DAY_DIFF__"; + private static final String AUTO_MONTH_DIFF_CONTEXT_PREFIX = "__AUTO_MONTH_DIFF__"; + private static final String AUTO_HOUR_DIFF_CONTEXT_PREFIX = "__AUTO_HOUR_DIFF__"; + private static final String MONITOR_FIELD_PREFIX_HOME = "home__"; + private static final String MONITOR_FIELD_PREFIX_TJBB_DBBB = "tjbb_dbbb__"; + private static final String FIELD_CODE_TOTAL_CHARGE = "home__totalChargedCap"; + private static final String FIELD_CODE_TOTAL_DISCHARGE = "home__totalDischargedCap"; + private static final String FIELD_CODE_DAY_CHARGE = "home__dayChargedCap"; + private static final String FIELD_CODE_DAY_DISCHARGE = "home__dayDisChargedCap"; + private static final String FIELD_CODE_PEAK_CHARGE = "tjbb_dbbb__activePeakKwh"; + private static final String FIELD_CODE_PEAK_DISCHARGE = "tjbb_dbbb__reActivePeakKwh"; + private static final String FIELD_CODE_HIGH_CHARGE = "tjbb_dbbb__activeHighKwh"; + private static final String FIELD_CODE_HIGH_DISCHARGE = "tjbb_dbbb__reActiveHighKwh"; + private static final String FIELD_CODE_FLAT_CHARGE = "tjbb_dbbb__activeFlatKwh"; + private static final String FIELD_CODE_FLAT_DISCHARGE = "tjbb_dbbb__reActiveFlatKwh"; + private static final String FIELD_CODE_VALLEY_CHARGE = "tjbb_dbbb__activeValleyKwh"; + private static final String FIELD_CODE_VALLEY_DISCHARGE = "tjbb_dbbb__reActiveValleyKwh"; + private static final String[] FIELD_SUFFIX_TOTAL_CHARGE = { + "activeTotalKwh", "totalChargeData", "totalCharge", "totalChargedCap", "chargedCap" + }; + private static final String[] FIELD_SUFFIX_TOTAL_DISCHARGE = { + "reActiveTotalKwh", "totalDischargeData", "totalDischarge", "totalDischargedCap", "disChargedCap" + }; + private static final String[] FIELD_SUFFIX_DAY_CHARGE = { + "dayChargedCap", "dayChargeData", "dailyChargeData", "dailyChargedCap" + }; + private static final String[] FIELD_SUFFIX_DAY_DISCHARGE = { + "dayDisChargedCap", "dayDischargeData", "dailyDischargeData", "dailyDisChargedCap" + }; + private static final String[] FIELD_SUFFIX_PEAK_CHARGE = {"activePeakKwh", "peakChargeDiff", "peakCharge"}; + private static final String[] FIELD_SUFFIX_PEAK_DISCHARGE = {"reActivePeakKwh", "peakDischargeDiff", "peakDischarge"}; + private static final String[] FIELD_SUFFIX_HIGH_CHARGE = {"activeHighKwh", "highChargeDiff", "highCharge"}; + private static final String[] FIELD_SUFFIX_HIGH_DISCHARGE = {"reActiveHighKwh", "highDischargeDiff", "highDischarge"}; + private static final String[] FIELD_SUFFIX_FLAT_CHARGE = {"activeFlatKwh", "flatChargeDiff", "flatCharge"}; + private static final String[] FIELD_SUFFIX_FLAT_DISCHARGE = {"reActiveFlatKwh", "flatDischargeDiff", "flatDischarge"}; + private static final String[] FIELD_SUFFIX_VALLEY_CHARGE = {"activeValleyKwh", "valleyChargeDiff", "valleyCharge"}; + private static final String[] FIELD_SUFFIX_VALLEY_DISCHARGE = {"reActiveValleyKwh", "valleyDischargeDiff", "valleyDischarge"}; + private static final long DAILY_CHARGE_INFLUX_QUERY_WINDOW_MS = TimeUnit.DAYS.toMillis(2); + private static final long CALC_POINT_INFLUX_QUERY_WINDOW_MS = TimeUnit.DAYS.toMillis(2); @Autowired private EmsBatteryClusterMapper emsBatteryClusterMapper; @@ -103,6 +176,8 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i @Autowired private EmsPcsDataMapper emsPcsDataMapper; @Autowired + private EmsPointConfigMapper emsPointConfigMapper; + @Autowired private EmsPcsBranchDataMapper emsPcsBranchDataMapper; @Autowired private EmsAmmeterDataMapper emsAmmeterDataMapper; @@ -124,6 +199,8 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i private EmsBatteryGroupMapper emsBatteryGroupMapper; @Autowired private EmsEmsDataMapper emsEmsDataMapper; + @Autowired + private EmsSiteMonitorPointMatchMapper emsSiteMonitorPointMatchMapper; @Autowired private EmsCoolingAlarmDataMapper emsCoolingAlarmDataMapper; @@ -147,57 +224,2576 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i @Autowired private RedisCache redisCache; + @Autowired + private InfluxPointDataWriter influxPointDataWriter; + private final BlockingQueue pointDataQueue = new LinkedBlockingQueue<>(POINT_QUEUE_CAPACITY); + private final Map calcExpressionCache = new ConcurrentHashMap<>(); + private final ScheduledExecutorService pointDataWriter = Executors.newSingleThreadScheduledExecutor(r -> { + Thread thread = new Thread(r); + thread.setName("point-data-writer"); + thread.setDaemon(true); + return thread; + }); + private final AtomicBoolean pointDataFlushing = new AtomicBoolean(false); // 构造方法(调用父类构造) public DeviceDataProcessServiceImpl(ObjectMapper objectMapper) { super(objectMapper); } + @PostConstruct + public void initPointDataBatchWriter() { + pointDataWriter.scheduleWithFixedDelay(this::flushPointDataSafely, + POINT_FLUSH_INTERVAL_MS, POINT_FLUSH_INTERVAL_MS, TimeUnit.MILLISECONDS); + } + + @PreDestroy + public void destroyPointDataBatchWriter() { + flushPointDataSafely(); + pointDataWriter.shutdown(); + } + @Override public void handleDeviceData(String message, String siteId) { JSONArray arraylist = parseJsonData(message); if (arraylist == null) return; - // 过滤掉空数据(空数据不处理,直接返回 - + long startMs = System.currentTimeMillis(); + log.info("开始处理设备数据, siteId: {}, messageSize: {}", siteId, arraylist.size()); + Set deviceIds = new LinkedHashSet<>(); + Map deviceCategoryMap = buildDeviceCategoryMap(siteId, arraylist); for (int i = 0; i < arraylist.size(); i++) { JSONObject obj = JSONObject.parseObject(arraylist.get(i).toString()); String deviceId = obj.getString("Device"); - String jsonData = obj.getString("Data"); + String jsonData = resolveJsonDataWithRedisFallback(siteId, deviceId, obj.getString("Data")); Long timestamp = obj.getLong("timestamp"); Date dataUpdateTime = DateUtils.convertUpdateTime(timestamp); - log.info("deviceId:" + deviceId); - String deviceNumber = siteId + deviceId; - boolean isEmpty = checkJsonDataEmpty(jsonData); - if (isEmpty) { - // 增加失败计数 - int failureCount = topicEmptyCounts.getOrDefault(deviceNumber, 0) + 1; - topicEmptyCounts.put(deviceNumber, failureCount); + if (StringUtils.isNotBlank(deviceId)) { + deviceIds.add(deviceId); + } + if (checkJsonDataEmpty(jsonData)) { + continue; + } - log.warn("设备 {} topic内没有数据,当前连续次数: {}", deviceId, failureCount); + try { + String mergedJsonData = mergeWithLatestRedisData(siteId, deviceId, jsonData); + obj.put("Data", mergedJsonData); + // 保留每个设备最新一条原始报文,供“点位最新值”从 Redis 读取 + redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceId, obj); + redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId, obj, 1, TimeUnit.MINUTES); - // 连续5次失败触发报警 - if (failureCount >= 5) { - // 添加设备告警 - iEmsAlarmRecordsService.addEmptyDataAlarmRecord(siteId, deviceId); - log.error("设备 {} 连续 {} 次topic内没有数据,触发告警", deviceNumber, failureCount); + String deviceCategory = resolveDeviceCategory(siteId, deviceId, deviceCategoryMap); + // 旧设备表落库链路已下线,MQTT 数据仅走点位映射与站点监控数据同步。 + Map pointIdValueMap = processPointConfigData(siteId, deviceId, deviceCategory, mergedJsonData, dataUpdateTime); + List pointIdSamples = pointIdValueMap.keySet().stream().limit(10).collect(Collectors.toList()); + log.info("MQTT点位映射完成,siteId: {}, deviceId: {}, deviceCategory: {}, pointCount: {}, pointIdSamples: {}", + siteId, deviceId, deviceCategory, pointIdValueMap.size(), pointIdSamples); + cacheLatestPointValuesByPointId(siteId, deviceId, pointIdValueMap, dataUpdateTime); + iEmsDeviceSettingService.syncSiteMonitorDataByMqtt(siteId, deviceId, JSON.toJSONString(pointIdValueMap), dataUpdateTime); + } catch (Exception e) { + log.warn("设备数据处理失败,siteId: {}, deviceId: {}, err: {}", + siteId, deviceId, e.getMessage(), e); + } + } + log.info("结束处理设备数据, siteId: {}, deviceCount: {}, deviceIds: {}, costMs: {}", + siteId, deviceIds.size(), String.join(",", deviceIds), System.currentTimeMillis() - startMs); + } + + private Map processPointConfigData(String siteId, String deviceId, String deviceCategory, String jsonData, Date dataUpdateTime) { + Map pointIdValueMap = new HashMap<>(); + if (StringUtils.isAnyBlank(siteId, deviceId, jsonData)) { + return pointIdValueMap; + } + if (!DB_NAME_PATTERN.matcher(siteId).matches()) { + log.warn("站点ID不合法,跳过点位映射落库,siteId: {}", siteId); + return pointIdValueMap; + } + + Map dataMap = JSON.parseObject(jsonData, new TypeReference>() { + }); + if (org.apache.commons.collections4.MapUtils.isEmpty(dataMap)) { + return pointIdValueMap; + } + + List pointConfigs = getPointConfigsWithCache(siteId, deviceId, deviceCategory); + if (CollectionUtils.isEmpty(pointConfigs)) { + return pointIdValueMap; + } + + List dataPointConfigs = new ArrayList<>(); + Set uniquePointKeys = new HashSet<>(); + for (EmsPointConfig pointConfig : pointConfigs) { + String pointContextKey = resolvePointContextKey(pointConfig); + if (pointConfig == null || StringUtils.isBlank(pointContextKey)) { + continue; + } + if (!uniquePointKeys.add(pointContextKey)) { + continue; + } + if (isComputedPoint(pointConfig)) { + continue; + } + dataPointConfigs.add(pointConfig); + } + Map rawDataValues = new HashMap<>(); + for (Map.Entry entry : dataMap.entrySet()) { + if (StringUtils.isBlank(entry.getKey())) { + continue; + } + BigDecimal pointValue = StringUtils.getBigDecimal(entry.getValue()); + if (pointValue == null) { + continue; + } + rawDataValues.put(entry.getKey().trim().toUpperCase(), pointValue); + } + + for (EmsPointConfig dataPointConfig : dataPointConfigs) { + BigDecimal sourceValue = getSourceValueFromRawData(dataPointConfig, rawDataValues); + if (sourceValue == null) { + continue; + } + BigDecimal convertedValue = convertPointValue(sourceValue, dataPointConfig); + String pointId = resolveInfluxPointKey(dataPointConfig); + if (StringUtils.isBlank(pointId)) { + continue; + } + enqueuePointData(siteId, deviceId, pointId, convertedValue, dataUpdateTime); + pointIdValueMap.put(pointId, convertedValue); + } + return pointIdValueMap; + } + + private void updateDailyChargeDataByQuartz(String siteId, + Map pointIdValueMap, + Date dataUpdateTime) { + if (StringUtils.isBlank(siteId) + || org.apache.commons.collections4.MapUtils.isEmpty(pointIdValueMap)) { + return; + } + + List mappingList = getPointMatchesBySiteId(siteId); + if (CollectionUtils.isEmpty(mappingList)) { + return; + } + + Map normalizedPointValueMap = new HashMap<>(); + for (Map.Entry entry : pointIdValueMap.entrySet()) { + if (StringUtils.isBlank(entry.getKey()) || entry.getValue() == null) { + continue; + } + normalizedPointValueMap.put(entry.getKey().trim().toUpperCase(), entry.getValue()); + } + if (normalizedPointValueMap.isEmpty()) { + return; + } + + EmsSiteMonitorPointMatch totalChargeMatch = firstNonBlankMatchByFieldSuffixIgnoreDevice( + mappingList, FIELD_SUFFIX_TOTAL_CHARGE + ); + EmsSiteMonitorPointMatch totalDischargeMatch = firstNonBlankMatchByFieldSuffixIgnoreDevice( + mappingList, FIELD_SUFFIX_TOTAL_DISCHARGE + ); + EmsSiteMonitorPointMatch dayChargeMatch = firstNonBlankMatchByFieldSuffixIgnoreDevice( + mappingList, FIELD_SUFFIX_DAY_CHARGE + ); + EmsSiteMonitorPointMatch dayDischargeMatch = firstNonBlankMatchByFieldSuffixIgnoreDevice( + mappingList, FIELD_SUFFIX_DAY_DISCHARGE + ); + String totalChargePointId = totalChargeMatch == null ? null : StringUtils.trimToNull(totalChargeMatch.getDataPoint()); + String totalDischargePointId = totalDischargeMatch == null ? null : StringUtils.trimToNull(totalDischargeMatch.getDataPoint()); + String dayChargePointId = dayChargeMatch == null ? null : StringUtils.trimToNull(dayChargeMatch.getDataPoint()); + String dayDischargePointId = dayDischargeMatch == null ? null : StringUtils.trimToNull(dayDischargeMatch.getDataPoint()); + log.info("日充放电自检, siteId: {}, totalCharge: {}->{} , totalDischarge: {}->{} , dayCharge: {}->{} , dayDischarge: {}->{}", + siteId, + totalChargeMatch == null ? "null" : StringUtils.defaultString(totalChargeMatch.getFieldCode()), + totalChargePointId, + totalDischargeMatch == null ? "null" : StringUtils.defaultString(totalDischargeMatch.getFieldCode()), + totalDischargePointId, + dayChargeMatch == null ? "null" : StringUtils.defaultString(dayChargeMatch.getFieldCode()), + dayChargePointId, + dayDischargeMatch == null ? "null" : StringUtils.defaultString(dayDischargeMatch.getFieldCode()), + dayDischargePointId); + BigDecimal nowTotalCharge = getPointValueByIdRaw(normalizedPointValueMap, totalChargePointId); + BigDecimal nowTotalDischarge = getPointValueByIdRaw(normalizedPointValueMap, totalDischargePointId); + BigDecimal nowDayCharge = getLatestPointValueFromRedis(dayChargePointId); + BigDecimal nowDayDischarge = getLatestPointValueFromRedis(dayDischargePointId); + if (nowTotalCharge == null || nowTotalDischarge == null) { + log.info("日充放电累计未匹配到有效点位值, siteId: {}, totalChargePointId: {}, totalDischargePointId: {}, pointKeys: {}", + siteId, totalChargePointId, totalDischargePointId, normalizedPointValueMap.keySet()); + return; + } + + EmsDailyChargeData dailyChargeData = new EmsDailyChargeData(); + Date recordDateTime = dataUpdateTime == null ? DateUtils.getNowDate() : dataUpdateTime; + dailyChargeData.setSiteId(siteId); + dailyChargeData.setDateTime(recordDateTime); + dailyChargeData.setTotalChargeData(nowTotalCharge); + dailyChargeData.setTotalDischargeData(nowTotalDischarge); + dailyChargeData.setChargeData(nowDayCharge); + dailyChargeData.setDischargeData(nowDayDischarge); + + EmsDailyChargeData existedDailyData = emsDailyChargeDataMapper.selectBySiteIdAndDateTime(siteId, recordDateTime); + if (existedDailyData != null && existedDailyData.getId() != null) { + dailyChargeData.setId(existedDailyData.getId()); + dailyChargeData.setDateTime(existedDailyData.getDateTime()); + } + + Date now = DateUtils.getNowDate(); + dailyChargeData.setCreateBy("system"); + dailyChargeData.setCreateTime(now); + dailyChargeData.setUpdateBy("system"); + dailyChargeData.setUpdateTime(now); + emsDailyChargeDataMapper.insertOrUpdateData(dailyChargeData); + + log.info("日充放电累计落库完成, siteId: {}, total[充:{} 放:{}], daily[充:{} 放:{}], points[总充:{} 总放:{} 日充:{} 日放:{}]", + siteId, + dailyChargeData.getTotalChargeData(), dailyChargeData.getTotalDischargeData(), + dailyChargeData.getChargeData(), dailyChargeData.getDischargeData(), + totalChargePointId, totalDischargePointId, dayChargePointId, dayDischargePointId); + } + + public void syncDailyChargeDataFromInfluxByQuartz() { + syncDailyChargeDataFromInfluxByQuartz(null); + } + + public void syncDailyChargeDataFromInfluxByQuartz(String siteId) { + List siteIds; + if (StringUtils.isNotBlank(siteId)) { + siteIds = Collections.singletonList(siteId.trim()); + } else { + siteIds = emsSiteMonitorPointMatchMapper.selectDistinctSiteIds(); + } + if (CollectionUtils.isEmpty(siteIds)) { + log.info("Quartz同步日充放电跳过,未找到站点列表, siteIdParam: {}", siteId); + return; + } + for (String currentSiteId : siteIds) { + if (StringUtils.isBlank(currentSiteId)) { + continue; + } + try { + syncSingleSiteDailyChargeFromInflux(currentSiteId.trim()); + } catch (Exception ex) { + log.error("Quartz同步日充放电失败, siteId: {}, err: {}", currentSiteId, ex.getMessage(), ex); + } + } + } + + private void syncSingleSiteDailyChargeFromInflux(String siteId) { + List mappingList = getPointMatchesBySiteId(siteId); + if (CollectionUtils.isEmpty(mappingList)) { + log.info("Quartz同步日充放电跳过,未找到点位映射, siteId: {}", siteId); + return; + } + + Date queryEndTime = DateUtils.getNowDate(); + Date queryStartTime = new Date(queryEndTime.getTime() - DAILY_CHARGE_INFLUX_QUERY_WINDOW_MS); + String totalChargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_TOTAL_CHARGE); + String totalDischargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_TOTAL_DISCHARGE); + String dayChargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_DAY_CHARGE); + String dayDischargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_DAY_DISCHARGE); + if (StringUtils.isBlank(dayChargePointId)) { + dayChargePointId = firstNonBlankPointByFieldSuffixIgnoreDevice(mappingList, FIELD_SUFFIX_DAY_CHARGE); + } + if (StringUtils.isBlank(dayDischargePointId)) { + dayDischargePointId = firstNonBlankPointByFieldSuffixIgnoreDevice(mappingList, FIELD_SUFFIX_DAY_DISCHARGE); + } + log.info("Quartz同步日充放电日累自检, siteId: {}, dayChargePoint: {}, dayDischargePoint: {}", + siteId, + dayChargePointId, + dayDischargePointId); + String peakChargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_PEAK_CHARGE); + String peakDischargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_PEAK_DISCHARGE); + String highChargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_HIGH_CHARGE); + String highDischargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_HIGH_DISCHARGE); + String flatChargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_FLAT_CHARGE); + String flatDischargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_FLAT_DISCHARGE); + String valleyChargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_VALLEY_CHARGE); + String valleyDischargePointId = resolvePointIdByFieldCodeIgnoreDevice(mappingList, FIELD_CODE_VALLEY_DISCHARGE); + + log.info("Quartz同步日充放电自检, siteId: {}, totalCharge: {} , totalDischarge: {}, dayCharge: {}, dayDischarge: {}, peak: {} / {}, high: {} / {}, flat: {} / {}, valley: {} / {}", + siteId, + totalChargePointId, + totalDischargePointId, + dayChargePointId, + dayDischargePointId, + peakChargePointId, + peakDischargePointId, + highChargePointId, + highDischargePointId, + flatChargePointId, + flatDischargePointId, + valleyChargePointId, + valleyDischargePointId); + + Map latestPointValues = new HashMap<>(); + Date latestDataTime = null; + for (String pointId : Arrays.asList( + totalChargePointId, totalDischargePointId, + dayChargePointId, dayDischargePointId, + peakChargePointId, peakDischargePointId, + highChargePointId, highDischargePointId, + flatChargePointId, flatDischargePointId, + valleyChargePointId, valleyDischargePointId)) { + if (StringUtils.isBlank(pointId)) { + continue; + } + LatestPointValue latestPointValue = getLatestPointValuePreferRedis(siteId, pointId, queryStartTime, queryEndTime); + if (latestPointValue == null || latestPointValue.getValue() == null) { + continue; + } + latestPointValues.put(pointId.trim().toUpperCase(), latestPointValue); + if (latestPointValue.getDataTime() != null + && (latestDataTime == null || latestPointValue.getDataTime().after(latestDataTime))) { + latestDataTime = latestPointValue.getDataTime(); + } + } + + if (latestPointValues.isEmpty()) { + log.info("Quartz同步日充放电跳过,点位最新值为空, siteId: {}", siteId); + return; + } + + Date recordDateTime = latestDataTime == null ? DateUtils.getNowDate() : latestDataTime; + BigDecimal totalChargeValue = getPointValueById(latestPointValues, totalChargePointId); + BigDecimal totalDischargeValue = getPointValueById(latestPointValues, totalDischargePointId); + BigDecimal dayChargeValue = getPointValueById(latestPointValues, dayChargePointId); + BigDecimal dayDischargeValue = getPointValueById(latestPointValues, dayDischargePointId); + log.info("单站点位值汇总, siteId: {}, totalCharge: {}({}), totalDischarge: {}({}), dayCharge: {}({}), dayDischarge: {}({}), dataTime: {}", + siteId, + totalChargeValue, + totalDischargeValue, + dayChargeValue, + dayDischargeValue, + pointValueSource(latestPointValues, totalChargePointId), + pointValueSource(latestPointValues, totalDischargePointId), + pointValueSource(latestPointValues, dayChargePointId), + pointValueSource(latestPointValues, dayDischargePointId), + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, recordDateTime)); + if (totalChargeValue == null && totalDischargeValue == null) { + log.info("Quartz同步日充放电跳过,总充/总放值为空, siteId: {}", siteId); + } else { + upsertDailyChargeData(siteId, recordDateTime, totalChargeValue, totalDischargeValue, dayChargeValue, dayDischargeValue); + } + + updateDailyEnergyHourlyDataByQuartz( + siteId, + recordDateTime, + peakChargePointId, + peakDischargePointId, + highChargePointId, + highDischargePointId, + flatChargePointId, + flatDischargePointId, + valleyChargePointId, + valleyDischargePointId, + queryStartTime, + queryEndTime + ); + syncDailyChargeRevenueByQuartz(siteId, recordDateTime); + } + + private void syncDailyChargeRevenueByQuartz(String siteId, Date dataUpdateTime) { + if (StringUtils.isBlank(siteId)) { + return; + } + Date revenueDate = dataUpdateTime == null ? DateUtils.getNowDate() : dataUpdateTime; + String day = DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, revenueDate); + List revenueRows = emsDailyEnergyDataMapper.getRevenueDataBySiteId(siteId, day, day); + + BigDecimal dayRevenue = BigDecimal.ZERO; + if (CollectionUtils.isNotEmpty(revenueRows)) { + AmmeterRevenueStatisListVo revenueRow = revenueRows.get(0); + BigDecimal activeTotalPrice = safeBigDecimal(revenueRow.getActivePeakPrice()) + .add(safeBigDecimal(revenueRow.getActiveHighPrice())) + .add(safeBigDecimal(revenueRow.getActiveFlatPrice())) + .add(safeBigDecimal(revenueRow.getActiveValleyPrice())); + BigDecimal reActiveTotalPrice = safeBigDecimal(revenueRow.getReActivePeakPrice()) + .add(safeBigDecimal(revenueRow.getReActiveHighPrice())) + .add(safeBigDecimal(revenueRow.getReActiveFlatPrice())) + .add(safeBigDecimal(revenueRow.getReActiveValleyPrice())); + dayRevenue = reActiveTotalPrice.subtract(activeTotalPrice); + } + BigDecimal totalRevenue = getYestLastData(siteId).add(dayRevenue); + + syncDailyRevenueToChargeData(siteId, revenueDate, totalRevenue, dayRevenue); + + log.info("Quartz同步日收益到充放电表完成, siteId: {}, date: {}, dayRevenue: {}, totalRevenue: {}", + siteId, day, dayRevenue, totalRevenue); + } + + private void updateDailyEnergyHourlyDataByQuartz(String siteId, + Date dataUpdateTime, + String peakChargePointId, + String peakDischargePointId, + String highChargePointId, + String highDischargePointId, + String flatChargePointId, + String flatDischargePointId, + String valleyChargePointId, + String valleyDischargePointId, + Date queryStartTime, + Date queryEndTime) { + log.info("Quartz日电量小时落库入口, siteId: {}, dataTime: {}", + siteId, + dataUpdateTime == null ? "null" : DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, dataUpdateTime)); + if (StringUtils.isBlank(siteId)) { + return; + } + + Date recordDateTime = dataUpdateTime == null ? DateUtils.getNowDate() : dataUpdateTime; + Integer dataHour = resolveHourOfDay(recordDateTime); + + LocalDateTime recordLocalTime = LocalDateTime.ofInstant(recordDateTime.toInstant(), ZoneId.systemDefault()); + String priceKey = RedisKeyConstants.ENERGY_PRICE_TIME + siteId + "_" + recordLocalTime.getYear() + + recordLocalTime.getMonthValue(); + EnergyPriceVo priceVo = redisCache.getCacheObject(priceKey); + if (priceVo == null) { + priceVo = emsEnergyPriceConfigService.getCurrentMonthPrice(siteId); + redisCache.setCacheObject(priceKey, priceVo, 31, TimeUnit.DAYS); + } + if (priceVo == null || CollectionUtils.isEmpty(priceVo.getRange())) { + log.info("Quartz日电量小时落库跳过,未找到有效电价时段配置, siteId: {}", siteId); + return; + } + + String costType = null; + for (EnergyPriceTimeRange timeRange : priceVo.getRange()) { + if (timeRange == null || StringUtils.isBlank(timeRange.getCostType())) { + continue; + } + if (isInPriceTimeRange(timeRange.getStartTime(), timeRange.getEndTime(), recordDateTime)) { + costType = timeRange.getCostType(); + break; + } + } + if (StringUtils.isBlank(costType)) { + log.info("Quartz日电量小时落库跳过,未命中电价时段, siteId: {}, dataTime: {}", + siteId, DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, recordDateTime)); + return; + } + + log.info("Quartz日电量小时自检, siteId: {}, peak: {} / {} , high: {} / {} , flat: {} / {} , valley: {} / {}", + siteId, + peakChargePointId, + peakDischargePointId, + highChargePointId, + highDischargePointId, + flatChargePointId, + flatDischargePointId, + valleyChargePointId, + valleyDischargePointId); + String currentChargePointId; + String currentDischargePointId; + BigDecimal currentCharge; + BigDecimal currentDischarge; + switch (costType) { + case "peak": + currentChargePointId = peakChargePointId; + currentDischargePointId = peakDischargePointId; + currentCharge = getLatestPointValuePreferRedisValue(siteId, peakChargePointId, queryStartTime, queryEndTime); + currentDischarge = getLatestPointValuePreferRedisValue(siteId, peakDischargePointId, queryStartTime, queryEndTime); + break; + case "high": + currentChargePointId = highChargePointId; + currentDischargePointId = highDischargePointId; + currentCharge = getLatestPointValuePreferRedisValue(siteId, highChargePointId, queryStartTime, queryEndTime); + currentDischarge = getLatestPointValuePreferRedisValue(siteId, highDischargePointId, queryStartTime, queryEndTime); + break; + case "flat": + currentChargePointId = flatChargePointId; + currentDischargePointId = flatDischargePointId; + currentCharge = getLatestPointValuePreferRedisValue(siteId, flatChargePointId, queryStartTime, queryEndTime); + currentDischarge = getLatestPointValuePreferRedisValue(siteId, flatDischargePointId, queryStartTime, queryEndTime); + break; + case "valley": + currentChargePointId = valleyChargePointId; + currentDischargePointId = valleyDischargePointId; + currentCharge = getLatestPointValuePreferRedisValue(siteId, valleyChargePointId, queryStartTime, queryEndTime); + currentDischarge = getLatestPointValuePreferRedisValue(siteId, valleyDischargePointId, queryStartTime, queryEndTime); + break; + default: + log.info("Quartz日电量小时落库跳过,未知电价时段类型, siteId: {}, costType: {}", siteId, costType); + return; + } + + if (currentCharge == null && currentDischarge == null) { + log.info("Quartz日电量小时值未匹配到有效Redis点位值, siteId: {}, costType: {}, chargePoint: {}, dischargePoint: {}", + siteId, costType, currentChargePointId, currentDischargePointId); + return; + } + + EmsDailyEnergyData energyData = new EmsDailyEnergyData(); + energyData.setSiteId(siteId); + energyData.setDataDate(recordDateTime); + energyData.setDataHour(dataHour); + if ("peak".equals(costType)) { + energyData.setPeakChargeDiff(currentCharge); + energyData.setPeakDischargeDiff(currentDischarge); + } else if ("high".equals(costType)) { + energyData.setHighChargeDiff(currentCharge); + energyData.setHighDischargeDiff(currentDischarge); + } else if ("flat".equals(costType)) { + energyData.setFlatChargeDiff(currentCharge); + energyData.setFlatDischargeDiff(currentDischarge); + } else if ("valley".equals(costType)) { + energyData.setValleyChargeDiff(currentCharge); + energyData.setValleyDischargeDiff(currentDischarge); + } + energyData.setCalcTime(recordDateTime); + + EmsDailyEnergyData existedEnergyData = emsDailyEnergyDataMapper.selectBySiteIdAndDateHour(siteId, recordDateTime, dataHour); + if (existedEnergyData != null && existedEnergyData.getId() != null) { + energyData.setId(existedEnergyData.getId()); + energyData.setDataDate(existedEnergyData.getDataDate()); + energyData.setDataHour(existedEnergyData.getDataHour()); + } + + Date now = DateUtils.getNowDate(); + energyData.setCreateBy("system"); + energyData.setCreateTime(now); + energyData.setUpdateBy("system"); + energyData.setUpdateTime(now); + emsDailyEnergyDataMapper.insertOrUpdateData(energyData); + + log.info("Quartz日电量小时落库完成, siteId: {}, hour: {}, costType: {}, charge: {}, discharge: {}, chargePoint: {}, dischargePoint: {}", + siteId, dataHour, costType, currentCharge, currentDischarge, currentChargePointId, currentDischargePointId); + } + + private boolean isInPriceTimeRange(String startTime, String endTime, Date dataUpdateTime) { + if (StringUtils.isAnyBlank(startTime, endTime) || dataUpdateTime == null) { + return false; + } + try { + String normalizedStartTime = startTime.trim(); + String normalizedEndTime = endTime.trim(); + LocalTime current = LocalTime.parse(DateUtils.parseDateToStr("HH:mm", dataUpdateTime)); + LocalTime start = LocalTime.parse(normalizedStartTime); + + // 兼容配置中的 24:00,将其视为次日 00:00。 + if ("24:00".equals(normalizedEndTime) || "24:00:00".equals(normalizedEndTime)) { + return !current.isBefore(start); + } + + LocalTime end = LocalTime.parse(normalizedEndTime); + if (start.equals(end)) { + return true; + } + if (start.isBefore(end)) { + return !current.isBefore(start) && current.isBefore(end); + } + return !current.isBefore(start) || current.isBefore(end); + } catch (Exception ex) { + log.warn("解析电价时段失败, start: {}, end: {}, err: {}", startTime, endTime, ex.getMessage()); + return false; + } + } + + private Integer resolveHourOfDay(Date dateTime) { + Date targetTime = dateTime == null ? DateUtils.getNowDate() : dateTime; + String hourText = DateUtils.parseDateToStr("HH", targetTime); + try { + return Integer.parseInt(hourText); + } catch (NumberFormatException ex) { + return LocalDateTime.now().getHour(); + } + } + + public void syncCalcPointDataFromInfluxByQuartz() { + syncCalcPointDataFromInfluxByQuartz(null); + } + + public void syncCalcPointDataFromInfluxByQuartz(String siteId) { + List siteIds; + if (StringUtils.isNotBlank(siteId)) { + siteIds = Collections.singletonList(siteId.trim()); + } else { + siteIds = emsSiteMonitorPointMatchMapper.selectDistinctSiteIds(); + } + if (CollectionUtils.isEmpty(siteIds)) { + return; + } + for (String currentSiteId : siteIds) { + if (StringUtils.isBlank(currentSiteId)) { + continue; + } + try { + syncSingleSiteCalcPointFromInflux(currentSiteId.trim()); + } catch (Exception ex) { + log.error("Quartz同步计算点失败, siteId: {}, err: {}", currentSiteId, ex.getMessage(), ex); + } + } + } + + private void syncSingleSiteCalcPointFromInflux(String siteId) { + List calcPointConfigs = getSiteCalcPointConfigs(siteId); + if (CollectionUtils.isEmpty(calcPointConfigs)) { + return; + } + List sourcePointConfigs = getSiteSourcePointConfigs(siteId); + if (CollectionUtils.isEmpty(sourcePointConfigs)) { + log.info("Quartz同步计算点跳过,未找到源点位配置, siteId: {}", siteId); + return; + } + + Date queryEndTime = DateUtils.getNowDate(); + Date queryStartTime = new Date(queryEndTime.getTime() - CALC_POINT_INFLUX_QUERY_WINDOW_MS); + Map contextValues = new HashMap<>(); + Date latestDataTime = null; + for (EmsPointConfig sourcePointConfig : sourcePointConfigs) { + if (sourcePointConfig == null) { + continue; + } + String pointId = resolveInfluxPointKey(sourcePointConfig); + if (StringUtils.isBlank(pointId)) { + continue; + } + InfluxPointDataWriter.PointValue latestPointValue = influxPointDataWriter.queryLatestPointValueByPointKey( + siteId, pointId, queryStartTime, queryEndTime + ); + if (latestPointValue == null || latestPointValue.getPointValue() == null) { + continue; + } + putSourcePointValueToContext(sourcePointConfig, latestPointValue.getPointValue(), contextValues); + if (latestDataTime == null + || (latestPointValue.getDataTime() != null && latestPointValue.getDataTime().after(latestDataTime))) { + latestDataTime = latestPointValue.getDataTime(); + } + } + + if (org.apache.commons.collections4.MapUtils.isEmpty(contextValues)) { + log.info("Quartz同步计算点跳过,InfluxDB未查询到源点位最新值, siteId: {}", siteId); + return; + } + Date calcTime = latestDataTime == null ? queryEndTime : latestDataTime; + Map calcPointIdValueMap = executeCalcPointConfigs( + siteId, SITE_LEVEL_CALC_DEVICE_ID, calcPointConfigs, contextValues, calcTime + ); + if (!calcPointIdValueMap.isEmpty()) { + cacheLatestPointValuesByPointId(siteId, SITE_LEVEL_CALC_DEVICE_ID, calcPointIdValueMap, calcTime); + iEmsDeviceSettingService.syncSiteMonitorDataByMqtt( + siteId, SITE_LEVEL_CALC_DEVICE_ID, JSON.toJSONString(calcPointIdValueMap), calcTime + ); + } + } + + private String firstNonBlankPointByFieldSuffixIgnoreDevice(List mappingList, + String[] fieldSuffixes, + String... fieldPrefixes) { + EmsSiteMonitorPointMatch match = firstNonBlankMatchByFieldSuffixIgnoreDevice( + mappingList, fieldSuffixes, fieldPrefixes + ); + return match == null ? null : StringUtils.trimToNull(match.getDataPoint()); + } + + private EmsSiteMonitorPointMatch firstNonBlankMatchByFieldSuffixIgnoreDevice(List mappingList, + String[] fieldSuffixes, + String... fieldPrefixes) { + if (CollectionUtils.isEmpty(mappingList) || fieldSuffixes == null || fieldSuffixes.length == 0) { + return null; + } + List normalizedPrefixes = new ArrayList<>(); + if (fieldPrefixes != null && fieldPrefixes.length > 0) { + for (String prefix : fieldPrefixes) { + String normalizedPrefix = StringUtils.trimToNull(prefix); + if (normalizedPrefix != null) { + normalizedPrefixes.add(normalizedPrefix.toLowerCase()); } + } + } + for (String suffix : fieldSuffixes) { + if (StringUtils.isBlank(suffix)) { + continue; + } + String normalizedSuffix = suffix.trim().toLowerCase(); + for (EmsSiteMonitorPointMatch mapping : mappingList) { + if (mapping == null || StringUtils.isBlank(mapping.getFieldCode())) { + continue; + } + String dataPoint = StringUtils.defaultString(mapping.getDataPoint()).trim(); + if (StringUtils.isBlank(dataPoint) || DELETED_FIELD_MARK.equalsIgnoreCase(dataPoint)) { + continue; + } + String fieldCode = mapping.getFieldCode().trim().toLowerCase(); + if (!normalizedPrefixes.isEmpty()) { + if (fieldCode.equals(normalizedSuffix)) { + return mapping; + } + boolean matchedPrefix = false; + for (String prefix : normalizedPrefixes) { + if (fieldCode.startsWith(prefix)) { + matchedPrefix = true; + String stripped = fieldCode.substring(prefix.length()); + if (stripped.equals(normalizedSuffix)) { + return mapping; + } + } + } + if (matchedPrefix) { + continue; + } + } else if (fieldCode.equals(normalizedSuffix)) { + return mapping; + } + } + } + return null; + } + + private String mergeWithLatestRedisData(String siteId, String deviceId, String currentJsonData) { + Map currentDataMap = parseDataJsonToMap(currentJsonData); + if (StringUtils.isAnyBlank(siteId, deviceId)) { + return JSON.toJSONString(currentDataMap); + } + String redisKey = RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceId; + Object latestObj = redisCache.getCacheObject(redisKey); + Map latestDataMap = extractDataMapFromLatest(latestObj); + if (latestDataMap.isEmpty()) { + return JSON.toJSONString(currentDataMap); + } + Map merged = new LinkedHashMap<>(latestDataMap); + currentDataMap.forEach((key, value) -> { + if (StringUtils.isBlank(key) || value == null) { return; } - // 数据读取成功,重置计数器 - topicEmptyCounts.remove(deviceNumber); - // 读取到数据后告警自恢复 - iEmsAlarmRecordsService.deleteEmptyDataAlarmRecord(siteId, deviceId); + merged.put(key, value); + }); + return JSON.toJSONString(merged); + } - // 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据 - redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceId, obj); - // 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据 - redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + siteId + "_" + deviceId, obj, 1, TimeUnit.MINUTES); - - // 处理设备数据 - processingDeviceData(siteId, deviceId, jsonData, dataUpdateTime); + private String resolveJsonDataWithRedisFallback(String siteId, String deviceId, String jsonData) { + if (!checkJsonDataEmpty(jsonData)) { + return jsonData; } + if (StringUtils.isAnyBlank(siteId, deviceId)) { + return jsonData; + } + String redisKey = RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + deviceId; + Object latestObj = redisCache.getCacheObject(redisKey); + Map latestDataMap = extractDataMapFromLatest(latestObj); + if (latestDataMap.isEmpty()) { + return jsonData; + } + return JSON.toJSONString(latestDataMap); + } + + private Map extractDataMapFromLatest(Object latestObj) { + if (latestObj == null) { + return new LinkedHashMap<>(); + } + JSONObject latestJson; + if (latestObj instanceof JSONObject) { + latestJson = (JSONObject) latestObj; + } else if (latestObj instanceof String) { + latestJson = JSON.parseObject((String) latestObj); + } else if (latestObj instanceof Map) { + latestJson = new JSONObject((Map) latestObj); + } else { + latestJson = JSON.parseObject(JSON.toJSONString(latestObj)); + } + if (latestJson == null || latestJson.isEmpty()) { + return new LinkedHashMap<>(); + } + Object dataObj = latestJson.get("Data"); + if (dataObj == null) { + return new LinkedHashMap<>(); + } + if (dataObj instanceof JSONObject) { + return new LinkedHashMap<>(((JSONObject) dataObj)); + } + if (dataObj instanceof Map) { + return new LinkedHashMap<>((Map) dataObj); + } + if (dataObj instanceof String) { + return parseDataJsonToMap((String) dataObj); + } + return parseDataJsonToMap(JSON.toJSONString(dataObj)); + } + + private Map parseDataJsonToMap(String jsonData) { + if (StringUtils.isBlank(jsonData)) { + return new LinkedHashMap<>(); + } + Map parsed = JSON.parseObject(jsonData, new TypeReference>() { + }); + return parsed == null ? new LinkedHashMap<>() : new LinkedHashMap<>(parsed); + } + + private BigDecimal getPointValueById(Map pointIdValueMap, String pointId) { + if (pointIdValueMap == null || pointIdValueMap.isEmpty() || StringUtils.isBlank(pointId)) { + return null; + } + LatestPointValue latestPointValue = pointIdValueMap.get(pointId.trim().toUpperCase()); + return latestPointValue == null ? null : latestPointValue.getValue(); + } + + private BigDecimal getPointValueByIdRaw(Map pointIdValueMap, String pointId) { + if (pointIdValueMap == null || pointIdValueMap.isEmpty() || StringUtils.isBlank(pointId)) { + return null; + } + return pointIdValueMap.get(pointId.trim().toUpperCase()); + } + + private String pointValueSource(Map pointIdValueMap, String pointId) { + if (pointIdValueMap == null || pointIdValueMap.isEmpty() || StringUtils.isBlank(pointId)) { + return "-"; + } + LatestPointValue latestPointValue = pointIdValueMap.get(pointId.trim().toUpperCase()); + if (latestPointValue == null) { + return "-"; + } + return latestPointValue.isFromRedis() ? "Redis" : "Influx"; + } + + private BigDecimal getLatestPointValueFromRedis(String pointId) { + String normalizedPointId = StringUtils.trimToNull(pointId); + if (normalizedPointId == null) { + return null; + } + log.info("点位值自检-Redis读取, pointId: {}", normalizedPointId); + List candidates = new ArrayList<>(); + candidates.add(normalizedPointId); + String upperPointId = normalizedPointId.toUpperCase(); + if (!StringUtils.equals(upperPointId, normalizedPointId)) { + candidates.add(upperPointId); + } + String lowerPointId = normalizedPointId.toLowerCase(); + if (!StringUtils.equals(lowerPointId, normalizedPointId) + && !StringUtils.equals(lowerPointId, upperPointId)) { + candidates.add(lowerPointId); + } + for (String candidate : candidates) { + Object raw = redisCache.getCacheObject(candidate); + if (raw == null) { + continue; + } + try { + JSONObject valueObj = raw instanceof JSONObject + ? (JSONObject) raw + : JSON.parseObject(JSON.toJSONString(raw)); + if (valueObj == null) { + continue; + } + BigDecimal pointValue = StringUtils.getBigDecimal(valueObj.get("pointValue")); + if (pointValue == null) { + pointValue = readBigDecimalField(raw, "pointValue"); + } + if (pointValue != null) { + log.info("点位值自检-Redis命中, pointId: {}, redisKey: {}", normalizedPointId, candidate); + return pointValue; + } + } catch (Exception ex) { + log.warn("读取点位Redis值失败, pointId: {}, redisKey: {}, err: {}", normalizedPointId, candidate, ex.getMessage()); + } + } + return null; + } + + private LatestPointValue getLatestPointValuePreferRedis(String siteId, String pointId, Date queryStartTime, Date queryEndTime) { + if (StringUtils.isBlank(siteId) || StringUtils.isBlank(pointId)) { + return null; + } + BigDecimal redisValue = getLatestPointValueFromRedis(pointId); + if (redisValue != null) { + return new LatestPointValue(redisValue, null, true); + } + log.info("点位值自检-Influx读取, siteId: {}, pointId: {}, range: {} ~ {}", + siteId, pointId, + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, queryStartTime), + DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, queryEndTime)); + InfluxPointDataWriter.PointValue influxPointValue = influxPointDataWriter.queryLatestPointValueByPointKey( + siteId, pointId, queryStartTime, queryEndTime + ); + if (influxPointValue == null || influxPointValue.getPointValue() == null) { + return null; + } + return new LatestPointValue(influxPointValue.getPointValue(), influxPointValue.getDataTime(), false); + } + + private BigDecimal getLatestPointValuePreferRedisValue(String siteId, String pointId, Date queryStartTime, Date queryEndTime) { + LatestPointValue value = getLatestPointValuePreferRedis(siteId, pointId, queryStartTime, queryEndTime); + return value == null ? null : value.getValue(); + } + + private String resolvePointIdByFieldCodeIgnoreDevice(List mappingList, String fieldCode) { + if (CollectionUtils.isEmpty(mappingList) || StringUtils.isBlank(fieldCode)) { + return null; + } + String normalized = fieldCode.trim().toLowerCase(); + for (EmsSiteMonitorPointMatch mapping : mappingList) { + if (mapping == null || StringUtils.isBlank(mapping.getFieldCode())) { + continue; + } + String dataPoint = StringUtils.defaultString(mapping.getDataPoint()).trim(); + if (StringUtils.isBlank(dataPoint) || DELETED_FIELD_MARK.equalsIgnoreCase(dataPoint)) { + continue; + } + if (mapping.getFieldCode().trim().toLowerCase().equals(normalized)) { + return dataPoint; + } + } + return null; + } + + private void upsertDailyChargeData(String siteId, + Date recordDateTime, + BigDecimal totalChargeValue, + BigDecimal totalDischargeValue, + BigDecimal dayChargeValue, + BigDecimal dayDischargeValue) { + if (StringUtils.isBlank(siteId)) { + return; + } + Date persistTime = recordDateTime == null ? DateUtils.getNowDate() : recordDateTime; + EmsDailyChargeData dailyChargeData = new EmsDailyChargeData(); + dailyChargeData.setSiteId(siteId); + dailyChargeData.setDateTime(persistTime); + dailyChargeData.setTotalChargeData(totalChargeValue); + dailyChargeData.setTotalDischargeData(totalDischargeValue); + dailyChargeData.setChargeData(dayChargeValue); + dailyChargeData.setDischargeData(dayDischargeValue); + + EmsDailyChargeData existedDailyData = emsDailyChargeDataMapper.selectBySiteIdAndDateTime(siteId, persistTime); + if (existedDailyData != null && existedDailyData.getId() != null) { + dailyChargeData.setId(existedDailyData.getId()); + dailyChargeData.setDateTime(existedDailyData.getDateTime()); + } + + Date now = DateUtils.getNowDate(); + dailyChargeData.setCreateBy("system"); + dailyChargeData.setCreateTime(now); + dailyChargeData.setUpdateBy("system"); + dailyChargeData.setUpdateTime(now); + emsDailyChargeDataMapper.insertOrUpdateData(dailyChargeData); + log.info("日充放电累计落库完成, siteId: {}, totalCharge: {}, totalDischarge: {}, dayCharge: {}, dayDischarge: {}", + siteId, totalChargeValue, totalDischargeValue, dayChargeValue, dayDischargeValue); + } + + private static class LatestPointValue { + private final BigDecimal value; + private final Date dataTime; + private final boolean fromRedis; + + LatestPointValue(BigDecimal value, Date dataTime, boolean fromRedis) { + this.value = value; + this.dataTime = dataTime; + this.fromRedis = fromRedis; + } + + public BigDecimal getValue() { + return value; + } + + public Date getDataTime() { + return dataTime; + } + + public boolean isFromRedis() { + return fromRedis; + } + } + + private BigDecimal readBigDecimalField(Object source, String fieldName) { + Object fieldValue = readFieldValue(source, fieldName); + return StringUtils.getBigDecimal(fieldValue); + } + + private Object readFieldValue(Object source, String fieldName) { + if (source == null || StringUtils.isBlank(fieldName)) { + return null; + } + Class current = source.getClass(); + while (current != null) { + try { + Field field = current.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(source); + } catch (NoSuchFieldException ex) { + current = current.getSuperclass(); + } catch (Exception ex) { + return null; + } + } + return null; + } + + private String firstNonBlankPointByFieldSuffix(Map mappingByFieldAndDevice, + String deviceId, + String[] fieldSuffixes) { + return firstNonBlankPointByFieldSuffix(mappingByFieldAndDevice, deviceId, fieldSuffixes, MONITOR_FIELD_PREFIX_HOME); + } + + private String firstNonBlankPointByFieldSuffix(Map mappingByFieldAndDevice, + String deviceId, + String[] fieldSuffixes, + String... fieldPrefixes) { + if (mappingByFieldAndDevice == null || fieldSuffixes == null || fieldSuffixes.length == 0) { + return null; + } + String normalizedDeviceId = StringUtils.defaultString(deviceId).trim(); + for (String suffix : fieldSuffixes) { + if (StringUtils.isBlank(suffix)) { + continue; + } + String normalizedSuffix = suffix.trim().toLowerCase(); + EmsSiteMonitorPointMatch exactMatch = resolvePointMatchByFieldSuffix( + mappingByFieldAndDevice, normalizedDeviceId, normalizedSuffix, fieldPrefixes + ); + if (exactMatch != null && StringUtils.isNotBlank(exactMatch.getDataPoint())) { + return exactMatch.getDataPoint().trim(); + } + EmsSiteMonitorPointMatch fallbackMatch = resolvePointMatchByFieldSuffix( + mappingByFieldAndDevice, "", normalizedSuffix, fieldPrefixes + ); + if (fallbackMatch != null && StringUtils.isNotBlank(fallbackMatch.getDataPoint())) { + return fallbackMatch.getDataPoint().trim(); + } + } + return null; + } + + private EmsSiteMonitorPointMatch resolvePointMatchByFieldSuffix(Map mappingByFieldAndDevice, + String deviceId, + String suffixLowerCase, + String... fieldPrefixes) { + if (mappingByFieldAndDevice == null || StringUtils.isBlank(suffixLowerCase)) { + return null; + } + List normalizedPrefixes = new ArrayList<>(); + if (fieldPrefixes != null && fieldPrefixes.length > 0) { + for (String prefix : fieldPrefixes) { + String normalizedPrefix = StringUtils.trimToNull(prefix); + if (normalizedPrefix != null) { + normalizedPrefixes.add(normalizedPrefix.toLowerCase()); + } + } + } + for (Map.Entry entry : mappingByFieldAndDevice.entrySet()) { + EmsSiteMonitorPointMatch mapping = entry.getValue(); + if (mapping == null || StringUtils.isBlank(mapping.getFieldCode())) { + continue; + } + String mappingDeviceId = StringUtils.defaultString(mapping.getDeviceId()).trim(); + if (!StringUtils.equals(mappingDeviceId, StringUtils.defaultString(deviceId).trim())) { + continue; + } + String dataPoint = StringUtils.defaultString(mapping.getDataPoint()).trim(); + if (StringUtils.isBlank(dataPoint) || DELETED_FIELD_MARK.equalsIgnoreCase(dataPoint)) { + continue; + } + String fieldCode = mapping.getFieldCode().trim().toLowerCase(); + if (!normalizedPrefixes.isEmpty() && normalizedPrefixes.stream().noneMatch(fieldCode::startsWith)) { + continue; + } + if (fieldCode.equals(suffixLowerCase)) { + return mapping; + } + } + return null; + } + + private Map getMonitorPointMappingByFieldAndDevice(String siteId) { + List mappingList = getPointMatchesBySiteId(siteId); + if (CollectionUtils.isEmpty(mappingList)) { + return Collections.emptyMap(); + } + return mappingList.stream() + .filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode())) + .collect(Collectors.toMap( + item -> buildFieldDeviceKey(item.getFieldCode(), item.getDeviceId()), + item -> item, + (a, b) -> b + )); + } + + private List getPointMatchesBySiteId(String siteId) { + if (StringUtils.isBlank(siteId)) { + return Collections.emptyList(); + } + String normalizedSiteId = siteId.trim(); + String redisKey = RedisKeyConstants.SITE_MONITOR_POINT_MATCH + normalizedSiteId; + log.info("单站映射自检-读取, siteId: {}, redisKey: {}", normalizedSiteId, redisKey); + Object cacheObj = redisCache.getCacheObject(redisKey); + List cached = parsePointMatchCache(cacheObj); + if (cached != null) { + log.info("单站映射自检-命中缓存, siteId: {}, size: {}", normalizedSiteId, cached.size()); + return cached; + } + List latest = emsSiteMonitorPointMatchMapper.selectBySiteId(normalizedSiteId); + if (latest == null) { + latest = Collections.emptyList(); + } + redisCache.setCacheObject(redisKey, latest, MONITOR_POINT_MATCH_REDIS_TTL_SECONDS, TimeUnit.SECONDS); + log.info("单站映射自检-落库后写缓存, siteId: {}, size: {}", normalizedSiteId, latest.size()); + return latest; + } + + private List parsePointMatchCache(Object cacheObj) { + if (cacheObj == null) { + return null; + } + try { + if (cacheObj instanceof String) { + return JSON.parseArray((String) cacheObj, EmsSiteMonitorPointMatch.class); + } + if (cacheObj instanceof List) { + List cacheList = (List) cacheObj; + if (cacheList.isEmpty()) { + return Collections.emptyList(); + } + if (cacheList.get(0) instanceof EmsSiteMonitorPointMatch) { + return (List) cacheList; + } + return JSON.parseArray(JSON.toJSONString(cacheObj), EmsSiteMonitorPointMatch.class); + } + return JSON.parseArray(JSON.toJSONString(cacheObj), EmsSiteMonitorPointMatch.class); + } catch (Exception ex) { + log.warn("解析单站监控点位映射缓存失败,key类型={}, err={}", + cacheObj.getClass().getName(), ex.getMessage()); + return null; + } + } + + private String buildFieldDeviceKey(String fieldCode, String deviceId) { + return StringUtils.defaultString(fieldCode).trim() + "|" + StringUtils.defaultString(deviceId).trim(); + } + + private BigDecimal safeBigDecimal(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value; + } + + private Map buildDeviceCategoryMap(String siteId, JSONArray arraylist) { + Map deviceCategoryMap = new HashMap<>(); + if (arraylist == null || arraylist.isEmpty()) { + return deviceCategoryMap; + } + for (int i = 0; i < arraylist.size(); i++) { + JSONObject item = JSONObject.parseObject(arraylist.get(i).toString()); + if (item == null) { + continue; + } + String deviceId = item.getString("Device"); + if (StringUtils.isBlank(deviceId) || deviceCategoryMap.containsKey(deviceId)) { + continue; + } + String category = getDeviceCategory(siteId, deviceId); + if (StringUtils.isNotBlank(category)) { + deviceCategoryMap.put(deviceId, category); + } + } + return deviceCategoryMap; + } + + private String resolveDeviceCategory(String siteId, String deviceId, Map deviceCategoryMap) { + if (StringUtils.isBlank(deviceId)) { + return ""; + } + String mappedCategory = deviceCategoryMap == null ? null : deviceCategoryMap.get(deviceId); + if (StringUtils.isNotBlank(mappedCategory)) { + return mappedCategory; + } + String runtimeCategory = getDeviceCategory(siteId, deviceId); + if (StringUtils.isNotBlank(runtimeCategory) && deviceCategoryMap != null) { + deviceCategoryMap.put(deviceId, runtimeCategory); + } + return StringUtils.defaultString(runtimeCategory); + } + + private Map buildBatchContextBySite(String siteId, JSONArray arraylist, Map deviceCategoryMap) { + Map contextBySite = new HashMap<>(); + if (arraylist == null || arraylist.isEmpty()) { + return contextBySite; + } + for (int i = 0; i < arraylist.size(); i++) { + JSONObject item = JSONObject.parseObject(arraylist.get(i).toString()); + if (item == null) { + continue; + } + String deviceId = item.getString("Device"); + String devicePrefix = normalizeVariablePart(deviceId); + String deviceCategory = resolveDeviceCategory(siteId, deviceId, deviceCategoryMap); + String jsonData = resolveJsonDataWithRedisFallback(siteId, deviceId, item.getString("Data")); + if (checkJsonDataEmpty(jsonData)) { + continue; + } + Map dataMap = JSON.parseObject(jsonData, new TypeReference>() { + }); + if (org.apache.commons.collections4.MapUtils.isEmpty(dataMap)) { + continue; + } + Map pointConfigByDataKey = new HashMap<>(); + List pointConfigs = getPointConfigsWithCache(siteId, deviceId, deviceCategory); + if (!CollectionUtils.isEmpty(pointConfigs)) { + for (EmsPointConfig pointConfig : pointConfigs) { + if (isComputedPoint(pointConfig)) { + continue; + } + String dataKey = StringUtils.defaultString(pointConfig.getDataKey()).trim().toUpperCase(); + if (StringUtils.isBlank(dataKey) || pointConfigByDataKey.containsKey(dataKey)) { + continue; + } + pointConfigByDataKey.put(dataKey, pointConfig); + } + } + for (Map.Entry entry : dataMap.entrySet()) { + if (StringUtils.isBlank(entry.getKey())) { + continue; + } + BigDecimal pointValue = StringUtils.getBigDecimal(entry.getValue()); + if (pointValue == null) { + continue; + } + String normalizedKey = normalizeVariablePart(entry.getKey()); + if (StringUtils.isBlank(normalizedKey)) { + continue; + } + // 兼容旧表达式:继续支持直接使用变量名(如 WD4) + contextBySite.put(normalizedKey, pointValue); + // 新规则:支持设备前缀变量(如 DONGHUAN_WD4) + if (StringUtils.isNotBlank(devicePrefix)) { + contextBySite.put(devicePrefix + "_" + normalizedKey, pointValue); + } + // 同时支持按点位ID引用(如 POINT4092) + EmsPointConfig pointConfig = pointConfigByDataKey.get(entry.getKey().trim().toUpperCase()); + if (pointConfig != null) { + String pointIdKey = resolvePointContextKey(pointConfig); + if (StringUtils.isNotBlank(pointIdKey)) { + BigDecimal convertedValue = convertPointValue(pointValue, pointConfig); + contextBySite.put(pointIdKey, convertedValue); + } + } + } + } + return contextBySite; + } + + private String normalizeVariablePart(String source) { + if (StringUtils.isBlank(source)) { + return null; + } + String normalized = VARIABLE_SAFE_PATTERN.matcher(source.trim().toUpperCase()).replaceAll("_"); + if (StringUtils.isBlank(normalized)) { + return null; + } + normalized = normalized.replaceAll("_+", "_"); + normalized = StringUtils.strip(normalized, "_"); + return StringUtils.isBlank(normalized) ? null : normalized; + } + + private boolean isComputedPoint(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return false; + } + String pointType = StringUtils.defaultString(pointConfig.getPointType()).trim().toLowerCase(); + return "calc".equals(pointType); + } + + private void enqueuePointData(String siteId, String deviceId, String pointKey, BigDecimal pointValue, Date dataTime) { + if (StringUtils.isAnyBlank(siteId, deviceId, pointKey) || pointValue == null) { + return; + } + PointDataRecord record = new PointDataRecord(siteId, deviceId, pointKey, pointValue, dataTime); + if (pointDataQueue.offer(record)) { + if (pointDataQueue.remainingCapacity() <= POINT_FLUSH_BATCH_SIZE) { + flushPointDataSafely(); + } + return; + } + + // 队列压力较高时,优先触发冲刷并短暂重试,避免 MQTT 处理线程直接阻塞在数据库写入上。 + for (int i = 0; i < POINT_ENQUEUE_RETRY_TIMES; i++) { + flushPointDataSafely(); + try { + if (pointDataQueue.offer(record, POINT_ENQUEUE_RETRY_WAIT_MS, TimeUnit.MILLISECONDS)) { + return; + } + } catch (InterruptedException interruptedException) { + Thread.currentThread().interrupt(); + break; + } + } + + log.warn("点位写入队列持续拥塞,降级同步写入 InfluxDB,siteId: {}, queueSize: {}", + siteId, pointDataQueue.size()); + writePointDataToInflux(Collections.singletonList(record)); + } + + 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 String resolveInfluxPointKey(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return null; + } + if (StringUtils.isNotBlank(pointConfig.getPointId())) { + return pointConfig.getPointId().trim(); + } + return null; + } + + private Map executeCalcPointConfigs(String siteId, String deviceId, List calcPointConfigs, + Map contextValues, Date dataUpdateTime) { + Map calcPointIdValueMap = new HashMap<>(); + if (CollectionUtils.isEmpty(calcPointConfigs)) { + return calcPointIdValueMap; + } + Map remaining = new HashMap<>(); + for (EmsPointConfig calcPointConfig : calcPointConfigs) { + String calcContextKey = resolveCalcContextKey(calcPointConfig); + if (calcPointConfig == null || StringUtils.isBlank(calcContextKey)) { + continue; + } + remaining.putIfAbsent(calcContextKey, calcPointConfig); + } + if (remaining.isEmpty()) { + return calcPointIdValueMap; + } + + while (!remaining.isEmpty()) { + boolean progressed = false; + List finishedKeys = new ArrayList<>(); + List unresolvedKeys = new ArrayList<>(); + + for (Map.Entry entry : remaining.entrySet()) { + String calcDataKey = entry.getKey(); + EmsPointConfig calcPointConfig = entry.getValue(); + try { + ExpressionValue calcValue = evaluateCalcExpression( + calcPointConfig.getCalcExpression(), contextValues, siteId, dataUpdateTime); + if (calcValue.isNumber()) { + contextValues.put(calcDataKey, calcValue.asNumber()); + putPointValueToContext(calcPointConfig, calcValue.asNumber(), contextValues); + } + // 计算点按站点维度统一落库,不再按配置中的 device_id 分流 + String pointId = resolveInfluxPointKey(calcPointConfig); + if (StringUtils.isNotBlank(pointId)) { + BigDecimal storedValue = calcValue.asNumber(); + enqueuePointData(siteId, deviceId, pointId, storedValue, dataUpdateTime); + calcPointIdValueMap.put(pointId, storedValue); + } + finishedKeys.add(calcDataKey); + progressed = true; + } catch (MissingVariableException missingVariableException) { + unresolvedKeys.add(calcDataKey + " <- " + missingVariableException.getVariable()); + } catch (IllegalArgumentException expressionException) { + log.warn("计算点表达式执行失败,siteId: {}, deviceId: {}, dataKey: {}, expression: {}, err: {}", + siteId, deviceId, calcPointConfig.getDataKey(), calcPointConfig.getCalcExpression(), + expressionException.getMessage()); + finishedKeys.add(calcDataKey); + } + } + finishedKeys.forEach(remaining::remove); + + if (!progressed) { + if (!unresolvedKeys.isEmpty()) { + log.warn("计算点依赖未就绪,跳过本轮计算,siteId: {}, deviceId: {}, unresolved: {}", + siteId, deviceId, String.join(", ", unresolvedKeys)); + } + break; + } + } + return calcPointIdValueMap; + } + + private BigDecimal getSourceValueFromRawData(EmsPointConfig pointConfig, Map rawDataValues) { + if (pointConfig == null || rawDataValues == null || rawDataValues.isEmpty()) { + return null; + } + String dataKey = StringUtils.defaultString(pointConfig.getDataKey()).trim().toUpperCase(); + return StringUtils.isBlank(dataKey) ? null : rawDataValues.get(dataKey); + } + + private void putPointValueToContext(EmsPointConfig pointConfig, BigDecimal pointValue, Map contextValues) { + if (pointConfig == null || pointValue == null || contextValues == null) { + return; + } + String pointKey = resolvePointContextKey(pointConfig); + if (StringUtils.isNotBlank(pointKey)) { + contextValues.put(pointKey, pointValue); + } + } + + private String resolveCalcContextKey(EmsPointConfig pointConfig) { + return resolvePointContextKey(pointConfig); + } + + private String resolvePointContextKey(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return null; + } + String pointId = StringUtils.defaultString(pointConfig.getPointId()).trim().toUpperCase(); + return StringUtils.isNotBlank(pointId) ? pointId : null; + } + + private ExpressionValue evaluateCalcExpression(String expression, Map contextValues, + String siteId, Date dataUpdateTime) { + if (StringUtils.isBlank(expression)) { + throw new IllegalArgumentException("计算表达式为空"); + } + if (!SIMPLE_CALC_EXPRESSION_PATTERN.matcher(expression).matches()) { + throw new IllegalArgumentException("计算表达式仅支持数字、字母、下划线、空格、运算符和函数语法"); + } + Map evaluateContext = contextValues == null + ? new HashMap<>() + : new HashMap<>(contextValues); + prepareAutoPeriodDiffContextValues(expression, evaluateContext, siteId, dataUpdateTime); + CompiledExpression compiledExpression = calcExpressionCache.computeIfAbsent(expression, this::compileExpression); + return compiledExpression.evaluate(evaluateContext); + } + + private void prepareAutoPeriodDiffContextValues(String expression, Map contextValues, + String siteId, Date dataUpdateTime) { + if (StringUtils.isBlank(expression) || contextValues == null || contextValues.isEmpty()) { + return; + } + Date effectiveTime = dataUpdateTime == null ? DateUtils.getNowDate() : dataUpdateTime; + prepareSingleAutoDiffContext(expression, contextValues, siteId, effectiveTime, "DAY_DIFF", + AUTO_DAY_DIFF_FUNCTION_PATTERN); + prepareSingleAutoDiffContext(expression, contextValues, siteId, effectiveTime, "MONTH_DIFF", + AUTO_MONTH_DIFF_FUNCTION_PATTERN); + prepareSingleAutoDiffContext(expression, contextValues, siteId, effectiveTime, "HOUR_DIFF", + AUTO_HOUR_DIFF_FUNCTION_PATTERN); + } + + private void prepareSingleAutoDiffContext(String expression, Map contextValues, String siteId, + Date effectiveTime, String functionName, Pattern functionPattern) { + Set variables = extractAutoDiffVariables(expression, functionPattern); + if (variables.isEmpty()) { + return; + } + for (String variable : variables) { + BigDecimal currentValue = contextValues.get(variable); + if (currentValue == null) { + continue; + } + BigDecimal diffValue = calculateAutoPeriodDiffValue(siteId, functionName, variable, currentValue, effectiveTime); + contextValues.put(resolveAutoDiffContextKey(functionName, variable), diffValue); + } + } + + private Set extractAutoDiffVariables(String expression, Pattern functionPattern) { + Set variables = new HashSet<>(); + Matcher matcher = functionPattern.matcher(expression); + while (matcher.find()) { + String variable = StringUtils.defaultString(matcher.group(1)).trim().toUpperCase(); + if (StringUtils.isNotBlank(variable)) { + variables.add(variable); + } + } + return variables; + } + + private BigDecimal calculateAutoPeriodDiffValue(String siteId, String functionName, String variable, + BigDecimal currentValue, Date effectiveTime) { + if (currentValue == null || StringUtils.isAnyBlank(siteId, functionName, variable) || effectiveTime == null) { + return BigDecimal.ZERO; + } + if ("DAY_DIFF".equalsIgnoreCase(functionName)) { + return calculateDayDiffValueFromInflux(siteId, variable, currentValue, effectiveTime); + } + if ("MONTH_DIFF".equalsIgnoreCase(functionName)) { + return calculateMonthDiffValueFromInflux(siteId, variable, currentValue, effectiveTime); + } + if ("HOUR_DIFF".equalsIgnoreCase(functionName)) { + return calculateHourDiffValueFromInflux(siteId, variable, currentValue, effectiveTime); + } + return BigDecimal.ZERO; + } + + private BigDecimal calculateDayDiffValueFromInflux(String siteId, String variable, + BigDecimal currentValue, Date effectiveTime) { + LocalDateTime currentDateTime = DateUtils.toLocalDateTime(effectiveTime); + if (currentDateTime == null) { + return BigDecimal.ZERO; + } + LocalDate currentDate = currentDateTime.toLocalDate(); + Date currentDayStart = DateUtils.toDate(currentDate); + Date previousDayStart = DateUtils.toDate(currentDate.minusDays(1)); + Date previousDayEnd = new Date(currentDayStart.getTime() - 1); + return calculateDiffByPreviousPeriodLastValue(siteId, variable, currentValue, previousDayStart, previousDayEnd); + } + + private BigDecimal calculateMonthDiffValueFromInflux(String siteId, String variable, + BigDecimal currentValue, Date effectiveTime) { + LocalDateTime currentDateTime = DateUtils.toLocalDateTime(effectiveTime); + if (currentDateTime == null) { + return BigDecimal.ZERO; + } + YearMonth currentMonth = YearMonth.from(currentDateTime); + LocalDate currentMonthStartDate = currentMonth.atDay(1); + Date currentMonthStart = DateUtils.toDate(currentMonthStartDate); + YearMonth previousMonth = currentMonth.minusMonths(1); + Date previousMonthStart = DateUtils.toDate(previousMonth.atDay(1)); + Date previousMonthEnd = new Date(currentMonthStart.getTime() - 1); + return calculateDiffByPreviousPeriodLastValue(siteId, variable, currentValue, previousMonthStart, previousMonthEnd); + } + + private BigDecimal calculateHourDiffValueFromInflux(String siteId, String variable, + BigDecimal currentValue, Date effectiveTime) { + LocalDateTime currentDateTime = DateUtils.toLocalDateTime(effectiveTime); + if (currentDateTime == null) { + return BigDecimal.ZERO; + } + LocalDateTime currentHourStartTime = currentDateTime.withMinute(0).withSecond(0).withNano(0); + Date currentHourStart = DateUtils.toDate(currentHourStartTime); + Date previousHourStart = DateUtils.toDate(currentHourStartTime.minusHours(1)); + Date previousHourEnd = new Date(currentHourStart.getTime() - 1); + return calculateDiffByPreviousPeriodLastValue(siteId, variable, currentValue, previousHourStart, previousHourEnd); + } + + private BigDecimal calculateDiffByPreviousPeriodLastValue(String siteId, String variable, + BigDecimal currentValue, Date previousPeriodStart, + Date previousPeriodEnd) { + if (StringUtils.isAnyBlank(siteId, variable) + || currentValue == null + || previousPeriodStart == null + || previousPeriodEnd == null + || previousPeriodEnd.before(previousPeriodStart)) { + return BigDecimal.ZERO; + } + String pointKey = resolveInfluxPointKeyByVariable(siteId, variable); + if (StringUtils.isBlank(pointKey)) { + return BigDecimal.ZERO; + } + InfluxPointDataWriter.PointValue previousPeriodLastPoint = influxPointDataWriter.queryLatestPointValueByPointKey( + siteId, pointKey, previousPeriodStart, previousPeriodEnd + ); + if (previousPeriodLastPoint == null || previousPeriodLastPoint.getPointValue() == null) { + return BigDecimal.ZERO; + } + return currentValue.subtract(previousPeriodLastPoint.getPointValue()); + } + + private String resolveInfluxPointKeyByVariable(String siteId, String variable) { + if (StringUtils.isAnyBlank(siteId, variable)) { + return null; + } + String normalizedVariable = normalizeVariablePart(variable); + String upperVariable = variable.trim().toUpperCase(); + List sourcePointConfigs = getSiteSourcePointConfigs(siteId); + for (EmsPointConfig sourcePointConfig : sourcePointConfigs) { + if (sourcePointConfig == null) { + continue; + } + String pointKey = resolveInfluxPointKey(sourcePointConfig); + if (StringUtils.isBlank(pointKey)) { + continue; + } + + String pointIdKey = resolvePointContextKey(sourcePointConfig); + if (StringUtils.isNotBlank(pointIdKey) && upperVariable.equals(pointIdKey)) { + return pointKey; + } + + String normalizedDataKey = normalizeVariablePart(sourcePointConfig.getDataKey()); + if (StringUtils.isNotBlank(normalizedVariable) + && StringUtils.isNotBlank(normalizedDataKey) + && normalizedVariable.equals(normalizedDataKey)) { + return pointKey; + } + + String devicePrefix = normalizeVariablePart(sourcePointConfig.getDeviceId()); + if (StringUtils.isNotBlank(devicePrefix) + && StringUtils.isNotBlank(normalizedDataKey) + && StringUtils.isNotBlank(normalizedVariable) + && normalizedVariable.equals(devicePrefix + "_" + normalizedDataKey)) { + return pointKey; + } + } + return null; + } + + private static String resolveAutoDayDiffContextKey(String variable) { + return AUTO_DAY_DIFF_CONTEXT_PREFIX + StringUtils.defaultString(variable).trim().toUpperCase(); + } + + private static String resolveAutoMonthDiffContextKey(String variable) { + return AUTO_MONTH_DIFF_CONTEXT_PREFIX + StringUtils.defaultString(variable).trim().toUpperCase(); + } + + private static String resolveAutoHourDiffContextKey(String variable) { + return AUTO_HOUR_DIFF_CONTEXT_PREFIX + StringUtils.defaultString(variable).trim().toUpperCase(); + } + + private static String resolveAutoDiffContextKey(String functionName, String variable) { + if ("DAY_DIFF".equalsIgnoreCase(functionName)) { + return resolveAutoDayDiffContextKey(variable); + } + if ("MONTH_DIFF".equalsIgnoreCase(functionName)) { + return resolveAutoMonthDiffContextKey(variable); + } + if ("HOUR_DIFF".equalsIgnoreCase(functionName)) { + return resolveAutoHourDiffContextKey(variable); + } + return null; + } + + private CompiledExpression compileExpression(String expression) { + return new CompiledExpression(expression); + } + + private void flushPointDataSafely() { + if (!pointDataFlushing.compareAndSet(false, true)) { + return; + } + try { + int drainedCount = 0; + while (drainedCount < POINT_FLUSH_MAX_DRAIN_PER_RUN) { + List batch = new ArrayList<>(POINT_FLUSH_BATCH_SIZE); + pointDataQueue.drainTo(batch, POINT_FLUSH_BATCH_SIZE); + if (batch.isEmpty()) { + break; + } + drainedCount += batch.size(); + writePointDataToInflux(batch); + } + + if (pointDataQueue.remainingCapacity() <= POINT_FLUSH_BATCH_SIZE) { + log.warn("点位写入队列积压较高,queueSize: {}", pointDataQueue.size()); + } + } catch (Exception e) { + log.error("批量写入点位数据失败: {}", e.getMessage(), e); + } finally { + pointDataFlushing.set(false); + } + } + + private void writePointDataToInflux(List records) { + if (CollectionUtils.isEmpty(records)) { + return; + } + List payloads = records.stream() + .map(item -> new InfluxPointDataWriter.PointWritePayload( + item.getSiteId(), + item.getDeviceId(), + item.getPointKey(), + item.getPointValue(), + item.getDataTime())) + .collect(Collectors.toList()); + influxPointDataWriter.writeBatch(payloads); + } + + private List getPointConfigsWithCache(String siteId, String deviceId, String deviceCategory) { + String normalizedCategory = StringUtils.defaultString(deviceCategory).trim().toUpperCase(); + String cacheKey = RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId + "_" + normalizedCategory; + List cached = redisCache.getCacheObject(cacheKey); + if (cached != null) { + return cached; + } + + // 1) 设备级点位(原有逻辑) + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setDeviceId(deviceId); + List cacheValue = emsPointConfigMapper.selectEmsPointConfigList(query); + redisCache.setCacheObject(cacheKey, cacheValue); + return cacheValue; + } + + private List getSiteCalcPointConfigs(String siteId) { + if (StringUtils.isBlank(siteId)) { + return Collections.emptyList(); + } + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setPointType("calc"); + List calcPoints = emsPointConfigMapper.selectEmsPointConfigList(query); + if (CollectionUtils.isEmpty(calcPoints)) { + return Collections.emptyList(); + } + Map uniqueByPointId = new LinkedHashMap<>(); + for (EmsPointConfig calcPoint : calcPoints) { + if (!isComputedPoint(calcPoint)) { + continue; + } + String calcKey = resolvePointContextKey(calcPoint); + if (StringUtils.isBlank(calcKey)) { + continue; + } + uniqueByPointId.putIfAbsent(calcKey, calcPoint); + } + return new ArrayList<>(uniqueByPointId.values()); + } + + private List getSiteSourcePointConfigs(String siteId) { + if (StringUtils.isBlank(siteId)) { + return Collections.emptyList(); + } + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + List pointConfigs = emsPointConfigMapper.selectEmsPointConfigList(query); + if (CollectionUtils.isEmpty(pointConfigs)) { + return Collections.emptyList(); + } + Map uniqueByPointId = new LinkedHashMap<>(); + for (EmsPointConfig pointConfig : pointConfigs) { + if (pointConfig == null || isComputedPoint(pointConfig)) { + continue; + } + String pointId = resolveInfluxPointKey(pointConfig); + if (StringUtils.isBlank(pointId)) { + continue; + } + uniqueByPointId.putIfAbsent(pointId.trim().toUpperCase(), pointConfig); + } + return new ArrayList<>(uniqueByPointId.values()); + } + + private void putSourcePointValueToContext(EmsPointConfig pointConfig, BigDecimal pointValue, Map contextValues) { + if (pointConfig == null || pointValue == null || contextValues == null) { + return; + } + String pointContextKey = resolvePointContextKey(pointConfig); + if (StringUtils.isNotBlank(pointContextKey)) { + contextValues.put(pointContextKey, pointValue); + } + String normalizedDataKey = normalizeVariablePart(pointConfig.getDataKey()); + if (StringUtils.isNotBlank(normalizedDataKey)) { + contextValues.put(normalizedDataKey, pointValue); + String devicePrefix = normalizeVariablePart(pointConfig.getDeviceId()); + if (StringUtils.isNotBlank(devicePrefix)) { + contextValues.put(devicePrefix + "_" + normalizedDataKey, pointValue); + } + } + } + + private void cacheLatestPointValuesByPointId(String siteId, String deviceId, + Map pointIdValueMap, + Date dataTime) { + if (StringUtils.isBlank(siteId) || org.apache.commons.collections4.MapUtils.isEmpty(pointIdValueMap)) { + log.warn("MQTT点位写Redis跳过,siteId: {}, deviceId: {}, pointMapEmpty: {}", + siteId, deviceId, org.apache.commons.collections4.MapUtils.isEmpty(pointIdValueMap)); + return; + } + Date latestTime = dataTime == null ? DateUtils.getNowDate() : dataTime; + int writeCount = 0; + List writeSamples = new ArrayList<>(); + for (Map.Entry entry : pointIdValueMap.entrySet()) { + String pointId = StringUtils.trimToNull(entry.getKey()); + BigDecimal pointValue = entry.getValue(); + if (StringUtils.isBlank(pointId) || pointValue == null) { + log.warn("MQTT点位写Redis忽略无效值,siteId: {}, deviceId: {}, rawPointId: {}, pointValue: {}", + siteId, deviceId, entry.getKey(), pointValue); + continue; + } + JSONObject cacheValue = new JSONObject(); + cacheValue.put("siteId", siteId); + cacheValue.put("deviceId", deviceId); + cacheValue.put("pointId", pointId); + cacheValue.put("pointValue", pointValue); + cacheValue.put("dataTime", latestTime); + cacheValue.put("timestamp", latestTime == null ? null : latestTime.getTime()); + redisCache.setCacheObject(pointId, cacheValue); + writeCount++; + if (writeSamples.size() < 10) { + writeSamples.add(pointId); + } + } + log.info("MQTT点位写Redis完成,siteId: {}, deviceId: {}, writeCount: {}, writeSamples: {}, dataTime: {}", + siteId, deviceId, writeCount, writeSamples, latestTime); + } + + private static class PointDataRecord { + private final String siteId; + private final String deviceId; + private final String pointKey; + private final BigDecimal pointValue; + private final Date dataTime; + + private PointDataRecord(String siteId, String deviceId, String pointKey, BigDecimal pointValue, Date dataTime) { + this.siteId = siteId; + this.deviceId = deviceId; + this.pointKey = pointKey; + this.pointValue = pointValue; + this.dataTime = dataTime; + } + + private String getSiteId() { + return siteId; + } + + private String getDeviceId() { + return deviceId; + } + + private String getPointKey() { + return pointKey; + } + + private BigDecimal getPointValue() { + return pointValue; + } + + private Date getDataTime() { + return dataTime; + } + } + + private static class PointLatestCacheValue { + private String siteId; + private String deviceId; + private String pointId; + private BigDecimal pointValue; + private Date dataTime; + + private String getSiteId() { + return siteId; + } + + private void setSiteId(String siteId) { + this.siteId = siteId; + } + + private String getDeviceId() { + return deviceId; + } + + private void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + private String getPointId() { + return pointId; + } + + private void setPointId(String pointId) { + this.pointId = pointId; + } + + private BigDecimal getPointValue() { + return pointValue; + } + + private void setPointValue(BigDecimal pointValue) { + this.pointValue = pointValue; + } + + private Date getDataTime() { + return dataTime; + } + + private void setDataTime(Date dataTime) { + this.dataTime = dataTime; + } + } + + 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 ExprTokenType { + NUMBER, + STRING, + IDENTIFIER, + OPERATOR, + LEFT_PAREN, + RIGHT_PAREN, + COMMA, + QUESTION, + COLON, + EOF + } + + private static class ExprToken { + private final ExprTokenType type; + private final String text; + private final BigDecimal number; + private final String stringValue; + + private ExprToken(ExprTokenType type, String text, BigDecimal number, String stringValue) { + this.type = type; + this.text = text; + this.number = number; + this.stringValue = stringValue; + } + + private static ExprToken number(BigDecimal value) { + return new ExprToken(ExprTokenType.NUMBER, null, value, null); + } + + private static ExprToken string(String value) { + return new ExprToken(ExprTokenType.STRING, null, null, value); + } + + private static ExprToken identifier(String value) { + return new ExprToken(ExprTokenType.IDENTIFIER, value, null, null); + } + + private static ExprToken operator(String value) { + return new ExprToken(ExprTokenType.OPERATOR, value, null, null); + } + + private static ExprToken symbol(ExprTokenType type, String text) { + return new ExprToken(type, text, null, null); + } + } + + private static class ExpressionValue { + private final BigDecimal numberValue; + private final String textValue; + + private ExpressionValue(BigDecimal numberValue, String textValue) { + this.numberValue = numberValue; + this.textValue = textValue; + } + + private static ExpressionValue ofNumber(BigDecimal numberValue) { + return new ExpressionValue(numberValue, null); + } + + private static ExpressionValue ofText(String textValue) { + return new ExpressionValue(null, textValue == null ? "" : textValue); + } + + private boolean isNumber() { + return numberValue != null; + } + + private BigDecimal asNumber() { + if (numberValue == null) { + throw new IllegalArgumentException("表达式值不是数值类型: " + textValue); + } + return numberValue; + } + + private String asText() { + return numberValue != null ? numberValue.stripTrailingZeros().toPlainString() : textValue; + } + + private boolean asBoolean() { + return isNumber() ? BigDecimal.ZERO.compareTo(numberValue) != 0 : StringUtils.isNotBlank(textValue); + } + + } + + private interface ExpressionNode { + ExpressionValue evaluate(Map contextValues); + } + + private static class NumberNode implements ExpressionNode { + private final BigDecimal value; + + private NumberNode(BigDecimal value) { + this.value = value; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + return ExpressionValue.ofNumber(value); + } + } + + private static class StringNode implements ExpressionNode { + private final String value; + + private StringNode(String value) { + this.value = value; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + return ExpressionValue.ofText(value); + } + } + + private static class VariableNode implements ExpressionNode { + private final String name; + + private VariableNode(String name) { + this.name = name; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + BigDecimal value = contextValues.get(name); + if (value == null) { + throw new MissingVariableException(name); + } + return ExpressionValue.ofNumber(value); + } + + private String getName() { + return name; + } + } + + private static class UnaryNode implements ExpressionNode { + private final String operator; + private final ExpressionNode node; + + private UnaryNode(String operator, ExpressionNode node) { + this.operator = operator; + this.node = node; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + ExpressionValue value = node.evaluate(contextValues); + switch (operator) { + case "+": + return value; + case "-": + return ExpressionValue.ofNumber(value.asNumber().negate()); + case "!": + return value.asBoolean() ? ExpressionValue.ofNumber(BigDecimal.ZERO) : ExpressionValue.ofNumber(BigDecimal.ONE); + default: + throw new IllegalArgumentException("不支持的一元操作符: " + operator); + } + } + } + + private static class BinaryNode implements ExpressionNode { + private final String operator; + private final ExpressionNode left; + private final ExpressionNode right; + + private BinaryNode(String operator, ExpressionNode left, ExpressionNode right) { + this.operator = operator; + this.left = left; + this.right = right; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + if ("&&".equals(operator)) { + ExpressionValue leftValue = left.evaluate(contextValues); + if (!leftValue.asBoolean()) { + return ExpressionValue.ofNumber(BigDecimal.ZERO); + } + return right.evaluate(contextValues).asBoolean() + ? ExpressionValue.ofNumber(BigDecimal.ONE) + : ExpressionValue.ofNumber(BigDecimal.ZERO); + } + if ("||".equals(operator)) { + ExpressionValue leftValue = left.evaluate(contextValues); + if (leftValue.asBoolean()) { + return ExpressionValue.ofNumber(BigDecimal.ONE); + } + return right.evaluate(contextValues).asBoolean() + ? ExpressionValue.ofNumber(BigDecimal.ONE) + : ExpressionValue.ofNumber(BigDecimal.ZERO); + } + + ExpressionValue leftValue = left.evaluate(contextValues); + ExpressionValue rightValue = right.evaluate(contextValues); + switch (operator) { + case "+": + if (!leftValue.isNumber() || !rightValue.isNumber()) { + return ExpressionValue.ofText(leftValue.asText() + rightValue.asText()); + } + return ExpressionValue.ofNumber(leftValue.asNumber().add(rightValue.asNumber())); + case "-": + return ExpressionValue.ofNumber(leftValue.asNumber().subtract(rightValue.asNumber())); + case "*": + return ExpressionValue.ofNumber(leftValue.asNumber().multiply(rightValue.asNumber())); + case "/": + if (BigDecimal.ZERO.compareTo(rightValue.asNumber()) == 0) { + throw new IllegalArgumentException("除数不能为0"); + } + return ExpressionValue.ofNumber(leftValue.asNumber().divide(rightValue.asNumber(), 10, RoundingMode.HALF_UP)); + case ">": + return leftValue.asNumber().compareTo(rightValue.asNumber()) > 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case ">=": + return leftValue.asNumber().compareTo(rightValue.asNumber()) >= 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case "<": + return leftValue.asNumber().compareTo(rightValue.asNumber()) < 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case "<=": + return leftValue.asNumber().compareTo(rightValue.asNumber()) <= 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case "==": + if (leftValue.isNumber() && rightValue.isNumber()) { + return leftValue.asNumber().compareTo(rightValue.asNumber()) == 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + } + return Objects.equals(leftValue.asText(), rightValue.asText()) + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case "!=": + if (leftValue.isNumber() && rightValue.isNumber()) { + return leftValue.asNumber().compareTo(rightValue.asNumber()) != 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + } + return !Objects.equals(leftValue.asText(), rightValue.asText()) + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + default: + throw new IllegalArgumentException("不支持的操作符: " + operator); + } + } + } + + private static class TernaryNode implements ExpressionNode { + private final ExpressionNode condition; + private final ExpressionNode trueNode; + private final ExpressionNode falseNode; + + private TernaryNode(ExpressionNode condition, ExpressionNode trueNode, ExpressionNode falseNode) { + this.condition = condition; + this.trueNode = trueNode; + this.falseNode = falseNode; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + return condition.evaluate(contextValues).asBoolean() + ? trueNode.evaluate(contextValues) + : falseNode.evaluate(contextValues); + } + } + + private static class FunctionNode implements ExpressionNode { + private final String functionName; + private final List arguments; + + private FunctionNode(String functionName, List arguments) { + this.functionName = StringUtils.defaultString(functionName).trim().toUpperCase(); + this.arguments = arguments == null ? Collections.emptyList() : arguments; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + if (("DAY_DIFF".equals(functionName) + || "MONTH_DIFF".equals(functionName) + || "HOUR_DIFF".equals(functionName)) && arguments.size() == 1) { + ExpressionNode argument = arguments.get(0); + if (!(argument instanceof VariableNode)) { + throw new IllegalArgumentException(functionName + "单参数模式仅支持变量参数"); + } + String variable = ((VariableNode) argument).getName(); + BigDecimal autoDiffValue = contextValues.get(resolveAutoDiffContextKey(functionName, variable)); + if (autoDiffValue == null) { + throw new MissingVariableException(variable); + } + return ExpressionValue.ofNumber(autoDiffValue); + } + if ("DAY_DIFF".equals(functionName) + || "MONTH_DIFF".equals(functionName) + || "HOUR_DIFF".equals(functionName)) { + throw new IllegalArgumentException(functionName + "函数参数数量错误,需1个参数"); + } + throw new IllegalArgumentException("不支持的函数: " + functionName); + } + } + + private static class CompiledExpression { + private final ExpressionNode root; + + private CompiledExpression(String expression) { + List tokens = tokenizeExpression(expression); + ExpressionParser parser = new ExpressionParser(tokens); + this.root = parser.parseExpression(); + if (!parser.isEnd()) { + ExprToken token = parser.peek(); + throw new IllegalArgumentException("表达式尾部有多余内容: " + token.text); + } + } + + private ExpressionValue evaluate(Map contextValues) { + return root.evaluate(contextValues); + } + } + + private static class ExpressionParser { + private final List tokens; + private int index; + + private ExpressionParser(List tokens) { + this.tokens = tokens; + this.index = 0; + } + + private ExpressionNode parseExpression() { + return parseTernary(); + } + + private ExpressionNode parseTernary() { + ExpressionNode condition = parseOr(); + if (match(ExprTokenType.QUESTION)) { + ExpressionNode trueNode = parseTernary(); + expect(ExprTokenType.COLON, "三元表达式缺少 ':'"); + ExpressionNode falseNode = parseTernary(); + return new TernaryNode(condition, trueNode, falseNode); + } + return condition; + } + + private ExpressionNode parseOr() { + ExpressionNode left = parseAnd(); + while (matchOperator("||")) { + left = new BinaryNode("||", left, parseAnd()); + } + return left; + } + + private ExpressionNode parseAnd() { + ExpressionNode left = parseEquality(); + while (matchOperator("&&")) { + left = new BinaryNode("&&", left, parseEquality()); + } + return left; + } + + private ExpressionNode parseEquality() { + ExpressionNode left = parseComparison(); + while (true) { + if (matchOperator("==")) { + left = new BinaryNode("==", left, parseComparison()); + continue; + } + if (matchOperator("!=")) { + left = new BinaryNode("!=", left, parseComparison()); + continue; + } + break; + } + return left; + } + + private ExpressionNode parseComparison() { + ExpressionNode left = parseAddSub(); + while (true) { + if (matchOperator(">=")) { + left = new BinaryNode(">=", left, parseAddSub()); + continue; + } + if (matchOperator("<=")) { + left = new BinaryNode("<=", left, parseAddSub()); + continue; + } + if (matchOperator(">")) { + left = new BinaryNode(">", left, parseAddSub()); + continue; + } + if (matchOperator("<")) { + left = new BinaryNode("<", left, parseAddSub()); + continue; + } + break; + } + return left; + } + + private ExpressionNode parseAddSub() { + ExpressionNode left = parseMulDiv(); + while (true) { + if (matchOperator("+")) { + left = new BinaryNode("+", left, parseMulDiv()); + continue; + } + if (matchOperator("-")) { + left = new BinaryNode("-", left, parseMulDiv()); + continue; + } + break; + } + return left; + } + + private ExpressionNode parseMulDiv() { + ExpressionNode left = parseUnary(); + while (true) { + if (matchOperator("*")) { + left = new BinaryNode("*", left, parseUnary()); + continue; + } + if (matchOperator("/")) { + left = new BinaryNode("/", left, parseUnary()); + continue; + } + break; + } + return left; + } + + private ExpressionNode parseUnary() { + if (matchOperator("+")) { + return new UnaryNode("+", parseUnary()); + } + if (matchOperator("-")) { + return new UnaryNode("-", parseUnary()); + } + if (matchOperator("!")) { + return new UnaryNode("!", parseUnary()); + } + return parsePrimary(); + } + + private ExpressionNode parsePrimary() { + ExprToken token = peek(); + if (match(ExprTokenType.NUMBER)) { + return new NumberNode(token.number); + } + if (match(ExprTokenType.STRING)) { + return new StringNode(token.stringValue); + } + if (match(ExprTokenType.IDENTIFIER)) { + String identifier = token.text; + if (match(ExprTokenType.LEFT_PAREN)) { + return parseFunction(identifier); + } + return new VariableNode(identifier.toUpperCase()); + } + if (match(ExprTokenType.LEFT_PAREN)) { + ExpressionNode nested = parseExpression(); + expect(ExprTokenType.RIGHT_PAREN, "括号不匹配,缺少右括号"); + return nested; + } + throw new IllegalArgumentException("表达式语法错误,当前位置: " + token.text); + } + + private ExpressionNode parseFunction(String identifier) { + if ("IF".equalsIgnoreCase(identifier)) { + ExpressionNode condition = parseExpression(); + expect(ExprTokenType.COMMA, "IF函数参数格式错误,缺少第1个逗号"); + ExpressionNode trueNode = parseExpression(); + expect(ExprTokenType.COMMA, "IF函数参数格式错误,缺少第2个逗号"); + ExpressionNode falseNode = parseExpression(); + expect(ExprTokenType.RIGHT_PAREN, "IF函数缺少右括号"); + return new TernaryNode(condition, trueNode, falseNode); + } + if ("DAY_DIFF".equalsIgnoreCase(identifier)) { + List arguments = parseFunctionArguments(identifier); + if (arguments.size() != 1) { + throw new IllegalArgumentException("DAY_DIFF函数参数数量错误,需1个参数"); + } + return new FunctionNode(identifier, arguments); + } + if ("MONTH_DIFF".equalsIgnoreCase(identifier)) { + List arguments = parseFunctionArguments(identifier); + if (arguments.size() != 1) { + throw new IllegalArgumentException("MONTH_DIFF函数参数数量错误,需1个参数"); + } + return new FunctionNode(identifier, arguments); + } + if ("HOUR_DIFF".equalsIgnoreCase(identifier)) { + List arguments = parseFunctionArguments(identifier); + if (arguments.size() != 1) { + throw new IllegalArgumentException("HOUR_DIFF函数参数数量错误,需1个参数"); + } + return new FunctionNode(identifier, arguments); + } + throw new IllegalArgumentException("不支持的函数: " + identifier); + } + + private List parseFunctionArguments(String functionName) { + List arguments = new ArrayList<>(); + if (match(ExprTokenType.RIGHT_PAREN)) { + return arguments; + } + while (true) { + arguments.add(parseExpression()); + if (match(ExprTokenType.COMMA)) { + continue; + } + expect(ExprTokenType.RIGHT_PAREN, functionName + "函数缺少右括号"); + return arguments; + } + } + + private ExprToken peek() { + if (index >= tokens.size()) { + return ExprToken.symbol(ExprTokenType.EOF, ""); + } + return tokens.get(index); + } + + private boolean isEnd() { + return peek().type == ExprTokenType.EOF; + } + + private boolean match(ExprTokenType type) { + if (peek().type != type) { + return false; + } + index++; + return true; + } + + private boolean matchOperator(String operator) { + ExprToken token = peek(); + if (token.type != ExprTokenType.OPERATOR || !operator.equals(token.text)) { + return false; + } + index++; + return true; + } + + private void expect(ExprTokenType type, String message) { + if (!match(type)) { + throw new IllegalArgumentException(message); + } + } + } + + private static List tokenizeExpression(String expression) { + if (StringUtils.isBlank(expression)) { + throw new IllegalArgumentException("计算表达式为空"); + } + List tokens = new ArrayList<>(); + int index = 0; + while (index < expression.length()) { + char ch = expression.charAt(index); + if (Character.isWhitespace(ch)) { + index++; + continue; + } + if (Character.isDigit(ch) || ch == '.') { + int start = index; + boolean hasDot = ch == '.'; + index++; + while (index < expression.length()) { + char next = expression.charAt(index); + if (Character.isDigit(next)) { + index++; + continue; + } + if (next == '.' && !hasDot) { + hasDot = true; + index++; + continue; + } + break; + } + String text = expression.substring(start, index); + try { + tokens.add(ExprToken.number(new BigDecimal(text))); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("数值格式错误: " + text); + } + continue; + } + if (Character.isLetter(ch) || ch == '_') { + int start = index; + index++; + while (index < expression.length()) { + char next = expression.charAt(index); + if (Character.isLetterOrDigit(next) || next == '_') { + index++; + continue; + } + break; + } + String text = expression.substring(start, index).trim().toUpperCase(); + tokens.add(ExprToken.identifier(text)); + continue; + } + if (ch == '\'' || ch == '"') { + char quote = ch; + index++; + StringBuilder sb = new StringBuilder(); + boolean escaped = false; + while (index < expression.length()) { + char current = expression.charAt(index++); + if (escaped) { + switch (current) { + case 'n': + sb.append('\n'); + break; + case 't': + sb.append('\t'); + break; + case 'r': + sb.append('\r'); + break; + case '\\': + sb.append('\\'); + break; + case '\'': + sb.append('\''); + break; + case '"': + sb.append('"'); + break; + default: + sb.append(current); + break; + } + escaped = false; + continue; + } + if (current == '\\') { + escaped = true; + continue; + } + if (current == quote) { + break; + } + sb.append(current); + } + if (escaped || index > expression.length() || expression.charAt(index - 1) != quote) { + throw new IllegalArgumentException("字符串字面量未闭合"); + } + tokens.add(ExprToken.string(sb.toString())); + continue; + } + if (ch == '(') { + tokens.add(ExprToken.symbol(ExprTokenType.LEFT_PAREN, "(")); + index++; + continue; + } + if (ch == ')') { + tokens.add(ExprToken.symbol(ExprTokenType.RIGHT_PAREN, ")")); + index++; + continue; + } + if (ch == ',') { + tokens.add(ExprToken.symbol(ExprTokenType.COMMA, ",")); + index++; + continue; + } + if (ch == '?') { + tokens.add(ExprToken.symbol(ExprTokenType.QUESTION, "?")); + index++; + continue; + } + if (ch == ':') { + tokens.add(ExprToken.symbol(ExprTokenType.COLON, ":")); + index++; + continue; + } + if (index + 1 < expression.length()) { + String twoChars = expression.substring(index, index + 2); + if ("&&".equals(twoChars) + || "||".equals(twoChars) + || ">=".equals(twoChars) + || "<=".equals(twoChars) + || "==".equals(twoChars) + || "!=".equals(twoChars)) { + tokens.add(ExprToken.operator(twoChars)); + index += 2; + continue; + } + } + if (ch == '+' || ch == '-' || ch == '*' || ch == '/' + || ch == '>' || ch == '<' || ch == '!') { + tokens.add(ExprToken.operator(String.valueOf(ch))); + index++; + continue; + } + throw new IllegalArgumentException("表达式包含非法字符: " + ch); + } + tokens.add(ExprToken.symbol(ExprTokenType.EOF, "")); + return tokens; } private JSONArray parseJsonData(String message) { @@ -215,51 +2811,8 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i return null; } - public void processingDeviceData(String siteId, String deviceId, String jsonData, Date dataUpdateTime) { - // 判断设备类型,并调用对应的方法处理数据 - String deviceCategory = getDeviceCategory(siteId, deviceId); - if (deviceId.contains(SiteDevice.BMSD.name())) { - batteryStackDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - if (SiteEnum.DDS.getCode().equals(siteId)) { - batteryGroupDataProcess(siteId, deviceId, jsonData); - batteryDataProcessFromBmsd(siteId, deviceId, jsonData, dataUpdateTime); - } - } else if (deviceId.contains(SiteDevice.BMSC.name())) { - batteryClusterDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - batteryDataProcessFromBmsc(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.PCS.name()) - || DeviceCategory.PCS.getCode().equals(deviceCategory)) { - pcsDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - pcsBranchDataProcess(siteId, deviceId, jsonData); - if (SiteEnum.DDS.getCode().equals(siteId)) { - batteryClusterDataProcess(siteId, jsonData, dataUpdateTime); - } - } else if (deviceId.contains(SiteDevice.LOAD.name()) - || deviceId.contains(SiteDevice.METEGF.name()) - || deviceId.contains(SiteDevice.METE.name()) - || deviceId.contains(SiteDevice.METE0.name()) - || DeviceCategory.AMMETER.getCode().equals(deviceCategory)) { - meteDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.XF.name()) - || DeviceCategory.XF.getCode().equals(deviceCategory)) { - meteXFProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.DH.name()) - || deviceId.contains(SiteDevice.donghuan.name()) - || DeviceCategory.DH.getCode().equals(deviceCategory)) { - dhDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.ZSLQ.name()) - || DeviceCategory.COOLING.getCode().equals(deviceCategory)) { - coolingDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains(SiteDevice.EMS.name()) - || DeviceCategory.EMS.getCode().equals(deviceCategory)) { - emsDataProcess(siteId, deviceId, jsonData, dataUpdateTime); - } else { - log.info("未找到匹配的设备类型,无法处理数据,siteId: " + siteId + ",deviceId: " + deviceId); - } - } - private String getDeviceCategory(String siteId, String deviceId) { - EmsDevicesSetting emsDevicesSetting = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId); + EmsDevicesSetting emsDevicesSetting = getDeviceSettingWithCache(siteId, deviceId); if (emsDevicesSetting != null) { return emsDevicesSetting.getDeviceCategory(); } @@ -351,10 +2904,13 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i redisCache.setCacheObject(RedisKeyConstants.XF + siteId + "_" + deviceId, xfData); // 状态枚举还没有提供 - EmsDevicesSetting emsDevicesSetting = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId); - emsDevicesSetting.setCommunicationStatus(StringUtils.getString(xfData.getDczt())); - emsDevicesSetting.setUpdatedAt(DateUtils.getNowDate()); - emsDevicesSettingMapper.updateEmsDevicesSetting(emsDevicesSetting); + EmsDevicesSetting emsDevicesSetting = getDeviceSettingWithCache(siteId, deviceId); + if (emsDevicesSetting != null) { + emsDevicesSetting.setCommunicationStatus(StringUtils.getString(xfData.getDczt())); + emsDevicesSetting.setUpdatedAt(DateUtils.getNowDate()); + emsDevicesSettingMapper.updateEmsDevicesSetting(emsDevicesSetting); + cacheDeviceSetting(siteId, deviceId, emsDevicesSetting); + } } private void dhDataProcess(String siteId, String deviceId, String dataJson, Date dateUpdateTime) { @@ -424,7 +2980,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i String status = dataStack.getWorkStatus(); int result = 5; - if (status != null && !status.trim().isEmpty()) { + if (status != null && !status.trim().isEmpty() && status.contains("null")) { // 1.0 -> 1, 2.0 -> 2 result = (int) Double.parseDouble(status.trim()); } @@ -880,7 +3436,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i //TODO 临时解决上送数据为浮点的问题 start String status = pcsData.getWorkStatus(); int result = 0; - if (status != null && !status.trim().isEmpty()) { + if (status != null && !status.trim().isEmpty() && status.contains("null")) { // 1.0 -> 1, 2.0 -> 2 result = (int) Double.parseDouble(status.trim()); } @@ -947,6 +3503,9 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i }); // 点位匹配数据 List pointMatchList = devicePointMatchDataProcessor.getDevicePointMatch(siteId, deviceId, DeviceMatchTable.AMMETER.getCode()); + if (CollectionUtils.isEmpty(pointMatchList)) { + pointMatchList = devicePointMatchDataProcessor.getDeviceDefaultPointMatch(siteId, DeviceMatchTable.AMMETER.getCode()); + } if (CollectionUtils.isEmpty(pointMatchList)) { log.info("未找到匹配的点位数据,无法处理LOAD总表数据,siteId: " + siteId + ",deviceId: " + deviceId); return; @@ -1020,7 +3579,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i log.info("临时解决上送数据为浮点的问题"); String status = data.getWorkStatus(); int result = 5; - if (status != null && !status.trim().isEmpty()) { + if (status != null && !status.trim().isEmpty() && status.contains("null")) { // 1.0 -> 1, 2.0 -> 2 result = (int) Double.parseDouble(status.trim()); } @@ -1053,10 +3612,59 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i // workStatus = enumMatch.get().getEnumCode(); // } // } - EmsDevicesSetting emsDevicesSetting = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId); + EmsDevicesSetting emsDevicesSetting = getDeviceSettingWithCache(siteId, deviceId); + if (emsDevicesSetting == null) { + return; + } emsDevicesSetting.setWorkStatus(workStatus); emsDevicesSetting.setUpdatedAt(DateUtils.getNowDate()); emsDevicesSettingMapper.updateEmsDevicesSetting(emsDevicesSetting); + cacheDeviceSetting(siteId, deviceId, emsDevicesSetting); + } + + private EmsDevicesSetting getDeviceSettingWithCache(String siteId, String deviceId) { + if (StringUtils.isAnyBlank(siteId, deviceId)) { + return null; + } + String cacheKey = RedisKeyConstants.DEVICE_SETTING + siteId + "_" + deviceId; + EmsDevicesSetting cached = redisCache.getCacheObject(cacheKey); + if (cached != null) { + return cached; + } + EmsDevicesSetting fromInitCache = getDeviceSettingFromInitCache(siteId, deviceId); + if (fromInitCache != null) { + cacheDeviceSetting(siteId, deviceId, fromInitCache); + return fromInitCache; + } + EmsDevicesSetting fromDb = emsDevicesSettingMapper.getDeviceBySiteAndDeviceId(deviceId, siteId); + if (fromDb != null) { + cacheDeviceSetting(siteId, deviceId, fromDb); + } + return fromDb; + } + + private EmsDevicesSetting getDeviceSettingFromInitCache(String siteId, String deviceId) { + Map> map = redisCache.getCacheObject(RedisKeyConstants.INIT_DEVICE_INFO); + if (map == null || map.isEmpty()) { + return null; + } + List list = map.get(siteId); + if (CollectionUtils.isEmpty(list)) { + return null; + } + for (EmsDevicesSetting item : list) { + if (item != null && deviceId.equals(item.getDeviceId())) { + return item; + } + } + return null; + } + + private void cacheDeviceSetting(String siteId, String deviceId, EmsDevicesSetting setting) { + if (StringUtils.isAnyBlank(siteId, deviceId) || setting == null) { + return; + } + redisCache.setCacheObject(RedisKeyConstants.DEVICE_SETTING + siteId + "_" + deviceId, setting, 10, TimeUnit.MINUTES); } private String getDeviceParent(String siteId, String deviceId) { @@ -1097,7 +3705,6 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i // 初始化当日数据 EmsDailyChargeData emsDailyChargeData = new EmsDailyChargeData(); emsDailyChargeData.setSiteId(siteId); - emsDailyChargeData.setDeviceId(deviceId); emsDailyChargeData.setDateTime(DateUtils.getNowDate()); emsDailyChargeData.setTotalChargeData(pcsData.getTotalAcChargeEnergy()); emsDailyChargeData.setTotalDischargeData(pcsData.getTotalAcDischargeEnergy()); @@ -1119,14 +3726,14 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i }); // 点位匹配数据 List pointMatchList = devicePointMatchDataProcessor.getDevicePointMatch(siteId, deviceId, DeviceMatchTable.AMMETER.getCode()); + if (CollectionUtils.isEmpty(pointMatchList)) { + pointMatchList = devicePointMatchDataProcessor.getDeviceDefaultPointMatch(siteId, DeviceMatchTable.AMMETER.getCode()); + } if (CollectionUtils.isEmpty(pointMatchList)) { log.info("未找到匹配的点位数据,无法处理电表数据,siteId: " + siteId + ",deviceId: " + deviceId); return; } - // 获取上次数据,便于后面计算差值均无则默认0 - EmsAmmeterData lastAmmeterData = getLastAmmeterData(siteId, deviceId); - EmsAmmeterData dataMete = new EmsAmmeterData(); // 更新时间 dataMete.setDataUpdateTime(dataUpdateTime); @@ -1148,80 +3755,14 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i // 处理储能电表-METE每日充放电数据 dealDailyChargeDate(siteId, deviceId, dataMete); - // 处理储能电表-METE每日数据(尖、峰、平、谷差值) -// if (SiteEnum.FX.getCode().equals(siteId)) { - dealAmmeterDailyDate(siteId, dataMete, dataUpdateTime, lastAmmeterData); -// } else if (SiteEnum.DDS.getCode().equals(siteId)) { -// dealDailyEnergyData(siteId, dataMete, yestData); -// } + // ems_daily_energy_data 仅保留页面修正与Quartz写入 } } - private EmsAmmeterData getLastAmmeterData(String siteId, String deviceId) { - // 先从redis取,取不到查数据 - EmsAmmeterData lastData = redisCache.getCacheObject(RedisKeyConstants.AMMETER + siteId + "_" + deviceId); - if (lastData == null) { - lastData = emsAmmeterDataMapper.getLastData(siteId, deviceId); - if (lastData == null) { - lastData = new EmsAmmeterData(); - lastData.setSiteId(siteId); - lastData.setDeviceId(deviceId); - lastData.setCurrentForwardActiveTotal(BigDecimal.ZERO); - lastData.setCurrentReverseActiveTotal(BigDecimal.ZERO); - } - } - return lastData; - } - - private void dealDDSDailyChargeDate(String siteId, EmsAmmeterData currentData, String deviceId) { - // 初始化今日充放电 - BigDecimal dailyDisChargeDate = new BigDecimal(0); - BigDecimal dailyChargeDate = new BigDecimal(0); - -// BigDecimal nowTotalDisChargeDate = StringUtils.getBigDecimal(obj.get("DQFXZYGDN")); -// BigDecimal nowTotalChargeDate = StringUtils.getBigDecimal(obj.get("DQZXZYGDN")); - BigDecimal nowTotalDisChargeDate = currentData.getCurrentReverseActiveTotal(); - BigDecimal nowTotalChargeDate = currentData.getCurrentForwardActiveTotal(); - // 初始化当日数据-总的 - EmsDailyChargeData emsDailyChargeData = initDailyChargeData(siteId, deviceId, nowTotalChargeDate, nowTotalDisChargeDate); - // 获取redis存放昨日最晚数据 - String yestDate = DateUtils.getYesterdayDate(); - String yestDateRedisKey = RedisKeyConstants.AMMETER + siteId + "_" + deviceId + "_" + yestDate; - EmsAmmeterData yestData = redisCache.getCacheObject(yestDateRedisKey); - if (yestData == null) { - // redis没有这查电表总数据表取截止到昨日最新第一条数据 - yestData = emsAmmeterDataMapper.getYestLatestDate(siteId, deviceId, yestDate); - // 数据存redis-有效期1天 - redisCache.setCacheObject(yestDateRedisKey, yestData, Constants.DATE_VALID_TIME, TimeUnit.DAYS); - } - if (yestData != null) { - // 今日总数据-昨日总数据=今日充放电 - BigDecimal yestTotalDisChargeDate = yestData.getCurrentReverseActiveTotal(); - BigDecimal yestTotalChargeDate = yestData.getCurrentForwardActiveTotal(); - - dailyChargeDate = nowTotalChargeDate.subtract(yestTotalChargeDate); - dailyDisChargeDate = nowTotalDisChargeDate.subtract(yestTotalDisChargeDate); - emsDailyChargeData.setChargeData(dailyChargeDate); - emsDailyChargeData.setDischargeData(dailyDisChargeDate); - } - - // 插入或更新每日充放电数据表 - emsDailyChargeDataMapper.insertOrUpdateData(emsDailyChargeData); - - // 初始化数据-尖峰平谷 - EmsDailyEnergyData energyData = initEnergyData(siteId); - // 计算尖峰平谷差值,更新表 - calcEnergyDiffAndRevenue(siteId, energyData, currentData, yestData); - energyData.setCalcTime(DateUtils.getNowDate()); - // 插入或更新电表每日差值数据表 - emsDailyEnergyDataMapper.insertOrUpdateData(energyData); - } - private EmsDailyChargeData initDailyChargeData(String siteId, String deviceId, BigDecimal nowTotalChargeDate, BigDecimal nowTotalDisChargeDate) { EmsDailyChargeData emsDailyChargeData = new EmsDailyChargeData(); emsDailyChargeData.setSiteId(siteId); - emsDailyChargeData.setDeviceId(deviceId); emsDailyChargeData.setDateTime(DateUtils.getNowDate()); emsDailyChargeData.setTotalChargeData(nowTotalChargeDate); emsDailyChargeData.setTotalDischargeData(nowTotalDisChargeDate); @@ -1232,15 +3773,7 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i return emsDailyChargeData; } - private void dealDailyEnergyData(String siteId, EmsAmmeterData currentData, EmsAmmeterData yestData) { - // 初始化数据-尖峰平谷 - EmsDailyEnergyData energyData = initEnergyData(siteId); - // 计算尖峰平谷差值,更新表 - calcEnergyDiffAndRevenue(siteId, energyData, currentData, yestData); - energyData.setCalcTime(DateUtils.getNowDate()); - // 插入或更新电表每日差值数据表 - emsDailyEnergyDataMapper.insertOrUpdateData(energyData); - } + private EmsAmmeterData dealDailyChargeDate(String siteId, String deviceId, EmsAmmeterData currentData) { // 初始化今日充放电 BigDecimal dailyDisChargeDate = new BigDecimal(0); @@ -1277,83 +3810,6 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i return yestData; } - private void calcEnergyDiffAndRevenue(String siteId, EmsDailyEnergyData energyData, EmsAmmeterData currentData, EmsAmmeterData yestData) { - - // 获取当月电价 - String key = RedisKeyConstants.ENERGY_PRICE_TIME + siteId + "_" + LocalDate.now().getYear() + LocalDate.now().getMonthValue(); - EnergyPriceVo priceVo = redisCache.getCacheObject(key); - - // 计算尖峰平谷差值 - // 正反向-尖 -// BigDecimal peakChargeDiff = StringUtils.getBigDecimal(obj.get("DQZXYGJDN")) -// .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActivePeak()); - BigDecimal peakChargeDiff = currentData.getCurrentForwardActivePeak() - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActivePeak()); - energyData.setPeakChargeDiff(peakChargeDiff); -// BigDecimal peakDischargeDiff = StringUtils.getBigDecimal(obj.get("DQFXYGJDN")) -// .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActivePeak()); - BigDecimal peakDischargeDiff = currentData.getCurrentReverseActivePeak() - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActivePeak()); - energyData.setPeakDischargeDiff(peakDischargeDiff); - // 正反向-峰 -// BigDecimal highChargeDiff = StringUtils.getBigDecimal(obj.get("DQZXYGFDN")) -// .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveHigh()); - BigDecimal highChargeDiff = currentData.getCurrentForwardActiveHigh() - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveHigh()); - energyData.setHighChargeDiff(highChargeDiff); -// BigDecimal highDischargeDiff = StringUtils.getBigDecimal(obj.get("DQFXYGFDN")) -// .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveHigh()); - BigDecimal highDischargeDiff = currentData.getCurrentReverseActiveHigh() - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveHigh()); - energyData.setHighDischargeDiff(highDischargeDiff); - // 正反向-平 -// BigDecimal flatChargeDiff = StringUtils.getBigDecimal(obj.get("DQZXYGPDN")) -// .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveFlat()); - BigDecimal flatChargeDiff = currentData.getCurrentForwardActiveFlat() - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveFlat()); - energyData.setFlatChargeDiff(flatChargeDiff); -// BigDecimal flatDisChargeDiff = StringUtils.getBigDecimal(obj.get("DQFXYGPDN")) -// .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveFlat()); - BigDecimal flatDisChargeDiff = currentData.getCurrentReverseActiveFlat() - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveFlat()); - energyData.setFlatDischargeDiff(flatDisChargeDiff); - // 正反向-谷 -// BigDecimal valleyChargeDiff = StringUtils.getBigDecimal(obj.get("DQZXYGGDN")) -// .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveValley()); - BigDecimal valleyChargeDiff = currentData.getCurrentForwardActiveValley() - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentForwardActiveValley()); - energyData.setValleyChargeDiff(valleyChargeDiff); -// BigDecimal valleyDisChargeDiff = StringUtils.getBigDecimal(obj.get("DQFXYGGDN")) -// .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveValley()); - BigDecimal valleyDisChargeDiff = currentData.getCurrentReverseActiveValley() - .subtract(yestData == null ? new BigDecimal(0) : yestData.getCurrentReverseActiveValley()); - energyData.setValleyDischargeDiff(valleyDisChargeDiff); - - - BigDecimal totalRevenue = getYestLastData(siteId); - BigDecimal dayRevenue = BigDecimal.ZERO; - BigDecimal price = BigDecimal.ZERO; - // 计算当日收益,尖峰平谷收益累加,(放电量-充电量)*电价 - if (priceVo != null) { - price = priceVo.getPeak() == null ? BigDecimal.ZERO : priceVo.getPeak(); - BigDecimal peakRevenue = peakDischargeDiff.subtract(peakChargeDiff).multiply(price); - dayRevenue = dayRevenue.add(peakRevenue); - price = priceVo.getHigh() == null ? BigDecimal.ZERO : priceVo.getHigh(); - BigDecimal highRevenue = highDischargeDiff.subtract(highChargeDiff).multiply(price); - dayRevenue = dayRevenue.add(highRevenue); - price = priceVo.getFlat() == null ? BigDecimal.ZERO : priceVo.getFlat(); - BigDecimal flatRevenue = flatDisChargeDiff.subtract(flatChargeDiff).multiply(price); - dayRevenue = dayRevenue.add(flatRevenue); - price = priceVo.getValley() == null ? BigDecimal.ZERO : priceVo.getValley(); - BigDecimal valleyRevenue = valleyDisChargeDiff.subtract(valleyChargeDiff).multiply(price); - dayRevenue = dayRevenue.add(valleyRevenue); - energyData.setDayRevenue(dayRevenue); - } - // 总收益 = 昨日总收益+今日实时收益 - totalRevenue = totalRevenue.add(dayRevenue); - energyData.setTotalRevenue(totalRevenue); - } - private BigDecimal getYestLastData(String siteId) { // dds存的是累计到昨日总收益 String yestDate = DateUtils.getYesterdayDayString(); @@ -1373,172 +3829,39 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i return yestLastTotalRevenue; } - private void dealAmmeterDailyDate(String siteId, EmsAmmeterData currentData, Date dataUpdateTime, EmsAmmeterData lastData) { - // 先获取当月电价配置,redis没有这查数据库,都没有则返回 - String priceKey = RedisKeyConstants.ENERGY_PRICE_TIME + siteId + "_" + LocalDate.now().getYear() + LocalDate.now().getMonthValue(); - EnergyPriceVo priceVo = redisCache.getCacheObject(priceKey); - if (priceVo == null) { - priceVo = emsEnergyPriceConfigService.getCurrentMonthPrice(siteId); - redisCache.setCacheObject(priceKey, priceVo, 31, TimeUnit.DAYS); - if (priceVo == null) { - return; - } + private void syncDailyRevenueToChargeData(String siteId, Date revenueDate, BigDecimal totalRevenue, BigDecimal dayRevenue) { + if (StringUtils.isBlank(siteId)) { + return; } - List timeRanges = priceVo.getRange(); - if (timeRanges == null) { + Date persistDate = revenueDate == null ? DateUtils.getNowDate() : revenueDate; + BigDecimal persistTotalRevenue = safeBigDecimal(totalRevenue); + BigDecimal persistDayRevenue = safeBigDecimal(dayRevenue); + + int updated = emsDailyChargeDataMapper.updateRevenueBySiteAndDate(siteId, persistDate, persistTotalRevenue, persistDayRevenue, "system"); + if (updated > 0) { return; } - // 根据时间范围判断数据类型(尖峰平谷),无法确定数据类型则不处理 - String costType = ""; - String startTime = ""; - for (EnergyPriceTimeRange timeRange : timeRanges) { - startTime = timeRange.getStartTime(); - if (isInPriceTimeRange(startTime, timeRange.getEndTime(), dataUpdateTime)) { - costType = timeRange.getCostType(); - break; - } - } - if (StringUtils.isEmpty(costType)) { + EmsDailyChargeData existedData = emsDailyChargeDataMapper.selectBySiteIdAndDateTime(siteId, persistDate); + if (existedData != null) { + existedData.setTotalRevenue(persistTotalRevenue); + existedData.setDayRevenue(persistDayRevenue); + existedData.setUpdateBy("system"); + existedData.setUpdateTime(DateUtils.getNowDate()); + emsDailyChargeDataMapper.updateEmsDailyChargeData(existedData); return; } - //初始化电表每日差值对象 - EmsDailyEnergyData energyData = initEnergyData(siteId); - - // 根据 costType,计算本次与上次数据差值,累加到对应的数据类型里面 - setDiffByCostType(siteId, costType, energyData, lastData, currentData, priceVo); - - // 插入或更新电表每日差值数据表 - emsDailyEnergyDataMapper.insertOrUpdateData(energyData); - } - - private void setDiffByCostType(String siteId, String costType, EmsDailyEnergyData energyData, EmsAmmeterData lastData, - EmsAmmeterData currentData, EnergyPriceVo priceVo) { -// BigDecimal currentChargeData = StringUtils.getBigDecimal(obj.get("ZXYGDN")); -// BigDecimal currentDischargeData = StringUtils.getBigDecimal(obj.get("FXYGDN")); - BigDecimal currentChargeData = currentData.getCurrentForwardActiveTotal(); - BigDecimal currentDischargeData = currentData.getCurrentReverseActiveTotal(); - currentChargeData = currentChargeData != null ? currentChargeData : BigDecimal.ZERO; - currentDischargeData = currentDischargeData != null ? currentDischargeData : BigDecimal.ZERO; - - // 获取上次实时总收益+当日实时总收益,初始化电价 - Map revenueMap = getRealTimeData(siteId); - BigDecimal totalRevenue = revenueMap.get("totalRevenue") == null ? BigDecimal.ZERO : revenueMap.get("totalRevenue"); - BigDecimal dayRevenue = revenueMap.get("dayRevenue") == null ? BigDecimal.ZERO : revenueMap.get("dayRevenue"); - BigDecimal price = BigDecimal.ZERO; - // 计算时段差值,按照数据类型累加 - // 计算当日累加收益,尖峰平谷收益在当日原基础累加,(放电量-充电量)*电价 - BigDecimal chargeDiffData = currentChargeData.subtract( - lastData.getCurrentForwardActiveTotal() == null ? BigDecimal.ZERO : lastData.getCurrentForwardActiveTotal()); - BigDecimal disChargeDiffData = currentDischargeData.subtract( - lastData.getCurrentReverseActiveTotal() == null ? BigDecimal.ZERO : lastData.getCurrentReverseActiveTotal() - ); - switch (costType) { - case "peak": - // 增加电量 - BigDecimal peakCharge = energyData.getPeakChargeDiff() == null ? BigDecimal.ZERO : energyData.getPeakChargeDiff(); - energyData.setPeakChargeDiff(peakCharge.add(chargeDiffData)); - BigDecimal peakDischarge = energyData.getPeakDischargeDiff() == null ? BigDecimal.ZERO : energyData.getPeakDischargeDiff(); - energyData.setPeakDischargeDiff(peakDischarge.add(disChargeDiffData)); - - // 电价 - price = priceVo.getPeak() == null ? BigDecimal.ZERO : priceVo.getPeak(); - break; - case "high": - // 增加电量 - BigDecimal highCharge = energyData.getHighChargeDiff() == null ? BigDecimal.ZERO : energyData.getHighChargeDiff(); - energyData.setHighChargeDiff(highCharge.add(chargeDiffData)); - BigDecimal highDischarge = energyData.getHighDischargeDiff() == null ? BigDecimal.ZERO : energyData.getHighDischargeDiff(); - energyData.setHighDischargeDiff(highDischarge.add(disChargeDiffData)); - - // 电价 - price = priceVo.getHigh() == null ? BigDecimal.ZERO : priceVo.getHigh(); - break; - case "flat": - // 增加电量 - BigDecimal flatCharge = energyData.getFlatChargeDiff() == null ? BigDecimal.ZERO : energyData.getFlatChargeDiff(); - energyData.setFlatChargeDiff(flatCharge.add(chargeDiffData)); - BigDecimal flatDischarge = energyData.getFlatDischargeDiff() == null ? BigDecimal.ZERO : energyData.getFlatDischargeDiff(); - energyData.setFlatDischargeDiff(flatDischarge.add(disChargeDiffData)); - - // 电价 - price = priceVo.getFlat() == null ? BigDecimal.ZERO : priceVo.getFlat(); - break; - case "valley": - // 增加电量 - BigDecimal valleyCharge = energyData.getValleyChargeDiff() == null ? BigDecimal.ZERO : energyData.getValleyChargeDiff(); - energyData.setValleyChargeDiff(valleyCharge.add(chargeDiffData)); - BigDecimal valleyDischarge = energyData.getValleyDischargeDiff() == null ? BigDecimal.ZERO : energyData.getValleyDischargeDiff(); - energyData.setValleyDischargeDiff(valleyDischarge.add(disChargeDiffData)); - - // 电价 - price = priceVo.getValley() == null ? BigDecimal.ZERO : priceVo.getValley(); - break; - default: - return; - } - - // 计算本次累加收益 - BigDecimal addRevenue = disChargeDiffData.subtract(chargeDiffData).multiply(price); - dayRevenue = dayRevenue.add(addRevenue); - energyData.setDayRevenue(dayRevenue); - // 总收益 = 上次实时总收益+今日实时增加的收益 - totalRevenue = totalRevenue.add(addRevenue); - energyData.setTotalRevenue(totalRevenue); - - // 存redis便于下次取用 - String today = DateUtils.getDate(); - String redisKey = RedisKeyConstants.FXX_REALTIME_REVENUE + siteId + "_" + today; - Map realTimeRevenue = new HashMap<>(); - realTimeRevenue.put("totalRevenue", totalRevenue); - realTimeRevenue.put("dayRevenue", dayRevenue); - redisCache.setCacheObject(redisKey, realTimeRevenue, 1, TimeUnit.DAYS); - } - - private Map getRealTimeData(String siteId) { - // fx取实时总收益和当天实时收益 - String today = DateUtils.getDate(); - String redisKey = RedisKeyConstants.FXX_REALTIME_REVENUE + siteId + "_" + today; - Map realTimeRevenue = redisCache.getCacheObject(redisKey); - if (realTimeRevenue == null) { - // 查数据库 - realTimeRevenue = emsEnergyPriceConfigService.getDayRevenueMap(siteId); - if (realTimeRevenue == null) { - realTimeRevenue = new HashMap<>(); - realTimeRevenue.put("totalRevenue", BigDecimal.ZERO); - realTimeRevenue.put("dayRevenue", BigDecimal.ZERO); - } - redisCache.setCacheObject(redisKey, realTimeRevenue, 1, TimeUnit.DAYS); - } - return realTimeRevenue; - } - - private boolean isInPriceTimeRange(String startTime, String endTime, Date dataUpdateTime) { - if (startTime == null || endTime == null || dataUpdateTime == null) { - return false; - } - LocalDateTime time = DateUtils.toLocalDateTime(dataUpdateTime); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); - String dataTimeStr = time.format(formatter); - // 比较时间范围 - return dataTimeStr.compareTo(startTime) >= 0 - && dataTimeStr.compareTo(endTime) < 0; - } - - private EmsDailyEnergyData initEnergyData(String siteId) { - // 先获取数据库当天数据,存在则更新时间,不存在则初始化 - EmsDailyEnergyData energyData = emsDailyEnergyDataMapper.getDataByDate(siteId, DateUtils.getDate()); - if (energyData == null) { - energyData = new EmsDailyEnergyData(); - energyData.setSiteId(siteId); - energyData.setDataDate(DateUtils.getNowDate()); - energyData.setCreateBy("system"); - energyData.setCreateTime(DateUtils.getNowDate()); - } - energyData.setUpdateBy("system"); - energyData.setCalcTime(DateUtils.getNowDate()); - return energyData; + EmsDailyChargeData chargeData = new EmsDailyChargeData(); + chargeData.setSiteId(siteId); + chargeData.setDateTime(persistDate); + chargeData.setTotalRevenue(persistTotalRevenue); + chargeData.setDayRevenue(persistDayRevenue); + chargeData.setCreateBy("system"); + chargeData.setCreateTime(DateUtils.getNowDate()); + chargeData.setUpdateBy("system"); + chargeData.setUpdateTime(DateUtils.getNowDate()); + emsDailyChargeDataMapper.insertEmsDailyChargeData(chargeData); } // 数据分组处理 @@ -1905,19 +4228,16 @@ public class DeviceDataProcessServiceImpl extends AbstractBatteryDataProcessor i // 空数据不处理 private boolean checkJsonDataEmpty(String jsonData) { - boolean flag = false; + if (StringUtils.isEmpty(jsonData)) { + return true; + } try { - if (StringUtils.isEmpty(jsonData)) { - return true; - } JsonNode jsonNode = objectMapper.readTree(jsonData); // 判断是否为空对象({}) - if (jsonNode.isObject() && jsonNode.isEmpty()) { - return true; - } - } catch (JsonProcessingException e) { - throw new RuntimeException(e); + return jsonNode.isObject() && jsonNode.isEmpty(); + } catch (Exception e) { + log.warn("设备数据Data字段解析失败,按空数据处理,data: {}, err: {}", jsonData, e.getMessage()); + return true; } - return flag; } } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsAlarmRecordsServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsAlarmRecordsServiceImpl.java index a679c7e..c3a4acd 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsAlarmRecordsServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsAlarmRecordsServiceImpl.java @@ -174,6 +174,27 @@ public class EmsAlarmRecordsServiceImpl implements IEmsAlarmRecordsService return ticketNo; } + @Override + public String closeAlarm(Long id, Long userId) { + EmsAlarmRecords emsAlarmRecords = emsAlarmRecordsMapper.selectEmsAlarmRecordsById(id); + if (emsAlarmRecords == null) { + return "告警记录不存在"; + } + if (AlarmStatus.DONE.getCode().equals(emsAlarmRecords.getStatus())) { + return "告警已关闭"; + } + emsAlarmRecords.setStatus(AlarmStatus.DONE.getCode()); + emsAlarmRecords.setAlarmEndTime(DateUtils.getNowDate()); + emsAlarmRecords.setUpdateTime(DateUtils.getNowDate()); + if (userId == null) { + userId = 1L; + } + SysUser user = sysUserMapper.selectUserById(userId); + emsAlarmRecords.setUpdateBy(user != null ? user.getUserName() : "system"); + emsAlarmRecordsMapper.updateEmsAlarmRecords(emsAlarmRecords); + return "success"; + } + // 订阅失败-增加告警 @Override public void addSubFailedAlarmRecord(String topic) { diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDailyChargeDataServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDailyChargeDataServiceImpl.java new file mode 100644 index 0000000..18c683c --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDailyChargeDataServiceImpl.java @@ -0,0 +1,49 @@ +package com.xzzn.ems.service.impl; + +import com.xzzn.common.utils.DateUtils; +import com.xzzn.ems.domain.EmsDailyChargeData; +import com.xzzn.ems.mapper.EmsDailyChargeDataMapper; +import com.xzzn.ems.service.IEmsDailyChargeDataService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 站点每日充放电数据Service实现 + */ +@Service +public class EmsDailyChargeDataServiceImpl implements IEmsDailyChargeDataService { + + @Autowired + private EmsDailyChargeDataMapper emsDailyChargeDataMapper; + + @Override + public List selectEmsDailyChargeDataList(EmsDailyChargeData emsDailyChargeData) { + return emsDailyChargeDataMapper.selectEmsDailyChargeDataList(emsDailyChargeData); + } + + @Override + public EmsDailyChargeData selectEmsDailyChargeDataById(Long id) { + return emsDailyChargeDataMapper.selectEmsDailyChargeDataById(id); + } + + @Override + public int insertEmsDailyChargeData(EmsDailyChargeData emsDailyChargeData, String operName) { + emsDailyChargeData.setCreateBy(operName); + emsDailyChargeData.setCreateTime(DateUtils.getNowDate()); + return emsDailyChargeDataMapper.insertEmsDailyChargeData(emsDailyChargeData); + } + + @Override + public int updateEmsDailyChargeData(EmsDailyChargeData emsDailyChargeData, String operName) { + emsDailyChargeData.setUpdateBy(operName); + emsDailyChargeData.setUpdateTime(DateUtils.getNowDate()); + return emsDailyChargeDataMapper.updateEmsDailyChargeData(emsDailyChargeData); + } + + @Override + public int deleteEmsDailyChargeDataByIds(Long[] ids) { + return emsDailyChargeDataMapper.deleteEmsDailyChargeDataByIds(ids); + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDailyEnergyDataServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDailyEnergyDataServiceImpl.java new file mode 100644 index 0000000..23a954a --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsDailyEnergyDataServiceImpl.java @@ -0,0 +1,49 @@ +package com.xzzn.ems.service.impl; + +import com.xzzn.common.utils.DateUtils; +import com.xzzn.ems.domain.EmsDailyEnergyData; +import com.xzzn.ems.mapper.EmsDailyEnergyDataMapper; +import com.xzzn.ems.service.IEmsDailyEnergyDataService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 站点每日收益数据Service实现 + */ +@Service +public class EmsDailyEnergyDataServiceImpl implements IEmsDailyEnergyDataService { + + @Autowired + private EmsDailyEnergyDataMapper emsDailyEnergyDataMapper; + + @Override + public List selectEmsDailyEnergyDataList(EmsDailyEnergyData emsDailyEnergyData) { + return emsDailyEnergyDataMapper.selectEmsDailyEnergyDataList(emsDailyEnergyData); + } + + @Override + public EmsDailyEnergyData selectEmsDailyEnergyDataById(Long id) { + return emsDailyEnergyDataMapper.selectEmsDailyEnergyDataById(id); + } + + @Override + public int insertEmsDailyEnergyData(EmsDailyEnergyData emsDailyEnergyData, String operName) { + emsDailyEnergyData.setCreateBy(operName); + emsDailyEnergyData.setCreateTime(DateUtils.getNowDate()); + return emsDailyEnergyDataMapper.insertEmsDailyEnergyData(emsDailyEnergyData); + } + + @Override + public int updateEmsDailyEnergyData(EmsDailyEnergyData emsDailyEnergyData, String operName) { + emsDailyEnergyData.setUpdateBy(operName); + emsDailyEnergyData.setUpdateTime(DateUtils.getNowDate()); + return emsDailyEnergyDataMapper.updateEmsDailyEnergyData(emsDailyEnergyData); + } + + @Override + public int deleteEmsDailyEnergyDataByIds(Long[] ids) { + return emsDailyEnergyDataMapper.deleteEmsDailyEnergyDataByIds(ids); + } +} 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 b9f1ab0..4336a71 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 @@ -9,42 +9,78 @@ import com.xzzn.common.core.modbus.ModbusProcessor; import com.xzzn.common.core.modbus.domain.DeviceConfig; import com.xzzn.common.core.modbus.domain.WriteTagConfig; import com.xzzn.common.core.redis.RedisCache; +import com.xzzn.common.enums.BusinessStatus; +import com.xzzn.common.enums.BusinessType; import com.xzzn.common.enums.DeviceCategory; import com.xzzn.common.enums.DeviceType; +import com.xzzn.common.enums.OperatorType; import com.xzzn.common.enums.PointType; import com.xzzn.common.enums.WorkStatus; import com.xzzn.common.exception.ServiceException; import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsDailyChargeData; import com.xzzn.ems.domain.EmsDevicesSetting; +import com.xzzn.ems.domain.EmsPointEnumMatch; +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.EmsPointMatch; import com.xzzn.ems.domain.EmsPcsSetting; +import com.xzzn.ems.domain.EmsSiteMonitorItem; +import com.xzzn.ems.domain.EmsSiteMonitorPointMatch; import com.xzzn.ems.domain.vo.DeviceUpdateRequest; import com.xzzn.ems.domain.vo.DevicesSettingVo; import com.xzzn.ems.domain.vo.PointDataRequest; import com.xzzn.ems.domain.vo.PointQueryResponse; +import com.xzzn.ems.domain.vo.SiteMonitorDataSaveItemVo; +import com.xzzn.ems.domain.vo.SiteMonitorDataSaveRequest; +import com.xzzn.ems.domain.vo.SiteMonitorDataVo; +import com.xzzn.ems.domain.vo.SiteMonitorProjectDisplayVo; +import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingSaveRequest; +import com.xzzn.ems.domain.vo.SiteMonitorProjectPointMappingVo; +import com.xzzn.ems.domain.vo.WorkStatusEnumMappingVo; import com.xzzn.ems.mapper.EmsBatteryDataMinutesMapper; +import com.xzzn.ems.mapper.EmsDailyChargeDataMapper; +import com.xzzn.ems.mapper.EmsDailyEnergyDataMapper; import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsPcsSettingMapper; +import com.xzzn.ems.mapper.EmsPointConfigMapper; +import com.xzzn.ems.mapper.EmsPointEnumMatchMapper; import com.xzzn.ems.mapper.EmsPointMatchMapper; +import com.xzzn.ems.mapper.EmsSiteMonitorDataMapper; +import com.xzzn.ems.mapper.EmsSiteMonitorItemMapper; +import com.xzzn.ems.mapper.EmsSiteMonitorPointMatchMapper; import com.xzzn.ems.service.IEmsDeviceSettingService; +import com.xzzn.ems.service.InfluxPointDataWriter; +import com.xzzn.ems.utils.DevicePointMatchDataProcessor; +import com.xzzn.system.domain.SysOperLog; +import com.xzzn.system.service.ISysOperLogService; import java.math.BigDecimal; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.Collections; import org.slf4j.Logger; 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; /** * 站点信息 服务层实现 @@ -56,6 +92,48 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService private static final Logger log = LoggerFactory.getLogger(EmsDeviceSettingServiceImpl.class); private static final String DDS_SITE_ID = "021_DDS_01"; + private static final String MODULE_HOME = "HOME"; + private static final String MODULE_SBJK = "SBJK"; + private static final String MODULE_TJBB = "TJBB"; + private static final String MENU_SBJK_EMS = "SBJK_EMS"; + private static final Integer USE_FIXED_DISPLAY_YES = 1; + private static final String FIELD_CURVE_PCS_ACTIVE_POWER = "SBJK_SSYX__curvePcsActivePower"; + private static final String FIELD_CURVE_PCS_REACTIVE_POWER = "SBJK_SSYX__curvePcsReactivePower"; + private static final String HISTORY_TABLE_HOME = "ems_site_monitor_data_home_his"; + private static final String HISTORY_TABLE_SBJK = "ems_site_monitor_data_sbjk_his"; + private static final String HISTORY_TABLE_TJBB = "ems_site_monitor_data_tjbb_his"; + private static final String DELETED_FIELD_MARK = "__DELETED__"; + private static final String PCS_MATCH_DEVICE_CATEGORY = "PCS"; + private static final String STACK_MATCH_DEVICE_CATEGORY = "STACK"; + private static final String CLUSTER_MATCH_DEVICE_CATEGORY = "CLUSTER"; + private static final String MATCH_FIELD_WORK_STATUS = "workStatus"; + private static final String MATCH_FIELD_GRID_STATUS = "gridStatus"; + private static final String MATCH_FIELD_DEVICE_STATUS = "deviceStatus"; + private static final String MATCH_FIELD_CONTROL_MODE = "controlMode"; + private static final String MATCH_FIELD_PCS_COMMUNICATION_STATUS = "pcsCommunicationStatus"; + private static final String MATCH_FIELD_EMS_COMMUNICATION_STATUS = "emsCommunicationStatus"; + private static final String SITE_LEVEL_CALC_DEVICE_ID = "SITE_CALC"; + private static final Set DEVICE_DIMENSION_MENU_CODES = new HashSet<>(Arrays.asList( + "SBJK_EMS", "SBJK_PCS", "SBJK_BMSZL", "SBJK_BMSDCC", "SBJK_DTDC", "SBJK_DB", "SBJK_YL", "SBJK_DH", "SBJK_XF" + )); + private static final Set DEVICE_DIMENSION_FIELD_CODES = new HashSet<>(Arrays.asList( + FIELD_CURVE_PCS_ACTIVE_POWER, + FIELD_CURVE_PCS_REACTIVE_POWER, + "SBJK_SSYX__curvePcsMaxTemp", + "SBJK_SSYX__curveBatteryAveSoc", + "SBJK_SSYX__curveBatteryAveTemp" + )); + private static final Map MENU_DEVICE_CATEGORY_MAP = buildMenuDeviceCategoryMap(); + private static final long MONITOR_ITEM_CACHE_TTL_MS = 60_000L; + private static final long PROJECT_DISPLAY_CACHE_TTL_MS = 15_000L; + private static final int MONITOR_POINT_MATCH_REDIS_TTL_SECONDS = 300; + private static final int DISPLAY_DEBUG_SAMPLE_SIZE = 20; + private static final Set HOME_CHARGE_FIELD_SET = new HashSet<>(Arrays.asList( + "dayChargedCap", "dayDisChargedCap", "yesterdayChargedCap", "yesterdayDisChargedCap", "totalChargedCap", "totalDischargedCap" + )); + private static final Set HOME_REVENUE_FIELD_SET = new HashSet<>(Arrays.asList( + "totalRevenue", "dayRevenue", "yesterdayRevenue" + )); @Autowired private EmsDevicesSettingMapper emsDevicesMapper; @Autowired @@ -63,13 +141,67 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService @Autowired private EmsPcsSettingMapper emsPcsSettingMapper; @Autowired + private EmsPointConfigMapper emsPointConfigMapper; + @Autowired + private EmsPointEnumMatchMapper emsPointEnumMatchMapper; + @Autowired private RedisCache redisCache; @Autowired private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper; @Autowired + private EmsDailyChargeDataMapper emsDailyChargeDataMapper; + @Autowired + private EmsDailyEnergyDataMapper emsDailyEnergyDataMapper; + @Autowired private EmsBatteryClusterServiceImpl emsBatteryClusterServiceImpl; @Autowired private ModbusProcessor modbusProcessor; + @Autowired + private EmsSiteMonitorItemMapper emsSiteMonitorItemMapper; + @Autowired + private EmsSiteMonitorPointMatchMapper emsSiteMonitorPointMatchMapper; + @Autowired + private EmsSiteMonitorDataMapper emsSiteMonitorDataMapper; + @Autowired + private InfluxPointDataWriter influxPointDataWriter; + @Autowired + private ISysOperLogService operLogService; + + private volatile List monitorItemCache = Collections.emptyList(); + private volatile long monitorItemCacheExpireAt = 0L; + private final ConcurrentMap projectDisplayCache = new ConcurrentHashMap<>(); + private static final Map ENUM_SCOPE_NAME_MAP = buildEnumScopeNameMap(); + private static final Set MANAGED_ENUM_SCOPE_KEYS = ENUM_SCOPE_NAME_MAP.keySet(); + + private static Map buildEnumScopeNameMap() { + Map result = new LinkedHashMap<>(); + result.put(buildEnumScopeKey(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS), "PCS-工作状态"); + result.put(buildEnumScopeKey(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_GRID_STATUS), "PCS-并网状态"); + result.put(buildEnumScopeKey(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_DEVICE_STATUS), "PCS-设备状态"); + result.put(buildEnumScopeKey(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_CONTROL_MODE), "PCS-控制模式"); + result.put(buildEnumScopeKey(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS), "BMS总览-工作状态"); + result.put(buildEnumScopeKey(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_PCS_COMMUNICATION_STATUS), "BMS总览-与PCS通信"); + result.put(buildEnumScopeKey(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_EMS_COMMUNICATION_STATUS), "BMS总览-与EMS通信"); + result.put(buildEnumScopeKey(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS), "BMS电池簇-工作状态"); + result.put(buildEnumScopeKey(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_PCS_COMMUNICATION_STATUS), "BMS电池簇-与PCS通信"); + result.put(buildEnumScopeKey(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_EMS_COMMUNICATION_STATUS), "BMS电池簇-与EMS通信"); + return result; + } + + private static Map buildMenuDeviceCategoryMap() { + Map map = new HashMap<>(); + map.put("SBJK_SSYX", DeviceCategory.PCS.getCode()); + map.put("SBJK_EMS", DeviceCategory.EMS.getCode()); + map.put("SBJK_PCS", DeviceCategory.PCS.getCode()); + map.put("SBJK_BMSZL", DeviceCategory.STACK.getCode()); + map.put("SBJK_BMSDCC", DeviceCategory.CLUSTER.getCode()); + map.put("SBJK_DTDC", DeviceCategory.BATTERY.getCode()); + map.put("SBJK_DB", DeviceCategory.AMMETER.getCode()); + map.put("SBJK_YL", DeviceCategory.COOLING.getCode()); + map.put("SBJK_DH", DeviceCategory.DH.getCode()); + map.put("SBJK_XF", DeviceCategory.XF.getCode()); + return map; + } /** * 获取设备详细信息 @@ -106,9 +238,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 @@ -168,9 +418,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 @@ -376,6 +643,1366 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService return emsDevicesMapper.getAllDeviceCategoryBySiteId(siteId); } + @Override + public List getSiteMonitorProjectPointMapping(String siteId) { + List result = new ArrayList<>(); + if (StringUtils.isBlank(siteId)) { + return result; + } + List itemList = getEnabledMonitorItems(); + if (itemList == null || itemList.isEmpty()) { + return result; + } + List mappingList = getPointMatchesBySiteId(siteId); + Set deletedFieldCodeSet = mappingList.stream() + .filter(item -> StringUtils.isNotBlank(item.getFieldCode())) + .filter(item -> DELETED_FIELD_MARK.equals(item.getDataPoint())) + .map(EmsSiteMonitorPointMatch::getFieldCode) + .collect(Collectors.toSet()); + Map mappingByFieldAndDevice = mappingList.stream() + .filter(item -> StringUtils.isNotBlank(item.getFieldCode())) + .filter(item -> !DELETED_FIELD_MARK.equals(item.getDataPoint())) + .collect(Collectors.toMap( + item -> buildMatchKey(item.getFieldCode(), item.getDeviceId()), + item -> item, + (a, b) -> b + )); + Map> deviceMetaByMenu = buildDeviceMetaByMenu(siteId, itemList); + + itemList.forEach(item -> { + if (deletedFieldCodeSet.contains(item.getFieldCode())) { + return; + } + if (isDeviceDimensionItem(item)) { + List deviceMetaList = deviceMetaByMenu.getOrDefault(item.getMenuCode(), Collections.emptyList()); + if (deviceMetaList.isEmpty()) { + result.add(buildMappingVo(item, null, null, null)); + return; + } + for (DeviceMeta deviceMeta : deviceMetaList) { + EmsSiteMonitorPointMatch pointMatch = getMatchWithFallback(mappingByFieldAndDevice, item.getFieldCode(), deviceMeta.getDeviceId()); + result.add(buildMappingVo(item, pointMatch, deviceMeta.getDeviceId(), deviceMeta.getDeviceName())); + } + return; + } + EmsSiteMonitorPointMatch pointMatch = getMatchWithFallback(mappingByFieldAndDevice, item.getFieldCode(), null); + result.add(buildMappingVo(item, pointMatch, null, null)); + }); + return result; + } + + @Override + public int saveSiteMonitorProjectPointMapping(SiteMonitorProjectPointMappingSaveRequest request, String operName) { + if (request == null || StringUtils.isBlank(request.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + String siteId = request.getSiteId(); + List itemList = getEnabledMonitorItems(); + if (itemList == null || itemList.isEmpty()) { + throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item"); + } + Set fieldCodeSet = itemList.stream().map(EmsSiteMonitorItem::getFieldCode).collect(Collectors.toSet()); + Set deletedFieldCodeSet = new HashSet<>(); + if (request.getDeletedFieldCodes() != null) { + request.getDeletedFieldCodes().stream() + .filter(StringUtils::isNotBlank) + .map(String::trim) + .filter(fieldCodeSet::contains) + .forEach(deletedFieldCodeSet::add); + } + int deletedRows = emsSiteMonitorPointMatchMapper.deleteBySiteId(siteId); + + List saveList = new ArrayList<>(); + if (request.getMappings() != null) { + for (SiteMonitorProjectPointMappingVo mapping : request.getMappings()) { + if (mapping == null || StringUtils.isBlank(mapping.getFieldCode())) { + continue; + } + String fieldCode = mapping.getFieldCode().trim(); + if (!fieldCodeSet.contains(fieldCode) || deletedFieldCodeSet.contains(fieldCode)) { + continue; + } + String deviceId = StringUtils.isBlank(mapping.getDeviceId()) ? "" : mapping.getDeviceId().trim(); + if (isDeviceDimensionField(fieldCode) && StringUtils.isBlank(deviceId)) { + 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.setDeviceId(deviceId); + pointMatch.setDataPoint(dataPoint); + pointMatch.setFixedDataPoint(fixedDataPoint); + pointMatch.setUseFixedDisplay(useFixedDisplay); + pointMatch.setCreateBy(operName); + pointMatch.setUpdateBy(operName); + saveList.add(pointMatch); + } + } + + for (String deletedFieldCode : deletedFieldCodeSet) { + EmsSiteMonitorPointMatch pointMatch = new EmsSiteMonitorPointMatch(); + pointMatch.setSiteId(siteId); + pointMatch.setFieldCode(deletedFieldCode); + pointMatch.setDeviceId(""); + pointMatch.setDataPoint(DELETED_FIELD_MARK); + pointMatch.setFixedDataPoint(null); + pointMatch.setUseFixedDisplay(0); + pointMatch.setCreateBy(operName); + pointMatch.setUpdateBy(operName); + saveList.add(pointMatch); + } + + // 点位映射变更后清理“单站监控最新值”缓存,避免页面回退读取到旧快照 + clearSiteMonitorLatestCache(siteId); + redisCache.deleteObject(buildSiteMonitorPointMatchRedisKey(StringUtils.trim(siteId))); + + if (saveList.isEmpty()) { + return deletedRows; + } + int insertedRows = emsSiteMonitorPointMatchMapper.insertBatch(saveList); + return deletedRows + insertedRows; + } + + @Override + public List getSiteWorkStatusEnumMappings(String siteId) { + List defaults = buildDefaultEnumMappings(); + if (StringUtils.isBlank(siteId)) { + return defaults; + } + String normalizedSiteId = siteId.trim(); + List result = new ArrayList<>(); + for (String scopeKey : MANAGED_ENUM_SCOPE_KEYS) { + String[] scope = parseEnumScopeKey(scopeKey); + List dbList = emsPointEnumMatchMapper.selectList(normalizedSiteId, scope[0], scope[1]); + if (CollectionUtils.isEmpty(dbList)) { + result.addAll(defaults.stream() + .filter(item -> scopeKey.equals(buildEnumScopeKey(item.getDeviceCategory(), item.getMatchField()))) + .collect(Collectors.toList())); + continue; + } + for (EmsPointEnumMatch match : dbList) { + if (match == null) { + continue; + } + WorkStatusEnumMappingVo vo = new WorkStatusEnumMappingVo(); + vo.setDeviceCategory(scope[0]); + vo.setMatchField(scope[1]); + vo.setMatchFieldName(getScopeName(scope[0], scope[1])); + vo.setEnumCode(StringUtils.nvl(match.getEnumCode(), "")); + vo.setEnumName(StringUtils.nvl(match.getEnumName(), "")); + vo.setEnumDesc(StringUtils.nvl(match.getEnumDesc(), "")); + vo.setDataEnumCode(StringUtils.nvl(match.getDataEnumCode(), "")); + result.add(vo); + } + } + return result; + } + + @Override + public int saveSiteWorkStatusEnumMappings(String siteId, List mappings, String operName) { + if (StringUtils.isBlank(siteId)) { + throw new ServiceException("站点ID不能为空"); + } + String normalizedSiteId = siteId.trim(); + int deletedRows = 0; + for (String scopeKey : MANAGED_ENUM_SCOPE_KEYS) { + String[] scope = parseEnumScopeKey(scopeKey); + deletedRows += emsPointEnumMatchMapper.deleteByScope(normalizedSiteId, scope[0], scope[1]); + } + int insertedRows = 0; + if (mappings != null) { + for (WorkStatusEnumMappingVo item : mappings) { + if (item == null) { + continue; + } + String deviceCategory = StringUtils.trim(item.getDeviceCategory()); + String matchField = StringUtils.trim(item.getMatchField()); + if (!isManagedEnumScope(deviceCategory, matchField)) { + continue; + } + String enumCode = StringUtils.trim(item.getEnumCode()); + String enumName = StringUtils.trim(item.getEnumName()); + String enumDesc = StringUtils.trim(item.getEnumDesc()); + String dataEnumCode = StringUtils.trim(item.getDataEnumCode()); + if (StringUtils.isBlank(enumCode) && StringUtils.isBlank(enumName) && StringUtils.isBlank(dataEnumCode)) { + continue; + } + if (StringUtils.isBlank(enumCode)) { + throw new ServiceException(String.format("%s 枚举编码不能为空", getScopeName(deviceCategory, matchField))); + } + EmsPointEnumMatch insertItem = new EmsPointEnumMatch(); + insertItem.setSiteId(normalizedSiteId); + insertItem.setDeviceCategory(deviceCategory); + insertItem.setMatchField(matchField); + insertItem.setEnumCode(enumCode); + insertItem.setEnumName(StringUtils.defaultString(enumName)); + insertItem.setEnumDesc(StringUtils.defaultString(enumDesc)); + insertItem.setDataEnumCode(StringUtils.defaultString(dataEnumCode)); + insertItem.setCreateBy(operName); + insertItem.setUpdateBy(operName); + insertedRows += emsPointEnumMatchMapper.insertEmsPointEnumMatch(insertItem); + } + } + syncPointEnumMatchToRedis(normalizedSiteId, PCS_MATCH_DEVICE_CATEGORY); + syncPointEnumMatchToRedis(normalizedSiteId, STACK_MATCH_DEVICE_CATEGORY); + syncPointEnumMatchToRedis(normalizedSiteId, CLUSTER_MATCH_DEVICE_CATEGORY); + projectDisplayCache.remove(normalizedSiteId); + return deletedRows + insertedRows; + } + + private void syncPointEnumMatchToRedis(String siteId, String deviceCategory) { + if (StringUtils.isAnyBlank(siteId, deviceCategory)) { + return; + } + String cacheKey = DevicePointMatchDataProcessor.getPointEnumMacthCacheKey(siteId, deviceCategory); + redisCache.deleteObject(cacheKey); + List pointEnumMatchList = emsPointEnumMatchMapper.selectList(siteId, deviceCategory, null); + if (!CollectionUtils.isEmpty(pointEnumMatchList)) { + redisCache.setCacheList(cacheKey, pointEnumMatchList); + } + } + + private List buildDefaultEnumMappings() { + List result = new ArrayList<>(); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "0", "运行")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "1", "停机")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "2", "故障")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "3", "待机")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "4", "充电")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "5", "放电")); + + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_GRID_STATUS, "0", "并网")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_GRID_STATUS, "1", "未并网")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_DEVICE_STATUS, "0", "离线")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_DEVICE_STATUS, "1", "在线")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_CONTROL_MODE, "0", "远程")); + result.add(buildEnumMapping(PCS_MATCH_DEVICE_CATEGORY, MATCH_FIELD_CONTROL_MODE, "1", "本地")); + + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "0", "静置")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "1", "充电")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "2", "放电")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "3", "浮充")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "4", "待机")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "5", "运行")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "9", "故障")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_PCS_COMMUNICATION_STATUS, "0", "正常")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_PCS_COMMUNICATION_STATUS, "1", "通讯中断")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_PCS_COMMUNICATION_STATUS, "2", "异常")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_EMS_COMMUNICATION_STATUS, "0", "正常")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_EMS_COMMUNICATION_STATUS, "1", "通讯中断")); + result.add(buildEnumMapping(STACK_MATCH_DEVICE_CATEGORY, MATCH_FIELD_EMS_COMMUNICATION_STATUS, "2", "异常")); + + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "0", "静置")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "1", "充电")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "2", "放电")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "3", "待机")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "5", "运行")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_WORK_STATUS, "9", "故障")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_PCS_COMMUNICATION_STATUS, "0", "正常")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_PCS_COMMUNICATION_STATUS, "1", "通讯中断")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_PCS_COMMUNICATION_STATUS, "2", "异常")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_EMS_COMMUNICATION_STATUS, "0", "正常")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_EMS_COMMUNICATION_STATUS, "1", "通讯中断")); + result.add(buildEnumMapping(CLUSTER_MATCH_DEVICE_CATEGORY, MATCH_FIELD_EMS_COMMUNICATION_STATUS, "2", "异常")); + return result; + } + + private WorkStatusEnumMappingVo buildEnumMapping(String deviceCategory, String matchField, String enumCode, String enumName) { + WorkStatusEnumMappingVo vo = new WorkStatusEnumMappingVo(); + vo.setDeviceCategory(deviceCategory); + vo.setMatchField(matchField); + vo.setMatchFieldName(getScopeName(deviceCategory, matchField)); + vo.setEnumCode(enumCode); + vo.setEnumName(enumName); + vo.setEnumDesc(""); + vo.setDataEnumCode(""); + return vo; + } + + private boolean isManagedEnumScope(String deviceCategory, String matchField) { + return MANAGED_ENUM_SCOPE_KEYS.contains(buildEnumScopeKey(deviceCategory, matchField)); + } + + private static String buildEnumScopeKey(String deviceCategory, String matchField) { + return StringUtils.defaultString(deviceCategory).trim() + "|" + StringUtils.defaultString(matchField).trim(); + } + + private static String[] parseEnumScopeKey(String scopeKey) { + String[] values = StringUtils.defaultString(scopeKey).split("\\|", 2); + if (values.length < 2) { + return new String[]{"", ""}; + } + return values; + } + + private String getScopeName(String deviceCategory, String matchField) { + String name = ENUM_SCOPE_NAME_MAP.get(buildEnumScopeKey(deviceCategory, matchField)); + if (StringUtils.isNotBlank(name)) { + return name; + } + return StringUtils.defaultString(deviceCategory) + "-" + StringUtils.defaultString(matchField); + } + + private SiteMonitorProjectPointMappingVo buildMappingVo(EmsSiteMonitorItem item, EmsSiteMonitorPointMatch pointMatch, + String deviceId, String deviceName) { + SiteMonitorProjectPointMappingVo vo = new SiteMonitorProjectPointMappingVo(); + vo.setModuleCode(item.getModuleCode()); + vo.setModuleName(item.getModuleName()); + vo.setMenuCode(item.getMenuCode()); + vo.setMenuName(item.getMenuName()); + vo.setSectionName(item.getSectionName()); + vo.setFieldCode(item.getFieldCode()); + vo.setFieldName(item.getFieldName()); + vo.setDeviceId(deviceId); + vo.setDeviceName(deviceName); + vo.setDataPoint(pointMatch == null ? "" : StringUtils.nvl(pointMatch.getDataPoint(), "")); + vo.setFixedDataPoint(pointMatch == null ? "" : StringUtils.nvl(pointMatch.getFixedDataPoint(), "")); + vo.setUseFixedDisplay(pointMatch == null ? 0 : (pointMatch.getUseFixedDisplay() == null ? 0 : pointMatch.getUseFixedDisplay())); + return vo; + } + + private EmsSiteMonitorPointMatch getMatchWithFallback(Map mappingByFieldAndDevice, + String fieldCode, String deviceId) { + EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildMatchKey(fieldCode, deviceId)); + if (exact != null) { + return exact; + } + return mappingByFieldAndDevice.get(buildMatchKey(fieldCode, null)); + } + + private String buildMatchKey(String fieldCode, String deviceId) { + return StringUtils.nvl(fieldCode, "") + "|" + StringUtils.nvl(deviceId, ""); + } + + private Map> buildDeviceMetaByMenu(String siteId, List itemList) { + Map> result = new HashMap<>(); + if (StringUtils.isBlank(siteId) || itemList == null || itemList.isEmpty()) { + return result; + } + Set menuCodeSet = itemList.stream() + .filter(this::isDeviceDimensionItem) + .map(EmsSiteMonitorItem::getMenuCode) + .collect(Collectors.toSet()); + for (String menuCode : menuCodeSet) { + String deviceCategory = MENU_DEVICE_CATEGORY_MAP.get(menuCode); + if (StringUtils.isBlank(deviceCategory)) { + continue; + } + List> deviceList = emsDevicesMapper.getDeviceInfosBySiteIdAndCategory(siteId, deviceCategory); + if (deviceList == null || deviceList.isEmpty()) { + continue; + } + List deviceMetas = new ArrayList<>(); + for (Map item : deviceList) { + if (item == null || item.get("id") == null) { + continue; + } + String deviceId = String.valueOf(item.get("id")); + if (StringUtils.isBlank(deviceId)) { + continue; + } + String deviceName = item.get("deviceName") == null ? deviceId : String.valueOf(item.get("deviceName")); + deviceMetas.add(new DeviceMeta(deviceId, deviceName)); + } + deviceMetas.sort(Comparator.comparing(DeviceMeta::getDeviceName, Comparator.nullsLast(String::compareTo)) + .thenComparing(DeviceMeta::getDeviceId, Comparator.nullsLast(String::compareTo))); + result.put(menuCode, deviceMetas); + } + return result; + } + + private boolean isDeviceDimensionField(String fieldCode) { + if (StringUtils.isBlank(fieldCode)) { + return false; + } + String normalizedFieldCode = fieldCode.trim(); + if (DEVICE_DIMENSION_FIELD_CODES.contains(normalizedFieldCode)) { + return true; + } + if (!normalizedFieldCode.contains("__")) { + return false; + } + String menuCode = normalizedFieldCode.substring(0, normalizedFieldCode.indexOf("__")); + return isDeviceDimensionMenu(menuCode); + } + + private boolean isDeviceDimensionItem(EmsSiteMonitorItem item) { + if (item == null) { + return false; + } + if (isDeviceDimensionField(item.getFieldCode())) { + return true; + } + return isDeviceDimensionMenu(item.getMenuCode()); + } + + private boolean isDeviceDimensionMenu(String menuCode) { + return StringUtils.isNotBlank(menuCode) && DEVICE_DIMENSION_MENU_CODES.contains(menuCode); + } + + private static class DeviceMeta { + private final String deviceId; + private final String deviceName; + + private DeviceMeta(String deviceId, String deviceName) { + this.deviceId = deviceId; + this.deviceName = deviceName; + } + + public String getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + } + + @Override + public List getSiteMonitorProjectDisplay(String siteId) { + String normalizedSiteId = StringUtils.trim(siteId); + if (StringUtils.isBlank(normalizedSiteId)) { + return new ArrayList<>(); + } + DisplayCacheEntry cacheEntry = projectDisplayCache.get(normalizedSiteId); + long now = System.currentTimeMillis(); + if (cacheEntry != null && cacheEntry.getExpireAt() > now) { + return cloneDisplayList(cacheEntry.getData()); + } + + List mappingList = getSiteMonitorProjectPointMapping(normalizedSiteId); + if (mappingList.isEmpty()) { + return new ArrayList<>(); + } + Set pointIds = mappingList.stream() + .map(SiteMonitorProjectPointMappingVo::getDataPoint) + .filter(StringUtils::isNotBlank) + .map(item -> item.trim().toUpperCase()) + .collect(Collectors.toSet()); + Map pointConfigByPointId = buildPointConfigByPointId(normalizedSiteId, pointIds); + Map homeLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(normalizedSiteId, MODULE_HOME))); + Map sbjkLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(normalizedSiteId, MODULE_SBJK))); + Map tjbbLatestMap = safeRedisMap(redisCache.getCacheMap(buildSiteMonitorLatestRedisKey(normalizedSiteId, MODULE_TJBB))); + Map> enumDataCodeMapByScope = buildEnumDataCodeMapByScope(normalizedSiteId); + Map pointSnapshotCache = new HashMap<>(); + HomeRunningSnapshot homeRunningSnapshot = buildHomeRunningSnapshot(normalizedSiteId); + + List result = new ArrayList<>(); + int totalCount = 0; + int fixedHitCount = 0; + int pointHitCount = 0; + int cacheHitCount = 0; + int emptyCount = 0; + List emptySamples = new ArrayList<>(); + List pointHitSamples = new ArrayList<>(); + List cacheHitSamples = new ArrayList<>(); + for (SiteMonitorProjectPointMappingVo mapping : mappingList) { + totalCount++; + SiteMonitorProjectDisplayVo vo = new SiteMonitorProjectDisplayVo(); + BeanUtils.copyProperties(mapping, vo); + if (USE_FIXED_DISPLAY_YES.equals(mapping.getUseFixedDisplay()) && StringUtils.isNotBlank(mapping.getFixedDataPoint())) { + vo.setFieldValue(mapping.getFixedDataPoint()); + vo.setValueTime(null); + fixedHitCount++; + result.add(vo); + continue; + } + if (MODULE_HOME.equals(mapping.getModuleCode()) && homeRunningSnapshot != null) { + String homeFieldKey = resolveHomeFieldKey(mapping.getFieldCode()); + if (StringUtils.isNotBlank(homeFieldKey)) { + String homeFieldValue = homeRunningSnapshot.getValue(homeFieldKey); + if (homeFieldValue != null) { + vo.setFieldValue(homeFieldValue); + vo.setValueTime(homeRunningSnapshot.getValueTime()); + result.add(vo); + continue; + } + } + } + // 与“点位配置列表最新值”一致:按 pointId -> 点位配置(dataKey/deviceId) -> MQTT 最新报文读取 + PointLatestSnapshot latestSnapshot = null; + String dataPoint = StringUtils.trim(mapping.getDataPoint()); + if (StringUtils.isNotBlank(dataPoint)) { + String pointCacheKey = dataPoint.toUpperCase(); + if (pointSnapshotCache.containsKey(pointCacheKey)) { + latestSnapshot = pointSnapshotCache.get(pointCacheKey); + } else { + latestSnapshot = getLatestSnapshotByPointId(normalizedSiteId, dataPoint, pointConfigByPointId); + pointSnapshotCache.put(pointCacheKey, latestSnapshot); + } + } + if (latestSnapshot != null && latestSnapshot.getFieldValue() != null) { + vo.setFieldValue(latestSnapshot.getFieldValue()); + vo.setValueTime(latestSnapshot.getValueTime()); + applyEnumMappingIfNecessary(mapping, vo, enumDataCodeMapByScope); + pointHitCount++; + if (pointHitSamples.size() < DISPLAY_DEBUG_SAMPLE_SIZE) { + pointHitSamples.add(mapping.getFieldCode() + "->" + StringUtils.defaultString(mapping.getDataPoint())); + } + result.add(vo); + continue; + } + if (isSbjkEmsMenu(mapping)) { + if (StringUtils.isBlank(vo.getFieldValue())) { + emptyCount++; + if (emptySamples.size() < DISPLAY_DEBUG_SAMPLE_SIZE) { + emptySamples.add(mapping.getFieldCode() + "->" + StringUtils.defaultString(mapping.getDataPoint())); + } + } + applyEnumMappingIfNecessary(mapping, vo, enumDataCodeMapByScope); + result.add(vo); + continue; + } + Object cacheObj = null; + if (MODULE_HOME.equals(mapping.getModuleCode())) { + cacheObj = homeLatestMap.get(mapping.getFieldCode()); + } else if (MODULE_SBJK.equals(mapping.getModuleCode())) { + cacheObj = sbjkLatestMap.get(mapping.getFieldCode()); + } else if (MODULE_TJBB.equals(mapping.getModuleCode())) { + cacheObj = tjbbLatestMap.get(mapping.getFieldCode()); + } + if (cacheObj != null) { + JSONObject snapshot = parseFieldSnapshot(cacheObj); + vo.setFieldValue(snapshot.getString("fieldValue")); + vo.setValueTime(parseValueTime(snapshot.get("valueTime"))); + if (StringUtils.isNotBlank(vo.getFieldValue())) { + cacheHitCount++; + if (cacheHitSamples.size() < DISPLAY_DEBUG_SAMPLE_SIZE) { + cacheHitSamples.add(mapping.getFieldCode() + "->cache"); + } + } + } + if (StringUtils.isBlank(vo.getFieldValue())) { + emptyCount++; + if (emptySamples.size() < DISPLAY_DEBUG_SAMPLE_SIZE) { + emptySamples.add(mapping.getFieldCode() + "->" + StringUtils.defaultString(mapping.getDataPoint())); + } + } + applyEnumMappingIfNecessary(mapping, vo, enumDataCodeMapByScope); + result.add(vo); + } + log.info("SiteMonitorDisplayDebug siteId={}, total={}, fixedHit={}, pointHit={}, cacheHit={}, empty={}, emptySamples={}, pointHitSamples={}, cacheHitSamples={}", + normalizedSiteId, totalCount, fixedHitCount, pointHitCount, cacheHitCount, emptyCount, + emptySamples, pointHitSamples, cacheHitSamples); + projectDisplayCache.put(normalizedSiteId, new DisplayCacheEntry(cloneDisplayList(result), now + PROJECT_DISPLAY_CACHE_TTL_MS)); + return result; + } + + private HomeRunningSnapshot buildHomeRunningSnapshot(String siteId) { + if (StringUtils.isBlank(siteId)) { + return null; + } + HomeRunningSnapshot snapshot = new HomeRunningSnapshot(); + String today = DateUtils.getDate(); + Map totalChargeData = emsDailyChargeDataMapper.getAllSiteChargeData(today, siteId); + if (totalChargeData != null) { + snapshot.put("totalChargedCap", totalChargeData.get("totalChargedCap")); + snapshot.put("totalDischargedCap", totalChargeData.get("totalDischargedCap")); + } + + LocalDate now = LocalDate.now(); + LocalDate yesterday = now.minusDays(1); + List chargeList = emsDailyChargeDataMapper.getSingleSiteChargeData( + siteId, + DateUtils.toDate(yesterday), + DateUtils.toDate(now) + ); + if (!CollectionUtils.isEmpty(chargeList)) { + for (SiteMonitorDataVo item : chargeList) { + if (item == null || StringUtils.isBlank(item.getAmmeterDate())) { + continue; + } + if (today.equals(item.getAmmeterDate())) { + snapshot.put("dayChargedCap", item.getChargedCap()); + snapshot.put("dayDisChargedCap", item.getDisChargedCap()); + } else if (DateUtils.getYesterdayDayString().equals(item.getAmmeterDate())) { + snapshot.put("yesterdayChargedCap", item.getChargedCap()); + snapshot.put("yesterdayDisChargedCap", item.getDisChargedCap()); + } + } + } + + EmsDailyChargeData todayChargeData = emsDailyChargeDataMapper.selectBySiteIdAndDateTime(siteId, DateUtils.toDate(now)); + EmsDailyChargeData yesterdayChargeData = emsDailyChargeDataMapper.selectBySiteIdAndDateTime(siteId, DateUtils.toDate(yesterday)); + if (todayChargeData != null) { + snapshot.put("totalRevenue", todayChargeData.getTotalRevenue()); + snapshot.put("dayRevenue", todayChargeData.getDayRevenue()); + } else { + Map latestRevenueData = emsDailyEnergyDataMapper.getRealTimeRevenue(siteId); + if (latestRevenueData != null) { + snapshot.put("totalRevenue", latestRevenueData.get("totalRevenue")); + snapshot.put("dayRevenue", latestRevenueData.get("dayRevenue")); + } + } + if (yesterdayChargeData != null) { + snapshot.put("yesterdayRevenue", yesterdayChargeData.getDayRevenue()); + } + return snapshot; + } + + private String resolveHomeFieldKey(String fieldCode) { + String normalizedFieldCode = StringUtils.trim(fieldCode); + if (StringUtils.isBlank(normalizedFieldCode)) { + return null; + } + int splitIndex = normalizedFieldCode.lastIndexOf("__"); + String fieldKey = splitIndex >= 0 ? normalizedFieldCode.substring(splitIndex + 2) : normalizedFieldCode; + if (HOME_CHARGE_FIELD_SET.contains(fieldKey) || HOME_REVENUE_FIELD_SET.contains(fieldKey)) { + return fieldKey; + } + return null; + } + + private static class HomeRunningSnapshot { + private final Map valueMap = new HashMap<>(); + private final Date valueTime = new Date(); + + public void put(String key, BigDecimal value) { + if (StringUtils.isBlank(key) || value == null) { + return; + } + valueMap.put(key, value.stripTrailingZeros().toPlainString()); + } + + public String getValue(String key) { + return valueMap.get(key); + } + + public Date getValueTime() { + return valueTime; + } + } + + private Map> buildEnumDataCodeMapByScope(String siteId) { + Map> result = new HashMap<>(); + if (StringUtils.isBlank(siteId)) { + return result; + } + List categories = Arrays.asList(PCS_MATCH_DEVICE_CATEGORY, STACK_MATCH_DEVICE_CATEGORY, CLUSTER_MATCH_DEVICE_CATEGORY); + for (String deviceCategory : categories) { + String cacheKey = DevicePointMatchDataProcessor.getPointEnumMacthCacheKey(siteId, deviceCategory); + List enumList = redisCache.getCacheList(cacheKey); + if (CollectionUtils.isEmpty(enumList)) { + enumList = emsPointEnumMatchMapper.selectList(siteId, deviceCategory, null); + } + if (CollectionUtils.isEmpty(enumList)) { + continue; + } + for (EmsPointEnumMatch item : enumList) { + if (item == null) { + continue; + } + String matchField = StringUtils.trim(item.getMatchField()); + String enumCode = StringUtils.trim(item.getEnumCode()); + String dataEnumCode = normalizeEnumDataValue(item.getDataEnumCode()); + if (StringUtils.isAnyBlank(matchField, enumCode, dataEnumCode)) { + continue; + } + String scopeKey = buildEnumScopeKey(deviceCategory, matchField); + if (!MANAGED_ENUM_SCOPE_KEYS.contains(scopeKey)) { + continue; + } + result.computeIfAbsent(scopeKey, key -> new HashMap<>()).put(dataEnumCode, enumCode); + } + } + return result; + } + + private void applyEnumMappingIfNecessary(SiteMonitorProjectPointMappingVo mapping, + SiteMonitorProjectDisplayVo vo, + Map> enumDataCodeMapByScope) { + if (mapping == null || vo == null || CollectionUtils.isEmpty(enumDataCodeMapByScope)) { + return; + } + String rawFieldValue = StringUtils.trim(vo.getFieldValue()); + if (StringUtils.isBlank(rawFieldValue)) { + return; + } + String scopeKey = resolveEnumScopeKey(mapping.getMenuCode(), mapping.getFieldCode()); + if (StringUtils.isBlank(scopeKey)) { + return; + } + Map enumMap = enumDataCodeMapByScope.get(scopeKey); + if (CollectionUtils.isEmpty(enumMap)) { + return; + } + String mappedValue = enumMap.get(normalizeEnumDataValue(rawFieldValue)); + if (StringUtils.isNotBlank(mappedValue)) { + vo.setFieldValue(mappedValue); + } + } + + private String resolveEnumScopeKey(String menuCode, String fieldCode) { + String normalizedMenuCode = StringUtils.trim(menuCode); + String deviceCategory = StringUtils.trim(MENU_DEVICE_CATEGORY_MAP.get(normalizedMenuCode)); + if (StringUtils.isBlank(deviceCategory)) { + return null; + } + String matchField = null; + String normalizedFieldCode = StringUtils.trim(fieldCode); + if (StringUtils.isNotBlank(normalizedFieldCode)) { + int splitIndex = normalizedFieldCode.lastIndexOf("__"); + matchField = splitIndex >= 0 ? normalizedFieldCode.substring(splitIndex + 2) : normalizedFieldCode; + } + matchField = StringUtils.trim(matchField); + if (StringUtils.isBlank(matchField)) { + return null; + } + String scopeKey = buildEnumScopeKey(deviceCategory, matchField); + return MANAGED_ENUM_SCOPE_KEYS.contains(scopeKey) ? scopeKey : null; + } + + private String normalizeEnumDataValue(String value) { + String normalized = StringUtils.trim(value); + if (StringUtils.isBlank(normalized)) { + return normalized; + } + if (normalized.matches("^-?\\d+\\.0+$")) { + return normalized.substring(0, normalized.indexOf('.')); + } + return normalized; + } + + private boolean isSbjkEmsMenu(SiteMonitorProjectPointMappingVo mapping) { + return mapping != null && MENU_SBJK_EMS.equals(mapping.getMenuCode()); + } + + private List cloneDisplayList(List source) { + if (source == null || source.isEmpty()) { + return new ArrayList<>(); + } + List copy = new ArrayList<>(source.size()); + for (SiteMonitorProjectDisplayVo item : source) { + if (item == null) { + continue; + } + SiteMonitorProjectDisplayVo target = new SiteMonitorProjectDisplayVo(); + BeanUtils.copyProperties(item, target); + copy.add(target); + } + return copy; + } + + private Map buildPointConfigByPointId(String siteId, Set pointIds) { + Map result = new HashMap<>(); + if (StringUtils.isBlank(siteId) || pointIds == null || pointIds.isEmpty()) { + return result; + } + List configList = emsPointConfigMapper.selectBySiteIdAndPointIds( + siteId.trim(), + new ArrayList<>(pointIds) + ); + if (configList == null || configList.isEmpty()) { + return result; + } + for (EmsPointConfig config : configList) { + if (config == null || StringUtils.isBlank(config.getPointId())) { + continue; + } + String pointId = config.getPointId().trim().toUpperCase(); + result.putIfAbsent(pointId, config); + } + return result; + } + + private PointLatestSnapshot getLatestSnapshotByPointId(String siteId, String pointId, Map pointConfigByPointId) { + if (StringUtils.isAnyBlank(siteId, pointId) || pointConfigByPointId == null || pointConfigByPointId.isEmpty()) { + return null; + } + EmsPointConfig pointConfig = pointConfigByPointId.get(pointId.trim().toUpperCase()); + // 直接按 pointId 拉取最新值(pointId 全局唯一),不再依赖 deviceId 维度 + PointLatestSnapshot curveSnapshot = getLatestSnapshotFromCurve(siteId, pointId, pointConfig); + if (curveSnapshot != null) { + return curveSnapshot; + } + + // 兼容旧逻辑:Influx 无数据时,再按点位配置回退到 MQTT 原始报文解析 + if (pointConfig != null && StringUtils.isNoneBlank(pointConfig.getDeviceId(), pointConfig.getDataKey())) { + String redisKey = RedisKeyConstants.ORIGINAL_MQTT_DATA + siteId + "_" + pointConfig.getDeviceId().trim(); + Object raw = redisCache.getCacheObject(redisKey); + if (raw != null) { + JSONObject root = toJsonObject(raw); + if (root != null) { + JSONObject dataObject = extractDataObject(root); + if (dataObject != null) { + Object rawValue = getValueIgnoreCase(dataObject, pointConfig.getDataKey()); + BigDecimal sourceValue = StringUtils.getBigDecimal(rawValue); + if (sourceValue != 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); + } + } + } + } + } + return null; + } + + private PointLatestSnapshot getLatestSnapshotFromCurve(String siteId, String pointId, EmsPointConfig pointConfig) { + if (StringUtils.isAnyBlank(siteId, pointId)) { + return null; + } + String configPointId = pointConfig == null ? null : pointConfig.getPointId(); + String influxPointKey = StringUtils.isNotBlank(configPointId) + ? configPointId.trim() + : pointId.trim(); + Date endTime = DateUtils.getNowDate(); + // 优先近24小时快速取最新值,查不到再回退到7天窗口 + Date fastStartTime = DateUtils.addDays(endTime, -1); + InfluxPointDataWriter.PointValue latest = influxPointDataWriter.queryLatestPointValueByPointKey( + siteId.trim(), + influxPointKey, + fastStartTime, + endTime + ); + if (latest == null) { + Date fallbackStartTime = DateUtils.addDays(endTime, -7); + latest = influxPointDataWriter.queryLatestPointValueByPointKey( + siteId.trim(), + influxPointKey, + fallbackStartTime, + endTime + ); + } + if (latest == null || latest.getPointValue() == null) { + return null; + } + return new PointLatestSnapshot(latest.getPointValue().stripTrailingZeros().toPlainString(), latest.getDataTime()); + } + + 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; + } + } + + private static class DisplayCacheEntry { + private final List data; + private final long expireAt; + + private DisplayCacheEntry(List data, long expireAt) { + this.data = data; + this.expireAt = expireAt; + } + + public List getData() { + return data; + } + + public long getExpireAt() { + return expireAt; + } + } + + @Override + public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName) { + if (request == null || StringUtils.isBlank(request.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + if (request.getItems() == null || request.getItems().isEmpty()) { + return 0; + } + List itemList = getEnabledMonitorItems(); + if (itemList == null || itemList.isEmpty()) { + throw new ServiceException("单站监控配置项为空,请先初始化 ems_site_monitor_item"); + } + Set fieldCodeSet = itemList.stream().map(EmsSiteMonitorItem::getFieldCode).collect(Collectors.toSet()); + + Map itemMap = itemList.stream() + .collect(Collectors.toMap(EmsSiteMonitorItem::getFieldCode, item -> item, (a, b) -> a)); + + Date now = DateUtils.getNowDate(); + Map homeLatestUpdates = new HashMap<>(); + Map sbjkLatestUpdates = new HashMap<>(); + Map tjbbLatestUpdates = new HashMap<>(); + Map homeHistoryByMinute = new HashMap<>(); + Map sbjkHistoryByMinute = new HashMap<>(); + Map tjbbHistoryByMinute = new HashMap<>(); + for (SiteMonitorDataSaveItemVo item : request.getItems()) { + if (item == null || StringUtils.isBlank(item.getFieldCode())) { + continue; + } + String fieldCode = item.getFieldCode().trim(); + if (!fieldCodeSet.contains(fieldCode)) { + continue; + } + EmsSiteMonitorItem itemDef = itemMap.get(fieldCode); + if (itemDef == null || StringUtils.isBlank(itemDef.getModuleCode())) { + continue; + } + Date valueTime = item.getValueTime() == null ? now : item.getValueTime(); + JSONObject snapshot = buildFieldSnapshot(item.getFieldValue(), valueTime); + Date statisMinute = truncateToMinute(valueTime); + if (MODULE_HOME.equals(itemDef.getModuleCode())) { + homeLatestUpdates.put(fieldCode, snapshot); + mergeHistorySnapshot(homeHistoryByMinute, statisMinute, fieldCode, snapshot); + } else if (MODULE_SBJK.equals(itemDef.getModuleCode())) { + sbjkLatestUpdates.put(fieldCode, snapshot); + mergeHistorySnapshot(sbjkHistoryByMinute, statisMinute, fieldCode, snapshot); + } else if (MODULE_TJBB.equals(itemDef.getModuleCode())) { + tjbbLatestUpdates.put(fieldCode, snapshot); + mergeHistorySnapshot(tjbbHistoryByMinute, statisMinute, fieldCode, snapshot); + } + } + if (homeLatestUpdates.isEmpty() && sbjkLatestUpdates.isEmpty() && tjbbLatestUpdates.isEmpty()) { + return 0; + } + int rows = 0; + if (!homeLatestUpdates.isEmpty()) { + upsertLatestToRedis(request.getSiteId(), MODULE_HOME, homeLatestUpdates); + rows += upsertHistoryByMinute(HISTORY_TABLE_HOME, request.getSiteId(), homeHistoryByMinute, operName); + } + if (!sbjkLatestUpdates.isEmpty()) { + upsertLatestToRedis(request.getSiteId(), MODULE_SBJK, sbjkLatestUpdates); + rows += upsertHistoryByMinute(HISTORY_TABLE_SBJK, request.getSiteId(), sbjkHistoryByMinute, operName); + } + if (!tjbbLatestUpdates.isEmpty()) { + upsertLatestToRedis(request.getSiteId(), MODULE_TJBB, tjbbLatestUpdates); + rows += upsertHistoryByMinute(HISTORY_TABLE_TJBB, request.getSiteId(), tjbbHistoryByMinute, operName); + } + projectDisplayCache.remove(StringUtils.trim(request.getSiteId())); + return rows; + } + + @Override + public int syncSiteMonitorDataByMqtt(String siteId, String deviceId, String jsonData, Date valueTime) { + if (StringUtils.isAnyBlank(siteId, jsonData)) { + return 0; + } + Map dataMap = JSON.parseObject(jsonData, new TypeReference>() {}); + if (dataMap == null || dataMap.isEmpty()) { + return 0; + } + Map upperDataMap = new HashMap<>(); + dataMap.forEach((k, v) -> { + if (StringUtils.isNotBlank(k)) { + upperDataMap.put(k.toUpperCase(), v); + } + }); + + List mappingList = getPointMatchesBySiteId(siteId); + if (mappingList == null || mappingList.isEmpty()) { + return 0; + } + List itemList = getEnabledMonitorItems(); + if (itemList == null || itemList.isEmpty()) { + return 0; + } + Map itemMap = itemList.stream() + .filter(item -> StringUtils.isNotBlank(item.getFieldCode())) + .collect(Collectors.toMap(EmsSiteMonitorItem::getFieldCode, item -> item, (a, b) -> a)); + + Date actualValueTime = valueTime == null ? DateUtils.getNowDate() : valueTime; + Date statisMinute = truncateToMinute(actualValueTime); + Map homeLatestUpdates = new HashMap<>(); + Map sbjkLatestUpdates = new HashMap<>(); + Map tjbbLatestUpdates = new HashMap<>(); + Map homeHistoryByMinute = new HashMap<>(); + Map sbjkHistoryByMinute = new HashMap<>(); + Map tjbbHistoryByMinute = new HashMap<>(); + int matchedCount = 0; + int missCount = 0; + List missSamples = new ArrayList<>(); + + for (EmsSiteMonitorPointMatch mapping : mappingList) { + if (mapping == null || StringUtils.isBlank(mapping.getFieldCode())) { + continue; + } + if (DELETED_FIELD_MARK.equals(mapping.getDataPoint())) { + continue; + } + // pointId 在系统内唯一,按 pointId 匹配即可,不再按 deviceId 过滤 + EmsSiteMonitorItem itemDef = itemMap.get(mapping.getFieldCode()); + if (itemDef == null || StringUtils.isBlank(itemDef.getModuleCode())) { + continue; + } + if (USE_FIXED_DISPLAY_YES.equals(mapping.getUseFixedDisplay())) { + continue; + } + String point = mapping.getDataPoint(); + if (StringUtils.isBlank(point)) { + continue; + } + point = point.trim(); + Object value = upperDataMap.get(point.toUpperCase()); + if (value == null && StringUtils.isNotBlank(deviceId)) { + value = upperDataMap.get((deviceId + point).toUpperCase()); + } + if (value == null) { + missCount++; + if (missSamples.size() < DISPLAY_DEBUG_SAMPLE_SIZE) { + missSamples.add(mapping.getFieldCode() + "->" + point); + } + continue; + } + matchedCount++; + + JSONObject snapshot = buildFieldSnapshot(String.valueOf(value), actualValueTime); + + if (MODULE_HOME.equals(itemDef.getModuleCode())) { + homeLatestUpdates.put(mapping.getFieldCode(), snapshot); + mergeHistorySnapshot(homeHistoryByMinute, statisMinute, mapping.getFieldCode(), snapshot); + } else if (MODULE_SBJK.equals(itemDef.getModuleCode())) { + sbjkLatestUpdates.put(mapping.getFieldCode(), snapshot); + mergeHistorySnapshot(sbjkHistoryByMinute, statisMinute, mapping.getFieldCode(), snapshot); + } else if (MODULE_TJBB.equals(itemDef.getModuleCode())) { + tjbbLatestUpdates.put(mapping.getFieldCode(), snapshot); + mergeHistorySnapshot(tjbbHistoryByMinute, statisMinute, mapping.getFieldCode(), snapshot); + } + } + + int rows = 0; + if (!homeLatestUpdates.isEmpty()) { + upsertLatestToRedis(siteId, MODULE_HOME, homeLatestUpdates); + rows += upsertHistoryByMinute(HISTORY_TABLE_HOME, siteId, homeHistoryByMinute, "mqtt"); + } + if (!sbjkLatestUpdates.isEmpty()) { + upsertLatestToRedis(siteId, MODULE_SBJK, sbjkLatestUpdates); + rows += upsertHistoryByMinute(HISTORY_TABLE_SBJK, siteId, sbjkHistoryByMinute, "mqtt"); + } + if (!tjbbLatestUpdates.isEmpty()) { + upsertLatestToRedis(siteId, MODULE_TJBB, tjbbLatestUpdates); + rows += upsertHistoryByMinute(HISTORY_TABLE_TJBB, siteId, tjbbHistoryByMinute, "mqtt"); + } + log.info("SiteMonitorSyncDebug siteId={}, deviceId={}, mappingTotal={}, matched={}, miss={}, homeUpdates={}, sbjkUpdates={}, tjbbUpdates={}, missSamples={}", + siteId, deviceId, mappingList.size(), matchedCount, missCount, + homeLatestUpdates.size(), sbjkLatestUpdates.size(), tjbbLatestUpdates.size(), missSamples); + return rows; + } + + private int upsertHistoryByMinute(String tableName, String siteId, Map minuteSnapshotMap, String operName) { + int rows = 0; + for (Map.Entry entry : minuteSnapshotMap.entrySet()) { + Date statisMinute = entry.getKey(); + JSONObject merged = mergeHistoryRecord(tableName, siteId, statisMinute, entry.getValue()); + Map hotColumns = extractHotColumns(merged); + rows += emsSiteMonitorDataMapper.upsertHistoryJsonByMinute( + tableName, + siteId, + statisMinute, + merged.toJSONString(), + hotColumns.get("hotSoc"), + hotColumns.get("hotTotalActivePower"), + hotColumns.get("hotTotalReactivePower"), + hotColumns.get("hotDayChargedCap"), + hotColumns.get("hotDayDisChargedCap"), + operName + ); + } + return rows; + } + + private JSONObject mergeHistoryRecord(String tableName, String siteId, Date statisMinute, JSONObject updates) { + String existingJson = emsSiteMonitorDataMapper.selectHistoryJsonByMinute(tableName, siteId, statisMinute); + JSONObject merged = StringUtils.isBlank(existingJson) ? new JSONObject() : JSON.parseObject(existingJson); + if (merged == null) { + merged = new JSONObject(); + } + if (updates != null) { + merged.putAll(updates); + } + return merged; + } + + private void upsertLatestToRedis(String siteId, String moduleCode, Map latestUpdates) { + if (latestUpdates == null || latestUpdates.isEmpty()) { + return; + } + String redisKey = buildSiteMonitorLatestRedisKey(siteId, moduleCode); + Map redisMap = new HashMap<>(); + latestUpdates.forEach((fieldCode, snapshot) -> redisMap.put(fieldCode, snapshot.toJSONString())); + redisCache.setAllCacheMapValue(redisKey, redisMap); + redisCache.expire(redisKey, 7, TimeUnit.DAYS); + } + + private void mergeHistorySnapshot(Map historyByMinute, Date statisMinute, String fieldCode, JSONObject snapshot) { + JSONObject minuteObj = historyByMinute.computeIfAbsent(statisMinute, k -> new JSONObject()); + minuteObj.put(fieldCode, snapshot); + } + + private Map safeRedisMap(Map source) { + return source == null ? new HashMap<>() : source; + } + + private List getEnabledMonitorItems() { + long now = System.currentTimeMillis(); + List cached = monitorItemCache; + if (now < monitorItemCacheExpireAt && cached != null) { + return cached; + } + synchronized (this) { + now = System.currentTimeMillis(); + if (now < monitorItemCacheExpireAt && monitorItemCache != null) { + return monitorItemCache; + } + List latest = emsSiteMonitorItemMapper.selectEnabledList(); + if (latest == null) { + latest = Collections.emptyList(); + } + monitorItemCache = latest; + monitorItemCacheExpireAt = now + MONITOR_ITEM_CACHE_TTL_MS; + return latest; + } + } + + private List getPointMatchesBySiteId(String siteId) { + if (StringUtils.isBlank(siteId)) { + return Collections.emptyList(); + } + String normalizedSiteId = siteId.trim(); + String redisKey = buildSiteMonitorPointMatchRedisKey(normalizedSiteId); + Object cacheObj = redisCache.getCacheObject(redisKey); + List cached = parsePointMatchCache(cacheObj); + if (cached != null) { + return cached; + } + List latest = emsSiteMonitorPointMatchMapper.selectBySiteId(normalizedSiteId); + if (latest == null) { + latest = Collections.emptyList(); + } + redisCache.setCacheObject(redisKey, latest, MONITOR_POINT_MATCH_REDIS_TTL_SECONDS, TimeUnit.SECONDS); + return latest; + } + + private List parsePointMatchCache(Object cacheObj) { + if (cacheObj == null) { + return null; + } + try { + if (cacheObj instanceof String) { + return JSON.parseArray((String) cacheObj, EmsSiteMonitorPointMatch.class); + } + if (cacheObj instanceof List) { + List cacheList = (List) cacheObj; + if (cacheList.isEmpty()) { + return Collections.emptyList(); + } + if (cacheList.get(0) instanceof EmsSiteMonitorPointMatch) { + return (List) cacheList; + } + return JSON.parseArray(JSON.toJSONString(cacheObj), EmsSiteMonitorPointMatch.class); + } + return JSON.parseArray(JSON.toJSONString(cacheObj), EmsSiteMonitorPointMatch.class); + } catch (Exception ex) { + log.warn("解析单站监控点位映射缓存失败,key类型={}, err={}", + cacheObj.getClass().getName(), ex.getMessage()); + return null; + } + } + + private Map extractHotColumns(JSONObject merged) { + Map result = new HashMap<>(); + result.put("hotSoc", extractFieldValue(merged, + new String[]{"SBJK_SSYX__soc", "TJBB_DCDQX__soc_stat", "HOME__avgSoc"}, + new String[]{"__soc", "__soc_stat", "__avgSoc"})); + result.put("hotTotalActivePower", extractFieldValue(merged, + new String[]{"SBJK_SSYX__totalActivePower"}, + new String[]{"__totalActivePower", "__activePower", "__activePower_stat"})); + result.put("hotTotalReactivePower", extractFieldValue(merged, + new String[]{"SBJK_SSYX__totalReactivePower"}, + new String[]{"__totalReactivePower", "__reactivePower", "__reactivePower_stat"})); + result.put("hotDayChargedCap", extractFieldValue(merged, + new String[]{"HOME__dayChargedCap", "SBJK_SSYX__dayChargedCap_rt", "TJBB_GLTJ__chargedCap_stat"}, + new String[]{"__dayChargedCap", "__dayChargedCap_rt", "__chargedCap_stat"})); + result.put("hotDayDisChargedCap", extractFieldValue(merged, + new String[]{"HOME__dayDisChargedCap", "SBJK_SSYX__dayDisChargedCap_rt", "TJBB_GLTJ__disChargedCap_stat"}, + new String[]{"__dayDisChargedCap", "__dayDisChargedCap_rt", "__disChargedCap_stat"})); + return result; + } + + private String extractFieldValue(JSONObject merged, String[] exactKeys, String[] suffixKeys) { + if (merged == null || merged.isEmpty()) { + return null; + } + if (exactKeys != null) { + for (String key : exactKeys) { + String value = getSnapshotFieldValue(merged.get(key)); + if (StringUtils.isNotBlank(value)) { + return value; + } + } + } + if (suffixKeys != null) { + for (Map.Entry entry : merged.entrySet()) { + String fieldCode = entry.getKey(); + if (StringUtils.isBlank(fieldCode)) { + continue; + } + for (String suffix : suffixKeys) { + if (StringUtils.isNotBlank(suffix) && fieldCode.endsWith(suffix)) { + String value = getSnapshotFieldValue(entry.getValue()); + if (StringUtils.isNotBlank(value)) { + return value; + } + } + } + } + } + return null; + } + + private String getSnapshotFieldValue(Object snapshotObj) { + if (snapshotObj == null) { + return null; + } + if (snapshotObj instanceof JSONObject) { + return ((JSONObject) snapshotObj).getString("fieldValue"); + } + if (snapshotObj instanceof Map) { + Object val = ((Map) snapshotObj).get("fieldValue"); + return val == null ? null : String.valueOf(val); + } + return String.valueOf(snapshotObj); + } + + private JSONObject buildFieldSnapshot(String fieldValue, Date valueTime) { + JSONObject snapshot = new JSONObject(); + snapshot.put("fieldValue", fieldValue); + snapshot.put("valueTime", valueTime == null ? null : valueTime.getTime()); + return snapshot; + } + + private JSONObject parseFieldSnapshot(Object cacheObj) { + if (cacheObj == null) { + return new JSONObject(); + } + if (cacheObj instanceof JSONObject) { + return (JSONObject) cacheObj; + } + if (cacheObj instanceof Map) { + return new JSONObject((Map) cacheObj); + } + try { + JSONObject snapshot = JSON.parseObject(String.valueOf(cacheObj)); + return snapshot == null ? new JSONObject() : snapshot; + } catch (Exception e) { + return new JSONObject(); + } + } + + private Date parseValueTime(Object valueTimeObj) { + if (valueTimeObj == null) { + return null; + } + if (valueTimeObj instanceof Date) { + return (Date) valueTimeObj; + } + if (valueTimeObj instanceof Number) { + return new Date(((Number) valueTimeObj).longValue()); + } + try { + return DateUtils.parseDate(valueTimeObj.toString()); + } catch (Exception e) { + return null; + } + } + + private Date truncateToMinute(Date date) { + Date source = date == null ? DateUtils.getNowDate() : date; + long millis = source.getTime(); + long minuteMillis = 60_000L; + return new Date((millis / minuteMillis) * minuteMillis); + } + + private String buildSiteMonitorLatestRedisKey(String siteId, String moduleCode) { + return RedisKeyConstants.SITE_MONITOR_LATEST + siteId + "_" + moduleCode; + } + + private String buildSiteMonitorPointMatchRedisKey(String siteId) { + return RedisKeyConstants.SITE_MONITOR_POINT_MATCH + siteId; + } + + private void clearSiteMonitorLatestCache(String siteId) { + if (StringUtils.isBlank(siteId)) { + return; + } + redisCache.deleteObject(buildSiteMonitorLatestRedisKey(siteId, MODULE_HOME)); + redisCache.deleteObject(buildSiteMonitorLatestRedisKey(siteId, MODULE_SBJK)); + redisCache.deleteObject(buildSiteMonitorLatestRedisKey(siteId, MODULE_TJBB)); + } + // 辅助方法:根据值查找对应的对象(用于比较器中获取完整对象) private PointQueryResponse findByValue(List list, Object value) { return list.stream() @@ -572,11 +2199,37 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService log.info("设备控制指令发送数据: {}", JSON.toJSONString(deviceConfig)); boolean result = modbusProcessor.writeDataToDevice(deviceConfig); if (!result) { + recordDeviceOperationLog(request, false, "设备控制指令发送失败"); throw new ServiceException("设备控制指令发送失败"); } + recordDeviceOperationLog(request, true, null); return true; } + private void recordDeviceOperationLog(DeviceUpdateRequest request, boolean success, String errorMsg) { + try { + SysOperLog operLog = new SysOperLog(); + operLog.setTitle("设备控制-开关机/写功率"); + operLog.setBusinessType(BusinessType.UPDATE.ordinal()); + operLog.setMethod(this.getClass().getName() + ".updateDeviceStatus"); + operLog.setRequestMethod("SERVICE"); + operLog.setOperatorType(OperatorType.OTHER.ordinal()); + operLog.setOperName("system"); + operLog.setOperIp("127.0.0.1"); + operLog.setOperUrl("/ems/siteConfig/updateDeviceStatus"); + operLog.setOperTime(DateUtils.getNowDate()); + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(request), 0, 2000)); + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(Collections.singletonMap("success", success)), 0, 2000)); + operLog.setStatus(success ? BusinessStatus.SUCCESS.ordinal() : BusinessStatus.FAIL.ordinal()); + if (!success) { + operLog.setErrorMsg(StringUtils.substring(errorMsg, 0, 2000)); + } + operLogService.insertOperlog(operLog); + } catch (Exception e) { + log.error("记录sys_oper_log失败, siteId={}, deviceId={}", request.getSiteId(), request.getDeviceId(), e); + } + } + public DeviceConfig getDeviceConfig(EmsDevicesSetting device) { DeviceConfig deviceConfig = new DeviceConfig(); deviceConfig.setDeviceNumber(device.getDeviceId()); @@ -593,6 +2246,10 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService public List getWriteTags(String workStatus, EmsPcsSetting pcsSetting) { List writeTags = new ArrayList<>(); BigDecimal power; + BigDecimal powerMultiplier = pcsSetting.getPowerMultiplier(); + if (powerMultiplier == null || BigDecimal.ZERO.compareTo(powerMultiplier) == 0) { + powerMultiplier = BigDecimal.ONE; + } if (WorkStatus.NORMAL.getCode().equals(workStatus)) { // 开机先发送开机指令再发送有功功率给定值 @@ -616,7 +2273,7 @@ public class EmsDeviceSettingServiceImpl implements IEmsDeviceSettingService if (power == null) { power = BigDecimal.ZERO; } - clusterWriteTag.setValue(power); + clusterWriteTag.setValue(power.multiply(powerMultiplier)); writeTags.add(clusterWriteTag); } if (WorkStatus.STOP.getCode().equals(workStatus)) { diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsEnergyPriceConfigServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsEnergyPriceConfigServiceImpl.java index 682282a..8276e26 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsEnergyPriceConfigServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsEnergyPriceConfigServiceImpl.java @@ -4,11 +4,13 @@ import com.xzzn.common.constant.RedisKeyConstants; import com.xzzn.common.core.redis.RedisCache; import com.xzzn.common.utils.DateUtils; import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsDailyChargeData; import com.xzzn.ems.domain.EmsDailyEnergyData; import com.xzzn.ems.domain.EmsEnergyPriceConfig; import com.xzzn.ems.domain.EmsPriceTimeConfig; import com.xzzn.ems.domain.vo.EnergyPriceTimeRange; import com.xzzn.ems.domain.vo.EnergyPriceVo; +import com.xzzn.ems.mapper.EmsDailyChargeDataMapper; import com.xzzn.ems.mapper.EmsDailyEnergyDataMapper; import com.xzzn.ems.mapper.EmsEnergyPriceConfigMapper; import com.xzzn.ems.mapper.EmsPriceTimeConfigMapper; @@ -17,6 +19,7 @@ import com.xzzn.ems.service.IEmsEnergyPriceConfigService; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; +import java.util.Date; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -45,6 +48,8 @@ public class EmsEnergyPriceConfigServiceImpl implements IEmsEnergyPriceConfigSer @Autowired private RedisCache redisCache; @Autowired + private EmsDailyChargeDataMapper emsDailyChargeDataMapper; + @Autowired private EmsDailyEnergyDataMapper emsDailyEnergyDataMapper; /** @@ -280,22 +285,21 @@ public class EmsEnergyPriceConfigServiceImpl implements IEmsEnergyPriceConfigSer BigDecimal dayRevenue = BigDecimal.ZERO; BigDecimal yesterdayRevenue = BigDecimal.ZERO; // 获取昨日数据 - String yesterday = DateUtils.getYesterdayDayString(); - EmsDailyEnergyData yesterdayData = emsDailyEnergyDataMapper.getDataByDate(siteId, yesterday); + Date yesterday = DateUtils.toDate(LocalDate.now().minusDays(1)); + EmsDailyChargeData yesterdayData = emsDailyChargeDataMapper.selectBySiteIdAndDateTime(siteId, yesterday); if (yesterdayData != null) { yesterdayRevenue = yesterdayData.getDayRevenue() == null ? BigDecimal.ZERO : yesterdayData.getDayRevenue(); } // 当日实时数据 String today = DateUtils.getDate(); + Map lastData = emsDailyEnergyDataMapper.getRealTimeRevenue(siteId); + if (lastData != null) { + totalRevenue = lastData.get("totalRevenue") == null ? BigDecimal.ZERO : lastData.get("totalRevenue"); + } + EmsDailyEnergyData todayData = emsDailyEnergyDataMapper.getDataByDate(siteId,today); - if (todayData == null) { - Map lastData = emsDailyEnergyDataMapper.getRealTimeRevenue(siteId); - if (lastData != null) { - totalRevenue = lastData.get("totalRevenue") == null ? BigDecimal.ZERO : lastData.get("totalRevenue"); - } - } else { - totalRevenue = todayData.getTotalRevenue() == null ? BigDecimal.ZERO : todayData.getTotalRevenue(); + if (todayData != null) { // 获取当月电价 int currentMonth = LocalDate.now().getMonthValue(); int currentYear = LocalDate.now().getYear(); diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointCalcConfigServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointCalcConfigServiceImpl.java new file mode 100644 index 0000000..5fe7d81 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointCalcConfigServiceImpl.java @@ -0,0 +1,144 @@ +package com.xzzn.ems.service.impl; + +import com.xzzn.common.exception.ServiceException; +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.EmsPointCalcConfig; +import com.xzzn.ems.service.IEmsPointConfigService; +import com.xzzn.ems.service.IEmsPointCalcConfigService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Service +public class EmsPointCalcConfigServiceImpl implements IEmsPointCalcConfigService { + private static final Pattern CALC_EXPRESSION_PATTERN = Pattern.compile("^[0-9A-Za-z_+\\-*/().,?:<>=!&|\\s]+$"); + private static final String CALC_POINT_TYPE = "calc"; + + @Autowired + private IEmsPointConfigService pointConfigService; + + @Override + public List selectPointCalcConfigList(EmsPointCalcConfig pointCalcConfig) { + EmsPointConfig query = toPointConfigQuery(pointCalcConfig); + List configList = pointConfigService.selectPointConfigList(query); + if (configList == null || configList.isEmpty()) { + return new ArrayList<>(); + } + return configList.stream().map(this::toCalcConfig).collect(Collectors.toList()); + } + + @Override + public EmsPointCalcConfig selectPointCalcConfigById(Long id) { + EmsPointConfig config = pointConfigService.selectPointConfigById(id); + if (config == null || !CALC_POINT_TYPE.equalsIgnoreCase(config.getPointType())) { + return null; + } + return toCalcConfig(config); + } + + @Override + public int insertPointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName) { + if (pointCalcConfig == null || StringUtils.isBlank(pointCalcConfig.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + if (StringUtils.isBlank(pointCalcConfig.getPointId())) { + throw new ServiceException("点位ID不能为空"); + } + validateCalcExpression(pointCalcConfig.getCalcExpression()); + return pointConfigService.insertPointConfig(toPointConfig(pointCalcConfig), operName); + } + + @Override + public int updatePointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName) { + if (pointCalcConfig == null || StringUtils.isBlank(pointCalcConfig.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + if (StringUtils.isBlank(pointCalcConfig.getPointId())) { + throw new ServiceException("点位ID不能为空"); + } + validateCalcExpression(pointCalcConfig.getCalcExpression()); + return pointConfigService.updatePointConfig(toPointConfig(pointCalcConfig), operName); + } + + @Override + public int deletePointCalcConfigByIds(Long[] ids) { + return pointConfigService.deletePointConfigByIds(ids); + } + + @Override + public int deleteBySiteId(String siteId) { + if (StringUtils.isBlank(siteId)) { + return 0; + } + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setPointType(CALC_POINT_TYPE); + List configList = pointConfigService.selectPointConfigList(query); + if (configList == null || configList.isEmpty()) { + return 0; + } + Long[] ids = configList.stream().map(EmsPointConfig::getId).toArray(Long[]::new); + return pointConfigService.deletePointConfigByIds(ids); + } + + private void validateCalcExpression(String expression) { + if (StringUtils.isBlank(expression)) { + throw new ServiceException("计算表达式不能为空"); + } + if (!CALC_EXPRESSION_PATTERN.matcher(expression).matches()) { + throw new ServiceException("计算表达式仅支持数字、字母、下划线、空格、运算符和函数语法"); + } + } + + private EmsPointConfig toPointConfig(EmsPointCalcConfig calcConfig) { + EmsPointConfig pointConfig = new EmsPointConfig(); + pointConfig.setId(calcConfig.getId()); + pointConfig.setPointId(calcConfig.getPointId()); + pointConfig.setSiteId(calcConfig.getSiteId()); + pointConfig.setPointType(CALC_POINT_TYPE); + pointConfig.setDeviceCategory(calcConfig.getDeviceCategory()); + pointConfig.setDeviceId(""); + pointConfig.setRegisterAddress(""); + pointConfig.setPointName(calcConfig.getPointName()); + pointConfig.setDataKey(calcConfig.getDataKey()); + pointConfig.setPointDesc(calcConfig.getPointDesc()); + pointConfig.setDataUnit(calcConfig.getDataUnit()); + pointConfig.setCalcExpression(calcConfig.getCalcExpression()); + pointConfig.setRemark(calcConfig.getRemark()); + return pointConfig; + } + + private EmsPointConfig toPointConfigQuery(EmsPointCalcConfig calcConfig) { + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(calcConfig == null ? null : calcConfig.getSiteId()); + query.setDeviceCategory(calcConfig == null ? null : calcConfig.getDeviceCategory()); + query.setDataKey(calcConfig == null ? null : calcConfig.getDataKey()); + query.setPointDesc(calcConfig == null ? null : calcConfig.getPointDesc()); + query.setPointType(CALC_POINT_TYPE); + return query; + } + + private EmsPointCalcConfig toCalcConfig(EmsPointConfig pointConfig) { + EmsPointCalcConfig calcConfig = new EmsPointCalcConfig(); + calcConfig.setId(pointConfig.getId()); + calcConfig.setPointId(pointConfig.getPointId()); + calcConfig.setSiteId(pointConfig.getSiteId()); + calcConfig.setDeviceCategory(pointConfig.getDeviceCategory()); + calcConfig.setPointName(pointConfig.getPointName()); + calcConfig.setDataKey(pointConfig.getDataKey()); + calcConfig.setPointDesc(pointConfig.getPointDesc()); + calcConfig.setDataUnit(pointConfig.getDataUnit()); + calcConfig.setCalcExpression(pointConfig.getCalcExpression()); + calcConfig.setRemark(pointConfig.getRemark()); + calcConfig.setCreateBy(pointConfig.getCreateBy()); + calcConfig.setCreateTime(pointConfig.getCreateTime()); + calcConfig.setUpdateBy(pointConfig.getUpdateBy()); + calcConfig.setUpdateTime(pointConfig.getUpdateTime()); + return calcConfig; + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java new file mode 100644 index 0000000..09e9515 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointConfigServiceImpl.java @@ -0,0 +1,2011 @@ +package com.xzzn.ems.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.xzzn.common.exception.ServiceException; +import com.xzzn.common.constant.RedisKeyConstants; +import com.xzzn.common.core.redis.RedisCache; +import com.xzzn.common.utils.DateUtils; +import com.xzzn.common.utils.StringUtils; +import com.xzzn.ems.domain.EmsPointConfig; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveRequest; +import com.xzzn.ems.domain.vo.PointConfigCurveValueVo; +import com.xzzn.ems.domain.vo.PointConfigGenerateRecentRequest; +import com.xzzn.ems.domain.vo.PointConfigLatestValueItemVo; +import com.xzzn.ems.domain.vo.PointConfigLatestValueRequest; +import com.xzzn.ems.domain.vo.PointConfigLatestValueVo; +import com.xzzn.ems.mapper.EmsPointConfigMapper; +import com.xzzn.ems.service.IEmsPointConfigService; +import com.xzzn.ems.service.InfluxPointDataWriter; +import org.apache.commons.collections4.CollectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +@Service +public class EmsPointConfigServiceImpl implements IEmsPointConfigService { + private static final Logger log = LoggerFactory.getLogger(EmsPointConfigServiceImpl.class); + private static final String TEMPLATE_SITE_ID = "DEFAULT"; + private static final int CSV_IMPORT_BATCH_SIZE = 200; + private static final Pattern DB_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); + private static final Pattern INTEGER_PATTERN = Pattern.compile("^-?\\d+$"); + private static final Pattern DECIMAL_PATTERN = Pattern.compile("^-?\\d+(\\.\\d+)?$"); + private static final Pattern CALC_EXPRESSION_PATTERN = Pattern.compile("^[0-9A-Za-z_+\\-*/().,?:<>=!&|\\s]+$"); + private static final Pattern AUTO_DAY_DIFF_FUNCTION_PATTERN = Pattern.compile("(?i)DAY_DIFF\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"); + private static final Pattern AUTO_MONTH_DIFF_FUNCTION_PATTERN = Pattern.compile("(?i)MONTH_DIFF\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"); + private static final Pattern AUTO_HOUR_DIFF_FUNCTION_PATTERN = Pattern.compile("(?i)HOUR_DIFF\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"); + private static final String AUTO_DAY_DIFF_CONTEXT_PREFIX = "__AUTO_DAY_DIFF__"; + private static final String AUTO_MONTH_DIFF_CONTEXT_PREFIX = "__AUTO_MONTH_DIFF__"; + private static final String AUTO_HOUR_DIFF_CONTEXT_PREFIX = "__AUTO_HOUR_DIFF__"; + private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Autowired + private EmsPointConfigMapper emsPointConfigMapper; + @Autowired + private RedisCache redisCache; + @Autowired + private InfluxPointDataWriter influxPointDataWriter; + + @Override + public List selectPointConfigList(EmsPointConfig pointConfig) { + return emsPointConfigMapper.selectEmsPointConfigList(pointConfig); + } + + @Override + public EmsPointConfig selectPointConfigById(Long id) { + return emsPointConfigMapper.selectEmsPointConfigById(id); + } + + @Override + public int insertPointConfig(EmsPointConfig pointConfig, String operName) { + if (pointConfig == null || StringUtils.isBlank(pointConfig.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + normalizeAndValidatePointConfig(pointConfig); + applyCollectDefaultsForInsert(pointConfig); + pointConfig.setCreateBy(operName); + pointConfig.setUpdateBy(operName); + int rows; + try { + rows = emsPointConfigMapper.insertEmsPointConfig(pointConfig); + } catch (Exception ex) { + throw translatePointConfigPersistenceException(ex, pointConfig == null ? null : pointConfig.getPointId()); + } + if (rows > 0) { + invalidatePointConfigCache(pointConfig.getSiteId(), pointConfig.getDeviceId()); + } + return rows; + } + + @Override + public int updatePointConfig(EmsPointConfig pointConfig, String operName) { + EmsPointConfig oldConfig = pointConfig.getId() == null ? null : emsPointConfigMapper.selectEmsPointConfigById(pointConfig.getId()); + normalizeAndValidatePointConfig(pointConfig); + pointConfig.setUpdateBy(operName); + int rows; + try { + rows = emsPointConfigMapper.updateEmsPointConfig(pointConfig); + } catch (Exception ex) { + throw translatePointConfigPersistenceException(ex, pointConfig == null ? null : pointConfig.getPointId()); + } + if (rows > 0) { + if (oldConfig != null) { + invalidatePointConfigCache(oldConfig.getSiteId(), oldConfig.getDeviceId()); + } + invalidatePointConfigCache(pointConfig.getSiteId(), pointConfig.getDeviceId()); + } + return rows; + } + + @Override + public int deletePointConfigByIds(Long[] ids) { + if (ids != null) { + for (Long id : ids) { + EmsPointConfig oldConfig = emsPointConfigMapper.selectEmsPointConfigById(id); + if (oldConfig != null) { + invalidatePointConfigCache(oldConfig.getSiteId(), oldConfig.getDeviceId()); + } + } + } + return emsPointConfigMapper.deleteEmsPointConfigByIds(ids); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String importTemplateBySite(ImportPointTemplateRequest request, String operName) { + String targetSiteId = request.getSiteId(); + if (StringUtils.isBlank(targetSiteId)) { + throw new ServiceException("站点ID不能为空"); + } + if (TEMPLATE_SITE_ID.equals(targetSiteId)) { + throw new ServiceException("模板站点不支持作为导入目标站点"); + } + + int templateCount = emsPointConfigMapper.countBySiteId(TEMPLATE_SITE_ID); + if (templateCount <= 0) { + throw new ServiceException("模板点位配置不存在,无法导入"); + } + + boolean overwrite = Boolean.TRUE.equals(request.getOverwrite()); + int targetCount = emsPointConfigMapper.countBySiteId(targetSiteId); + if (targetCount > 0 && !overwrite) { + throw new ServiceException("目标站点已存在点位配置,请勾选“覆盖已存在数据”后重试"); + } + + if (targetCount > 0) { + emsPointConfigMapper.deleteBySiteId(targetSiteId); + } + + int importCount = emsPointConfigMapper.copyTemplateToSite(TEMPLATE_SITE_ID, targetSiteId, operName); + invalidatePointConfigCacheBySite(targetSiteId); + return String.format("导入成功:站点 %s,点位 %d 条", targetSiteId, importCount); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String importCsvBySite(String siteId, Boolean overwrite, MultipartFile file, String operName) { + if (StringUtils.isBlank(siteId)) { + throw new ServiceException("站点ID不能为空"); + } + if (!DB_NAME_PATTERN.matcher(siteId).matches()) { + throw new ServiceException(String.format("站点ID不合法(siteId=%s)", siteId)); + } + if (TEMPLATE_SITE_ID.equals(siteId)) { + throw new ServiceException("模板站点不支持作为导入目标站点"); + } + if (file == null || file.isEmpty()) { + throw new ServiceException("请上传CSV文件"); + } + if (!StringUtils.endsWithIgnoreCase(file.getOriginalFilename(), ".csv")) { + throw new ServiceException("仅支持上传CSV文件"); + } + + List pointConfigList = parseCsv(file, siteId); + if (pointConfigList.isEmpty()) { + throw new ServiceException("CSV没有可导入的数据"); + } + + List pointIds = pointConfigList.stream() + .map(EmsPointConfig::getPointId) + .filter(StringUtils::isNotBlank) + .distinct() + .collect(Collectors.toList()); + Map existingPointMap = new HashMap<>(); + if (!pointIds.isEmpty()) { + List existingList = emsPointConfigMapper.selectBySiteIdAndPointIds(siteId, pointIds); + if (CollectionUtils.isNotEmpty(existingList)) { + for (EmsPointConfig existing : existingList) { + if (existing != null && StringUtils.isNotBlank(existing.getPointId())) { + existingPointMap.put(existing.getPointId(), existing); + } + } + } + } + + List insertList = new ArrayList<>(); + int updateCount = 0; + int insertCount = 0; + long importStart = System.currentTimeMillis(); + for (EmsPointConfig pointConfig : pointConfigList) { + EmsPointConfig existing = existingPointMap.get(pointConfig.getPointId()); + if (existing == null) { + pointConfig.setCreateBy(operName); + pointConfig.setUpdateBy(operName); + insertList.add(pointConfig); + continue; + } + pointConfig.setId(existing.getId()); + pointConfig.setUpdateBy(operName); + try { + updateCount += emsPointConfigMapper.updateEmsPointConfigForImport(pointConfig); + } catch (Exception ex) { + throw translatePointConfigPersistenceException(ex, pointConfig.getPointId()); + } + } + + for (int fromIndex = 0; fromIndex < insertList.size(); fromIndex += CSV_IMPORT_BATCH_SIZE) { + int toIndex = Math.min(fromIndex + CSV_IMPORT_BATCH_SIZE, insertList.size()); + List batchList = insertList.subList(fromIndex, toIndex); + long batchStart = System.currentTimeMillis(); + try { + insertCount += 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); + } + int importCount = updateCount + insertCount; + log.info("点位CSV导入完成,siteId={}, total={}, insert={}, update={}, costMs={}", + siteId, importCount, insertCount, updateCount, System.currentTimeMillis() - importStart); + + invalidatePointConfigCacheBySite(siteId); + return String.format("导入成功:站点 %s,新增 %d 条,更新 %d 条", siteId, insertCount, updateCount); + } + + @Override + public String getRegisterAddress(String siteId, String deviceCategory, String deviceId, String dataKey) { + if (StringUtils.isAnyBlank(siteId, deviceCategory, deviceId, dataKey)) { + return null; + } + return emsPointConfigMapper.getRegisterAddress(siteId, deviceCategory, deviceId, dataKey); + } + + @Override + public List getLatestValues(PointConfigLatestValueRequest request) { + List result = new ArrayList<>(); + if (request == null || request.getPoints() == null || request.getPoints().isEmpty()) { + log.warn("latestValues 查询参数为空,request: {}", request); + return result; + } + log.info("latestValues 开始查询,pointCount: {}", request.getPoints().size()); + for (PointConfigLatestValueItemVo item : request.getPoints()) { + if (item == null || StringUtils.isBlank(item.getPointId())) { + log.warn("latestValues 跳过空点位项,item: {}", item); + continue; + } + PointConfigLatestValueVo latestValue = queryLatestValueByPointIdFromRedis(item); + result.add(latestValue); + } + long hitCount = result.stream().filter(vo -> vo.getPointValue() != null).count(); + log.info("latestValues 查询结束,total: {}, hit: {}, miss: {}", + result.size(), hitCount, result.size() - hitCount); + return result; + } + + @Override + public List getCurveData(PointConfigCurveRequest request) { + if (request == null || StringUtils.isBlank(request.getSiteId())) { + return new ArrayList<>(); + } + String siteId = StringUtils.trim(request.getSiteId()); + String pointId = StringUtils.trim(request.getPointId()); + if (StringUtils.isAnyBlank(siteId, pointId)) { + return new ArrayList<>(); + } + Date[] range = resolveTimeRange(request); + return queryCurveDataFromInflux(siteId, pointId, range[0], range[1]); + } + + @Override + public String generateRecent7DaysData(PointConfigGenerateRecentRequest request) { + if (request == null || StringUtils.isAnyBlank(request.getSiteId(), request.getPointId())) { + throw new ServiceException("站点ID和点位ID不能为空"); + } + String siteId = StringUtils.trim(request.getSiteId()); + String pointId = StringUtils.trim(request.getPointId()); + String deviceId = StringUtils.trimToEmpty(request.getDeviceId()); + + EmsPointConfig targetConfig = selectPointConfigByExactPointId(siteId, pointId); + if (targetConfig == null || !"calc".equalsIgnoreCase(StringUtils.defaultString(targetConfig.getPointType()))) { + throw new ServiceException("仅计算点支持按表达式生成最近7天数据"); + } + String expression = StringUtils.trimToNull(targetConfig.getCalcExpression()); + if (StringUtils.isBlank(expression)) { + throw new ServiceException("计算点缺少表达式,无法生成数据"); + } + + List payloads = buildCalcRecentPayloads(siteId, deviceId, pointId, expression); + if (payloads.isEmpty()) { + throw new ServiceException("未生成有效数据,请检查表达式依赖点是否有历史数据"); + } + influxPointDataWriter.writeBatch(payloads); + return String.format("已生成点位 %s 最近7天数据,共 %d 条", pointId, payloads.size()); + } + + private List buildCalcRecentPayloads(String siteId, String deviceId, String pointId, String expression) { + CompiledExpression compiledExpression = new CompiledExpression(expression); + List evaluateTimes = buildEvaluateTimes(expression); + if (evaluateTimes.isEmpty()) { + return Collections.emptyList(); + } + EvaluateGranularity granularity = resolveEvaluateGranularity(expression); + LocalDateTime start = evaluateTimes.get(0); + LocalDateTime end = evaluateTimes.get(evaluateTimes.size() - 1); + + Set variables = extractExpressionVariables(expression); + Map> variableSeriesMap = loadVariableSeries(siteId, variables, start.minusDays(35), end); + Map periodDiffStateMap = new HashMap<>(); + initializeRecentPeriodDiffState(expression, evaluateTimes, variableSeriesMap, periodDiffStateMap); + List payloads = new ArrayList<>(evaluateTimes.size()); + for (LocalDateTime evaluateTime : evaluateTimes) { + Map contextValues = buildContextValuesByTime(evaluateTime, variableSeriesMap, granularity); + if (contextValues.isEmpty()) { + continue; + } + BigDecimal value; + try { + prepareAutoPeriodDiffContextValues(expression, contextValues, evaluateTime, periodDiffStateMap); + value = compiledExpression.evaluate(contextValues).asNumber().setScale(4, RoundingMode.HALF_UP); + } catch (IllegalArgumentException ex) { + continue; + } + payloads.add(new InfluxPointDataWriter.PointWritePayload( + siteId, + deviceId, + pointId, + value, + Timestamp.valueOf(evaluateTime) + )); + } + return payloads; + } + + private void initializeRecentPeriodDiffState(String expression, + List evaluateTimes, + Map> variableSeriesMap, + Map periodDiffStateMap) { + if (StringUtils.isBlank(expression) + || CollectionUtils.isEmpty(evaluateTimes) + || variableSeriesMap == null + || periodDiffStateMap == null) { + return; + } + LocalDateTime firstEvaluateTime = evaluateTimes.get(0); + initializeSingleRecentDiffState("DAY_DIFF", expression, AUTO_DAY_DIFF_FUNCTION_PATTERN, + "yyyy-MM-dd", firstEvaluateTime, variableSeriesMap, periodDiffStateMap); + initializeSingleRecentDiffState("MONTH_DIFF", expression, AUTO_MONTH_DIFF_FUNCTION_PATTERN, + "yyyy-MM", firstEvaluateTime, variableSeriesMap, periodDiffStateMap); + initializeSingleRecentDiffState("HOUR_DIFF", expression, AUTO_HOUR_DIFF_FUNCTION_PATTERN, + "yyyy-MM-dd HH", firstEvaluateTime, variableSeriesMap, periodDiffStateMap); + } + + private void initializeSingleRecentDiffState(String functionName, + String expression, + Pattern functionPattern, + String periodFormat, + LocalDateTime firstEvaluateTime, + Map> variableSeriesMap, + Map periodDiffStateMap) { + Set variables = extractAutoDiffVariables(expression, functionPattern); + if (variables.isEmpty()) { + return; + } + long previousMillis = Timestamp.valueOf(firstEvaluateTime).getTime() - 1; + if (previousMillis < Long.MIN_VALUE + 1) { + return; + } + String previousPeriod = DateUtils.parseDateToStr(periodFormat, new Date(previousMillis)); + for (String variable : variables) { + List values = variableSeriesMap.get(variable); + InfluxPointDataWriter.PointValue previous = findLatestInWindow(values, Long.MIN_VALUE, previousMillis); + if (previous == null || previous.getPointValue() == null) { + continue; + } + String stateKey = functionName.toUpperCase(Locale.ROOT) + "_" + variable.toUpperCase(Locale.ROOT); + PeriodDiffState state = periodDiffStateMap.computeIfAbsent(stateKey, k -> new PeriodDiffState()); + state.period = previousPeriod; + state.lastValue = previous.getPointValue(); + } + } + + private List buildEvaluateTimes(String expression) { + LocalDateTime now = LocalDateTime.now(); + List times = new ArrayList<>(); + EvaluateGranularity granularity = resolveEvaluateGranularity(expression); + boolean containsHourDiff = granularity == EvaluateGranularity.HOUR; + boolean containsMonthDiff = granularity == EvaluateGranularity.MONTH; + if (containsHourDiff) { + int totalHours = 24 * 7; + LocalDateTime latestCompletedHourEnd = now.withMinute(0).withSecond(0).withNano(0).minusSeconds(1); + for (int i = totalHours - 1; i >= 0; i--) { + times.add(latestCompletedHourEnd.minusHours(i)); + } + return times; + } + if (containsMonthDiff) { + times.add(now.withSecond(0).withNano(0)); + return times; + } + for (int i = 6; i >= 0; i--) { + LocalDateTime dayTime = now.minusDays(i).withHour(23).withMinute(59).withSecond(59).withNano(0); + if (dayTime.isAfter(now)) { + dayTime = now.withNano(0); + } + times.add(dayTime); + } + return times; + } + + private EvaluateGranularity resolveEvaluateGranularity(String expression) { + if (AUTO_HOUR_DIFF_FUNCTION_PATTERN.matcher(expression).find()) { + return EvaluateGranularity.HOUR; + } + if (AUTO_MONTH_DIFF_FUNCTION_PATTERN.matcher(expression).find()) { + return EvaluateGranularity.MONTH; + } + return EvaluateGranularity.DAY; + } + + private Set extractExpressionVariables(String expression) { + Set reserved = new HashSet<>(); + reserved.add("IF"); + reserved.add("DAY_DIFF"); + reserved.add("MONTH_DIFF"); + reserved.add("HOUR_DIFF"); + Set variables = new HashSet<>(); + for (ExprToken token : tokenizeExpression(expression)) { + if (token.type == ExprTokenType.IDENTIFIER) { + String identifier = StringUtils.defaultString(token.text).trim().toUpperCase(Locale.ROOT); + if (StringUtils.isNotBlank(identifier) && !reserved.contains(identifier)) { + variables.add(identifier); + } + } + } + return variables; + } + + private Map> loadVariableSeries(String siteId, + Set variables, + LocalDateTime start, + LocalDateTime end) { + Map> variableSeriesMap = new HashMap<>(); + if (variables == null || variables.isEmpty()) { + return variableSeriesMap; + } + Date startDate = Timestamp.valueOf(start); + Date endDate = Timestamp.valueOf(end); + for (String variable : variables) { + List values = influxPointDataWriter.queryCurveDataByPointKey(siteId, variable, startDate, endDate); + if (values == null) { + values = new ArrayList<>(); + } + values.sort(Comparator.comparing(InfluxPointDataWriter.PointValue::getDataTime, Comparator.nullsLast(Date::compareTo))); + variableSeriesMap.put(variable, values); + } + return variableSeriesMap; + } + + private Map buildContextValuesByTime(LocalDateTime evaluateTime, + Map> variableSeriesMap, + EvaluateGranularity granularity) { + Map context = new HashMap<>(); + if (variableSeriesMap == null || variableSeriesMap.isEmpty()) { + return context; + } + long evaluateMillis = Timestamp.valueOf(evaluateTime).getTime(); + long windowStartMillis = resolveWindowStartMillis(evaluateTime, granularity); + for (Map.Entry> entry : variableSeriesMap.entrySet()) { + InfluxPointDataWriter.PointValue latest = findLatestInWindow(entry.getValue(), windowStartMillis, evaluateMillis); + if (latest == null && granularity == EvaluateGranularity.MONTH) { + latest = findLatestInWindow(entry.getValue(), Long.MIN_VALUE, evaluateMillis); + } + if (latest == null || latest.getPointValue() == null) { + continue; + } + context.put(entry.getKey(), latest.getPointValue()); + } + return context; + } + + private long resolveWindowStartMillis(LocalDateTime evaluateTime, EvaluateGranularity granularity) { + if (granularity == EvaluateGranularity.HOUR) { + return Timestamp.valueOf(evaluateTime.withMinute(0).withSecond(0).withNano(0)).getTime(); + } + if (granularity == EvaluateGranularity.DAY) { + return Timestamp.valueOf(evaluateTime.withHour(0).withMinute(0).withSecond(0).withNano(0)).getTime(); + } + return Long.MIN_VALUE; + } + + private InfluxPointDataWriter.PointValue findLatestInWindow(List values, + long startMillisInclusive, + long endMillisInclusive) { + if (values == null || values.isEmpty()) { + return null; + } + InfluxPointDataWriter.PointValue latest = null; + for (InfluxPointDataWriter.PointValue value : values) { + if (value == null || value.getDataTime() == null) { + continue; + } + long currentMillis = value.getDataTime().getTime(); + if (currentMillis > endMillisInclusive) { + break; + } + if (currentMillis < startMillisInclusive) { + continue; + } + latest = value; + } + return latest; + } + + private enum EvaluateGranularity { + DAY, + HOUR, + MONTH + } + + private void prepareAutoPeriodDiffContextValues(String expression, + Map contextValues, + LocalDateTime evaluateTime, + Map periodDiffStateMap) { + if (StringUtils.isBlank(expression) || contextValues == null || contextValues.isEmpty()) { + return; + } + prepareSingleAutoDiffContext(expression, contextValues, evaluateTime, "DAY_DIFF", + AUTO_DAY_DIFF_FUNCTION_PATTERN, "yyyy-MM-dd", periodDiffStateMap); + prepareSingleAutoDiffContext(expression, contextValues, evaluateTime, "MONTH_DIFF", + AUTO_MONTH_DIFF_FUNCTION_PATTERN, "yyyy-MM", periodDiffStateMap); + prepareSingleAutoDiffContext(expression, contextValues, evaluateTime, "HOUR_DIFF", + AUTO_HOUR_DIFF_FUNCTION_PATTERN, "yyyy-MM-dd HH", periodDiffStateMap); + } + + private void prepareSingleAutoDiffContext(String expression, + Map contextValues, + LocalDateTime evaluateTime, + String functionName, + Pattern functionPattern, + String periodFormat, + Map periodDiffStateMap) { + Set variables = extractAutoDiffVariables(expression, functionPattern); + if (variables.isEmpty()) { + return; + } + String currentPeriod = DateUtils.parseDateToStr(periodFormat, Timestamp.valueOf(evaluateTime)); + for (String variable : variables) { + BigDecimal currentValue = contextValues.get(variable); + if (currentValue == null) { + continue; + } + BigDecimal diffValue = calculateAutoPeriodDiffValue(functionName, variable, currentValue, currentPeriod, periodDiffStateMap); + contextValues.put(resolveAutoDiffContextKey(functionName, variable), diffValue); + } + } + + private Set extractAutoDiffVariables(String expression, Pattern functionPattern) { + Set variables = new HashSet<>(); + java.util.regex.Matcher matcher = functionPattern.matcher(expression); + while (matcher.find()) { + String variable = StringUtils.defaultString(matcher.group(1)).trim().toUpperCase(Locale.ROOT); + if (StringUtils.isNotBlank(variable)) { + variables.add(variable); + } + } + return variables; + } + + private BigDecimal calculateAutoPeriodDiffValue(String functionName, + String variable, + BigDecimal currentValue, + String currentPeriod, + Map periodDiffStateMap) { + if (currentValue == null || StringUtils.isAnyBlank(functionName, variable, currentPeriod)) { + return BigDecimal.ZERO; + } + String stateKey = functionName.toUpperCase(Locale.ROOT) + "_" + variable.toUpperCase(Locale.ROOT); + PeriodDiffState state = periodDiffStateMap.computeIfAbsent(stateKey, k -> new PeriodDiffState()); + if (!StringUtils.equals(state.period, currentPeriod)) { + state.baseline = state.lastValue == null ? currentValue : state.lastValue; + state.period = currentPeriod; + state.periodDiffValue = currentValue.subtract(state.baseline); + } else if (state.baseline == null) { + state.baseline = currentValue; + state.periodDiffValue = currentValue.subtract(state.baseline); + } else { + state.periodDiffValue = currentValue.subtract(state.baseline); + } + state.lastValue = currentValue; + return state.periodDiffValue == null ? BigDecimal.ZERO : state.periodDiffValue; + } + + private static String resolveAutoDayDiffContextKey(String variable) { + return AUTO_DAY_DIFF_CONTEXT_PREFIX + StringUtils.defaultString(variable).trim().toUpperCase(Locale.ROOT); + } + + private static String resolveAutoMonthDiffContextKey(String variable) { + return AUTO_MONTH_DIFF_CONTEXT_PREFIX + StringUtils.defaultString(variable).trim().toUpperCase(Locale.ROOT); + } + + private static String resolveAutoHourDiffContextKey(String variable) { + return AUTO_HOUR_DIFF_CONTEXT_PREFIX + StringUtils.defaultString(variable).trim().toUpperCase(Locale.ROOT); + } + + private static String resolveAutoDiffContextKey(String functionName, String variable) { + if ("DAY_DIFF".equalsIgnoreCase(functionName)) { + return resolveAutoDayDiffContextKey(variable); + } + if ("MONTH_DIFF".equalsIgnoreCase(functionName)) { + return resolveAutoMonthDiffContextKey(variable); + } + if ("HOUR_DIFF".equalsIgnoreCase(functionName)) { + return resolveAutoHourDiffContextKey(variable); + } + return null; + } + + private PointConfigLatestValueVo queryLatestValueByPointIdFromRedis(PointConfigLatestValueItemVo item) { + PointConfigLatestValueVo vo = new PointConfigLatestValueVo(); + vo.setSiteId(item.getSiteId()); + vo.setDeviceId(item.getDeviceId()); + vo.setDataKey(item.getDataKey()); + + String pointId = StringUtils.trimToNull(item.getPointId()); + vo.setPointId(pointId); + if (StringUtils.isBlank(pointId)) { + return vo; + } + + String hitRedisKey = pointId; + Object raw = redisCache.getCacheObject(pointId); + if (raw == null) { + log.warn("latestValues Redis未命中,siteId: {}, deviceId: {}, pointId: {}, dataKey: {}", + item.getSiteId(), item.getDeviceId(), pointId, item.getDataKey()); + return vo; + } + log.info("latestValues Redis命中,siteId: {}, deviceId: {}, pointId: {}, redisKey: {}, rawType: {}", + item.getSiteId(), item.getDeviceId(), pointId, hitRedisKey, raw.getClass().getName()); + + JSONObject root = toJsonObject(raw); + if (root == null) { + log.warn("latestValues Redis值无法转JSON,siteId: {}, deviceId: {}, pointId: {}, redisKey: {}, raw: {}", + item.getSiteId(), item.getDeviceId(), pointId, hitRedisKey, raw); + return vo; + } + + Object rawValue = root.get("pointValue"); + BigDecimal pointValue = toBigDecimalOrNull(rawValue); + if (pointValue == null) { + pointValue = readBigDecimalField(raw, "pointValue"); + } + if (pointValue != null) { + vo.setPointValue(pointValue); + } + + if (StringUtils.isBlank(vo.getSiteId())) { + vo.setSiteId(root.getString("siteId")); + if (StringUtils.isBlank(vo.getSiteId())) { + vo.setSiteId(readStringField(raw, "siteId")); + } + } + if (StringUtils.isBlank(vo.getDeviceId())) { + vo.setDeviceId(root.getString("deviceId")); + if (StringUtils.isBlank(vo.getDeviceId())) { + vo.setDeviceId(readStringField(raw, "deviceId")); + } + } + if (StringUtils.isBlank(vo.getPointId())) { + vo.setPointId(root.getString("pointId")); + if (StringUtils.isBlank(vo.getPointId())) { + vo.setPointId(readStringField(raw, "pointId")); + } + } + Date dataTime = extractCacheDataTime(root); + if (dataTime == null) { + dataTime = readDateField(raw, "dataTime"); + } + vo.setDataTime(dataTime); + log.info("latestValues 解析完成,siteId: {}, deviceId: {}, pointId: {}, dataKey: {}, pointValue: {}, dataTime: {}", + vo.getSiteId(), vo.getDeviceId(), vo.getPointId(), vo.getDataKey(), vo.getPointValue(), vo.getDataTime()); + return vo; + } + + private BigDecimal toBigDecimalOrNull(Object value) { + if (value == null) { + return null; + } + if (value instanceof BigDecimal) { + return (BigDecimal) value; + } + try { + return new BigDecimal(value.toString()); + } catch (Exception ex) { + return null; + } + } + + private String readStringField(Object source, String fieldName) { + Object value = readFieldValue(source, fieldName); + return value == null ? null : String.valueOf(value); + } + + private BigDecimal readBigDecimalField(Object source, String fieldName) { + Object value = readFieldValue(source, fieldName); + return toBigDecimalOrNull(value); + } + + private Date readDateField(Object source, String fieldName) { + Object value = readFieldValue(source, fieldName); + if (value instanceof Date) { + return (Date) value; + } + return null; + } + + private Object readFieldValue(Object source, String fieldName) { + if (source == null || StringUtils.isBlank(fieldName)) { + return null; + } + Class current = source.getClass(); + while (current != null) { + try { + Field field = current.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(source); + } catch (NoSuchFieldException ex) { + current = current.getSuperclass(); + } catch (Exception ex) { + return null; + } + } + return null; + } + + private Date extractCacheDataTime(JSONObject root) { + if (root == null) { + return null; + } + Date dataTime = root.getDate("dataTime"); + if (dataTime != null) { + return dataTime; + } + Long timestamp = root.getLong("timestamp"); + if (timestamp != null) { + return DateUtils.convertUpdateTime(timestamp); + } + return null; + } + + private List queryCurveDataFromInflux(String siteId, String pointId, Date startTime, Date endTime) { + if (StringUtils.isBlank(pointId)) { + return new ArrayList<>(); + } + List values = influxPointDataWriter.queryCurveDataByPointKey(siteId, pointId.trim(), startTime, endTime); + if (values == null || values.isEmpty()) { + return new ArrayList<>(); + } + return values.stream().map(value -> { + PointConfigCurveValueVo vo = new PointConfigCurveValueVo(); + vo.setDataTime(value.getDataTime()); + vo.setPointValue(value.getPointValue()); + return vo; + }).collect(Collectors.toList()); + } + + private Date[] resolveTimeRange(PointConfigCurveRequest request) { + LocalDateTime end = LocalDateTime.now(); + String rangeType = StringUtils.defaultIfBlank(request.getRangeType(), "day").toLowerCase(Locale.ROOT); + LocalDateTime start; + if ("custom".equals(rangeType)) { + if (StringUtils.isAnyBlank(request.getStartTime(), request.getEndTime())) { + throw new ServiceException("自定义时间范围必须传入开始时间和结束时间"); + } + start = parseDateTime(request.getStartTime()); + end = parseDateTime(request.getEndTime()); + } else if ("week".equals(rangeType)) { + start = end.minusDays(7); + } else if ("month".equals(rangeType)) { + start = end.minusDays(30); + } else { + start = end.minusDays(1); + } + if (start.isAfter(end)) { + throw new ServiceException("开始时间不能晚于结束时间"); + } + return new Date[]{ + Timestamp.valueOf(start), + Timestamp.valueOf(end) + }; + } + + private LocalDateTime parseDateTime(String value) { + try { + return LocalDateTime.parse(value, DATETIME_FORMATTER); + } catch (DateTimeParseException ex) { + throw new ServiceException("时间格式错误,请使用 yyyy-MM-dd HH:mm:ss"); + } + } + + private RuntimeException translatePointConfigPersistenceException(Exception ex, String pointId) { + if (isPointIdDuplicateException(ex)) { + String normalizedPointId = StringUtils.trim(pointId); + if (StringUtils.isBlank(normalizedPointId)) { + return new ServiceException("点位ID已存在(point_id 全局唯一),请修改后重试"); + } + return new ServiceException("点位ID已存在:" + normalizedPointId + "(point_id 全局唯一),请修改后重试"); + } + if (ex instanceof RuntimeException) { + return (RuntimeException) ex; + } + return new ServiceException("点位配置保存失败,请稍后重试"); + } + + private boolean isPointIdDuplicateException(Throwable throwable) { + Throwable current = throwable; + while (current != null) { + if (current instanceof DuplicateKeyException || current instanceof DataIntegrityViolationException) { + String message = current.getMessage(); + if (StringUtils.isNotBlank(message) && message.toLowerCase(Locale.ROOT).contains("uk_point_id")) { + return true; + } + } + String message = current.getMessage(); + if (StringUtils.isNotBlank(message)) { + String lowerMessage = message.toLowerCase(Locale.ROOT); + if (lowerMessage.contains("duplicate entry") && lowerMessage.contains("uk_point_id")) { + return true; + } + } + current = current.getCause(); + } + return false; + } + + private void invalidatePointConfigCache(String siteId, String deviceId) { + if (StringUtils.isBlank(siteId)) { + return; + } + if (StringUtils.isBlank(deviceId)) { + invalidatePointConfigCacheBySite(siteId); + return; + } + Collection keys = redisCache.keys(RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_" + deviceId + "_*"); + if (keys != null && !keys.isEmpty()) { + redisCache.deleteObject(keys); + } + } + + private void invalidatePointConfigCacheBySite(String siteId) { + if (StringUtils.isBlank(siteId)) { + return; + } + Collection keys = redisCache.keys(RedisKeyConstants.POINT_CONFIG_DEVICE + siteId + "_*"); + if (keys != null && !keys.isEmpty()) { + redisCache.deleteObject(keys); + } + Collection pointKeys = redisCache.keys(RedisKeyConstants.POINT_CONFIG_POINT + siteId + "_*"); + if (pointKeys != null && !pointKeys.isEmpty()) { + redisCache.deleteObject(pointKeys); + } + } + + 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 Date extractDataTime(JSONObject root) { + if (root == null) { + return null; + } + Long timestamp = root.getLong("timestamp"); + if (timestamp != null) { + return DateUtils.convertUpdateTime(timestamp); + } + 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 EmsPointConfig getPointConfig(String siteId, + String deviceId, + String dataKey, + Map> configMapCache) { + if (StringUtils.isAnyBlank(siteId, deviceId, dataKey)) { + return null; + } + String cacheKey = siteId + "|" + deviceId; + Map configByKey = configMapCache.get(cacheKey); + if (configByKey == null) { + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setDeviceId(deviceId); + List configList = emsPointConfigMapper.selectEmsPointConfigList(query); + configByKey = new HashMap<>(); + if (configList != null) { + for (EmsPointConfig config : configList) { + if (config != null && StringUtils.isNotBlank(config.getDataKey())) { + configByKey.put(config.getDataKey().toUpperCase(Locale.ROOT), config); + } + } + } + configMapCache.put(cacheKey, configByKey); + } + return configByKey.get(dataKey.toUpperCase(Locale.ROOT)); + } + + private EmsPointConfig selectPointConfigByExactPointId(String siteId, String pointId) { + if (StringUtils.isAnyBlank(siteId, pointId)) { + return null; + } + String normalizedPointId = pointId.trim(); + String cacheKey = RedisKeyConstants.POINT_CONFIG_POINT + siteId + "_" + normalizedPointId; + EmsPointConfig cached = redisCache.getCacheObject(cacheKey); + if (cached != null) { + return cached; + } + EmsPointConfig query = new EmsPointConfig(); + query.setSiteId(siteId); + query.setPointId(normalizedPointId); + List pointConfigs = emsPointConfigMapper.selectEmsPointConfigList(query); + if (CollectionUtils.isEmpty(pointConfigs)) { + return null; + } + for (EmsPointConfig pointConfig : pointConfigs) { + if (pointConfig != null && StringUtils.equals(pointConfig.getPointId(), normalizedPointId)) { + redisCache.setCacheObject(cacheKey, pointConfig); + return pointConfig; + } + } + 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 PeriodDiffState { + private String period; + private BigDecimal baseline; + private BigDecimal lastValue; + private BigDecimal periodDiffValue; + } + + private static class MissingVariableException extends IllegalArgumentException { + private MissingVariableException(String variable) { + super("缺少变量: " + variable); + } + } + + private enum ExprTokenType { + NUMBER, + STRING, + IDENTIFIER, + OPERATOR, + LEFT_PAREN, + RIGHT_PAREN, + COMMA, + QUESTION, + COLON, + EOF + } + + private static class ExprToken { + private final ExprTokenType type; + private final String text; + private final BigDecimal number; + private final String stringValue; + + private ExprToken(ExprTokenType type, String text, BigDecimal number, String stringValue) { + this.type = type; + this.text = text; + this.number = number; + this.stringValue = stringValue; + } + + private static ExprToken number(BigDecimal value) { + return new ExprToken(ExprTokenType.NUMBER, null, value, null); + } + + private static ExprToken string(String value) { + return new ExprToken(ExprTokenType.STRING, null, null, value); + } + + private static ExprToken identifier(String value) { + return new ExprToken(ExprTokenType.IDENTIFIER, value, null, null); + } + + private static ExprToken operator(String value) { + return new ExprToken(ExprTokenType.OPERATOR, value, null, null); + } + + private static ExprToken symbol(ExprTokenType type, String text) { + return new ExprToken(type, text, null, null); + } + } + + private static class ExpressionValue { + private final BigDecimal numberValue; + private final String textValue; + + private ExpressionValue(BigDecimal numberValue, String textValue) { + this.numberValue = numberValue; + this.textValue = textValue; + } + + private static ExpressionValue ofNumber(BigDecimal numberValue) { + return new ExpressionValue(numberValue, null); + } + + private static ExpressionValue ofText(String textValue) { + return new ExpressionValue(null, textValue == null ? "" : textValue); + } + + private boolean isNumber() { + return numberValue != null; + } + + private BigDecimal asNumber() { + if (numberValue == null) { + throw new IllegalArgumentException("表达式值不是数值类型: " + textValue); + } + return numberValue; + } + + private String asText() { + return numberValue != null ? numberValue.stripTrailingZeros().toPlainString() : textValue; + } + + private boolean asBoolean() { + return isNumber() ? BigDecimal.ZERO.compareTo(numberValue) != 0 : StringUtils.isNotBlank(textValue); + } + } + + private interface ExpressionNode { + ExpressionValue evaluate(Map contextValues); + } + + private static class NumberNode implements ExpressionNode { + private final BigDecimal value; + + private NumberNode(BigDecimal value) { + this.value = value; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + return ExpressionValue.ofNumber(value); + } + } + + private static class StringNode implements ExpressionNode { + private final String value; + + private StringNode(String value) { + this.value = value; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + return ExpressionValue.ofText(value); + } + } + + private static class VariableNode implements ExpressionNode { + private final String name; + + private VariableNode(String name) { + this.name = name; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + BigDecimal value = contextValues.get(name); + if (value == null) { + throw new MissingVariableException(name); + } + return ExpressionValue.ofNumber(value); + } + + private String getName() { + return name; + } + } + + private static class UnaryNode implements ExpressionNode { + private final String operator; + private final ExpressionNode node; + + private UnaryNode(String operator, ExpressionNode node) { + this.operator = operator; + this.node = node; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + ExpressionValue value = node.evaluate(contextValues); + switch (operator) { + case "+": + return value; + case "-": + return ExpressionValue.ofNumber(value.asNumber().negate()); + case "!": + return value.asBoolean() ? ExpressionValue.ofNumber(BigDecimal.ZERO) : ExpressionValue.ofNumber(BigDecimal.ONE); + default: + throw new IllegalArgumentException("不支持的一元操作符: " + operator); + } + } + } + + private static class BinaryNode implements ExpressionNode { + private final String operator; + private final ExpressionNode left; + private final ExpressionNode right; + + private BinaryNode(String operator, ExpressionNode left, ExpressionNode right) { + this.operator = operator; + this.left = left; + this.right = right; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + if ("&&".equals(operator)) { + ExpressionValue leftValue = left.evaluate(contextValues); + if (!leftValue.asBoolean()) { + return ExpressionValue.ofNumber(BigDecimal.ZERO); + } + return right.evaluate(contextValues).asBoolean() + ? ExpressionValue.ofNumber(BigDecimal.ONE) + : ExpressionValue.ofNumber(BigDecimal.ZERO); + } + if ("||".equals(operator)) { + ExpressionValue leftValue = left.evaluate(contextValues); + if (leftValue.asBoolean()) { + return ExpressionValue.ofNumber(BigDecimal.ONE); + } + return right.evaluate(contextValues).asBoolean() + ? ExpressionValue.ofNumber(BigDecimal.ONE) + : ExpressionValue.ofNumber(BigDecimal.ZERO); + } + + ExpressionValue leftValue = left.evaluate(contextValues); + ExpressionValue rightValue = right.evaluate(contextValues); + switch (operator) { + case "+": + if (!leftValue.isNumber() || !rightValue.isNumber()) { + return ExpressionValue.ofText(leftValue.asText() + rightValue.asText()); + } + return ExpressionValue.ofNumber(leftValue.asNumber().add(rightValue.asNumber())); + case "-": + return ExpressionValue.ofNumber(leftValue.asNumber().subtract(rightValue.asNumber())); + case "*": + return ExpressionValue.ofNumber(leftValue.asNumber().multiply(rightValue.asNumber())); + case "/": + if (BigDecimal.ZERO.compareTo(rightValue.asNumber()) == 0) { + throw new IllegalArgumentException("除数不能为0"); + } + return ExpressionValue.ofNumber(leftValue.asNumber().divide(rightValue.asNumber(), 10, RoundingMode.HALF_UP)); + case ">": + return leftValue.asNumber().compareTo(rightValue.asNumber()) > 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case ">=": + return leftValue.asNumber().compareTo(rightValue.asNumber()) >= 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case "<": + return leftValue.asNumber().compareTo(rightValue.asNumber()) < 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case "<=": + return leftValue.asNumber().compareTo(rightValue.asNumber()) <= 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case "==": + if (leftValue.isNumber() && rightValue.isNumber()) { + return leftValue.asNumber().compareTo(rightValue.asNumber()) == 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + } + return Objects.equals(leftValue.asText(), rightValue.asText()) + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + case "!=": + if (leftValue.isNumber() && rightValue.isNumber()) { + return leftValue.asNumber().compareTo(rightValue.asNumber()) != 0 + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + } + return !Objects.equals(leftValue.asText(), rightValue.asText()) + ? ExpressionValue.ofNumber(BigDecimal.ONE) : ExpressionValue.ofNumber(BigDecimal.ZERO); + default: + throw new IllegalArgumentException("不支持的操作符: " + operator); + } + } + } + + private static class TernaryNode implements ExpressionNode { + private final ExpressionNode condition; + private final ExpressionNode trueNode; + private final ExpressionNode falseNode; + + private TernaryNode(ExpressionNode condition, ExpressionNode trueNode, ExpressionNode falseNode) { + this.condition = condition; + this.trueNode = trueNode; + this.falseNode = falseNode; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + return condition.evaluate(contextValues).asBoolean() + ? trueNode.evaluate(contextValues) + : falseNode.evaluate(contextValues); + } + } + + private static class FunctionNode implements ExpressionNode { + private final String functionName; + private final List arguments; + + private FunctionNode(String functionName, List arguments) { + this.functionName = StringUtils.defaultString(functionName).trim().toUpperCase(Locale.ROOT); + this.arguments = arguments == null ? Collections.emptyList() : arguments; + } + + @Override + public ExpressionValue evaluate(Map contextValues) { + if (("DAY_DIFF".equals(functionName) + || "MONTH_DIFF".equals(functionName) + || "HOUR_DIFF".equals(functionName)) && arguments.size() == 1) { + ExpressionNode argument = arguments.get(0); + if (!(argument instanceof VariableNode)) { + throw new IllegalArgumentException(functionName + "单参数模式仅支持变量参数"); + } + String variable = ((VariableNode) argument).getName(); + BigDecimal autoDiffValue = contextValues.get(resolveAutoDiffContextKey(functionName, variable)); + if (autoDiffValue == null) { + throw new MissingVariableException(variable); + } + return ExpressionValue.ofNumber(autoDiffValue); + } + if ("DAY_DIFF".equals(functionName) + || "MONTH_DIFF".equals(functionName) + || "HOUR_DIFF".equals(functionName)) { + throw new IllegalArgumentException(functionName + "函数参数数量错误,需1个参数"); + } + throw new IllegalArgumentException("不支持的函数: " + functionName); + } + } + + private static class CompiledExpression { + private final ExpressionNode root; + + private CompiledExpression(String expression) { + List tokens = tokenizeExpression(expression); + ExpressionParser parser = new ExpressionParser(tokens); + this.root = parser.parseExpression(); + if (!parser.isEnd()) { + ExprToken token = parser.peek(); + throw new IllegalArgumentException("表达式尾部有多余内容: " + token.text); + } + } + + private ExpressionValue evaluate(Map contextValues) { + return root.evaluate(contextValues); + } + } + + private static class ExpressionParser { + private final List tokens; + private int index; + + private ExpressionParser(List tokens) { + this.tokens = tokens; + this.index = 0; + } + + private ExpressionNode parseExpression() { + return parseTernary(); + } + + private ExpressionNode parseTernary() { + ExpressionNode condition = parseOr(); + if (match(ExprTokenType.QUESTION)) { + ExpressionNode trueNode = parseTernary(); + expect(ExprTokenType.COLON, "三元表达式缺少 ':'"); + ExpressionNode falseNode = parseTernary(); + return new TernaryNode(condition, trueNode, falseNode); + } + return condition; + } + + private ExpressionNode parseOr() { + ExpressionNode left = parseAnd(); + while (matchOperator("||")) { + left = new BinaryNode("||", left, parseAnd()); + } + return left; + } + + private ExpressionNode parseAnd() { + ExpressionNode left = parseEquality(); + while (matchOperator("&&")) { + left = new BinaryNode("&&", left, parseEquality()); + } + return left; + } + + private ExpressionNode parseEquality() { + ExpressionNode left = parseComparison(); + while (true) { + if (matchOperator("==")) { + left = new BinaryNode("==", left, parseComparison()); + continue; + } + if (matchOperator("!=")) { + left = new BinaryNode("!=", left, parseComparison()); + continue; + } + break; + } + return left; + } + + private ExpressionNode parseComparison() { + ExpressionNode left = parseAddSub(); + while (true) { + if (matchOperator(">=")) { + left = new BinaryNode(">=", left, parseAddSub()); + continue; + } + if (matchOperator("<=")) { + left = new BinaryNode("<=", left, parseAddSub()); + continue; + } + if (matchOperator(">")) { + left = new BinaryNode(">", left, parseAddSub()); + continue; + } + if (matchOperator("<")) { + left = new BinaryNode("<", left, parseAddSub()); + continue; + } + break; + } + return left; + } + + private ExpressionNode parseAddSub() { + ExpressionNode left = parseMulDiv(); + while (true) { + if (matchOperator("+")) { + left = new BinaryNode("+", left, parseMulDiv()); + continue; + } + if (matchOperator("-")) { + left = new BinaryNode("-", left, parseMulDiv()); + continue; + } + break; + } + return left; + } + + private ExpressionNode parseMulDiv() { + ExpressionNode left = parseUnary(); + while (true) { + if (matchOperator("*")) { + left = new BinaryNode("*", left, parseUnary()); + continue; + } + if (matchOperator("/")) { + left = new BinaryNode("/", left, parseUnary()); + continue; + } + break; + } + return left; + } + + private ExpressionNode parseUnary() { + if (matchOperator("+")) { + return new UnaryNode("+", parseUnary()); + } + if (matchOperator("-")) { + return new UnaryNode("-", parseUnary()); + } + if (matchOperator("!")) { + return new UnaryNode("!", parseUnary()); + } + return parsePrimary(); + } + + private ExpressionNode parsePrimary() { + ExprToken token = peek(); + if (match(ExprTokenType.NUMBER)) { + return new NumberNode(token.number); + } + if (match(ExprTokenType.STRING)) { + return new StringNode(token.stringValue); + } + if (match(ExprTokenType.IDENTIFIER)) { + String identifier = token.text; + if (match(ExprTokenType.LEFT_PAREN)) { + return parseFunction(identifier); + } + return new VariableNode(identifier.toUpperCase(Locale.ROOT)); + } + if (match(ExprTokenType.LEFT_PAREN)) { + ExpressionNode nested = parseExpression(); + expect(ExprTokenType.RIGHT_PAREN, "括号不匹配,缺少右括号"); + return nested; + } + throw new IllegalArgumentException("表达式语法错误,当前位置: " + token.text); + } + + private ExpressionNode parseFunction(String identifier) { + if ("IF".equalsIgnoreCase(identifier)) { + ExpressionNode condition = parseExpression(); + expect(ExprTokenType.COMMA, "IF函数参数格式错误,缺少第1个逗号"); + ExpressionNode trueNode = parseExpression(); + expect(ExprTokenType.COMMA, "IF函数参数格式错误,缺少第2个逗号"); + ExpressionNode falseNode = parseExpression(); + expect(ExprTokenType.RIGHT_PAREN, "IF函数缺少右括号"); + return new TernaryNode(condition, trueNode, falseNode); + } + if ("DAY_DIFF".equalsIgnoreCase(identifier) + || "MONTH_DIFF".equalsIgnoreCase(identifier) + || "HOUR_DIFF".equalsIgnoreCase(identifier)) { + List arguments = parseFunctionArguments(identifier); + if (arguments.size() != 1) { + throw new IllegalArgumentException(identifier.toUpperCase(Locale.ROOT) + "函数参数数量错误,需1个参数"); + } + return new FunctionNode(identifier, arguments); + } + throw new IllegalArgumentException("不支持的函数: " + identifier); + } + + private List parseFunctionArguments(String functionName) { + List arguments = new ArrayList<>(); + if (match(ExprTokenType.RIGHT_PAREN)) { + return arguments; + } + while (true) { + arguments.add(parseExpression()); + if (match(ExprTokenType.COMMA)) { + continue; + } + expect(ExprTokenType.RIGHT_PAREN, functionName + "函数缺少右括号"); + return arguments; + } + } + + private ExprToken peek() { + if (index >= tokens.size()) { + return ExprToken.symbol(ExprTokenType.EOF, ""); + } + return tokens.get(index); + } + + private boolean isEnd() { + return peek().type == ExprTokenType.EOF; + } + + private boolean match(ExprTokenType type) { + if (peek().type != type) { + return false; + } + index++; + return true; + } + + private boolean matchOperator(String operator) { + ExprToken token = peek(); + if (token.type != ExprTokenType.OPERATOR || !operator.equals(token.text)) { + return false; + } + index++; + return true; + } + + private void expect(ExprTokenType type, String message) { + if (!match(type)) { + throw new IllegalArgumentException(message); + } + } + } + + private static List tokenizeExpression(String expression) { + if (StringUtils.isBlank(expression)) { + throw new IllegalArgumentException("计算表达式为空"); + } + List tokens = new ArrayList<>(); + int index = 0; + while (index < expression.length()) { + char ch = expression.charAt(index); + if (Character.isWhitespace(ch)) { + index++; + continue; + } + if (Character.isDigit(ch) || ch == '.') { + int start = index; + boolean hasDot = ch == '.'; + index++; + while (index < expression.length()) { + char next = expression.charAt(index); + if (Character.isDigit(next)) { + index++; + continue; + } + if (next == '.' && !hasDot) { + hasDot = true; + index++; + continue; + } + break; + } + String text = expression.substring(start, index); + try { + tokens.add(ExprToken.number(new BigDecimal(text))); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("数值格式错误: " + text); + } + continue; + } + if (Character.isLetter(ch) || ch == '_') { + int start = index; + index++; + while (index < expression.length()) { + char next = expression.charAt(index); + if (Character.isLetterOrDigit(next) || next == '_') { + index++; + continue; + } + break; + } + String text = expression.substring(start, index).trim().toUpperCase(Locale.ROOT); + tokens.add(ExprToken.identifier(text)); + continue; + } + if (ch == '\'' || ch == '"') { + char quote = ch; + index++; + StringBuilder sb = new StringBuilder(); + boolean escaped = false; + while (index < expression.length()) { + char current = expression.charAt(index++); + if (escaped) { + switch (current) { + case 'n': + sb.append('\n'); + break; + case 't': + sb.append('\t'); + break; + case 'r': + sb.append('\r'); + break; + case '\\': + sb.append('\\'); + break; + case '\'': + sb.append('\''); + break; + case '"': + sb.append('"'); + break; + default: + sb.append(current); + break; + } + escaped = false; + continue; + } + if (current == '\\') { + escaped = true; + continue; + } + if (current == quote) { + break; + } + sb.append(current); + } + if (escaped || index > expression.length() || expression.charAt(index - 1) != quote) { + throw new IllegalArgumentException("字符串字面量未闭合"); + } + tokens.add(ExprToken.string(sb.toString())); + continue; + } + if (ch == '(') { + tokens.add(ExprToken.symbol(ExprTokenType.LEFT_PAREN, "(")); + index++; + continue; + } + if (ch == ')') { + tokens.add(ExprToken.symbol(ExprTokenType.RIGHT_PAREN, ")")); + index++; + continue; + } + if (ch == ',') { + tokens.add(ExprToken.symbol(ExprTokenType.COMMA, ",")); + index++; + continue; + } + if (ch == '?') { + tokens.add(ExprToken.symbol(ExprTokenType.QUESTION, "?")); + index++; + continue; + } + if (ch == ':') { + tokens.add(ExprToken.symbol(ExprTokenType.COLON, ":")); + index++; + continue; + } + if (index + 1 < expression.length()) { + String twoChars = expression.substring(index, index + 2); + if ("&&".equals(twoChars) + || "||".equals(twoChars) + || ">=".equals(twoChars) + || "<=".equals(twoChars) + || "==".equals(twoChars) + || "!=".equals(twoChars)) { + tokens.add(ExprToken.operator(twoChars)); + index += 2; + continue; + } + } + if (ch == '+' || ch == '-' || ch == '*' || ch == '/' + || ch == '>' || ch == '<' || ch == '!') { + tokens.add(ExprToken.operator(String.valueOf(ch))); + index++; + continue; + } + throw new IllegalArgumentException("表达式包含非法字符: " + ch); + } + tokens.add(ExprToken.symbol(ExprTokenType.EOF, "")); + return tokens; + } + + private List parseCsv(MultipartFile file, String siteId) { + List result = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { + String headerLine = reader.readLine(); + if (StringUtils.isBlank(headerLine)) { + throw new ServiceException("CSV表头不能为空"); + } + List headerList = parseCsvLine(removeUtf8Bom(headerLine)); + Map headerIndex = buildHeaderIndex(headerList); + assertRequiredHeader(headerIndex, "point_id", "point_id"); + assertRequiredHeader(headerIndex, "data_key", "data_key"); + assertRequiredHeader(headerIndex, "point_desc", "point_desc"); + + String line; + int lineNo = 1; + while ((line = reader.readLine()) != null) { + lineNo++; + if (StringUtils.isBlank(line)) { + continue; + } + List valueList = parseCsvLine(line); + EmsPointConfig pointConfig = new EmsPointConfig(); + pointConfig.setSiteId(siteId); + pointConfig.setPointId(getRequiredString(valueList, headerIndex, "point_id", lineNo)); + pointConfig.setDeviceCategory(getString(valueList, headerIndex, "device_category")); + pointConfig.setDeviceId(getString(valueList, headerIndex, "device_id")); + pointConfig.setDataKey(getRequiredString(valueList, headerIndex, "data_key", lineNo)); + pointConfig.setPointDesc(getRequiredString(valueList, headerIndex, "point_desc", lineNo)); + String registerAddress = getString(valueList, headerIndex, "register_address"); + pointConfig.setRegisterAddress(StringUtils.isBlank(registerAddress) ? "" : registerAddress); + pointConfig.setPointName(getString(valueList, headerIndex, "point_name")); + pointConfig.setDataUnit(getString(valueList, headerIndex, "data_unit")); + pointConfig.setDataA(getDecimal(valueList, headerIndex, "data_a", lineNo)); + pointConfig.setDataK(getDecimal(valueList, headerIndex, "data_k", lineNo)); + pointConfig.setDataB(getDecimal(valueList, headerIndex, "data_b", lineNo)); + pointConfig.setDataBit(getInteger(valueList, headerIndex, "data_bit", lineNo)); + pointConfig.setIsAlarm(getInteger(valueList, headerIndex, "is_alarm", lineNo)); + pointConfig.setPointType(getString(valueList, headerIndex, "point_type")); + pointConfig.setCalcExpression(getString(valueList, headerIndex, "calc_expression")); + pointConfig.setCollectEnabled(getInteger(valueList, headerIndex, "collect_enabled", lineNo)); + pointConfig.setCollectSource(getString(valueList, headerIndex, "collect_source")); + pointConfig.setModbusRegisterType(getString(valueList, headerIndex, "modbus_register_type")); + pointConfig.setModbusDataType(getString(valueList, headerIndex, "modbus_data_type")); + pointConfig.setModbusReadOrder(getInteger(valueList, headerIndex, "modbus_read_order", lineNo)); + pointConfig.setModbusGroup(getString(valueList, headerIndex, "modbus_group")); + pointConfig.setRemark(getString(valueList, headerIndex, "remark")); + normalizeAndValidatePointConfig(pointConfig); + applyCollectDefaultsForInsert(pointConfig); + result.add(pointConfig); + } + } catch (IOException e) { + throw new ServiceException("CSV读取失败: " + e.getMessage()); + } + return result; + } + + private void assertRequiredHeader(Map headerIndex, String headerKey, String headerName) { + if (!headerIndex.containsKey(headerKey)) { + throw new ServiceException("CSV缺少必填列: " + headerName); + } + } + + private Map buildHeaderIndex(List headerList) { + Map headerIndex = new HashMap<>(); + for (int i = 0; i < headerList.size(); i++) { + String normalizedHeader = toCanonicalHeaderKey(headerList.get(i)); + if (StringUtils.isBlank(normalizedHeader)) { + continue; + } + headerIndex.putIfAbsent(normalizedHeader, i); + } + return headerIndex; + } + + private String getRequiredString(List valueList, Map headerIndex, String key, int lineNo) { + String value = getString(valueList, headerIndex, key); + if (StringUtils.isBlank(value)) { + throw new ServiceException(String.format("CSV第%d行字段[%s]不能为空", lineNo, key)); + } + return value; + } + + private String getString(List valueList, Map headerIndex, String key) { + Integer index = headerIndex.get(key); + if (index == null || index < 0 || index >= valueList.size()) { + return null; + } + String value = valueList.get(index); + return StringUtils.isBlank(value) ? null : value.trim(); + } + + private Integer getInteger(List valueList, Map headerIndex, String key, int lineNo) { + String value = getString(valueList, headerIndex, key); + if (StringUtils.isBlank(value)) { + return null; + } + if (!INTEGER_PATTERN.matcher(value).matches()) { + throw new ServiceException(String.format("CSV第%d行字段[%s]必须是整数", lineNo, key)); + } + return Integer.parseInt(value); + } + + private BigDecimal getDecimal(List valueList, Map headerIndex, String key, int lineNo) { + String value = getString(valueList, headerIndex, key); + if (StringUtils.isBlank(value)) { + return null; + } + if (!DECIMAL_PATTERN.matcher(value).matches()) { + throw new ServiceException(String.format("CSV第%d行字段[%s]必须是数字", lineNo, key)); + } + return new BigDecimal(value); + } + + private String removeUtf8Bom(String content) { + if (StringUtils.isBlank(content)) { + return content; + } + if (content.charAt(0) == '\ufeff') { + return content.substring(1); + } + return content; + } + + private String normalizeHeader(String header) { + if (header == null) { + return ""; + } + return header.trim().toLowerCase().replace("-", "_").replace(" ", ""); + } + + private String toCanonicalHeaderKey(String header) { + String normalized = normalizeHeader(header); + if (StringUtils.isBlank(normalized)) { + return ""; + } + String compact = normalized.replace("_", ""); + switch (compact) { + case "siteid": + case "站点id": + return "site_id"; + case "pointid": + case "点位id": + return "point_id"; + case "devicecategory": + case "设备类型": + return "device_category"; + case "deviceid": + case "设备id": + return "device_id"; + case "pointname": + case "点位名称": + return "point_name"; + case "datakey": + case "数据键": + return "data_key"; + case "pointdesc": + case "点位描述": + return "point_desc"; + case "registeraddress": + case "寄存器地址": + return "register_address"; + case "dataunit": + case "datounit": + case "单位": + return "data_unit"; + case "dataa": + case "datoa": + case "a系数": + return "data_a"; + case "datak": + case "datok": + case "k系数": + return "data_k"; + case "datab": + case "datob": + case "b系数": + return "data_b"; + case "databit": + case "datobit": + case "位偏移": + return "data_bit"; + case "isalarm": + case "报警点位": + return "is_alarm"; + case "pointtype": + case "点位类型": + return "point_type"; + case "calcexpression": + case "计算表达式": + return "calc_expression"; + case "collectenabled": + case "是否启用采集": + return "collect_enabled"; + case "collectsource": + case "采集来源": + return "collect_source"; + case "modbusregistertype": + case "modbus寄存器类型": + return "modbus_register_type"; + case "modbusdatatype": + case "modbus数据类型": + return "modbus_data_type"; + case "modbusreadorder": + case "modbus读取顺序": + return "modbus_read_order"; + case "modbusgroup": + case "modbus分组": + return "modbus_group"; + case "remark": + case "备注": + return "remark"; + default: + return normalized; + } + } + + private List parseCsvLine(String line) { + List result = new ArrayList<>(); + if (line == null) { + return result; + } + StringBuilder current = new StringBuilder(); + boolean inQuotes = false; + for (int i = 0; i < line.length(); i++) { + char c = line.charAt(i); + if (c == '"') { + if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') { + current.append('"'); + i++; + } else { + inQuotes = !inQuotes; + } + continue; + } + if (c == ',' && !inQuotes) { + result.add(current.toString()); + current.setLength(0); + continue; + } + current.append(c); + } + result.add(current.toString()); + return result; + } + + private void normalizeAndValidatePointConfig(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return; + } + pointConfig.setPointId(StringUtils.trimToNull(pointConfig.getPointId())); + if (StringUtils.isBlank(pointConfig.getPointId())) { + throw new ServiceException("点位ID不能为空"); + } + pointConfig.setDeviceCategory(StringUtils.trimToNull(pointConfig.getDeviceCategory())); + pointConfig.setDeviceId(StringUtils.trimToNull(pointConfig.getDeviceId())); + pointConfig.setPointName(StringUtils.trimToNull(pointConfig.getPointName())); + pointConfig.setDataKey(StringUtils.trimToNull(pointConfig.getDataKey())); + pointConfig.setPointDesc(StringUtils.trimToNull(pointConfig.getPointDesc())); + pointConfig.setPointType(normalizePointType(pointConfig.getPointType())); + pointConfig.setCalcExpression(StringUtils.trimToNull(pointConfig.getCalcExpression())); + if ("calc".equals(pointConfig.getPointType())) { + if (StringUtils.isBlank(pointConfig.getCalcExpression())) { + throw new ServiceException("计算点必须填写计算表达式"); + } + if (!CALC_EXPRESSION_PATTERN.matcher(pointConfig.getCalcExpression()).matches()) { + throw new ServiceException("计算表达式仅支持数字、字母、下划线、空格、运算符和函数语法"); + } + } else { + pointConfig.setCalcExpression(null); + } + } + + private void applyCollectDefaultsForInsert(EmsPointConfig pointConfig) { + if (pointConfig == null) { + return; + } + if (pointConfig.getCollectEnabled() == null) { + pointConfig.setCollectEnabled(0); + } + if (StringUtils.isBlank(pointConfig.getCollectSource())) { + pointConfig.setCollectSource("MQTT"); + } + if (pointConfig.getModbusReadOrder() == null) { + pointConfig.setModbusReadOrder(0); + } + } + + private String normalizePointType(String pointType) { + String normalized = StringUtils.trimToEmpty(pointType).toLowerCase(Locale.ROOT); + if ("calc".equals(normalized) || "calculate".equals(normalized) || "计算点".equals(normalized)) { + return "calc"; + } + return "data"; + } + +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointMatchServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointMatchServiceImpl.java index 22231a3..6e712a3 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointMatchServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsPointMatchServiceImpl.java @@ -12,6 +12,7 @@ import com.xzzn.ems.domain.EmsPointMatch; import com.xzzn.ems.domain.vo.DevicePointMatchExportVo; import com.xzzn.ems.domain.vo.DevicePointMatchVo; import com.xzzn.ems.domain.vo.ImportPointDataRequest; +import com.xzzn.ems.domain.vo.ImportPointTemplateRequest; import com.xzzn.ems.enums.DeviceMatchTable; import com.xzzn.ems.mapper.EmsPointEnumMatchMapper; import com.xzzn.ems.mapper.EmsPointMatchMapper; @@ -20,8 +21,10 @@ import com.xzzn.ems.utils.DevicePointMatchDataProcessor; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import javax.validation.Validator; @@ -213,6 +216,99 @@ public class EmsPointMatchServiceImpl implements IEmsPointMatchService { return errorList; } + /** + * 按站点导入模板点位 + * + * @param request + * @param operName + * @return + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String importTemplateBySite(ImportPointTemplateRequest request, String operName) { + String targetSiteId = request.getSiteId(); + if (SITE_ID.equals(targetSiteId)) { + throw new ServiceException("模板站点不支持作为导入目标站点"); + } + + int templatePointCount = emsPointMatchMapper.countBySiteId(SITE_ID); + if (templatePointCount <= 0) { + throw new ServiceException("模板点位数据不存在,无法导入"); + } + + boolean overwrite = Boolean.TRUE.equals(request.getOverwrite()); + int targetPointCount = emsPointMatchMapper.countBySiteId(targetSiteId); + if (targetPointCount > 0 && !overwrite) { + throw new ServiceException("目标站点已存在点位数据,请勾选“覆盖已存在数据”后重试"); + } + + if (targetPointCount > 0) { + emsPointMatchMapper.deleteBySiteId(targetSiteId); + } + + int targetEnumCount = emsPointEnumMatchMapper.countBySiteId(targetSiteId); + if (targetEnumCount > 0) { + emsPointEnumMatchMapper.deleteBySiteId(targetSiteId); + } + + int importPointCount = emsPointMatchMapper.copyTemplateToSite(SITE_ID, targetSiteId, operName); + int importEnumCount = 0; + if (emsPointEnumMatchMapper.countBySiteId(SITE_ID) > 0) { + importEnumCount = emsPointEnumMatchMapper.copyTemplateToSite(SITE_ID, targetSiteId, operName); + } + + syncSiteToRedis(targetSiteId); + return String.format("导入成功:站点 %s,点位 %d 条,枚举 %d 条", targetSiteId, importPointCount, importEnumCount); + } + + @Override + public List selectPointMatchConfigList(EmsPointMatch emsPointMatch) { + return emsPointMatchMapper.selectEmsPointMatchList(emsPointMatch); + } + + @Override + public EmsPointMatch selectPointMatchById(Long id) { + return emsPointMatchMapper.selectEmsPointMatchById(id); + } + + @Override + public int insertPointMatch(EmsPointMatch emsPointMatch) { + int rows = emsPointMatchMapper.insertEmsPointMatch(emsPointMatch); + if (rows > 0 && StringUtils.isNotBlank(emsPointMatch.getSiteId()) && StringUtils.isNotBlank(emsPointMatch.getDeviceCategory())) { + syncToRedis(emsPointMatch.getSiteId(), emsPointMatch.getDeviceId(), emsPointMatch.getDeviceCategory()); + } + return rows; + } + + @Override + public int updatePointMatch(EmsPointMatch emsPointMatch) { + int rows = emsPointMatchMapper.updateEmsPointMatch(emsPointMatch); + if (rows > 0 && StringUtils.isNotBlank(emsPointMatch.getSiteId()) && StringUtils.isNotBlank(emsPointMatch.getDeviceCategory())) { + syncToRedis(emsPointMatch.getSiteId(), emsPointMatch.getDeviceId(), emsPointMatch.getDeviceCategory()); + } + return rows; + } + + @Override + public int deletePointMatchByIds(Long[] ids) { + List deletedList = new ArrayList<>(); + for (Long id : ids) { + EmsPointMatch pointMatch = emsPointMatchMapper.selectEmsPointMatchById(id); + if (pointMatch != null) { + deletedList.add(pointMatch); + } + } + int rows = emsPointMatchMapper.deleteEmsPointMatchByIds(ids); + if (rows > 0) { + deletedList.forEach(pointMatch -> { + if (StringUtils.isNotBlank(pointMatch.getSiteId()) && StringUtils.isNotBlank(pointMatch.getDeviceCategory())) { + syncToRedis(pointMatch.getSiteId(), pointMatch.getDeviceId(), pointMatch.getDeviceCategory()); + } + }); + } + return rows; + } + private boolean validDevicePointMatch(DevicePointMatchVo pointMatch, List errorList) { StringBuilder errorMsg = new StringBuilder(); if (StringUtils.isBlank(pointMatch.getMatchField())) { @@ -238,6 +334,9 @@ public class EmsPointMatchServiceImpl implements IEmsPointMatchService { List pointMatchData = emsPointMatchMapper.getDevicePointMatchList(siteId, deviceId, deviceCategory); // log.info("同步点位匹配数据到Redis key:{} data:{}", pointMatchKey, pointMatchData); if (CollectionUtils.isEmpty(pointMatchData)) { + if (redisCache.hasKey(pointMatchKey)) { + redisCache.deleteObject(pointMatchKey); + } return; } if (redisCache.hasKey(pointMatchKey)) { @@ -247,6 +346,10 @@ public class EmsPointMatchServiceImpl implements IEmsPointMatchService { log.info("点位匹配数据同步完成 data:{}", JSON.toJSONString(redisCache.getCacheList(pointMatchKey))); // 点位枚举匹配数据同步到Redis + syncPointEnumToRedis(siteId, deviceCategory); + } + + private void syncPointEnumToRedis(String siteId, String deviceCategory) { String pointEnumMatchKey = DevicePointMatchDataProcessor.getPointEnumMacthCacheKey(siteId, deviceCategory); List pointEnumMatchList = emsPointEnumMatchMapper.selectList(siteId, deviceCategory, null); if (!CollectionUtils.isEmpty(pointEnumMatchList)) { @@ -256,7 +359,22 @@ public class EmsPointMatchServiceImpl implements IEmsPointMatchService { redisCache.setCacheList(pointEnumMatchKey, pointEnumMatchList); log.info("点位枚举匹配数据同步完成 data:{}", JSON.toJSONString(redisCache.getCacheList(pointEnumMatchKey))); } + } + private void syncSiteToRedis(String siteId) { + List sitePointList = emsPointMatchMapper.selectBySiteId(siteId); + if (CollectionUtils.isEmpty(sitePointList)) { + return; + } + Set categorySet = new HashSet<>(); + for (EmsPointMatch pointMatch : sitePointList) { + categorySet.add(pointMatch.getDeviceCategory()); + if (StringUtils.isBlank(pointMatch.getDeviceId())) { + continue; + } + syncToRedis(siteId, pointMatch.getDeviceId(), pointMatch.getDeviceCategory()); + } + categorySet.forEach(category -> syncPointEnumToRedis(siteId, category)); } private void savePointMatchEnum(String matchFieldEnum, String dataEnum, EmsPointMatch savePoint) { diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsSiteServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsSiteServiceImpl.java index 1abb914..e391e8c 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsSiteServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsSiteServiceImpl.java @@ -2,7 +2,10 @@ package com.xzzn.ems.service.impl; import com.xzzn.common.constant.RedisKeyConstants; import com.xzzn.common.core.redis.RedisCache; +import com.xzzn.common.exception.ServiceException; import com.xzzn.common.enums.DeviceCategory; +import com.xzzn.common.utils.DateUtils; +import com.xzzn.common.utils.StringUtils; import com.xzzn.ems.domain.EmsBatteryData; import com.xzzn.ems.domain.EmsSiteSetting; import com.xzzn.ems.domain.vo.SiteDeviceListVo; @@ -12,9 +15,11 @@ import com.xzzn.ems.mapper.EmsSiteSettingMapper; import com.xzzn.ems.service.IEmsSiteService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * 站点信息 服务层实现 @@ -23,6 +28,7 @@ import java.util.Map; @Service public class EmsSiteServiceImpl implements IEmsSiteService { + private static final Pattern SITE_ID_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); @Autowired private EmsSiteSettingMapper emsSiteMapper; @@ -70,7 +76,14 @@ public class EmsSiteServiceImpl implements IEmsSiteService */ @Override public List getAllSiteInfoList(String siteName, String startTime, String endTime) { - return emsSiteMapper.getSiteInfoList(siteName,startTime,endTime); + List list = emsSiteMapper.getSiteInfoList(siteName,startTime,endTime); + if (list == null || list.isEmpty()) { + return list; + } + for (EmsSiteSetting site : list) { + site.setAuthorized(Boolean.TRUE); + } + return list; } /** @@ -145,4 +158,53 @@ public class EmsSiteServiceImpl implements IEmsSiteService List> deviceIdList = emsDevicesMapper.getDeviceInfosBySiteIdAndCategory(siteId, parentCategory); return deviceIdList; } + + @Override + @Transactional(rollbackFor = Exception.class) + public int addSite(EmsSiteSetting emsSiteSetting) { + validateSiteSetting(emsSiteSetting, true); + String siteId = emsSiteSetting.getSiteId(); + EmsSiteSetting existing = emsSiteMapper.selectEmsSiteSettingBySiteId(siteId); + if (existing != null) { + throw new ServiceException("站点ID已存在,请更换后重试"); + } + emsSiteSetting.setCreateTime(DateUtils.getNowDate()); + return emsSiteMapper.insertEmsSiteSetting(emsSiteSetting); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int updateSite(EmsSiteSetting emsSiteSetting) { + if (emsSiteSetting == null || emsSiteSetting.getId() == null) { + throw new ServiceException("站点主键不能为空"); + } + validateSiteSetting(emsSiteSetting, false); + EmsSiteSetting dbData = emsSiteMapper.selectEmsSiteSettingById(emsSiteSetting.getId()); + if (dbData == null) { + throw new ServiceException("站点不存在"); + } + if (StringUtils.isNotEmpty(emsSiteSetting.getSiteId()) + && !StringUtils.equals(emsSiteSetting.getSiteId(), dbData.getSiteId())) { + throw new ServiceException("不支持修改站点ID"); + } + emsSiteSetting.setSiteId(dbData.getSiteId()); + emsSiteSetting.setUpdateTime(DateUtils.getNowDate()); + return emsSiteMapper.updateEmsSiteSetting(emsSiteSetting); + } + + private void validateSiteSetting(EmsSiteSetting emsSiteSetting, boolean requireSiteId) { + if (emsSiteSetting == null) { + throw new ServiceException("参数不能为空"); + } + if (StringUtils.isBlank(emsSiteSetting.getSiteName())) { + throw new ServiceException("站点名称不能为空"); + } + if (requireSiteId && StringUtils.isBlank(emsSiteSetting.getSiteId())) { + throw new ServiceException("站点ID不能为空"); + } + if (StringUtils.isNotBlank(emsSiteSetting.getSiteId()) && !SITE_ID_PATTERN.matcher(emsSiteSetting.getSiteId()).matches()) { + throw new ServiceException("站点ID仅支持字母、数字、下划线"); + } + } + } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStatsReportServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStatsReportServiceImpl.java index 6efa8ca..d730df3 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStatsReportServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStatsReportServiceImpl.java @@ -379,20 +379,85 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService @Override public List getAmmeterRevenueDataResult(StatisAmmeterDateRequest requestVo) { - List resultList = emsDailyEnergyDataMapper.getRevenueDataBySiteId(requestVo.getSiteId(),requestVo.getStartTime(),requestVo.getEndTime()); + log.info("收益报表查询开始, siteId: {}, startTime: {}, endTime: {}", + requestVo.getSiteId(), requestVo.getStartTime(), requestVo.getEndTime()); + List resultList = emsDailyEnergyDataMapper.getRevenueDataBySiteId( + requestVo.getSiteId(), requestVo.getStartTime(), requestVo.getEndTime()); if (CollectionUtils.isEmpty(resultList)) { + log.warn("收益报表查询结果为空, siteId: {}, startTime: {}, endTime: {}", + requestVo.getSiteId(), requestVo.getStartTime(), requestVo.getEndTime()); return Collections.emptyList(); } //计算每天总收益和当日实际收益(放电总-充电总) resultList.forEach(ammeterRevenue -> { - ammeterRevenue.setActiveTotalPrice(ammeterRevenue.getActivePeakPrice().add(ammeterRevenue.getActiveHighPrice()).add(ammeterRevenue.getActiveFlatPrice()).add(ammeterRevenue.getActiveValleyPrice())); - ammeterRevenue.setReActiveTotalPrice(ammeterRevenue.getReActivePeakPrice().add(ammeterRevenue.getReActiveHighPrice()).add(ammeterRevenue.getReActiveFlatPrice()).add(ammeterRevenue.getReActiveValleyPrice())); + BigDecimal activePeakPrice = nz(ammeterRevenue.getActivePeakPrice()); + BigDecimal activeHighPrice = nz(ammeterRevenue.getActiveHighPrice()); + BigDecimal activeFlatPrice = nz(ammeterRevenue.getActiveFlatPrice()); + BigDecimal activeValleyPrice = nz(ammeterRevenue.getActiveValleyPrice()); + BigDecimal reActivePeakPrice = nz(ammeterRevenue.getReActivePeakPrice()); + BigDecimal reActiveHighPrice = nz(ammeterRevenue.getReActiveHighPrice()); + BigDecimal reActiveFlatPrice = nz(ammeterRevenue.getReActiveFlatPrice()); + BigDecimal reActiveValleyPrice = nz(ammeterRevenue.getReActiveValleyPrice()); + + ammeterRevenue.setActiveTotalPrice(activePeakPrice.add(activeHighPrice).add(activeFlatPrice).add(activeValleyPrice)); + ammeterRevenue.setReActiveTotalPrice(reActivePeakPrice.add(reActiveHighPrice).add(reActiveFlatPrice).add(reActiveValleyPrice)); + // 实际收益按“放电总价-充电总价”口径计算 ammeterRevenue.setActualRevenue(ammeterRevenue.getReActiveTotalPrice().subtract(ammeterRevenue.getActiveTotalPrice())); }); + int weatherMissingCount = 0; + int allPriceZeroCount = 0; + for (AmmeterRevenueStatisListVo row : resultList) { + String weatherDesc = row.getWeatherDesc(); + if (weatherDesc == null || weatherDesc.trim().isEmpty() || "--".equals(weatherDesc.trim())) { + weatherMissingCount++; + } + boolean allPriceZero = safeCompareToZero(row.getActivePeakPrice()) + && safeCompareToZero(row.getActiveHighPrice()) + && safeCompareToZero(row.getActiveFlatPrice()) + && safeCompareToZero(row.getActiveValleyPrice()) + && safeCompareToZero(row.getReActivePeakPrice()) + && safeCompareToZero(row.getReActiveHighPrice()) + && safeCompareToZero(row.getReActiveFlatPrice()) + && safeCompareToZero(row.getReActiveValleyPrice()); + if (allPriceZero) { + allPriceZeroCount++; + } + log.info("收益报表明细, date: {}, dayType: {}, weather: {}, charge[尖:{} 峰:{} 平:{} 谷:{} 总:{}], discharge[尖:{} 峰:{} 平:{} 谷:{} 总:{}], actualRevenue: {}", + row.getDataTime(), + row.getDayType(), + row.getWeatherDesc(), + row.getActivePeakPrice(), + row.getActiveHighPrice(), + row.getActiveFlatPrice(), + row.getActiveValleyPrice(), + row.getActiveTotalPrice(), + row.getReActivePeakPrice(), + row.getReActiveHighPrice(), + row.getReActiveFlatPrice(), + row.getReActiveValleyPrice(), + row.getReActiveTotalPrice(), + row.getActualRevenue()); + } + log.info("收益报表查询完成, siteId: {}, startTime: {}, endTime: {}, totalRows: {}, weatherMissingRows: {}, allPriceZeroRows: {}", + requestVo.getSiteId(), + requestVo.getStartTime(), + requestVo.getEndTime(), + resultList.size(), + weatherMissingCount, + allPriceZeroCount); + return resultList; } + private boolean safeCompareToZero(BigDecimal value) { + return value == null || value.compareTo(BigDecimal.ZERO) == 0; + } + + private BigDecimal nz(BigDecimal value) { + return value == null ? BigDecimal.ZERO : value; + } + public static AmmeterRevenueStatisListVo calculateDailyBill(AmmeterStatisListVo ammeter, List priceList) { AmmeterRevenueStatisListVo ammeterRevenue = new AmmeterRevenueStatisListVo(); ammeterRevenue.setDataTime(ammeter.getDataTime()); @@ -798,19 +863,19 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService Row row1 = sheet.createRow(0); Cell cell1 = row1.createCell(0); cell1.setCellValue("汇总"); - Cell cell2 = row1.createCell(1); + Cell cell2 = row1.createCell(3); cell2.setCellValue("充电价格"); - Cell cell3 = row1.createCell(6); + Cell cell3 = row1.createCell(8); cell3.setCellValue("放电价格"); - Cell cell4 = row1.createCell(11); + Cell cell4 = row1.createCell(13); cell4.setCellValue(""); // 合并充电量列 - CellRangeAddress mergeRegion1 = new CellRangeAddress(0, 0, 1, 5); + CellRangeAddress mergeRegion1 = new CellRangeAddress(0, 0, 3, 7); sheet.addMergedRegion(mergeRegion1); // 合并放电量列 - CellRangeAddress mergeRegion2 = new CellRangeAddress(0, 0, 6, 10); + CellRangeAddress mergeRegion2 = new CellRangeAddress(0, 0, 8, 12); sheet.addMergedRegion(mergeRegion2); // 设置第二行 @@ -818,27 +883,31 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService Cell cell5 = row2.createCell(0); cell5.setCellValue("日期"); Cell cell6 = row2.createCell(1); - cell6.setCellValue("尖"); + cell6.setCellValue("日期类型"); Cell cell7 = row2.createCell(2); - cell7.setCellValue("峰"); + cell7.setCellValue("天气情况"); Cell cell8 = row2.createCell(3); - cell8.setCellValue("平"); + cell8.setCellValue("尖"); Cell cell9 = row2.createCell(4); - cell9.setCellValue("谷"); + cell9.setCellValue("峰"); Cell cell10 = row2.createCell(5); - cell10.setCellValue("总"); + cell10.setCellValue("平"); Cell cell11 = row2.createCell(6); - cell11.setCellValue("尖"); + cell11.setCellValue("谷"); Cell cell12 = row2.createCell(7); - cell12.setCellValue("峰"); + cell12.setCellValue("总"); Cell cell13 = row2.createCell(8); - cell13.setCellValue("平"); + cell13.setCellValue("尖"); Cell cell14 = row2.createCell(9); - cell14.setCellValue("谷"); + cell14.setCellValue("峰"); Cell cell15 = row2.createCell(10); - cell15.setCellValue("总"); + cell15.setCellValue("平"); Cell cell16 = row2.createCell(11); - cell16.setCellValue("实际收益"); + cell16.setCellValue("谷"); + Cell cell17 = row2.createCell(12); + cell17.setCellValue("总"); + Cell cell18 = row2.createCell(13); + cell18.setCellValue("实际收益"); // 设置背景颜色 CellStyle headerStyle = workbook.createCellStyle(); @@ -908,27 +977,31 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService Cell dataCell1 = dataRow.createCell(0); dataCell1.setCellValue(ammeterRevenueStatisVo.getDataTime()); Cell dataCell2 = dataRow.createCell(1); - dataCell2.setCellValue(ammeterRevenueStatisVo.getActivePeakPrice().doubleValue()); + dataCell2.setCellValue(ammeterRevenueStatisVo.getDayType() == null ? "" : ammeterRevenueStatisVo.getDayType()); Cell dataCell3 = dataRow.createCell(2); - dataCell3.setCellValue(ammeterRevenueStatisVo.getActiveHighPrice().doubleValue()); + dataCell3.setCellValue(ammeterRevenueStatisVo.getWeatherDesc() == null ? "" : ammeterRevenueStatisVo.getWeatherDesc()); Cell dataCell4 = dataRow.createCell(3); - dataCell4.setCellValue(ammeterRevenueStatisVo.getActiveFlatPrice().doubleValue()); + dataCell4.setCellValue(ammeterRevenueStatisVo.getActivePeakPrice().doubleValue()); Cell dataCell5 = dataRow.createCell(4); - dataCell5.setCellValue(ammeterRevenueStatisVo.getActiveValleyPrice().doubleValue()); + dataCell5.setCellValue(ammeterRevenueStatisVo.getActiveHighPrice().doubleValue()); Cell dataCell6 = dataRow.createCell(5); - dataCell6.setCellValue(ammeterRevenueStatisVo.getActiveTotalPrice().doubleValue()); + dataCell6.setCellValue(ammeterRevenueStatisVo.getActiveFlatPrice().doubleValue()); Cell dataCell7 = dataRow.createCell(6); - dataCell7.setCellValue(ammeterRevenueStatisVo.getReActivePeakPrice().doubleValue()); + dataCell7.setCellValue(ammeterRevenueStatisVo.getActiveValleyPrice().doubleValue()); Cell dataCell8 = dataRow.createCell(7); - dataCell8.setCellValue(ammeterRevenueStatisVo.getReActiveHighPrice().doubleValue()); + dataCell8.setCellValue(ammeterRevenueStatisVo.getActiveTotalPrice().doubleValue()); Cell dataCell9 = dataRow.createCell(8); - dataCell9.setCellValue(ammeterRevenueStatisVo.getReActiveFlatPrice().doubleValue()); + dataCell9.setCellValue(ammeterRevenueStatisVo.getReActivePeakPrice().doubleValue()); Cell dataCell10 = dataRow.createCell(9); - dataCell10.setCellValue(ammeterRevenueStatisVo.getReActiveValleyPrice().doubleValue()); + dataCell10.setCellValue(ammeterRevenueStatisVo.getReActiveHighPrice().doubleValue()); Cell dataCell11 = dataRow.createCell(10); - dataCell11.setCellValue(ammeterRevenueStatisVo.getReActiveValleyPrice().doubleValue()); + dataCell11.setCellValue(ammeterRevenueStatisVo.getReActiveFlatPrice().doubleValue()); Cell dataCell12 = dataRow.createCell(11); - dataCell12.setCellValue(ammeterRevenueStatisVo.getActualRevenue().doubleValue()); + dataCell12.setCellValue(ammeterRevenueStatisVo.getReActiveValleyPrice().doubleValue()); + Cell dataCell13 = dataRow.createCell(12); + dataCell13.setCellValue(ammeterRevenueStatisVo.getReActiveTotalPrice().doubleValue()); + Cell dataCell14 = dataRow.createCell(13); + dataCell14.setCellValue(ammeterRevenueStatisVo.getActualRevenue().doubleValue()); // 根据行号设置背景色 if (i % 2 == 0) { @@ -961,27 +1034,31 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService Cell lastRowCell1 = lastRow.createCell(0); lastRowCell1.setCellValue("合计"); Cell lastRowCell2 = lastRow.createCell(1); - lastRowCell2.setCellValue(activePeakPrice.doubleValue()); + lastRowCell2.setCellValue("-"); Cell lastRowCell3 = lastRow.createCell(2); - lastRowCell3.setCellValue(activeHighPrice.doubleValue()); + lastRowCell3.setCellValue("-"); Cell lastRowCell4 = lastRow.createCell(3); - lastRowCell4.setCellValue(activeFlatPrice.doubleValue()); + lastRowCell4.setCellValue(activePeakPrice.doubleValue()); Cell lastRowCell5 = lastRow.createCell(4); - lastRowCell5.setCellValue(activeValleyPrice.doubleValue()); + lastRowCell5.setCellValue(activeHighPrice.doubleValue()); Cell lastRowCell6 = lastRow.createCell(5); - lastRowCell6.setCellValue(activeTotalPrice.doubleValue()); + lastRowCell6.setCellValue(activeFlatPrice.doubleValue()); Cell lastRowCell7 = lastRow.createCell(6); - lastRowCell7.setCellValue(reActivePeakPrice.doubleValue()); + lastRowCell7.setCellValue(activeValleyPrice.doubleValue()); Cell lastRowCell8 = lastRow.createCell(7); - lastRowCell8.setCellValue(reActiveHighPrice.doubleValue()); + lastRowCell8.setCellValue(activeTotalPrice.doubleValue()); Cell lastRowCell9 = lastRow.createCell(8); - lastRowCell9.setCellValue(reActiveFlatPrice.doubleValue()); + lastRowCell9.setCellValue(reActivePeakPrice.doubleValue()); Cell lastRowCell10 = lastRow.createCell(9); - lastRowCell10.setCellValue(reActiveValleyPrice.doubleValue()); + lastRowCell10.setCellValue(reActiveHighPrice.doubleValue()); Cell lastRowCell11 = lastRow.createCell(10); - lastRowCell11.setCellValue(reActiveTotalPrice.doubleValue()); + lastRowCell11.setCellValue(reActiveFlatPrice.doubleValue()); Cell lastRowCell12 = lastRow.createCell(11); - lastRowCell12.setCellValue(actualRevenue.doubleValue()); + lastRowCell12.setCellValue(reActiveValleyPrice.doubleValue()); + Cell lastRowCell13 = lastRow.createCell(12); + lastRowCell13.setCellValue(reActiveTotalPrice.doubleValue()); + Cell lastRowCell14 = lastRow.createCell(13); + lastRowCell14.setCellValue(actualRevenue.doubleValue()); Iterator lastRowCellIterator = lastRow.cellIterator(); while (lastRowCellIterator.hasNext()) { int i = lastRowCellIterator.next().getColumnIndex(); diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyRuntimeConfigServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyRuntimeConfigServiceImpl.java new file mode 100644 index 0000000..280e7be --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyRuntimeConfigServiceImpl.java @@ -0,0 +1,111 @@ +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"); + private static final BigDecimal DEFAULT_POWER_SET_MULTIPLIER = new BigDecimal("10"); + private static final Integer DEFAULT_PROTECT_INTERVENE_ENABLE = 1; + private static final BigDecimal DEFAULT_PROTECT_L1_DERATE_PERCENT = new BigDecimal("50"); + private static final Integer DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS = 5; + private static final Integer DEFAULT_PROTECT_L3_LATCH_ENABLE = 1; + private static final String DEFAULT_PROTECT_CONFLICT_POLICY = "MAX_LEVEL_WIN"; + + @Autowired + 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); + } + if (config.getPowerSetMultiplier() == null || config.getPowerSetMultiplier().compareTo(BigDecimal.ZERO) <= 0) { + config.setPowerSetMultiplier(DEFAULT_POWER_SET_MULTIPLIER); + } + if (config.getProtectInterveneEnable() == null) { + config.setProtectInterveneEnable(DEFAULT_PROTECT_INTERVENE_ENABLE); + } + if (config.getProtectL1DeratePercent() == null || config.getProtectL1DeratePercent().compareTo(BigDecimal.ZERO) < 0) { + config.setProtectL1DeratePercent(DEFAULT_PROTECT_L1_DERATE_PERCENT); + } + if (config.getProtectRecoveryStableSeconds() == null || config.getProtectRecoveryStableSeconds() < 0) { + config.setProtectRecoveryStableSeconds(DEFAULT_PROTECT_RECOVERY_STABLE_SECONDS); + } + if (config.getProtectL3LatchEnable() == null) { + config.setProtectL3LatchEnable(DEFAULT_PROTECT_L3_LATCH_ENABLE); + } + if (StringUtils.isEmpty(config.getProtectConflictPolicy())) { + config.setProtectConflictPolicy(DEFAULT_PROTECT_CONFLICT_POLICY); + } + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyTempServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyTempServiceImpl.java index 640cc91..c9b5bac 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyTempServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsStrategyTempServiceImpl.java @@ -92,6 +92,9 @@ public class EmsStrategyTempServiceImpl implements IEmsStrategyTempService temp.setStartTime(timeConfig.getStartTime()); temp.setEndTime(timeConfig.getEndTime()); temp.setChargeDischargePower(timeConfig.getChargeDischargePower()); + // 每个时间段可独立配置SOC上下限;为空时兼容旧的模板级配置 + temp.setSdcDown(timeConfig.getSdcDown() != null ? timeConfig.getSdcDown() : publicTemp.getSdcDown()); + temp.setSdcUp(timeConfig.getSdcUp() != null ? timeConfig.getSdcUp() : publicTemp.getSdcUp()); temp.setChargeStatus(timeConfig.getChargeStatus()); emsStrategyTempMapper.insertEmsStrategyTemp(temp); } diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsWeatherSyncServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsWeatherSyncServiceImpl.java new file mode 100644 index 0000000..da4ff65 --- /dev/null +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/EmsWeatherSyncServiceImpl.java @@ -0,0 +1,199 @@ +package com.xzzn.ems.service.impl; + +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.xzzn.common.exception.ServiceException; +import com.xzzn.common.utils.StringUtils; +import com.xzzn.common.utils.http.HttpUtils; +import com.xzzn.ems.config.WeatherApiProperties; +import com.xzzn.ems.domain.EmsSiteSetting; +import com.xzzn.ems.domain.vo.StatisAmmeterDateRequest; +import com.xzzn.ems.domain.vo.WeatherSyncResultVo; +import com.xzzn.ems.mapper.EmsSiteSettingMapper; +import com.xzzn.ems.mapper.EmsSiteWeatherDayMapper; +import com.xzzn.ems.service.IEmsWeatherSyncService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +@Service +public class EmsWeatherSyncServiceImpl implements IEmsWeatherSyncService { + private static final Logger log = LoggerFactory.getLogger(EmsWeatherSyncServiceImpl.class); + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + @Autowired + private WeatherApiProperties weatherApiProperties; + @Autowired + private EmsSiteSettingMapper emsSiteSettingMapper; + @Autowired + private EmsSiteWeatherDayMapper emsSiteWeatherDayMapper; + + @Override + public WeatherSyncResultVo syncWeatherByDateRange(StatisAmmeterDateRequest requestVo) { + if (!weatherApiProperties.isEnabled()) { + throw new ServiceException("天气同步未启用,请先设置 weather.api.enabled=true"); + } + if (requestVo == null || StringUtils.isEmpty(requestVo.getSiteId()) + || StringUtils.isEmpty(requestVo.getStartTime()) + || StringUtils.isEmpty(requestVo.getEndTime())) { + throw new ServiceException("siteId、startTime、endTime 不能为空"); + } + LocalDate startDate = parseDate(requestVo.getStartTime(), "startTime"); + LocalDate endDate = parseDate(requestVo.getEndTime(), "endTime"); + if (endDate.isBefore(startDate)) { + throw new ServiceException("endTime 不能早于 startTime"); + } + + EmsSiteSetting siteSetting = emsSiteSettingMapper.selectEmsSiteSettingBySiteId(requestVo.getSiteId()); + if (siteSetting == null) { + throw new ServiceException("未找到站点配置,siteId=" + requestVo.getSiteId()); + } + BigDecimal latitude = siteSetting.getLatitude(); + BigDecimal longitude = siteSetting.getLongitude(); + if (latitude == null || longitude == null) { + throw new ServiceException("站点缺少经纬度配置,siteId=" + requestVo.getSiteId()); + } + + String url = buildWeatherUrl(latitude, longitude, startDate, endDate); + String response = HttpUtils.sendGet(url); + if (StringUtils.isEmpty(response)) { + throw new ServiceException("调用天气接口失败,返回为空"); + } + JSONObject root = JSONObject.parseObject(response); + JSONObject daily = root == null ? null : root.getJSONObject("daily"); + JSONArray dates = daily == null ? null : daily.getJSONArray("time"); + JSONArray codes = daily == null ? null : daily.getJSONArray("weather_code"); + if (dates == null || codes == null || dates.size() == 0 || dates.size() != codes.size()) { + throw new ServiceException("天气接口返回格式异常,daily.time/weather_code 不可用"); + } + + WeatherSyncResultVo resultVo = new WeatherSyncResultVo(); + resultVo.setSiteId(requestVo.getSiteId()); + resultVo.setStartTime(requestVo.getStartTime()); + resultVo.setEndTime(requestVo.getEndTime()); + resultVo.setTotalDays(dates.size()); + + int updated = 0; + int inserted = 0; + for (int i = 0; i < dates.size(); i++) { + String dateStr = dates.getString(i); + int weatherCode = codes.getIntValue(i); + String weatherDesc = mapWeatherDesc(weatherCode); + + int updateRows = emsSiteWeatherDayMapper.updateWeatherDesc( + requestVo.getSiteId(), dateStr, weatherDesc, weatherCode); + if (updateRows > 0) { + updated++; + continue; + } + + int count = emsSiteWeatherDayMapper.selectCountBySiteAndDate(requestVo.getSiteId(), dateStr); + if (count == 0) { + emsSiteWeatherDayMapper.insertSiteWeatherDay( + requestVo.getSiteId(), dateStr, weatherDesc, weatherCode, "open-meteo"); + inserted++; + continue; + } + + // 理论上不会到这里,防御性兜底再更新一次 + updated += emsSiteWeatherDayMapper.updateWeatherDesc( + requestVo.getSiteId(), dateStr, weatherDesc, weatherCode); + } + resultVo.setUpdatedDays(updated); + resultVo.setInsertedDays(inserted); + resultVo.setSuccessDays(updated + inserted); + resultVo.setMessage("天气同步完成"); + + log.info("天气同步完成, siteId: {}, startTime: {}, endTime: {}, totalDays: {}, updatedDays: {}, insertedDays: {}", + requestVo.getSiteId(), requestVo.getStartTime(), requestVo.getEndTime(), + resultVo.getTotalDays(), updated, inserted); + return resultVo; + } + + private LocalDate parseDate(String value, String fieldName) { + try { + return LocalDate.parse(value, DATE_FORMATTER); + } catch (Exception e) { + throw new ServiceException(fieldName + " 格式错误,应为 yyyy-MM-dd"); + } + } + + private String buildWeatherUrl(BigDecimal latitude, BigDecimal longitude, LocalDate startDate, LocalDate endDate) { + StringBuilder builder = new StringBuilder(); + builder.append(weatherApiProperties.getBaseUrl()) + .append("?latitude=").append(latitude.stripTrailingZeros().toPlainString()) + .append("&longitude=").append(longitude.stripTrailingZeros().toPlainString()) + .append("&start_date=").append(startDate.format(DATE_FORMATTER)) + .append("&end_date=").append(endDate.format(DATE_FORMATTER)) + .append("&daily=weather_code") + .append("&timezone=").append(urlEncode(weatherApiProperties.getTimezone())); + if (StringUtils.isNotEmpty(weatherApiProperties.getApiKey())) { + builder.append("&apikey=").append(urlEncode(weatherApiProperties.getApiKey())); + } + return builder.toString(); + } + + private String urlEncode(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + throw new ServiceException("URL 编码失败"); + } + } + + private String mapWeatherDesc(int weatherCode) { + switch (weatherCode) { + case 0: + return "晴"; + case 1: + case 2: + case 3: + return "多云"; + case 45: + case 48: + return "雾"; + case 51: + case 53: + case 55: + return "毛毛雨"; + case 56: + case 57: + return "冻毛毛雨"; + case 61: + case 63: + case 65: + return "雨"; + case 66: + case 67: + return "冻雨"; + case 71: + case 73: + case 75: + return "雪"; + case 77: + return "雪粒"; + case 80: + case 81: + case 82: + return "阵雨"; + case 85: + case 86: + return "阵雪"; + case 95: + return "雷暴"; + case 96: + case 99: + return "雷暴伴冰雹"; + default: + return "未知(" + weatherCode + ")"; + } + } +} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/FXXDataProcessServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/FXXDataProcessServiceImpl.java deleted file mode 100644 index e95ac48..0000000 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/FXXDataProcessServiceImpl.java +++ /dev/null @@ -1,1122 +0,0 @@ -package com.xzzn.ems.service.impl; - -import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONArray; -import com.alibaba.fastjson2.JSONObject; -import com.alibaba.fastjson2.TypeReference; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.xzzn.common.constant.RedisKeyConstants; -import com.xzzn.common.core.redis.RedisCache; -import com.xzzn.common.enums.BranchStatus; -import com.xzzn.common.enums.ChargeStatus; -import com.xzzn.common.enums.CommunicationStatus; -import com.xzzn.common.enums.ControlModeStatus; -import com.xzzn.common.enums.DeviceRunningStatus; -import com.xzzn.common.enums.GridStatus; -import com.xzzn.common.enums.SwitchStatus; -import com.xzzn.common.enums.WorkStatus; -import com.xzzn.common.utils.DateUtils; -import com.xzzn.common.utils.StringUtils; -import com.xzzn.ems.domain.EmsAmmeterData; -import com.xzzn.ems.domain.EmsBatteryCluster; -import com.xzzn.ems.domain.EmsBatteryData; -import com.xzzn.ems.domain.EmsBatteryDataDailyLatest; -import com.xzzn.ems.domain.EmsBatteryDataMinutes; -import com.xzzn.ems.domain.EmsBatteryStack; -import com.xzzn.ems.domain.EmsCoolingData; -import com.xzzn.ems.domain.EmsDailyChargeData; -import com.xzzn.ems.domain.EmsDailyEnergyData; -import com.xzzn.ems.domain.EmsDevicesSetting; -import com.xzzn.ems.domain.EmsDhData; -import com.xzzn.ems.domain.EmsPcsBranchData; -import com.xzzn.ems.domain.EmsPcsData; -import com.xzzn.ems.domain.EmsPointMatch; -import com.xzzn.ems.domain.vo.EnergyPriceTimeRange; -import com.xzzn.ems.domain.vo.EnergyPriceVo; -import com.xzzn.ems.enums.DeviceMatchTable; -import com.xzzn.ems.mapper.EmsAmmeterDataMapper; -import com.xzzn.ems.mapper.EmsBatteryClusterMapper; -import com.xzzn.ems.mapper.EmsBatteryDataMapper; -import com.xzzn.ems.mapper.EmsBatteryDataMinutesMapper; -import com.xzzn.ems.mapper.EmsBatteryStackMapper; -import com.xzzn.ems.mapper.EmsCoolingDataMapper; -import com.xzzn.ems.mapper.EmsDailyChargeDataMapper; -import com.xzzn.ems.mapper.EmsDailyEnergyDataMapper; -import com.xzzn.ems.mapper.EmsDevicesSettingMapper; -import com.xzzn.ems.mapper.EmsDhDataMapper; -import com.xzzn.ems.mapper.EmsEnergyPriceConfigMapper; -import com.xzzn.ems.mapper.EmsPcsBranchDataMapper; -import com.xzzn.ems.mapper.EmsPcsDataMapper; -import com.xzzn.ems.service.IEmsAlarmRecordsService; -import com.xzzn.ems.service.IEmsEnergyPriceConfigService; -import com.xzzn.ems.service.IFXXDataProcessService; -import com.xzzn.ems.utils.AbstractBatteryDataProcessor; -import com.xzzn.ems.utils.DevicePointMatchDataProcessor; - -import java.lang.reflect.Field; -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -@Service -public class FXXDataProcessServiceImpl extends AbstractBatteryDataProcessor implements IFXXDataProcessService { - private static final Log log = LogFactory.getLog(FXXDataProcessServiceImpl.class); - private static final String SITE_ID = "021_FXX_01"; - - @Autowired - private EmsBatteryClusterMapper emsBatteryClusterMapper; - - @Autowired - private EmsBatteryStackMapper emsBatteryStackMapper; - - @Autowired - private EmsBatteryDataMapper emsBatteryDataMapper; - - @Autowired - private EmsPcsDataMapper emsPcsDataMapper; - - @Autowired - private EmsPcsBranchDataMapper emsPcsBranchDataMapper; - - @Autowired - private RedisCache redisCache; - - @Autowired - private EmsDevicesSettingMapper emsDevicesSettingMapper; - - @Autowired - private EmsAmmeterDataMapper emsAmmeterDataMapper; - @Autowired - private EmsBatteryDailyLatestServiceImpl emsBatteryDailyLatestServiceImpl; - @Autowired - private EmsBatteryDataMinutesMapper emsBatteryDataMinutesMapper; - @Autowired - private EmsDailyChargeDataMapper emsDailyChargeDataMapper; - @Autowired - private EmsDhDataMapper emsDhDataMapper; - @Autowired - private IEmsAlarmRecordsService iEmsAlarmRecordsService; - @Autowired - private EmsCoolingDataMapper emsCoolingDataMapper; - @Autowired - private EmsDailyEnergyDataMapper emsDailyEnergyDataMapper; - @Autowired - private EmsEnergyPriceConfigMapper emsEnergyPriceConfigMapper; - @Autowired - private IEmsEnergyPriceConfigService emsEnergyPriceConfigService; - @Autowired - private IEmsEnergyPriceConfigService iEmsEnergyPriceConfigService; - @Autowired - private DevicePointMatchDataProcessor devicePointMatchDataProcessor; - - // 构造方法(调用父类构造) - public FXXDataProcessServiceImpl(ObjectMapper objectMapper) { - super(objectMapper); - } - - @Override - public void handleFxData(String message) { - JSONArray arraylist = JSONArray.parseArray(message); - - for (int i = 0; i < arraylist.size(); i++) { - JSONObject obj = JSONObject.parseObject(arraylist.get(i).toString()); - - String deviceId = obj.get("Device").toString(); - String jsonData = obj.get("Data").toString(); - Long timestamp = Long.valueOf(obj.get("timestamp").toString()); - Date dataUpdateTime = DateUtils.convertUpdateTime(timestamp); - - log.info("deviceId:" + deviceId); - boolean isEmpty = checkJsonDataEmpty(jsonData); - if (isEmpty) { - // 添加设备告警 - iEmsAlarmRecordsService.addEmptyDataAlarmRecord(SITE_ID,deviceId); - return; - } - - // 存放mqtt原始每个设备最晚一次数据,便于后面点位获取数据 - redisCache.setCacheObject(RedisKeyConstants.ORIGINAL_MQTT_DATA + SITE_ID + "_" + deviceId, obj); - // 存放每次同步数据,失效时间(同同步时间)-用于判断是否正常同步数据 - redisCache.setCacheObject(RedisKeyConstants.SYNC_DATA + SITE_ID + "_" + deviceId, obj, 1, TimeUnit.MINUTES); - - if (deviceId.contains("BMSD")) { - batteryStackDataProcess(deviceId, jsonData); - - } else if (deviceId.contains("BMSC")) { - batteryClusterDataProcess(deviceId, jsonData); - batteryDataProcess(deviceId, jsonData,dataUpdateTime); - - } else if (deviceId.contains("PCS")) { - pcsDataProcess(deviceId, jsonData,dataUpdateTime); - pcsBranchDataProcess(deviceId, jsonData); - dealFXXDailyChargeDate(deviceId, jsonData); - } else if (deviceId.contains("LOAD")) { - loadDataProcess(deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains("METE")) { - meteDataProcess(deviceId, jsonData, dataUpdateTime); - } else if (deviceId.contains("donghuan")) { - dhDataProcess(deviceId, jsonData); - } else if (deviceId.contains("ZSLQ")) { - coolingDataProcess(deviceId, jsonData); - } - } - } - - private void coolingDataProcess(String deviceId, String jsonData) { - //中水冷却 - Map obj = JSON.parseObject(jsonData, new TypeReference>() { - }); - - EmsCoolingData coolingData = new EmsCoolingData(); - - coolingData.setGsTemp(StringUtils.getBigDecimal(obj.get("GSWD"))); - coolingData.setHsTemp(StringUtils.getBigDecimal(obj.get("HSWD"))); - coolingData.setGsPressure(StringUtils.getBigDecimal(obj.get("GSYL"))); - coolingData.setHsPressure(StringUtils.getBigDecimal(obj.get("HSYL"))); - coolingData.setLysTemp(StringUtils.getBigDecimal(obj.get("LYSWD"))); - coolingData.setVb01Kd(StringUtils.getBigDecimal(obj.get("VB1KD"))); - coolingData.setVb02Kd(StringUtils.getBigDecimal(obj.get("VB2KD"))); - - coolingData.setCreateBy("system"); - coolingData.setCreateTime(DateUtils.getNowDate()); - coolingData.setUpdateBy("system"); - coolingData.setUpdateTime(DateUtils.getNowDate()); - coolingData.setSiteId(SITE_ID); - coolingData.setDeviceId(deviceId); - - emsCoolingDataMapper.insertEmsCoolingData(coolingData); - - redisCache.setCacheObject(RedisKeyConstants.COOLING + SITE_ID + "_" +deviceId, coolingData); - - } - - private void dhDataProcess(String deviceId, String dataJson) { - //动环 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - //DH - EmsDhData dhData = new EmsDhData(); - dhData.setHumidity(StringUtils.getBigDecimal(obj.get("SD3"))); - dhData.setTemperature(StringUtils.getBigDecimal(obj.get("WD3"))); - - dhData.setCreateBy("system"); - dhData.setCreateTime(DateUtils.getNowDate()); - dhData.setUpdateBy("system"); - dhData.setUpdateTime(DateUtils.getNowDate()); - dhData.setSiteId(SITE_ID); - dhData.setDeviceId(deviceId); - emsDhDataMapper.insertEmsDhData(dhData); - - redisCache.setCacheObject(RedisKeyConstants.DH + SITE_ID + "_" +deviceId, dhData); - - } - - private void batteryStackDataProcess(String deviceId, String dataJson) { - - //电池堆 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - //BMS 电池簇 - EmsBatteryStack dataStack = new EmsBatteryStack(); - - // 其他非 BigDecimal 字段 - dataStack.setWorkStatus(WorkStatus.NORMAL.getCode()); // 或其他默认值 - dataStack.setPcsCommunicationStatus(CommunicationStatus.OK.getCode()); - dataStack.setEmsCommunicationStatus(CommunicationStatus.OK.getCode()); - - // 电池堆状态数据设置 - dataStack.setOperationStatus(StringUtils.getString(obj.get("DCZT"))); - dataStack.setStackVoltage(StringUtils.getBigDecimal(obj.get("DCDDY"))); - dataStack.setStackCurrent(StringUtils.getBigDecimal(obj.get("DCDDL"))); - dataStack.setStackSoc(StringUtils.getBigDecimal(obj.get("DCDSOC"))); - dataStack.setStackSoh(StringUtils.getBigDecimal(obj.get("DCDSOH"))); - - // 电压极值信息 - dataStack.setMaxCellVoltage(StringUtils.getBigDecimal(obj.get("ZGDCDY"))); - dataStack.setMaxVoltageGroupId(StringUtils.getLong(obj.get("ZGDCDYZH"))); - dataStack.setMaxVoltageCellId(StringUtils.getLong(obj.get("ZGDCDYZHDH"))); - dataStack.setMinCellVoltage(StringUtils.getBigDecimal(obj.get("ZDDCDY"))); - dataStack.setMinVoltageGroupId(StringUtils.getLong(obj.get("ZDDCDYZH"))); - dataStack.setMinVoltageCellId(StringUtils.getLong(obj.get("ZDDCDYZHDH"))); - - // 温度极值信息 - dataStack.setMaxCellTemp(StringUtils.getBigDecimal(obj.get("ZGDCWD"))); - dataStack.setMaxTempGroupId(StringUtils.getLong(obj.get("ZGDCWDZH"))); - dataStack.setMaxTempCellId(StringUtils.getLong(obj.get("ZGDCWDZHDH"))); - dataStack.setMinCellTemp(StringUtils.getBigDecimal(obj.get("ZDDCWD"))); - dataStack.setMinTempGroupId(StringUtils.getLong(obj.get("ZDDCWDZH"))); - dataStack.setMinTempCellId(StringUtils.getLong(obj.get("ZDDCWDZHDH"))); - - // 电量统计信息 - dataStack.setTotalChargeCapacity(StringUtils.getBigDecimal(obj.get("DLJCDDL"))); - dataStack.setTotalDischargeCapacity(StringUtils.getBigDecimal(obj.get("DLCFDDL"))); - dataStack.setSessionChargeCapacity(StringUtils.getBigDecimal(obj.get("DDCLJCDDL"))); - dataStack.setSessionDischargeCapacity(StringUtils.getBigDecimal(obj.get("DDCLJFDDL"))); - dataStack.setAvailableChargeCapacity(StringUtils.getBigDecimal(obj.get("DKCDL"))); - dataStack.setAvailableDischargeCapacity(StringUtils.getBigDecimal(obj.get("DKFDL"))); - - // 时间信息 - dataStack.setRemainingDischargeTime(StringUtils.getLong(obj.get("KYFDSJ"))); - dataStack.setRemainingChargeTime(StringUtils.getLong(obj.get("KYCDSJ"))); - - // 功率/电流限制 - dataStack.setMaxDischargePower(StringUtils.getBigDecimal(obj.get("YXZDFDGL"))); - dataStack.setMaxChargePower(StringUtils.getBigDecimal(obj.get("YXZDCDGL"))); - dataStack.setMaxDischargeCurrent(StringUtils.getBigDecimal(obj.get("YXZDFDDL"))); - dataStack.setMaxChargeCurrent(StringUtils.getBigDecimal(obj.get("YXZDCDDL"))); - - // 当日统计 - dataStack.setDailyDischargeCycles(StringUtils.getLong(obj.get("DTFDCS"))); - dataStack.setDailyChargeCycles(StringUtils.getLong(obj.get("DTCDCS"))); - dataStack.setDailyDischargeCapacity(StringUtils.getBigDecimal(obj.get("DTFDDL"))); - dataStack.setDailyChargeCapacity(StringUtils.getBigDecimal(obj.get("DTCDDL"))); - - // 系统状态 - dataStack.setOperatingTemp(StringUtils.getBigDecimal(obj.get("YXWD"))); - dataStack.setBmsStatus(StringUtils.getString(obj.get("BMSDDQZT"))); - dataStack.setBmsChargeStatus(StringUtils.getString(obj.get("BMSCFDZT"))); - dataStack.setStackInsulationResistance(StringUtils.getBigDecimal(obj.get("DCDJYDZ"))); - - - dataStack.setCreateBy("system"); - dataStack.setCreateTime(DateUtils.getNowDate()); - dataStack.setUpdateBy("system"); - dataStack.setUpdateTime(DateUtils.getNowDate()); - dataStack.setSiteId(SITE_ID); - dataStack.setDeviceId(deviceId); - - emsBatteryStackMapper.insertEmsBatteryStack(dataStack); - - redisCache.setCacheObject(RedisKeyConstants.STACK + SITE_ID + "_" +deviceId, dataStack); - - } - - private void saveDeviceData(List pointMatchList, Map obj, Object entity) { - Map pointMatchMap = pointMatchList.stream().collect(Collectors.toMap(data -> StringUtils.toCamelCase(data.getMatchField()), EmsPointMatch::getDataPoint)); - Field[] fields = entity.getClass().getDeclaredFields(); - for (Field field : fields) { - if (pointMatchMap.containsKey(field.getName())) { - field.setAccessible(true); - try { - Object matchValue = obj.get(pointMatchMap.get(field.getName())); - Class fieldType = field.getType(); - if (String.class.equals(fieldType)) { - matchValue = StringUtils.getString(matchValue); - } else if (Long.class.equals(fieldType)) { - matchValue = StringUtils.getLong(matchValue); - } else if (BigDecimal.class.equals(fieldType)) { - matchValue = StringUtils.getBigDecimal(matchValue); - } - field.set(entity, matchValue); - } catch (IllegalAccessException e) { - log.warn("batteryStackDataProcess 设置字段值时出错", e); - } - } - } - } - - private void saveDevicePointMatchData(List pointMatchList, String deviceId, - DeviceMatchTable pointMatchType, String dataDevice){ - if (CollectionUtils.isNotEmpty(pointMatchList)) { - return; - } - devicePointMatchDataProcessor.saveDevicePointMatch(SITE_ID, deviceId, pointMatchType, dataDevice); - } - - private void batteryClusterDataProcess(String deviceId, String dataJson) { - - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - EmsDevicesSetting joken = new EmsDevicesSetting(); - joken.setDeviceId(deviceId); - List up = emsDevicesSettingMapper.selectEmsDevicesSettingList(joken); - String stackDeviceId = ""; - if (up != null && up.size() >0) { - stackDeviceId = up.get(0).getParentId(); - if (stackDeviceId == null || stackDeviceId.isEmpty()) { - stackDeviceId = "1"; - } - } - //BMS 电池簇 - EmsBatteryCluster data = new EmsBatteryCluster(); - // 设置所有 BigDecimal 类型字段为 ZERO - data.setChargeableCapacity(StringUtils.getBigDecimal(obj.get("KCDL"))); - data.setClusterVoltage(StringUtils.getBigDecimal(obj.get("ZDY"))); - data.setClusterCurrent(StringUtils.getBigDecimal(obj.get("ZDL"))); - data.setTotalChargedCapacity(StringUtils.getBigDecimal(obj.get("LJCDDL"))); - data.setDischargeableCapacity(StringUtils.getBigDecimal(obj.get("KFDL"))); - data.setTotalDischargedCapacity(StringUtils.getBigDecimal(obj.get("LJFDDL"))); - data.setSoh(StringUtils.getBigDecimal(obj.get("ZSOH"))); - data.setAverageTemperature(StringUtils.getBigDecimal(obj.get("MKWD"))); - data.setInsulationResistance(StringUtils.getBigDecimal(obj.get("ZJYDZ"))); - data.setCurrentSoc(StringUtils.getBigDecimal(obj.get("ZSOC"))); - data.setMaxAllowedChargePower(StringUtils.getBigDecimal(obj.get("YXCDZDGL"))); - data.setMaxAllowedDischargePower(StringUtils.getBigDecimal(obj.get("YXFDZDGL"))); - data.setMaxAllowedChargeVoltage(StringUtils.getBigDecimal(obj.get("YXCDZDDY"))); - data.setMaxAllowedDischargeVoltage(StringUtils.getBigDecimal(obj.get("YXFDZDDY"))); - data.setMaxAllowedChargeCurrent(StringUtils.getBigDecimal(obj.get("YXCDZDDL"))); - data.setMaxAllowedDischargeCurrent(StringUtils.getBigDecimal(obj.get("YXFDZDDL"))); - data.setBatteryPackVoltage(StringUtils.getBigDecimal(obj.get("ZDY"))); - data.setBatteryPackCurrent(StringUtils.getBigDecimal(obj.get("ZDL"))); - data.setBatteryPackTemp(StringUtils.getBigDecimal(obj.get("MKWD"))); - data.setBatteryPackSoc(StringUtils.getBigDecimal(obj.get("ZSOC"))); - data.setBatteryPackSoh(StringUtils.getBigDecimal(obj.get("ZSOH"))); - data.setBatteryPackInsulationResistance(StringUtils.getBigDecimal(obj.get("ZJYDZ"))); - data.setAvgCellVoltage(StringUtils.getBigDecimal(obj.get("PJDTDY"))); - data.setAvgCellTemp(StringUtils.getBigDecimal(obj.get("PJDTWD"))); - data.setMaxCellVoltage(StringUtils.getBigDecimal(obj.get("ZGDTDY"))); - data.setMinCellVoltage(StringUtils.getBigDecimal(obj.get("ZDDTDY"))); - data.setMaxCellTemp(StringUtils.getBigDecimal(obj.get("ZGDTWD"))); - data.setMinCellTemp(StringUtils.getBigDecimal(obj.get("ZDDTWD"))); - data.setMaxCellSoc(StringUtils.getBigDecimal(obj.get("ZGDTSOC"))); - data.setMinCellSoc(StringUtils.getBigDecimal(obj.get("ZDDTSOC"))); - data.setMaxCellSoh(StringUtils.getBigDecimal(obj.get("ZGDTSOH"))); - data.setMinCellSoh(StringUtils.getBigDecimal(obj.get("ZDDTSOH"))); - data.setTotalChargeEnergy(StringUtils.getBigDecimal(obj.get("DCLJCDDL"))); - data.setTotalDischargeEnergy(StringUtils.getBigDecimal(obj.get("DCLJFDDL"))); - - // 其他非 BigDecimal 字段 - data.setMaxCellVoltageId(StringUtils.getString(obj.get("ZGDTDYDYD"))); - data.setMinCellVoltageId(StringUtils.getString(obj.get("ZDDTDYDYD"))); - data.setMaxCellTempId(StringUtils.getString(obj.get("ZGDTWDDYD"))); - data.setMinCellTempId(StringUtils.getString(obj.get("ZDDTWDDYD"))); - data.setMaxCellSocId(StringUtils.getString(obj.get("ZGDTSOCDYD"))); - data.setMinCellSocId(StringUtils.getString(obj.get("ZDDTSOCDYD"))); - data.setMaxCellSohId(StringUtils.getString(obj.get("ZGDTSOHDYD"))); - data.setMinCellSohId(StringUtils.getString(obj.get("ZDDTSOHDYD"))); - - data.setWorkStatus(WorkStatus.NORMAL.getCode()); // 或其他默认值 - data.setPcsCommunicationStatus(CommunicationStatus.OK.getCode()); - data.setEmsCommunicationStatus(CommunicationStatus.OK.getCode()); - data.setCreateBy("system"); - data.setCreateTime(DateUtils.getNowDate()); - data.setUpdateBy("system"); - data.setUpdateTime(DateUtils.getNowDate()); - data.setSiteId(SITE_ID); - data.setDeviceId(deviceId); - if (StringUtils.isNotBlank(stackDeviceId)) { - data.setStackDeviceId(stackDeviceId); - } else { - data.setStackDeviceId("1"); - } - emsBatteryClusterMapper.insertEmsBatteryCluster(data); - - redisCache.setCacheObject(RedisKeyConstants.CLUSTER + SITE_ID + "_" +deviceId, data); - - } - - private void batteryDataProcess(String deviceId, String dataJson, Date dataUpdateTime) { - EmsDevicesSetting joken = new EmsDevicesSetting(); - joken.setDeviceId(deviceId); - List up = emsDevicesSettingMapper.selectEmsDevicesSettingList(joken); - String stackDeviceId = ""; - if (up != null && up.size() >0) { - stackDeviceId = up.get(0).getParentId(); - if (stackDeviceId == null || stackDeviceId.isEmpty()) { - stackDeviceId = "1"; - } - } - //单体电池 - Map> records = processData(JSON.parseObject(dataJson, new TypeReference>() {})); - List list = new ArrayList<>(); - List dailyList = new ArrayList<>(); - List minutesList = new ArrayList<>(); - // 前一个小时 - LocalDateTime oneHourAgo = LocalDateTime.now().minus(1, ChronoUnit.HOURS); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - String oneHourAgoStr = oneHourAgo.format(formatter); - //单体电池 - for (Map.Entry> record : records.entrySet()) { - String recordId = record.getKey(); - Map fields = record.getValue(); - - EmsBatteryData batteryData = new EmsBatteryData(); - batteryData.setDeviceId(recordId); - batteryData.setBatteryCellId(recordId); - - batteryData.setSoc(StringUtils.getBigDecimal(fields.get("DTSOC"))); - batteryData.setSoh(StringUtils.getBigDecimal(fields.get("DTSOH"))); - batteryData.setTemperature(StringUtils.getBigDecimal(fields.get("DTWD"))); - batteryData.setVoltage(StringUtils.getBigDecimal(fields.get("DTDY"))); - - batteryData.setBatteryCluster(deviceId); - batteryData.setBatteryPack(stackDeviceId); - - // 时间戳 - batteryData.setDataTimestamp(dataUpdateTime); - // 系统管理字段 - batteryData.setCreateBy("system"); - batteryData.setCreateTime(DateUtils.getNowDate()); - batteryData.setUpdateBy("system"); - batteryData.setUpdateTime(DateUtils.getNowDate()); - // ID字段 - batteryData.setSiteId(SITE_ID); - batteryData.setClusterDeviceId(deviceId); - list.add(batteryData); - - // 每日最新数据 - EmsBatteryDataDailyLatest daily = new EmsBatteryDataDailyLatest(); - BeanUtils.copyProperties(batteryData, daily); - daily.setDateDay(DateUtils.getNowDate()); - dailyList.add(daily); - - // 分钟级的表,上报的数据直接存入,数据上限是 1 个小时 - EmsBatteryDataMinutes minutes = new EmsBatteryDataMinutes(); - BeanUtils.copyProperties(batteryData, minutes); - minutesList.add(minutes); - } - if (list.size() > 0) { - emsBatteryDataMapper.insertEmsBatteryDataList(list); - - redisCache.deleteList(RedisKeyConstants.BATTERY + SITE_ID + "_" + deviceId); - redisCache.setCacheList(RedisKeyConstants.BATTERY + SITE_ID + "_" + deviceId, list); - - } - - // 批量处理每日最新数据 - if (dailyList != null && dailyList.size() > 0) { - emsBatteryDailyLatestServiceImpl.batchProcessBatteryData(dailyList); - } - // 实时插入每分钟数据 - if (minutesList != null && minutesList.size() > 0) { - emsBatteryDataMinutesMapper.insertMinutesBatteryDataList(minutesList); - } - // 清理分钟级表里一小时前数据 - emsBatteryDataMinutesMapper.deleteByTimeBeforeOneHour(oneHourAgoStr); - - // 分片处理时级,天级,月级数据 - if (list.size() > 0) { - super.processBatch(list); - } - } - - private void pcsDataProcess(String deviceId, String dataJson, Date dataUpdateTime) { - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - //pcs - EmsPcsData pcsData = new EmsPcsData(); - - // 功率与能量类字段 - pcsData.setTotalActivePower(StringUtils.getBigDecimal(obj.get("YGGL"))); - pcsData.setDailyAcChargeEnergy(StringUtils.getBigDecimal(obj.get("RCDL"))); - pcsData.setTotalReactivePower(StringUtils.getBigDecimal(obj.get("XTWGGL"))); - pcsData.setDailyAcDischargeEnergy(StringUtils.getBigDecimal(obj.get("RFDL"))); - pcsData.setTotalApparentPower(StringUtils.getBigDecimal(obj.get("XTSZGL"))); - pcsData.setTotalPowerFactor(StringUtils.getBigDecimal(obj.get("GLYS"))); - pcsData.setDcPower(StringUtils.getBigDecimal(obj.get("XTSZGL"))); - pcsData.setTotalAcChargeEnergy(StringUtils.getBigDecimal(obj.get("ZCDL"))); - pcsData.setTotalAcDischargeEnergy(StringUtils.getBigDecimal(obj.get("ZFDL"))); - pcsData.setAcChargeActivePower(StringUtils.getBigDecimal(obj.get("JLCCDYGGL"))); - pcsData.setAcCapacitiveReactivePower(StringUtils.getBigDecimal(obj.get("JLCRXWGGL"))); - pcsData.setAcDischargeActivePower(StringUtils.getBigDecimal(obj.get("JLCFDYGGL"))); - pcsData.setAcInductiveReactivePower(StringUtils.getBigDecimal(obj.get("JLCGXWGGL"))); - pcsData.setMaxCapacitivePowerCapacity(StringUtils.getBigDecimal(obj.get("ZDRXWGNL"))); - pcsData.setMaxInductivePowerCapacity(StringUtils.getBigDecimal(obj.get("ZDGXWGNL"))); - pcsData.setMaxChargePowerCapacity(StringUtils.getBigDecimal(obj.get("ZDKCGL"))); - pcsData.setMaxDischargePowerCapacity(StringUtils.getBigDecimal(obj.get("ZDKFGL"))); - - - // 温度与环境参数 -// pcsData.setPcsModuleTemperature(StringUtils.getBigDecimal(obj.get("ChargeableCapacity"))); -// pcsData.setPcsEnvironmentTemperature(StringUtils.getBigDecimal(obj.get("ChargeableCapacity"))); -// pcsData.setAcFrequency(StringUtils.getBigDecimal(obj.get("ChargeableCapacity"))); - - // 状态指示类 - pcsData.setBranchStatus(BranchStatus.NORMAL.getCode()); - pcsData.setDischargeStatus(ChargeStatus.CHARGING.getCode()); - String acSwitchStatus = StringUtils.getString(obj.get("JLKGZT")); - pcsData.setAcSwitchStatus(SwitchStatus.CLOSED.getCode()); - String dcSwitchStatus = StringUtils.getString(obj.get("ZLKGZT")); - pcsData.setDcSwitchStatus(SwitchStatus.CLOSED.getCode()); - String controlMode = StringUtils.getString(obj.get("YCTT")); - pcsData.setRemoteControlStatus(ControlModeStatus.REMOTE.getCode()); - - // 电流参数 - pcsData.setSysUCurrent(StringUtils.getBigDecimal(obj.get("XTSCUXDL"))); - pcsData.setSysVCurrent(StringUtils.getBigDecimal(obj.get("XTSCVXDL"))); - pcsData.setSysWCurrent(StringUtils.getBigDecimal(obj.get("XTSCWXDL"))); - - // 直流参数 -// pcsData.setDcVoltage(StringUtils.getBigDecimal(obj.get("ChargeableCapacity"))); -// pcsData.setDcCurrent(StringUtils.getBigDecimal(obj.get("ChargeableCapacity"))); - - // 三相温度 - pcsData.setuTemperature(StringUtils.getBigDecimal(obj.get("DY1UXIGBTWD"))); - pcsData.setvTemperature(StringUtils.getBigDecimal(obj.get("DY1VXIGBTWD"))); - pcsData.setwTemperature(StringUtils.getBigDecimal(obj.get("DY1WXIGBTWD"))); - - // 时间与状态类字段 - pcsData.setDataUpdateTime(dataUpdateTime); - pcsData.setWorkStatus(WorkStatus.NORMAL.getCode()); - pcsData.setGridStatus(GridStatus.GRID.getCode()); - pcsData.setDeviceStatus(DeviceRunningStatus.ONLINE.getCode()); - pcsData.setControlMode(ControlModeStatus.REMOTE.getCode()); - - // 系统管理字段 - pcsData.setCreateBy("system"); - pcsData.setCreateTime(DateUtils.getNowDate()); - pcsData.setUpdateBy("system"); - pcsData.setUpdateTime(DateUtils.getNowDate()); - pcsData.setSiteId(SITE_ID); - pcsData.setDeviceId(deviceId); - pcsData.setDateMonth(DateUtils.getNowMonthLong()); - pcsData.setDateDay(DateUtils.getNowDayLong()); - - emsPcsDataMapper.insertEmsPcsData(pcsData); - redisCache.setCacheObject(RedisKeyConstants.PCS + SITE_ID + "_" +deviceId, pcsData); - - } - - private void pcsBranchDataProcess(String deviceId, String dataJson) { - - Map> records = processDataPrefix(JSON.parseObject(dataJson, new TypeReference>() {})); - List list = new ArrayList<>(); - - //PCS支路 - for (Map.Entry> record : records.entrySet()) { - String recordId = record.getKey(); - Map fields = record.getValue(); - - EmsPcsBranchData data = new EmsPcsBranchData(); - data.setDeviceId(deviceId); - data.setSiteId(SITE_ID); - data.setGridStatus(GridStatus.GRID.getCode()); - data.setDcPower(StringUtils.getBigDecimal(fields.get("ZLGL"))); - data.setDcVoltage(StringUtils.getBigDecimal(fields.get("ZLDY"))); - data.setDcCurrent(StringUtils.getBigDecimal(fields.get("ZLDL"))); - data.setGridUVoltage(StringUtils.getBigDecimal(fields.get("DWUXDY"))); - data.setGridVVoltage(StringUtils.getBigDecimal(fields.get("DWVXDY"))); - data.setGridWVoltage(StringUtils.getBigDecimal(fields.get("DWWXDY"))); - data.setOutputUCurrent(StringUtils.getBigDecimal(fields.get("SCUXDL"))); - data.setOutputVCurrent(StringUtils.getBigDecimal(fields.get("SCVXDL"))); - data.setOutputWCurrent(StringUtils.getBigDecimal(fields.get("SCWXDL"))); - data.setApparentPower(StringUtils.getBigDecimal(fields.get("SZGL"))); - data.setActivePower(StringUtils.getBigDecimal(fields.get("YGGL"))); - data.setReactivePower(StringUtils.getBigDecimal(fields.get("WGGL"))); - data.setPowerFactor(StringUtils.getBigDecimal(fields.get("GLYS"))); - data.setFrequency(StringUtils.getBigDecimal(fields.get("PL"))); - data.setInternalTemp(StringUtils.getBigDecimal(fields.get("DY1WD"))); - data.setuIgbtTemp(StringUtils.getBigDecimal(fields.get("UXIGBTWD"))); - data.setvIgbtTemp(StringUtils.getBigDecimal(fields.get("VXIGBTWD"))); - data.setwIgbtTemp(StringUtils.getBigDecimal(fields.get("WXIGBTWD"))); - data.setAvailablePower(StringUtils.getBigDecimal(fields.get("KYGL"))); - data.setTotalLoadRatio(StringUtils.getBigDecimal(fields.get("ZFZB"))); - data.setAcLeakageCurrent(StringUtils.getBigDecimal(fields.get("JLLDL"))); - data.setInsulationResistance(StringUtils.getBigDecimal(fields.get("JYZK"))); - data.setBranchId(recordId); - data.setCreateBy("system"); - data.setCreateTime(DateUtils.getNowDate()); - data.setUpdateBy("system"); - data.setUpdateTime(DateUtils.getNowDate()); - list.add(data); - } - if (list.size() > 0 ) { - emsPcsBranchDataMapper.insertPcsBranchDataList(list); - - redisCache.setCacheObject(RedisKeyConstants.BRANCH + SITE_ID + "_" +deviceId, list); - - } - - } - - private void loadDataProcess(String deviceId, String dataJson, Date dataUpdateTime) { - - //总表 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - EmsAmmeterData dataLoad = new EmsAmmeterData(); - // 更新时间 - dataLoad.setDataUpdateTime(dataUpdateTime); - - // 电能设置-组合有功 - dataLoad.setCurrentCombActiveTotal(StringUtils.getBigDecimal(obj.get("DQZHYGZDN"))); - dataLoad.setCurrentCombActivePeak(StringUtils.getBigDecimal(obj.get("DQZHYGJDN"))); - dataLoad.setCurrentCombActiveHigh(StringUtils.getBigDecimal(obj.get("DQZHYGFDN"))); - dataLoad.setCurrentCombActiveFlat(StringUtils.getBigDecimal(obj.get("DQZHYGPDN"))); - dataLoad.setCurrentCombActiveValley(StringUtils.getBigDecimal(obj.get("DQZHYGGDN"))); - // 电能设置-正向有功 - dataLoad.setCurrentForwardActiveTotal(StringUtils.getBigDecimal(obj.get("DQZXYGZDN"))); - dataLoad.setCurrentForwardActivePeak(StringUtils.getBigDecimal(obj.get("DQZXYGJDN"))); - dataLoad.setCurrentForwardActiveHigh(StringUtils.getBigDecimal(obj.get("DQZXYGFDN"))); - dataLoad.setCurrentForwardActiveFlat(StringUtils.getBigDecimal(obj.get("DQZXYGPDN"))); - dataLoad.setCurrentForwardActiveValley(StringUtils.getBigDecimal(obj.get("DQZXYGGDN"))); - // 电能设置-反向有功 - dataLoad.setCurrentReverseActiveTotal(StringUtils.getBigDecimal(obj.get("DQFXYGZDN"))); - dataLoad.setCurrentReverseActivePeak(StringUtils.getBigDecimal(obj.get("DQFXYGJDN"))); - dataLoad.setCurrentReverseActiveHigh(StringUtils.getBigDecimal(obj.get("DQFXYGFDN"))); - dataLoad.setCurrentReverseActiveFlat(StringUtils.getBigDecimal(obj.get("DQFXYGPDN"))); - dataLoad.setCurrentReverseActiveValley(StringUtils.getBigDecimal(obj.get("DQFXYGGDN"))); - // 电能设置-组合无功 - dataLoad.setCurrentCombReactiveTotal(StringUtils.getBigDecimal(obj.get("DQZHWGZDN"))); - dataLoad.setCurrentCombReactivePeak(StringUtils.getBigDecimal(obj.get("DQZHWGJDN"))); - dataLoad.setCurrentCombReactiveHigh(StringUtils.getBigDecimal(obj.get("DQZHWGFDN"))); - dataLoad.setCurrentCombReactiveFlat(StringUtils.getBigDecimal(obj.get("DQZHWGPDN"))); - dataLoad.setCurrentCombReactiveValley(StringUtils.getBigDecimal(obj.get("DQZHWGGDN"))); - // 电能设置-正向无功 - dataLoad.setCurrentForwardReactiveTotal(StringUtils.getBigDecimal(obj.get("DQZXWGZDN"))); - dataLoad.setCurrentForwardReactivePeak(StringUtils.getBigDecimal(obj.get("DQZXWGJDN"))); - dataLoad.setCurrentForwardReactiveHigh(StringUtils.getBigDecimal(obj.get("DQZXWGFDN"))); - dataLoad.setCurrentForwardReactiveFlat(StringUtils.getBigDecimal(obj.get("DQZXWGPDN"))); - dataLoad.setCurrentForwardReactiveValley(StringUtils.getBigDecimal(obj.get("DQZXWGGDN"))); - // 电能设置-反向无功 - dataLoad.setCurrentReverseReactiveTotal(StringUtils.getBigDecimal(obj.get("DQFXWGZDN"))); - dataLoad.setCurrentReverseReactivePeak(StringUtils.getBigDecimal(obj.get("DQFXWGJDN"))); - dataLoad.setCurrentReverseReactiveHigh(StringUtils.getBigDecimal(obj.get("DQFXWGFDN"))); - dataLoad.setCurrentReverseReactiveFlat(StringUtils.getBigDecimal(obj.get("DQFXWGPDN"))); - dataLoad.setCurrentReverseReactiveValley(StringUtils.getBigDecimal(obj.get("DQFXWGGDN"))); - - // 电压+电流 - dataLoad.setPhaseAVoltage(StringUtils.getBigDecimal(obj.get("AXDY"))); - dataLoad.setPhaseBVoltage(StringUtils.getBigDecimal(obj.get("BXDY"))); - dataLoad.setPhaseCVoltage(StringUtils.getBigDecimal(obj.get("CXDY"))); - dataLoad.setPhaseACurrent(StringUtils.getBigDecimal(obj.get("AXDL"))); - dataLoad.setPhaseBCurrent(StringUtils.getBigDecimal(obj.get("BXDL"))); - dataLoad.setPhaseCCurrent(StringUtils.getBigDecimal(obj.get("CXDL"))); - - dataLoad.setAbLineVoltage(StringUtils.getBigDecimal(obj.get("ABXDY"))); - dataLoad.setCbLineVoltage(StringUtils.getBigDecimal(obj.get("CBXDY"))); - dataLoad.setAcLineVoltage(StringUtils.getBigDecimal(obj.get("ACXDY"))); - - // 频率 - dataLoad.setFrequency(StringUtils.getBigDecimal(obj.get("PL"))); - - // 功率 有功+总+无功+无总+视在 - dataLoad.setPhaseAActivePower(StringUtils.getBigDecimal(obj.get("AXYGGL"))); - dataLoad.setPhaseBActivePower(StringUtils.getBigDecimal(obj.get("BXYGGL"))); - dataLoad.setPhaseCActivePower(StringUtils.getBigDecimal(obj.get("CXYGGL"))); - dataLoad.setTotalActivePower(StringUtils.getBigDecimal(obj.get("ZYGGL"))); - dataLoad.setPhaseAReactivePower(StringUtils.getBigDecimal(obj.get("AXWGGL"))); - dataLoad.setPhaseBReactivePower(StringUtils.getBigDecimal(obj.get("BXWGGL"))); - dataLoad.setPhaseCReactivePower(StringUtils.getBigDecimal(obj.get("CXWGGL"))); - dataLoad.setTotalReactivePower(StringUtils.getBigDecimal(obj.get("ZWGGL"))); - dataLoad.setPhaseAApparentPower(StringUtils.getBigDecimal(obj.get("AXSZGL"))); - dataLoad.setPhaseBApparentPower(StringUtils.getBigDecimal(obj.get("BXSZGL"))); - dataLoad.setPhaseCApparentPower(StringUtils.getBigDecimal(obj.get("CXSZGL"))); - dataLoad.setTotalApparentPower(StringUtils.getBigDecimal(obj.get("ZSZGL"))); - - // 功率因数 - dataLoad.setPhaseAPowerFactor(StringUtils.getBigDecimal(obj.get("AXGLYS"))); - dataLoad.setPhaseBPowerFactor(StringUtils.getBigDecimal(obj.get("BXGLYS"))); - dataLoad.setPhaseCPowerFactor(StringUtils.getBigDecimal(obj.get("CXGLYS"))); - dataLoad.setTotalPowerFactor(StringUtils.getBigDecimal(obj.get("ZGLYS"))); - - // 需量 - dataLoad.setForwardAcMaxDemand(StringUtils.getBigDecimal(obj.get("ZXYGZDXL"))); - dataLoad.setReverseAcMaxDemand(StringUtils.getBigDecimal(obj.get("FXYGZDXL"))); - dataLoad.setDailyForwardMaxDemand(StringUtils.getBigDecimal(obj.get("DRZXYGZDXL"))); - - dataLoad.setCreateBy("system"); - dataLoad.setCreateTime(DateUtils.getNowDate()); - dataLoad.setUpdateBy("system"); - dataLoad.setUpdateTime(DateUtils.getNowDate()); - dataLoad.setSiteId(SITE_ID); - dataLoad.setDeviceId(deviceId); - - emsAmmeterDataMapper.insertEmsAmmeterData(dataLoad); - - redisCache.setCacheObject(RedisKeyConstants.AMMETER + SITE_ID + "_" +deviceId, dataLoad); - - } - - private void dealFXXDailyChargeDate(String deviceId, String dataJson) { - log.info("start dealFXXDailyChargeDate"); - //日充放电数据 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - // 初始化当日数据 - EmsDailyChargeData emsDailyChargeData = new EmsDailyChargeData(); - emsDailyChargeData.setSiteId(SITE_ID); - emsDailyChargeData.setDeviceId(deviceId); - emsDailyChargeData.setDateTime(DateUtils.getNowDate()); - emsDailyChargeData.setTotalChargeData(StringUtils.getBigDecimal(obj.get("ZCDL"))); - emsDailyChargeData.setTotalDischargeData(StringUtils.getBigDecimal(obj.get("ZFDL"))); - emsDailyChargeData.setChargeData(StringUtils.getBigDecimal(obj.get("RCDL"))); - emsDailyChargeData.setDischargeData(StringUtils.getBigDecimal(obj.get("RFDL"))); - emsDailyChargeData.setCreateBy("system"); - emsDailyChargeData.setCreateTime(DateUtils.getNowDate()); - emsDailyChargeData.setUpdateBy("system"); - emsDailyChargeData.setUpdateTime(DateUtils.getNowDate()); - // 插入或更新每日充放电数据表 - emsDailyChargeDataMapper.insertOrUpdateData(emsDailyChargeData); - log.info("end dealFXXDailyChargeDate"); - } - - private void meteDataProcess(String deviceId, String dataJson, Date dataUpdateTime) { - - //总表 - Map obj = JSON.parseObject(dataJson, new TypeReference>() { - }); - - // 获取上次数据,便于后面计算差值均无则默认0 - EmsAmmeterData lastAmmeterData = getLastAmmeterData(deviceId); - - EmsAmmeterData dataLoad = new EmsAmmeterData(); - // 更新时间 - dataLoad.setDataUpdateTime(dataUpdateTime); - // 电压+电流 - dataLoad.setPhaseAVoltage(StringUtils.getBigDecimal(obj.get("AXDY"))); - dataLoad.setPhaseBVoltage(StringUtils.getBigDecimal(obj.get("BXDY"))); - dataLoad.setPhaseCVoltage(StringUtils.getBigDecimal(obj.get("CXDY"))); - dataLoad.setPhaseACurrent(StringUtils.getBigDecimal(obj.get("AXDL"))); - dataLoad.setPhaseBCurrent(StringUtils.getBigDecimal(obj.get("BXDL"))); - dataLoad.setPhaseCCurrent(StringUtils.getBigDecimal(obj.get("CXDL"))); - - dataLoad.setAbLineVoltage(StringUtils.getBigDecimal(obj.get("ABXDY"))); - dataLoad.setCbLineVoltage(StringUtils.getBigDecimal(obj.get("BCXDY"))); - dataLoad.setAcLineVoltage(StringUtils.getBigDecimal(obj.get("CAXDY"))); - - // 频率 - dataLoad.setFrequency(StringUtils.getBigDecimal(obj.get("DWPL"))); - - // 功率 - dataLoad.setPhaseAActivePower(StringUtils.getBigDecimal(obj.get("AXYGGL"))); - dataLoad.setPhaseBActivePower(StringUtils.getBigDecimal(obj.get("BXYGGL"))); - dataLoad.setPhaseCActivePower(StringUtils.getBigDecimal(obj.get("CXYGGL"))); - dataLoad.setTotalActivePower(StringUtils.getBigDecimal(obj.get("ZYGGL"))); - dataLoad.setPhaseAReactivePower(StringUtils.getBigDecimal(obj.get("AXWGGL"))); - dataLoad.setPhaseBReactivePower(StringUtils.getBigDecimal(obj.get("BXWGGL"))); - dataLoad.setPhaseCReactivePower(StringUtils.getBigDecimal(obj.get("CXWGGL"))); - dataLoad.setTotalReactivePower(StringUtils.getBigDecimal(obj.get("ZWGGL"))); - - dataLoad.setTotalApparentPower(StringUtils.getBigDecimal(obj.get("ZSZGL"))); - dataLoad.setTotalPowerFactor(StringUtils.getBigDecimal(obj.get("ZGLYS"))); - - // 二次相关数据 - dataLoad.setSecondaryAbLineVoltage(StringUtils.getBigDecimal(obj.get("ECABXDY"))); - dataLoad.setSecondaryAPhaseCurrent(StringUtils.getBigDecimal(obj.get("ECAXDL"))); - dataLoad.setSecondaryAPhaseVoltage(StringUtils.getBigDecimal(obj.get("ECAXDY"))); - dataLoad.setSecondaryAPowerFactor(StringUtils.getBigDecimal(obj.get("ECAXGLYS"))); - dataLoad.setSecondaryAApparentPower(StringUtils.getBigDecimal(obj.get("ECAXSZGL"))); - dataLoad.setSecondaryAReactivePower(StringUtils.getBigDecimal(obj.get("ECAXWGGL"))); - dataLoad.setSecondaryAActivePower(StringUtils.getBigDecimal(obj.get("ECAXYGGL"))); - dataLoad.setSecondaryBcLineVoltage(StringUtils.getBigDecimal(obj.get("ECBCXDY"))); - dataLoad.setSecondaryBPhaseCurrent(StringUtils.getBigDecimal(obj.get("ECBXDL"))); - dataLoad.setSecondaryBPhaseVoltage(StringUtils.getBigDecimal(obj.get("ECBXDY"))); - dataLoad.setSecondaryBPowerFactor(StringUtils.getBigDecimal(obj.get("ECBXGLYS"))); - dataLoad.setSecondaryBApparentPower(StringUtils.getBigDecimal(obj.get("ECBXSZGL"))); - dataLoad.setSecondaryBReactivePower(StringUtils.getBigDecimal(obj.get("ECBXWGGL"))); - dataLoad.setSecondaryBActivePower(StringUtils.getBigDecimal(obj.get("ECBXYGGL"))); - dataLoad.setSecondaryCaLineVoltage(StringUtils.getBigDecimal(obj.get("ECCAXDY"))); - dataLoad.setSecondaryCPhaseCurrent(StringUtils.getBigDecimal(obj.get("ECCXDL"))); - dataLoad.setSecondaryCPhaseVoltage(StringUtils.getBigDecimal(obj.get("ECCXDY"))); - dataLoad.setSecondaryCPowerFactor(StringUtils.getBigDecimal(obj.get("ECCXGLYS"))); - dataLoad.setSecondaryCApparentPower(StringUtils.getBigDecimal(obj.get("ECCXSZGL"))); - dataLoad.setSecondaryCReactivePower(StringUtils.getBigDecimal(obj.get("ECCXWGGL"))); - dataLoad.setSecondaryCActivePower(StringUtils.getBigDecimal(obj.get("ECCXYGGL"))); - dataLoad.setSecondaryGridFrequency(StringUtils.getBigDecimal(obj.get("ECDWPL"))); - dataLoad.setSecondaryReverseReactiveEnergy(StringUtils.getBigDecimal(obj.get("ECFXWGDN"))); - dataLoad.setSecondaryNegativeActiveEnergy(StringUtils.getBigDecimal(obj.get("ECFXYGDN"))); - dataLoad.setSecondaryTotalPowerFactor(StringUtils.getBigDecimal(obj.get("ECZGLYS"))); - dataLoad.setSecondaryTotalApparentPower(StringUtils.getBigDecimal(obj.get("ECZSZFL"))); - dataLoad.setSecondaryTotalReactivePower(StringUtils.getBigDecimal(obj.get("ECZWGGL"))); - dataLoad.setSecondaryPositiveReactiveEnergy(StringUtils.getBigDecimal(obj.get("ECZXWGDN"))); - dataLoad.setSecondaryPositiveActiveEnergy(StringUtils.getBigDecimal(obj.get("ECZXYGDN"))); - dataLoad.setSecondaryTotalActivePower(StringUtils.getBigDecimal(obj.get("ECZYGGL"))); - - // 需量 - dataLoad.setReverseReactiveEnergyEqMinus(StringUtils.getBigDecimal(obj.get("FXWGDN"))); - dataLoad.setReverseActiveEnergyEpMinus(StringUtils.getBigDecimal(obj.get("FXYGDN"))); - dataLoad.setPositiveReactiveEnergyEqPlus(StringUtils.getBigDecimal(obj.get("ZXWGDN"))); - dataLoad.setPositiveActiveEnergyEpPlus(StringUtils.getBigDecimal(obj.get("ZXYGDN"))); - - // 正反向有功无功电能 - dataLoad.setCurrentForwardActiveTotal(StringUtils.getBigDecimal(obj.get("ZXYGDN"))); - dataLoad.setCurrentReverseActiveTotal(StringUtils.getBigDecimal(obj.get("FXYGDN"))); - dataLoad.setCurrentForwardReactiveTotal(StringUtils.getBigDecimal(obj.get("ZXWGDN"))); - dataLoad.setCurrentReverseReactiveTotal(StringUtils.getBigDecimal(obj.get("FXWGDN"))); - - dataLoad.setCreateBy("system"); - dataLoad.setCreateTime(DateUtils.getNowDate()); - dataLoad.setUpdateBy("system"); - dataLoad.setUpdateTime(DateUtils.getNowDate()); - dataLoad.setSiteId(SITE_ID); - dataLoad.setDeviceId(deviceId); - - emsAmmeterDataMapper.insertEmsAmmeterData(dataLoad); - - redisCache.setCacheObject(RedisKeyConstants.AMMETER + SITE_ID + "_" +deviceId, dataLoad); - - // 处理电表每日充放电数据 - dealAmmeterDailyDate(obj, dataUpdateTime, lastAmmeterData); - } - - private EmsAmmeterData getLastAmmeterData(String deviceId) { - // 先从redis取,取不到查数据 - EmsAmmeterData lastData = redisCache.getCacheObject(RedisKeyConstants.AMMETER + SITE_ID + "_" +deviceId); - if (lastData == null) { - lastData = emsAmmeterDataMapper.getLastData(SITE_ID,deviceId); - if (lastData == null) { - lastData = new EmsAmmeterData(); - lastData.setSiteId(SITE_ID); - lastData.setDeviceId(deviceId); - lastData.setCurrentForwardActiveTotal(BigDecimal.ZERO); - lastData.setCurrentReverseActiveTotal(BigDecimal.ZERO); - } - } - return lastData; - } - - private void dealAmmeterDailyDate(Map obj, Date dataUpdateTime, EmsAmmeterData lastData) { - // 先获取当月电价配置,redis没有这查数据库,都没有则返回 - String priceKey = RedisKeyConstants.ENERGY_PRICE_TIME + SITE_ID + "_" + LocalDate.now().getYear() + LocalDate.now().getMonthValue(); - EnergyPriceVo priceVo = redisCache.getCacheObject(priceKey); - if (priceVo == null) { - priceVo = emsEnergyPriceConfigService.getCurrentMonthPrice(SITE_ID); - redisCache.setCacheObject(priceKey, priceVo, 31, TimeUnit.DAYS); - if (priceVo == null) { - return; - } - } - List timeRanges = priceVo.getRange(); - if (timeRanges == null) { - return; - } - - // 根据时间范围判断数据类型(尖峰平谷),无法确定数据类型则不处理 - String costType = ""; - String startTime = ""; - for (EnergyPriceTimeRange timeRange : timeRanges) { - startTime = timeRange.getStartTime(); - if (isInPriceTimeRange(startTime, timeRange.getEndTime(), dataUpdateTime)) { - costType = timeRange.getCostType(); - break; - } - } - if (StringUtils.isEmpty(costType)) { - return; - } - - //初始化电表每日差值对象 - EmsDailyEnergyData energyData = initEnergyData(); - - // 根据 costType,计算本次与上次数据差值,累加到对应的数据类型里面 - setDiffByCostType(costType,energyData,lastData,obj,priceVo); - - // 插入或更新电表每日差值数据表 - emsDailyEnergyDataMapper.insertOrUpdateData(energyData); - } - - private void setDiffByCostType(String costType, EmsDailyEnergyData energyData, EmsAmmeterData lastData, - Map obj, EnergyPriceVo priceVo) { - BigDecimal currentChargeData = StringUtils.getBigDecimal(obj.get("ZXYGDN")); - BigDecimal currentDischargeData = StringUtils.getBigDecimal(obj.get("FXYGDN")); - currentChargeData = currentChargeData != null ? currentChargeData : BigDecimal.ZERO; - currentDischargeData = currentDischargeData != null ? currentDischargeData : BigDecimal.ZERO; - - // 获取上次实时总收益+当日实时总收益,初始化电价 - Map revenueMap = getRealTimeData(SITE_ID); - BigDecimal totalRevenue = revenueMap.get("totalRevenue") == null ? BigDecimal.ZERO : revenueMap.get("totalRevenue"); - BigDecimal dayRevenue = revenueMap.get("dayRevenue") == null ? BigDecimal.ZERO : revenueMap.get("dayRevenue"); - BigDecimal price = BigDecimal.ZERO; - // 计算时段差值,按照数据类型累加 - // 计算当日累加收益,尖峰平谷收益在当日原基础累加,(放电量-充电量)*电价 - BigDecimal chargeDiffData = currentChargeData.subtract( - lastData.getCurrentForwardActiveTotal() == null ? BigDecimal.ZERO : lastData.getCurrentForwardActiveTotal()); - BigDecimal disChargeDiffData = currentDischargeData.subtract( - lastData.getCurrentReverseActiveTotal() == null ? BigDecimal.ZERO : lastData.getCurrentReverseActiveTotal() - ); - switch (costType) { - case "peak": - // 增加电量 - BigDecimal peakCharge = energyData.getPeakChargeDiff() == null ? BigDecimal.ZERO : energyData.getPeakChargeDiff(); - energyData.setPeakChargeDiff(peakCharge.add(chargeDiffData)); - BigDecimal peakDischarge = energyData.getPeakDischargeDiff() == null ? BigDecimal.ZERO : energyData.getPeakDischargeDiff(); - energyData.setPeakDischargeDiff(peakDischarge.add(disChargeDiffData)); - - // 电价 - price = priceVo.getPeak() == null ? BigDecimal.ZERO : priceVo.getPeak(); - break; - case "high": - // 增加电量 - BigDecimal highCharge = energyData.getHighChargeDiff() == null ? BigDecimal.ZERO : energyData.getHighChargeDiff(); - energyData.setHighChargeDiff(highCharge.add(chargeDiffData)); - BigDecimal highDischarge = energyData.getHighDischargeDiff() == null ? BigDecimal.ZERO : energyData.getHighDischargeDiff(); - energyData.setHighDischargeDiff(highDischarge.add(disChargeDiffData)); - - // 电价 - price = priceVo.getHigh() == null ? BigDecimal.ZERO : priceVo.getHigh(); - break; - case "flat": - // 增加电量 - BigDecimal flatCharge = energyData.getFlatChargeDiff() == null ? BigDecimal.ZERO : energyData.getFlatChargeDiff(); - energyData.setFlatChargeDiff(flatCharge.add(chargeDiffData)); - BigDecimal flatDischarge = energyData.getFlatDischargeDiff() == null ? BigDecimal.ZERO : energyData.getFlatDischargeDiff(); - energyData.setFlatDischargeDiff(flatDischarge.add(disChargeDiffData)); - - // 电价 - price = priceVo.getFlat() == null ? BigDecimal.ZERO : priceVo.getFlat(); - break; - case "valley": - // 增加电量 - BigDecimal valleyCharge = energyData.getValleyChargeDiff() == null ? BigDecimal.ZERO : energyData.getValleyChargeDiff(); - energyData.setValleyChargeDiff(valleyCharge.add(chargeDiffData)); - BigDecimal valleyDischarge = energyData.getValleyDischargeDiff() == null ? BigDecimal.ZERO : energyData.getValleyDischargeDiff(); - energyData.setValleyDischargeDiff(valleyDischarge.add(disChargeDiffData)); - - // 电价 - price = priceVo.getValley() == null ? BigDecimal.ZERO : priceVo.getValley(); - break; - default: - return; - } - - // 计算本次累加收益 - BigDecimal addRevenue = disChargeDiffData.subtract(chargeDiffData).multiply(price); - dayRevenue = dayRevenue.add(addRevenue); - energyData.setDayRevenue(dayRevenue); - // 总收益 = 上次实时总收益+今日实时增加的收益 - totalRevenue = totalRevenue.add(addRevenue); - energyData.setTotalRevenue(totalRevenue); - - // 存redis便于下次取用 - String today = DateUtils.getDate(); - String redisKey = RedisKeyConstants.FXX_REALTIME_REVENUE + SITE_ID + "_" + today; - Map realTimeRevenue = new HashMap<>(); - realTimeRevenue.put("totalRevenue", totalRevenue); - realTimeRevenue.put("dayRevenue",dayRevenue); - redisCache.setCacheObject(redisKey, realTimeRevenue, 1, TimeUnit.DAYS); - } - - private Map getRealTimeData(String siteId) { - // fx取实时总收益和当天实时收益 - String today = DateUtils.getDate(); - String redisKey = RedisKeyConstants.FXX_REALTIME_REVENUE + siteId + "_" + today; - Map realTimeRevenue = redisCache.getCacheObject(redisKey); - if (realTimeRevenue == null) { - // 查数据库 - realTimeRevenue = iEmsEnergyPriceConfigService.getDayRevenueMap(SITE_ID); - if (realTimeRevenue == null) { - realTimeRevenue = new HashMap<>(); - realTimeRevenue.put("totalRevenue", BigDecimal.ZERO); - realTimeRevenue.put("dayRevenue", BigDecimal.ZERO); - } - redisCache.setCacheObject(redisKey, realTimeRevenue, 1, TimeUnit.DAYS); - } - return realTimeRevenue; - } - - private boolean isInPriceTimeRange(String startTime, String endTime, Date dataUpdateTime) { - if (startTime == null || endTime == null || dataUpdateTime == null) { - return false; - } - LocalDateTime time = DateUtils.toLocalDateTime(dataUpdateTime); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); - String dataTimeStr = time.format(formatter); - // 比较时间范围 - return dataTimeStr.compareTo(startTime) >= 0 - && dataTimeStr.compareTo(endTime) < 0; - } - - private EmsDailyEnergyData initEnergyData() { - // 先获取数据库当天数据,存在则更新时间,不存在则初始化 - EmsDailyEnergyData energyData = emsDailyEnergyDataMapper.getDataByDate(SITE_ID,DateUtils.getDate()); - if (energyData == null) { - energyData = new EmsDailyEnergyData(); - energyData.setSiteId(SITE_ID); - energyData.setDataDate(DateUtils.getNowDate()); - energyData.setCreateBy("system"); - energyData.setCreateTime(DateUtils.getNowDate()); - } - energyData.setUpdateBy("system"); - energyData.setCalcTime(DateUtils.getNowDate()); - return energyData; - } - - // 数据分组处理 - private static Map> processData(Map rawData) { - Map> records = new HashMap<>(); - - for (Map.Entry entry : rawData.entrySet()) { - String key = entry.getKey(); - // 提取记录ID(最后3位) - String recordId = key.substring(key.length() - 3); - try { - Long.parseLong(recordId); - } catch (Exception e) { - continue; - } - - // 提取字段类型(前缀) - String fieldType = key.substring(0, key.length() - 3); - - // 初始化记录 - records.putIfAbsent(recordId, new HashMap<>()); - // 存入字段值 - records.get(recordId).put(fieldType, entry.getValue()); - } - return records; - } - - private static Map> processDataPrefix(Map rawData) { - Map> records = new HashMap<>(); - - for (Map.Entry entry : rawData.entrySet()) { - String key = entry.getKey(); - // 确保键长度足够 - if (key.length() < 3) { - continue; - } - - // 提取记录ID(前3位) - String recordId = key.substring(0, 3); - if (!recordId.startsWith("DY")) { - continue; - } - - // 提取字段类型(剩余部分) - String fieldType = key.substring(3); - - // 初始化记录 - records.putIfAbsent(recordId, new HashMap<>()); - // 存入字段值 - records.get(recordId).put(fieldType, entry.getValue()); - } - return records; - } - - @Override - public void processBatch(List batchData) { - super.processBatch(batchData); - } - - // 空数据不处理 - private boolean checkJsonDataEmpty(String jsonData) { - boolean flag = false; - try { - if (StringUtils.isEmpty(jsonData)) { - flag = true; - } - JsonNode jsonNode = objectMapper.readTree(jsonData); - // 判断是否为空对象({}) - if (jsonNode.isObject() && jsonNode.isEmpty()) { - flag = true ; - } - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - return flag; - } -} diff --git a/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java b/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java index 5df0d62..07a28ea 100644 --- a/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java +++ b/ems-system/src/main/java/com/xzzn/ems/service/impl/GeneralQueryServiceImpl.java @@ -2,16 +2,17 @@ package com.xzzn.ems.service.impl; import com.xzzn.common.enums.DeviceCategory; import com.xzzn.common.utils.DateUtils; +import com.xzzn.ems.domain.EmsPointConfig; import com.xzzn.ems.domain.EmsPointMatch; -import com.xzzn.ems.domain.vo.DevicePointDataList; -import com.xzzn.ems.domain.vo.GeneralQueryDataVo; -import com.xzzn.ems.domain.vo.GeneralQueryResponse; -import com.xzzn.ems.domain.vo.PointNameRequest; -import com.xzzn.ems.domain.vo.SiteBatteryListVo; +import com.xzzn.ems.domain.vo.*; import com.xzzn.ems.mapper.EmsBatteryDataMonthMapper; +import com.xzzn.ems.mapper.EmsPointConfigMapper; import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsPointMatchMapper; import com.xzzn.ems.service.IGeneralQueryService; +import com.xzzn.ems.service.InfluxPointDataWriter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.text.ParseException; @@ -19,24 +20,9 @@ import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.TemporalAdjusters; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; +import java.util.*; import java.util.stream.Collectors; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; - /** * 综合查询 服务层实现 * @@ -49,23 +35,26 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService @Autowired private EmsPointMatchMapper emsPointMatchMapper; @Autowired + private EmsPointConfigMapper emsPointConfigMapper; + @Autowired private EmsDevicesSettingMapper emsDevicesSettingMapper; @Autowired private EmsBatteryDataMonthMapper emsBatteryDataMonthMapper; + @Autowired + private InfluxPointDataWriter influxPointDataWriter; @Override - public List getPointNameList(PointNameRequest request) { + public List getPointNameList(PointNameRequest request) { List siteIds = request.getSiteIds(); if (siteIds == null || siteIds.isEmpty()) { return Collections.emptyList(); } - - String deviceCategory = request.getDeviceCategory(); - if (deviceCategory == null) { - return Collections.emptyList(); - } - - return emsPointMatchMapper.getPointNameList(siteIds,deviceCategory,request.getPointName()); + return emsPointConfigMapper.getPointNameList( + siteIds, + request.getDeviceCategory(), + request.getDeviceId(), + request.getPointName() + ); } @Override @@ -96,86 +85,190 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService @Override public List getPointValueList(PointNameRequest request){ - List result = new ArrayList<>(); - List querySiteIds = new ArrayList<>(); - List siteIds = request.getSiteIds(); - String deviceId = request.getDeviceId(); - String deviceCategory = request.getDeviceCategory(); - // 处理点位名称 - String pointName = request.getPointName(); - // 根据入参获取点位对应的表和字段 - List matchInfo = emsPointMatchMapper.getMatchInfo(siteIds,deviceId,deviceCategory,pointName); - if (CollectionUtils.isEmpty(matchInfo) && DeviceCategory.AMMETER.getCode().equals(deviceCategory)) { - pointName = processPointName(pointName); - matchInfo = emsPointMatchMapper.getMatchInfo(siteIds,deviceId,deviceCategory,pointName); - } - if (CollectionUtils.isEmpty(matchInfo)) { - return result; - } else { - for (EmsPointMatch emsPointMatch : matchInfo) { - querySiteIds.add(emsPointMatch.getSiteId()); - } + if (siteIds == null || siteIds.isEmpty()) { + return Collections.emptyList(); } - // 单体电池特殊校验 - Map> siteDeviceMap = request.getSiteDeviceMap(); - if (DeviceCategory.BATTERY.getCode().equals(deviceCategory) && (siteDeviceMap == null || siteDeviceMap.size() == 0)) { - return result; + String deviceCategory = request.getDeviceCategory(); + List pointIds = resolvePointIds(request); + + List pointNames = resolvePointNames(request); + if (pointIds.isEmpty() && pointNames.isEmpty()) { + return Collections.emptyList(); } // 处理时间范围,如果未传根据数据单位设默认值 dealDataTime(request); + Date startDate = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, request.getStartDate()); + Date endDate = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, request.getEndDate()); + if (request.getDataUnit() == 3) { + endDate = DateUtils.adjustToEndOfDay(request.getEndDate()); + } - try { - // 不同的site_id根据设备类型和字段,默认取第一个匹配到的表和表字段只会有一个, - EmsPointMatch pointMatch = matchInfo.get(0); - String finalPointName = pointName; - Optional emsPointMatch = matchInfo.stream() - .filter(item -> finalPointName.equals(item.getPointName()) || finalPointName.equals(item.getDataPointName())) - .findFirst(); - if (emsPointMatch.isPresent()) { - pointMatch = emsPointMatch.get(); + List selectedDeviceIds = pointIds.isEmpty() ? resolveSelectedDeviceIds(request) : Collections.emptyList(); + List pointConfigs = emsPointConfigMapper.getConfigListForGeneralQuery( + siteIds, deviceCategory, pointIds, pointNames, selectedDeviceIds + ); + if (pointConfigs == null || pointConfigs.isEmpty()) { + return Collections.emptyList(); + } + Map selectedPointNameById = buildSelectedPointNameById(request); + + List dataVoList = new ArrayList<>(); + for (EmsPointConfig pointConfig : pointConfigs) { + String selectedPointName = selectedPointNameById.get(resolveInfluxPointKey(pointConfig)); + dataVoList.addAll(queryPointCurve(pointConfig, request.getDataUnit(), startDate, endDate, selectedPointName)); + } + + if (dataVoList.isEmpty()) { + return Collections.emptyList(); + } + + if (request.getDataUnit() == 1) { + try { + dataVoList = dealWithMinutesData(siteIds, dataVoList, deviceCategory, request.getStartDate(), request.getEndDate()); + } catch (ParseException e) { + throw new RuntimeException(e); } - String tableName = pointMatch.getMatchTable(); - String tableField = pointMatch.getMatchField(); - Long dataType = pointMatch.getDataType(); - if (DeviceCategory.BATTERY.getCode().equals(deviceCategory)) { - // 单体电池数据特殊处理 - result = generalQueryBatteryData(querySiteIds,tableName,tableField,request,deviceCategory,dataType); - } else { - // 其他设备数据 - result = generalQueryCommonData(querySiteIds,tableName,tableField,request,deviceCategory,dataType); + } + return convertCommonToResultList(dataVoList, 1L); + } + + private List resolvePointNames(PointNameRequest request) { + List names = new ArrayList<>(); + if (request.getPointNames() != null && !request.getPointNames().isEmpty()) { + names.addAll(request.getPointNames()); + } else if (request.getPointName() != null && !"".equals(request.getPointName().trim())) { + names.addAll(Arrays.stream(request.getPointName().split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList())); + } + return names.stream().distinct().collect(Collectors.toList()); + } + + private List resolvePointIds(PointNameRequest request) { + List ids = new ArrayList<>(); + if (request.getPointIds() != null && !request.getPointIds().isEmpty()) { + ids.addAll(request.getPointIds()); + } else if (request.getPointId() != null && !"".equals(request.getPointId().trim())) { + ids.addAll(Arrays.stream(request.getPointId().split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList())); + } + return ids.stream().distinct().collect(Collectors.toList()); + } + + private List resolveSelectedDeviceIds(PointNameRequest request) { + List selected = new ArrayList<>(); + if (request.getDeviceId() != null && !"".equals(request.getDeviceId().trim())) { + selected.add(request.getDeviceId().trim()); + } + Map> siteDeviceMap = request.getSiteDeviceMap(); + if (siteDeviceMap != null && !siteDeviceMap.isEmpty()) { + for (List devices : siteDeviceMap.values()) { + if (devices == null) { + continue; + } + for (String deviceId : devices) { + if (deviceId != null && !"".equals(deviceId.trim())) { + selected.add(deviceId.trim()); + } + } } - } catch (ParseException e) { - throw new RuntimeException(e); + } + return selected.stream().distinct().collect(Collectors.toList()); + } + + private List queryPointCurve(EmsPointConfig config, int dataUnit, Date startDate, Date endDate, + String selectedPointName) { + if (config == null || config.getSiteId() == null) { + return Collections.emptyList(); + } + String influxPointKey = resolveInfluxPointKey(config); + if (influxPointKey == null) { + return Collections.emptyList(); + } + // 与点位列表曲线保持一致:按 siteId + pointKey 查询,避免 deviceId 维度导致综合查询漏数 + List values = influxPointDataWriter.queryCurveDataByPointKey( + config.getSiteId(), influxPointKey, startDate, endDate + ); + if (values == null || values.isEmpty()) { + return Collections.emptyList(); + } + + // 每个时间桶取该桶最后一条值 + Map latestByBucket = new LinkedHashMap<>(); + for (InfluxPointDataWriter.PointValue value : values) { + latestByBucket.put(formatByDataUnit(value.getDataTime(), dataUnit), value.getPointValue()); + } + + List result = new ArrayList<>(); + String displayDeviceId = buildDisplayDeviceId(config, selectedPointName); + for (Map.Entry entry : latestByBucket.entrySet()) { + GeneralQueryDataVo vo = new GeneralQueryDataVo(); + vo.setSiteId(config.getSiteId()); + vo.setDeviceId(displayDeviceId); + vo.setValueDate(entry.getKey()); + vo.setPointValue(entry.getValue()); + result.add(vo); } return result; } - private String processPointName(String pointName) { - // 特殊处理 - if ("正向有功电能".equals(pointName)) { - pointName = "当前正向总有功电能"; + private String resolveInfluxPointKey(EmsPointConfig config) { + if (config == null) { + return null; } - if ("反向有功电能".equals(pointName)) { - pointName = "当前反向总有功电能"; + if (config.getPointId() != null && !"".equals(config.getPointId().trim())) { + return config.getPointId().trim(); } - if ("正向无功电能".equals(pointName)) { - pointName = "当前正向总无功电能"; - } - if ("反向无功电能".equals(pointName)) { - pointName = "当前反向总无功电能"; - } - if ("有功功率".equals(pointName)) { - pointName = "功率"; - } - if ("无功功率".equals(pointName)) { - pointName = "总无功功率"; + return null; + } + + private String buildDisplayDeviceId(EmsPointConfig config, String selectedPointName) { + if (selectedPointName != null && !"".equals(selectedPointName.trim())) { + return selectedPointName.trim(); } + String pointName = config.getPointName() == null || "".equals(config.getPointName().trim()) + ? config.getDataKey() : config.getPointName().trim(); return pointName; } + private Map buildSelectedPointNameById(PointNameRequest request) { + Map selectedNameById = new HashMap<>(); + if (request == null || request.getPointIds() == null || request.getPointNames() == null) { + return selectedNameById; + } + List pointIds = request.getPointIds(); + List pointNames = request.getPointNames(); + int size = Math.min(pointIds.size(), pointNames.size()); + for (int i = 0; i < size; i++) { + String pointId = pointIds.get(i); + String pointName = pointNames.get(i); + if (pointId == null || "".equals(pointId.trim()) || pointName == null || "".equals(pointName.trim())) { + continue; + } + selectedNameById.put(pointId.trim(), pointName.trim()); + } + return selectedNameById; + } + + private String formatByDataUnit(Date dataTime, int dataUnit) { + if (dataTime == null) { + return ""; + } + if (dataUnit == 3) { + return new SimpleDateFormat("yyyy-MM-dd").format(dataTime); + } + if (dataUnit == 2) { + return new SimpleDateFormat("yyyy-MM-dd HH:00").format(dataTime); + } + return new SimpleDateFormat("yyyy-MM-dd HH:mm:00").format(dataTime); + } + private List generalQueryCommonData(List querySiteIds, String tableName, String tableField, PointNameRequest request, String deviceCategory, Long dataType) throws ParseException { @@ -384,7 +477,8 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService // 4. 构建DeviceItem return new DevicePointDataList(deviceId, pointValueList,parentDeviceId, - stats.max, stats.min,stats.avg,stats.diff,stats.maxDate,stats.minDate); + stats.max, stats.min,stats.avg,stats.diff,stats.maxDate,stats.minDate, + stats.q1, stats.median, stats.q3); })// 关键排序步骤:先按deviceId升序,再按parentDeviceId升序 .sorted( Comparator.comparing(DevicePointDataList::getDeviceId) // 第一排序键:deviceId @@ -420,7 +514,8 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService Stats stats = clacStats(deviceDataList); return new DevicePointDataList(deviceId, deviceDataList,null, - stats.max, stats.min,stats.avg,stats.diff,stats.maxDate,stats.minDate); + stats.max, stats.min,stats.avg,stats.diff,stats.maxDate,stats.minDate, + stats.q1, stats.median, stats.q3); }) .collect(Collectors.toList()); @@ -443,7 +538,7 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService .collect(Collectors.toList()); if (validPairs.isEmpty()) { - return new Stats(null, null, null, null, null, null); + return new Stats(null, null, null, null, null, null, null, null, null); } // 计算最大最小值 Optional maxPair = validPairs.stream().max((p1, p2) -> p1.value.compareTo(p2.value)); @@ -453,8 +548,17 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService BigDecimal avgValue = sum.divide(BigDecimal.valueOf(validPairs.size()), 4, BigDecimal.ROUND_HALF_UP); // 增量数据,计算差值 BigDecimal diff = maxPair.get().value.subtract(minPair.get().value); + // 计算分位数统计值 + List sortedValues = validPairs.stream() + .map(pair -> pair.value) + .sorted() + .collect(Collectors.toList()); + BoxPlotData boxStats = calculateBoxPlotData(sortedValues); - return new Stats(maxPair.get().value,minPair.get().value,avgValue,diff,maxPair.get().time,minPair.get().time); + return new Stats(maxPair.get().value,minPair.get().value,avgValue,diff,maxPair.get().time,minPair.get().time, + boxStats == null ? null : boxStats.q1, + boxStats == null ? null : boxStats.median, + boxStats == null ? null : boxStats.q3); } private BigDecimal convertToBigDecimal(Object pointValue) { @@ -707,15 +811,22 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService private final String minDate; private final BigDecimal avg; private final BigDecimal diff; + private final BigDecimal q1; + private final BigDecimal median; + private final BigDecimal q3; public Stats(BigDecimal max, BigDecimal min, BigDecimal avg, BigDecimal diff, - String maxDate, String minDate) { + String maxDate, String minDate, + BigDecimal q1, BigDecimal median, BigDecimal q3) { this.max = max; this.maxDate = maxDate; this.min = min; this.minDate = minDate; this.avg = avg; this.diff = diff; + this.q1 = q1; + this.median = median; + this.q3 = q3; } } 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 937bea0..a7e9b63 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,9 +12,11 @@ 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.EmsDailyChargeData; +import com.xzzn.ems.domain.EmsDevicesSetting; import com.xzzn.ems.domain.EmsEmsData; import com.xzzn.ems.domain.EmsPcsBranchData; -import com.xzzn.ems.domain.EmsPcsData; +import com.xzzn.ems.domain.EmsSiteMonitorPointMatch; import com.xzzn.ems.domain.EmsStrategyTemp; import com.xzzn.ems.domain.EmsXfData; import com.xzzn.ems.domain.vo.*; @@ -28,12 +30,16 @@ import com.xzzn.ems.mapper.EmsDevicesSettingMapper; import com.xzzn.ems.mapper.EmsEnergyPriceConfigMapper; import com.xzzn.ems.mapper.EmsPcsDataMapper; import com.xzzn.ems.mapper.EmsPointMatchMapper; +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; +import java.beans.PropertyDescriptor; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; @@ -42,6 +48,7 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -53,7 +60,9 @@ import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -65,6 +74,29 @@ import org.springframework.util.CollectionUtils; public class SingleSiteServiceImpl implements ISingleSiteService { private static final Logger log = LoggerFactory.getLogger(SingleSiteServiceImpl.class); + private static final String RUNNING_GRAPH_DEBUG = "RunningGraphDebug"; + private static final String FIELD_CURVE_PCS_ACTIVE_POWER = "SBJK_SSYX__curvePcsActivePower"; + private static final String FIELD_CURVE_PCS_REACTIVE_POWER = "SBJK_SSYX__curvePcsReactivePower"; + private static final String FIELD_CURVE_PCS_MAX_TEMP = "SBJK_SSYX__curvePcsMaxTemp"; + private static final String FIELD_CURVE_BATTERY_AVE_SOC = "SBJK_SSYX__curveBatteryAveSoc"; + private static final String FIELD_CURVE_BATTERY_AVE_TEMP = "SBJK_SSYX__curveBatteryAveTemp"; + private static final String FIELD_TOTAL_ACTIVE_POWER = "SBJK_SSYX__totalActivePower"; + private static final String FIELD_TOTAL_REACTIVE_POWER = "SBJK_SSYX__totalReactivePower"; + private static final String FIELD_SOC = "SBJK_SSYX__soc"; + private static final String FIELD_HOME_AVG_TEMP = "HOME__avgTemp"; + private static final String RUNNING_GRAPH_DEFAULT_DEVICE = "SITE"; + private static final int USE_FIXED_DISPLAY_YES = 1; + private static final String DEVICE_INFO_ID = "id"; + private static final String DEVICE_INFO_NAME = "deviceName"; + private static final String DEVICE_INFO_COMM_STATUS = "communicationStatus"; + private static final String DEVICE_INFO_DEVICE_STATUS = "deviceStatus"; + private static final Set 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 = "温度"; @@ -90,6 +122,10 @@ public class SingleSiteServiceImpl implements ISingleSiteService { private EmsDevicesSettingMapper emsDevicesSettingMapper; @Autowired private EmsPointMatchMapper emsPointMatchMapper; + @Autowired + private EmsSiteMonitorPointMatchMapper emsSiteMonitorPointMatchMapper; + @Autowired + private InfluxPointDataWriter influxPointDataWriter; @Autowired private RedisCache redisCache; @@ -106,6 +142,8 @@ public class SingleSiteServiceImpl implements ISingleSiteService { @Autowired private IEmsEnergyPriceConfigService iEmsEnergyPriceConfigService; @Autowired + private IEmsDeviceSettingService iEmsDeviceSettingService; + @Autowired private DevicePointMatchDataProcessor devicePointMatchDataProcessor; @Override @@ -178,6 +216,60 @@ public class SingleSiteServiceImpl implements ISingleSiteService { return siteMonitorHomeVo; } + @Override + public SiteMonitorHomeVo getSiteMonitorTotalDataVo(String siteId) { + SiteMonitorHomeVo siteMonitorHomeVo = new SiteMonitorHomeVo(); + if (StringUtils.isEmpty(siteId)) { + return siteMonitorHomeVo; + } + + String nowDate = DateUtils.getDate(); + Map chargeDataMap = emsDailyChargeDataMapper.getAllSiteChargeData(nowDate, siteId); + BigDecimal totalChargedCap = BigDecimal.ZERO; + BigDecimal totalDischargedCap = BigDecimal.ZERO; + if (chargeDataMap != null) { + totalChargedCap = chargeDataMap.get("totalChargedCap") == null ? BigDecimal.ZERO : chargeDataMap.get("totalChargedCap"); + totalDischargedCap = chargeDataMap.get("totalDischargedCap") == null ? BigDecimal.ZERO : chargeDataMap.get("totalDischargedCap"); + } + siteMonitorHomeVo.setTotalChargedCap(totalChargedCap); + siteMonitorHomeVo.setTotalDischargedCap(totalDischargedCap); + + Map revenueMap = emsDailyEnergyDataMapper.getRealTimeRevenue(siteId); + siteMonitorHomeVo.setTotalRevenue(revenueMap == null || revenueMap.get("totalRevenue") == null ? BigDecimal.ZERO : revenueMap.get("totalRevenue")); + siteMonitorHomeVo.setDayRevenue(revenueMap == null || revenueMap.get("dayRevenue") == null ? BigDecimal.ZERO : revenueMap.get("dayRevenue")); + + Date yesterdayDate = DateUtils.toDate(LocalDate.now().minusDays(1)); + EmsDailyChargeData yesterdayChargeData = emsDailyChargeDataMapper.selectBySiteIdAndDateTime(siteId, yesterdayDate); + siteMonitorHomeVo.setYesterdayRevenue(yesterdayChargeData == null || yesterdayChargeData.getDayRevenue() == null + ? BigDecimal.ZERO + : yesterdayChargeData.getDayRevenue()); + + List alarmList = emsAlarmRecordsMapper.getAlarmRecordsBySiteId(siteId); + siteMonitorHomeVo.setSiteMonitorHomeAlarmVo(alarmList); + + LocalDate sevenDaysAgo = LocalDate.now().minusDays(6); + Date startDate = DateUtils.toDate(sevenDaysAgo); + Date endDate = new Date(); + List siteMonitorDataVoList = emsDailyChargeDataMapper.getSingleSiteChargeData(siteId, startDate, endDate); + if (!CollectionUtils.isEmpty(siteMonitorDataVoList)) { + for (SiteMonitorDataVo sitePcsData : siteMonitorDataVoList) { + String pcsDate = sitePcsData.getAmmeterDate(); + if (DateUtils.checkIsToday(pcsDate)) { + siteMonitorHomeVo.setDayChargedCap(sitePcsData.getChargedCap()); + siteMonitorHomeVo.setDayDisChargedCap(sitePcsData.getDisChargedCap()); + } + if (DateUtils.getYesterdayDayString().equals(pcsDate)) { + siteMonitorHomeVo.setYesterdayChargedCap(sitePcsData.getChargedCap()); + siteMonitorHomeVo.setYesterdayDisChargedCap(sitePcsData.getDisChargedCap()); + } + } + } + + siteMonitorHomeVo.setEnergyStorageAvailElec(totalDischargedCap.subtract(totalChargedCap)); + siteMonitorHomeVo.setSiteMonitorDataVo(siteMonitorDataVoList); + return siteMonitorHomeVo; + } + // 获取单站监控实时运行头部数据 @Override public SiteMonitorRunningHeadInfoVo getSiteRunningHeadInfo(String siteId) { @@ -217,46 +309,61 @@ public class SingleSiteServiceImpl implements ISingleSiteService { public SiteMonitorRuningInfoVo getRunningGraphStorage(RunningGraphRequest request) { SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo(); if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) { + log.info("{} storage skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request); return siteMonitorRuningInfoVo; } -// // 时间暂定今日+昨日 -// Date today = DateUtils.getNowDate(); -// Date yesterday = DateUtils.addDays(today, -1); - Date startDate = request.getStartDate(); - Date endDate = request.getEndDate(); + String siteId = request.getSiteId(); + Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate()); + Date startDate = dateRange[0]; + Date endDate = dateRange[1]; + List mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId); + Map mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList); + List pcsDeviceList = getDeviceMetaListByCategory(siteId, DeviceCategory.PCS.getCode()); + if (CollectionUtils.isEmpty(pcsDeviceList)) { + pcsDeviceList = Collections.singletonList(new DeviceMeta(RUNNING_GRAPH_DEFAULT_DEVICE, RUNNING_GRAPH_DEFAULT_DEVICE)); + } - //pcs有功无功 List pcsPowerList = new ArrayList<>(); - List energyStoragePowList = emsPcsDataMapper.getStoragePowerList(request.getSiteId(), startDate, endDate); + for (DeviceMeta pcsDevice : pcsDeviceList) { + String deviceId = StringUtils.defaultString(pcsDevice.getDeviceId()); + String activePointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_ACTIVE_POWER); + String reactivePointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_REACTIVE_POWER); - // List -> 按pcs的deviceId分组转成List - if (!CollectionUtils.isEmpty(energyStoragePowList)) { - Map> dataMap = energyStoragePowList.stream() - .collect(Collectors.groupingBy( - EnergyStoragePowVo::getDeviceId, - Collectors.toList())); + List activeValues = queryInfluxPointValues(siteId, activePointId, startDate, endDate); + List reactiveValues = queryInfluxPointValues(siteId, reactivePointId, startDate, endDate); + Map reactiveByTs = reactiveValues.stream() + .filter(v -> v != null && v.getDataTime() != null && v.getPointValue() != null) + .collect(Collectors.toMap(v -> v.getDataTime().getTime(), InfluxPointDataWriter.PointValue::getPointValue, (a, b) -> b)); - pcsPowerList = dataMap.entrySet().stream() - .map(entry -> { - PcsPowerList pcdData = new PcsPowerList(); - pcdData.setDeviceId(entry.getKey()); - pcdData.setEnergyStoragePowList(entry.getValue()); - return pcdData; - }).collect(Collectors.toList()); + List energyStoragePowList = new ArrayList<>(); + for (InfluxPointDataWriter.PointValue activeValue : activeValues) { + if (activeValue == null || activeValue.getDataTime() == null || activeValue.getPointValue() == null) { + continue; + } + Date dataTime = activeValue.getDataTime(); + EnergyStoragePowVo vo = new EnergyStoragePowVo(); + vo.setDeviceId(deviceId); + vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime)); + vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime)); + vo.setGroupTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, dataTime)); + vo.setPcsTotalActPower(activeValue.getPointValue()); + vo.setPcsTotalReactivePower(reactiveByTs.get(dataTime.getTime())); + energyStoragePowList.add(vo); + } -// // 生成时间列表(每5分钟一个) -// List targetMinutes = new ArrayList<>(12); -// LocalDateTime startLocalDate = DateUtils.toLocalDateTime(startDate).truncatedTo(ChronoUnit.DAYS); -// LocalDateTime endLocalDate = DateUtils.toLocalDateTime(endDate).with(LocalDateTime.now().toLocalTime()); -// while (startLocalDate.isBefore(endLocalDate)) { -// targetMinutes.add(startLocalDate); -// startLocalDate = startLocalDate.plusMinutes(5); // 递增5分钟 -// } -// // 根据时间列表填充数据 -// pcsPowerList = fullFillData(pcsPowerList, targetMinutes); + PcsPowerList pcdData = new PcsPowerList(); + pcdData.setDeviceId(deviceId); + pcdData.setEnergyStoragePowList(energyStoragePowList); + pcsPowerList.add(pcdData); } siteMonitorRuningInfoVo.setPcsPowerList(pcsPowerList); + int pointCount = pcsPowerList.stream() + .filter(item -> item != null && item.getEnergyStoragePowList() != null) + .mapToInt(item -> item.getEnergyStoragePowList().size()) + .sum(); + log.info("{} storage, siteId={}, startDate={}, endDate={}, deviceCount={}, pointCount={}", + RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pcsPowerList.size(), pointCount); return siteMonitorRuningInfoVo; } @@ -316,49 +423,52 @@ public class SingleSiteServiceImpl implements ISingleSiteService { @Override public SiteMonitorRuningInfoVo getRunningGraphPcsMaxTemp(RunningGraphRequest request) { SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo(); - List pcsMaxTempList = new ArrayList<>(); -// // 时间暂定今日+昨日 -// Date today = new Date(); -// Date yesterday = DateUtils.addDays(today, -1); + if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) { + log.info("{} pcsMaxTemp skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request); + return siteMonitorRuningInfoVo; + } String siteId = request.getSiteId(); - Date startDate = request.getStartDate(); - Date endDate = request.getEndDate(); - //PCS最高温度list - List pcsMaxTempVos = emsPcsDataMapper.getPcsMaxTemp(siteId, startDate, endDate); -// if (SiteEnum.FX.getCode().equals(siteId)) { -// pcsMaxTempVos = emsPcsDataMapper.getFXMaxTemp(siteId, startDate, endDate); -// } else if (SiteEnum.DDS.getCode().equals(siteId)) { -// pcsMaxTempVos = emsPcsDataMapper.getDDSMaxTemp(siteId, startDate, endDate); -// } + Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate()); + Date startDate = dateRange[0]; + Date endDate = dateRange[1]; + List mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId); + Map mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList); + List pcsDeviceList = getDeviceMetaListByCategory(siteId, DeviceCategory.PCS.getCode()); + if (CollectionUtils.isEmpty(pcsDeviceList)) { + pcsDeviceList = Collections.singletonList(new DeviceMeta(RUNNING_GRAPH_DEFAULT_DEVICE, RUNNING_GRAPH_DEFAULT_DEVICE)); + } - // List -> 按pcs的deviceId分组转成List - if (!CollectionUtils.isEmpty(pcsMaxTempVos)) { - Map> dataMap = pcsMaxTempVos.stream() - .collect(Collectors.groupingBy( - PcsMaxTempVo::getDeviceId, - Collectors.toList())); - - pcsMaxTempList = dataMap.entrySet().stream() - .map(entry -> { - PcsMaxTempList pcdData = new PcsMaxTempList(); - pcdData.setDeviceId(entry.getKey()); - pcdData.setMaxTempVoList(entry.getValue()); - return pcdData; - }).collect(Collectors.toList()); - -// // 生成时间列表(每小时一个) -// List targetHours = new ArrayList<>(60); -// LocalDateTime startDate = DateUtils.toLocalDateTime(yesterday).truncatedTo(ChronoUnit.DAYS); -// LocalDateTime endDate = DateUtils.toLocalDateTime(today); -// while (startDate.isBefore(endDate)) { -// targetHours.add(startDate); -// startDate = startDate.plusHours(1); // 递增1小时 -// } -// // 根据时间列表填充数据 -// pcsMaxTempList = fullFillMaxTempData(pcsMaxTempList,targetHours); + List pcsMaxTempList = new ArrayList<>(); + for (DeviceMeta pcsDevice : pcsDeviceList) { + String deviceId = StringUtils.defaultString(pcsDevice.getDeviceId()); + String pointId = firstNonBlankPointByDevice(mappingByFieldAndDevice, deviceId, FIELD_CURVE_PCS_MAX_TEMP); + List values = queryInfluxPointValues(siteId, pointId, startDate, endDate); + List pcsMaxTempVos = new ArrayList<>(); + for (InfluxPointDataWriter.PointValue value : values) { + if (value == null || value.getDataTime() == null || value.getPointValue() == null) { + continue; + } + Date dataTime = value.getDataTime(); + PcsMaxTempVo vo = new PcsMaxTempVo(); + vo.setDeviceId(deviceId); + vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime)); + vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime)); + vo.setTemp(value.getPointValue()); + pcsMaxTempVos.add(vo); + } + PcsMaxTempList pcdData = new PcsMaxTempList(); + pcdData.setDeviceId(deviceId); + pcdData.setMaxTempVoList(pcsMaxTempVos); + pcsMaxTempList.add(pcdData); } siteMonitorRuningInfoVo.setPcsMaxTempList(pcsMaxTempList); + int pointCount = pcsMaxTempList.stream() + .filter(item -> item != null && item.getMaxTempVoList() != null) + .mapToInt(item -> item.getMaxTempVoList().size()) + .sum(); + log.info("{} pcsMaxTemp, siteId={}, startDate={}, endDate={}, deviceCount={}, pointCount={}", + RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pcsMaxTempList.size(), pointCount); return siteMonitorRuningInfoVo; } private List fullFillMaxTempData(List pcsMaxTempList, List targetHours) { @@ -415,14 +525,32 @@ public class SingleSiteServiceImpl implements ISingleSiteService { @Override public SiteMonitorRuningInfoVo getRunningGraphBatterySoc(RunningGraphRequest request) { SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo(); - if (!StringUtils.isEmpty(request.getSiteId())) { -// // 时间暂定今日+昨日 -// Date today = new Date(); -// Date yesterday = DateUtils.addDays(today, -1); - //电池平均soclist - List batteryAveSOCList = emsBatteryStackMapper.getAveSocList(request.getSiteId(), request.getStartDate(), request.getEndDate()); - siteMonitorRuningInfoVo.setBatteryAveSOCList(batteryAveSOCList); + if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) { + log.info("{} batteryAveSoc skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request); + return siteMonitorRuningInfoVo; } + Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate()); + Date startDate = dateRange[0]; + Date endDate = dateRange[1]; + Map mappingByField = getMonitorPointMappingByField(request.getSiteId()); + String pointId = firstNonBlankPoint(mappingByField, FIELD_CURVE_BATTERY_AVE_SOC, FIELD_SOC); + List values = queryInfluxPointValues(request.getSiteId(), pointId, startDate, endDate); + List batteryAveSOCList = new ArrayList<>(); + for (InfluxPointDataWriter.PointValue value : values) { + if (value == null || value.getDataTime() == null || value.getPointValue() == null) { + continue; + } + Date dataTime = value.getDataTime(); + BatteryAveSOCVo vo = new BatteryAveSOCVo(); + vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime)); + vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime)); + vo.setBatterySOC(value.getPointValue()); + batteryAveSOCList.add(vo); + } + siteMonitorRuningInfoVo.setBatteryAveSOCList(batteryAveSOCList); + int pointCount = CollectionUtils.isEmpty(batteryAveSOCList) ? 0 : batteryAveSOCList.size(); + log.info("{} batteryAveSoc, siteId={}, startDate={}, endDate={}, pointId={}, pointCount={}", + RUNNING_GRAPH_DEBUG, request.getSiteId(), startDate, endDate, pointId, pointCount); return siteMonitorRuningInfoVo; } // 获取单站监控实时运行-电池平均温度 @@ -430,40 +558,178 @@ public class SingleSiteServiceImpl implements ISingleSiteService { public SiteMonitorRuningInfoVo getRunningGraphBatteryTemp(RunningGraphRequest request) { SiteMonitorRuningInfoVo siteMonitorRuningInfoVo = new SiteMonitorRuningInfoVo(); if (Objects.isNull(request) || StringUtils.isEmpty(request.getSiteId())) { + log.info("{} batteryAveTemp skip, request invalid, request={}", RUNNING_GRAPH_DEBUG, request); return siteMonitorRuningInfoVo; } String siteId = request.getSiteId(); - Date startDate = request.getStartDate(); - Date endDate = request.getEndDate(); - //电池平均温度list,优先从电池堆取,电池堆没有的话再从电池簇取 + Date[] dateRange = normalizeRunningGraphDateRange(request.getStartDate(), request.getEndDate()); + Date startDate = dateRange[0]; + Date endDate = dateRange[1]; + Map mappingByField = getMonitorPointMappingByField(siteId); + String pointId = firstNonBlankPoint(mappingByField, FIELD_CURVE_BATTERY_AVE_TEMP, FIELD_HOME_AVG_TEMP); + List values = queryInfluxPointValues(siteId, pointId, startDate, endDate); List batteryAveTempList = new ArrayList<>(); - batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, startDate, endDate); - // 电池堆暂无数据,从电池簇取 - if (CollectionUtils.isEmpty(batteryAveTempList)) { - batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, startDate, endDate); + for (InfluxPointDataWriter.PointValue value : values) { + if (value == null || value.getDataTime() == null || value.getPointValue() == null) { + continue; + } + Date dataTime = value.getDataTime(); + BatteryAveTempVo vo = new BatteryAveTempVo(); + vo.setDateDay(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, dataTime)); + vo.setCreateDate(DateUtils.parseDateToStr("HH:mm:00", dataTime)); + vo.setBatteryTemp(value.getPointValue()); + batteryAveTempList.add(vo); } -// if (SiteEnum.FX.getCode().equals(siteId)) { -// batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, startDate, endDate); -// } else if (SiteEnum.DDS.getCode().equals(siteId)) { -// batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, startDate, endDate); -// } siteMonitorRuningInfoVo.setBatteryAveTempList(batteryAveTempList); -// if (!StringUtils.isEmpty(siteId)) { -// // 时间暂定今日+昨日 -// Date today = new Date(); -// Date yesterday = DateUtils.addDays(today, -1); -// //电池平均温度list -// List batteryAveTempList = new ArrayList<>(); -// if (SiteEnum.FX.getCode().equals(siteId)) { -// batteryAveTempList = emsBatteryClusterMapper.getBatteryAveTempList(siteId, yesterday, today); -// } else if (SiteEnum.DDS.getCode().equals(siteId)) { -// batteryAveTempList = emsBatteryStackMapper.getBatteryAveTempList(siteId, yesterday, today); -// } -// siteMonitorRuningInfoVo.setBatteryAveTempList(batteryAveTempList); -// } + int pointCount = CollectionUtils.isEmpty(batteryAveTempList) ? 0 : batteryAveTempList.size(); + log.info("{} batteryAveTemp, siteId={}, startDate={}, endDate={}, pointId={}, pointCount={}", + RUNNING_GRAPH_DEBUG, siteId, startDate, endDate, pointId, pointCount); return siteMonitorRuningInfoVo; } + private Date[] normalizeRunningGraphDateRange(Date startDate, Date endDate) { + Date normalizedStart = startDate; + Date normalizedEnd = endDate; + if (normalizedStart == null || normalizedEnd == null) { + Date today = DateUtils.getNowDate(); + Date yesterday = DateUtils.addDays(today, -1); + normalizedStart = normalizedStart == null ? yesterday : normalizedStart; + normalizedEnd = normalizedEnd == null ? today : normalizedEnd; + } + LocalDate startDay = DateUtils.toLocalDateTime(normalizedStart).toLocalDate(); + LocalDate endDay = DateUtils.toLocalDateTime(normalizedEnd).toLocalDate(); + Date dayStart = DateUtils.toDate(startDay.atStartOfDay()); + Date dayEnd = DateUtils.toDate(endDay.plusDays(1).atStartOfDay().minusNanos(1_000_000)); + return new Date[]{dayStart, dayEnd}; + } + + private Map getMonitorPointMappingByField(String siteId) { + if (StringUtils.isBlank(siteId)) { + return Collections.emptyMap(); + } + List mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId.trim()); + if (CollectionUtils.isEmpty(mappingList)) { + return Collections.emptyMap(); + } + return mappingList.stream() + .filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode())) + .collect(Collectors.toMap( + item -> item.getFieldCode().trim(), + item -> item, + (a, b) -> b + )); + } + + private Map buildMonitorPointMappingByFieldAndDevice(List mappingList) { + if (CollectionUtils.isEmpty(mappingList)) { + return Collections.emptyMap(); + } + return mappingList.stream() + .filter(item -> item != null && StringUtils.isNotBlank(item.getFieldCode())) + .collect(Collectors.toMap( + item -> buildFieldDeviceKey(item.getFieldCode(), item.getDeviceId()), + item -> item, + (a, b) -> b + )); + } + + private String buildFieldDeviceKey(String fieldCode, String deviceId) { + return StringUtils.defaultString(fieldCode).trim() + "|" + StringUtils.defaultString(deviceId).trim(); + } + + private String firstNonBlankPointByDevice(Map mappingByFieldAndDevice, + String deviceId, String... fieldCodes) { + if (mappingByFieldAndDevice == null || fieldCodes == null) { + return null; + } + String normalizedDeviceId = StringUtils.defaultString(deviceId).trim(); + for (String fieldCode : fieldCodes) { + if (StringUtils.isBlank(fieldCode)) { + continue; + } + EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildFieldDeviceKey(fieldCode, normalizedDeviceId)); + if (exact != null && StringUtils.isNotBlank(exact.getDataPoint())) { + return exact.getDataPoint().trim(); + } + EmsSiteMonitorPointMatch fallback = mappingByFieldAndDevice.get(buildFieldDeviceKey(fieldCode, "")); + if (fallback != null && StringUtils.isNotBlank(fallback.getDataPoint())) { + return fallback.getDataPoint().trim(); + } + } + return null; + } + + private List getDeviceMetaListByCategory(String siteId, String deviceCategory) { + if (StringUtils.isBlank(siteId) || StringUtils.isBlank(deviceCategory)) { + return Collections.emptyList(); + } + List> deviceList = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, deviceCategory); + if (CollectionUtils.isEmpty(deviceList)) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (Map item : deviceList) { + if (item == null || item.get("id") == null) { + continue; + } + String deviceId = String.valueOf(item.get("id")).trim(); + if (StringUtils.isBlank(deviceId)) { + continue; + } + String deviceName = item.get("deviceName") == null ? deviceId : String.valueOf(item.get("deviceName")).trim(); + result.add(new DeviceMeta(deviceId, deviceName)); + } + result.sort(Comparator.comparing(DeviceMeta::getDeviceName, Comparator.nullsLast(String::compareTo)) + .thenComparing(DeviceMeta::getDeviceId, Comparator.nullsLast(String::compareTo))); + return result; + } + + private static class DeviceMeta { + private final String deviceId; + private final String deviceName; + + private DeviceMeta(String deviceId, String deviceName) { + this.deviceId = deviceId; + this.deviceName = deviceName; + } + + public String getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + } + + private String firstNonBlankPoint(Map mappingByField, String... fieldCodes) { + if (mappingByField == null || fieldCodes == null) { + return null; + } + for (String fieldCode : fieldCodes) { + if (StringUtils.isBlank(fieldCode)) { + continue; + } + EmsSiteMonitorPointMatch match = mappingByField.get(fieldCode); + if (match != null && StringUtils.isNotBlank(match.getDataPoint())) { + return match.getDataPoint().trim(); + } + } + return null; + } + + private List queryInfluxPointValues(String siteId, String pointId, Date startDate, Date endDate) { + if (StringUtils.isBlank(siteId) || StringUtils.isBlank(pointId) || startDate == null || endDate == null) { + return Collections.emptyList(); + } + List values = influxPointDataWriter.queryCurveDataByPointKey(siteId, pointId, startDate, endDate); + if (CollectionUtils.isEmpty(values)) { + return Collections.emptyList(); + } + values.sort(Comparator.comparing(InfluxPointDataWriter.PointValue::getDataTime, Comparator.nullsLast(Date::compareTo))); + return values; + } + // 根据site_id获取pcs详细数据+支路数据 @Override public List getPcsDetailInfo(String siteId) { @@ -472,17 +738,27 @@ public class SingleSiteServiceImpl implements ISingleSiteService { if (!StringUtils.isEmpty(siteId)) { // 获取该设备下所有pcs的id List> pcsIds = emsDevicesSettingMapper.getDeviceInfosBySiteIdAndCategory(siteId, DeviceCategory.PCS.getCode()); + List mappingList = emsSiteMonitorPointMatchMapper.selectBySiteId(siteId); + Map mappingByFieldAndDevice = buildMonitorPointMappingByFieldAndDevice(mappingList); + Map latestPointCache = new HashMap<>(); for (Map pcsDevice : pcsIds) { PcsDetailInfoVo pcsDetailInfoVo = new PcsDetailInfoVo(); - pcsDetailInfoVo.setDeviceName(pcsDevice.get("deviceName").toString()); - pcsDetailInfoVo.setCommunicationStatus(pcsDevice.get("communicationStatus") == null ? - "" :pcsDevice.get("communicationStatus").toString()); - // 从redis取pcs单个详细数据 - String pcsId = pcsDevice.get("id").toString(); - EmsPcsData pcsData = redisCache.getCacheObject(RedisKeyConstants.PCS +siteId+"_"+pcsId); - if (pcsData != null) { - BeanUtils.copyProperties(pcsData, pcsDetailInfoVo); + String pcsId = String.valueOf(pcsDevice.get(DEVICE_INFO_ID)); + pcsDetailInfoVo.setSiteId(siteId); + pcsDetailInfoVo.setDeviceId(pcsId); + pcsDetailInfoVo.setDeviceName(String.valueOf(pcsDevice.get(DEVICE_INFO_NAME))); + + fillPcsDetailByLatestPointMapping(siteId, pcsId, pcsDetailInfoVo, mappingByFieldAndDevice, latestPointCache); + if (StringUtils.isBlank(pcsDetailInfoVo.getCommunicationStatus())) { + pcsDetailInfoVo.setCommunicationStatus(pcsDevice.get(DEVICE_INFO_COMM_STATUS) == null + ? "" + : pcsDevice.get(DEVICE_INFO_COMM_STATUS).toString()); + } + if (StringUtils.isBlank(pcsDetailInfoVo.getDeviceStatus())) { + pcsDetailInfoVo.setDeviceStatus(pcsDevice.get(DEVICE_INFO_DEVICE_STATUS) == null + ? "" + : pcsDevice.get(DEVICE_INFO_DEVICE_STATUS).toString()); } // 支路信息数据 List pcsBranchInfoList = new ArrayList<>(); @@ -493,7 +769,6 @@ public class SingleSiteServiceImpl implements ISingleSiteService { // // 告警设备点位个数 // int alarmNum = emsPointMatchMapper.getDevicePointAlarmNum(siteId, pcsId, DeviceCategory.PCS.getCode()); pcsDetailInfoVo.setAlarmNum(alarmNum); - pcsDetailInfoVo.setDeviceStatus(pcsDevice.get("deviceStatus") == null ? "" : pcsDevice.get("deviceStatus").toString()); // 处理枚举匹配字段 devicePointMatchDataProcessor.convertFieldValueToEnumMatch(siteId, DeviceCategory.PCS.getCode(), pcsDetailInfoVo); @@ -504,6 +779,114 @@ public class SingleSiteServiceImpl implements ISingleSiteService { return pcsDetailInfoVoList; } + private void fillPcsDetailByLatestPointMapping(String siteId, + String deviceId, + PcsDetailInfoVo 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 (PCS_DETAIL_META_FIELDS.contains(fieldCode)) { + continue; + } + EmsSiteMonitorPointMatch pointMatch = resolvePointMatchByFieldAndDevice(mappingByFieldAndDevice, fieldCode, deviceId); + if (pointMatch == null) { + continue; + } + Object fieldValue; + Date valueTime = null; + if (USE_FIXED_DISPLAY_YES == (pointMatch.getUseFixedDisplay() == null ? 0 : pointMatch.getUseFixedDisplay()) + && StringUtils.isNotBlank(pointMatch.getFixedDataPoint())) { + fieldValue = pointMatch.getFixedDataPoint().trim(); + } else { + InfluxPointDataWriter.PointValue latestValue = getLatestPointValueByPointId(siteId, pointMatch.getDataPoint(), latestPointCache); + if (latestValue == null || latestValue.getPointValue() == null) { + continue; + } + fieldValue = latestValue.getPointValue(); + valueTime = latestValue.getDataTime(); + } + Object convertedValue = convertFieldValueByType(fieldValue, pd.getPropertyType()); + if (convertedValue == null) { + continue; + } + beanWrapper.setPropertyValue(fieldCode, convertedValue); + if (valueTime != null && (latestDataTime == null || valueTime.after(latestDataTime))) { + latestDataTime = valueTime; + } + } + if (latestDataTime != null) { + target.setDataUpdateTime(latestDataTime); + } + } + + private EmsSiteMonitorPointMatch resolvePointMatchByFieldAndDevice(Map mappingByFieldAndDevice, + String fieldCode, + String deviceId) { + if (mappingByFieldAndDevice == null || StringUtils.isBlank(fieldCode)) { + return null; + } + String normalizedField = fieldCode.trim(); + String normalizedDeviceId = StringUtils.defaultString(deviceId).trim(); + EmsSiteMonitorPointMatch exact = mappingByFieldAndDevice.get(buildFieldDeviceKey(normalizedField, normalizedDeviceId)); + if (exact != null) { + return exact; + } + return mappingByFieldAndDevice.get(buildFieldDeviceKey(normalizedField, "")); + } + + private InfluxPointDataWriter.PointValue getLatestPointValueByPointId(String siteId, + String pointId, + Map latestPointCache) { + if (StringUtils.isAnyBlank(siteId, pointId)) { + return null; + } + String pointCacheKey = pointId.trim().toUpperCase(); + if (latestPointCache.containsKey(pointCacheKey)) { + return latestPointCache.get(pointCacheKey); + } + Date endTime = DateUtils.getNowDate(); + Date fastStartTime = DateUtils.addDays(endTime, -1); + InfluxPointDataWriter.PointValue latest = influxPointDataWriter.queryLatestPointValueByPointKey( + siteId.trim(), pointId.trim(), fastStartTime, endTime + ); + if (latest == null) { + Date fallbackStartTime = DateUtils.addDays(endTime, -7); + latest = influxPointDataWriter.queryLatestPointValueByPointKey( + siteId.trim(), pointId.trim(), fallbackStartTime, endTime + ); + } + latestPointCache.put(pointCacheKey, latest); + return latest; + } + + private Object convertFieldValueByType(Object rawValue, Class targetType) { + if (rawValue == null || targetType == null) { + return null; + } + if (BigDecimal.class.equals(targetType)) { + return StringUtils.getBigDecimal(rawValue); + } + if (String.class.equals(targetType)) { + if (rawValue instanceof BigDecimal) { + return ((BigDecimal) rawValue).stripTrailingZeros().toPlainString(); + } + return String.valueOf(rawValue); + } + if (Date.class.equals(targetType) && rawValue instanceof Date) { + return rawValue; + } + return null; + } + private void processBranchDataInfo(String siteId, String pcsId, List pcsBranchInfoList) { if (!StringUtils.isEmpty(pcsId)) { List pcsBranchData = redisCache.getCacheObject(RedisKeyConstants.BRANCH +siteId+"_"+pcsId); @@ -687,45 +1070,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/java/com/xzzn/ems/utils/DevicePointMatchDataProcessor.java b/ems-system/src/main/java/com/xzzn/ems/utils/DevicePointMatchDataProcessor.java index a34ff2c..0a91a33 100644 --- a/ems-system/src/main/java/com/xzzn/ems/utils/DevicePointMatchDataProcessor.java +++ b/ems-system/src/main/java/com/xzzn/ems/utils/DevicePointMatchDataProcessor.java @@ -135,6 +135,8 @@ public class DevicePointMatchDataProcessor { return new HashMap<>(); } return pointEnumMatchList.stream() + .filter(Objects::nonNull) + .filter(data -> StringUtils.isNotEmpty(data.getMatchField())) .collect(Collectors.groupingBy(data -> StringUtils.toCamelCase(data.getMatchField()))); } @@ -237,8 +239,14 @@ public class DevicePointMatchDataProcessor { * 转换字段值为配置枚举值 */ public void convertFieldValueToEnumMatch(String siteId, String deviceCategory, Object entity) { + if (entity == null) { + return; + } Map> pointEnumMatchMap = this.getPointEnumMatchMap(siteId, deviceCategory); + if (pointEnumMatchMap == null || pointEnumMatchMap.isEmpty()) { + return; + } Field[] fields = entity.getClass().getDeclaredFields(); for (Field field : fields) { String fieldName = field.getName(); @@ -252,7 +260,9 @@ public class DevicePointMatchDataProcessor { if (CollectionUtils.isNotEmpty(pointEnumMatchList) && matchValue != null) { String finalMatchValue = String.valueOf(matchValue).replace(".0", ""); Optional enumMatch = pointEnumMatchList.stream() - .filter(data -> data.getDataEnumCode().equals(finalMatchValue)).findFirst(); + .filter(Objects::nonNull) + .filter(data -> Objects.equals(data.getDataEnumCode(), finalMatchValue)) + .findFirst(); if (enumMatch.isPresent()) { matchValue = enumMatch.get().getEnumCode(); } diff --git a/ems-system/src/main/resources/mapper/ems/EmsBatteryStackMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsBatteryStackMapper.xml index 3a3b74c..03fb24c 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsBatteryStackMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsBatteryStackMapper.xml @@ -512,10 +512,16 @@ - \ No newline at end of file + diff --git a/ems-system/src/main/resources/mapper/ems/EmsDailyChageDataMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsDailyChageDataMapper.xml index 4b1fb36..da078cd 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsDailyChageDataMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsDailyChageDataMapper.xml @@ -7,12 +7,13 @@ - + + @@ -21,20 +22,22 @@ - select id, site_id, device_id, date_time, total_charge_Data, total_discharge_Data, charge_data, discharge_data, create_by, create_time, update_by, update_time, remark from ems_daily_charge_data + select id, site_id, date_time, total_charge_Data, total_discharge_Data, charge_data, discharge_data, total_revenue, day_revenue, create_by, create_time, update_by, update_time, remark from ems_daily_charge_data + + insert into ems_daily_charge_data site_id, - device_id, date_time, total_charge_Data, total_discharge_Data, charge_data, discharge_data, + total_revenue, + day_revenue, create_by, create_time, update_by, @@ -60,12 +72,13 @@ #{siteId}, - #{deviceId}, #{dateTime}, #{totalChargeData}, #{totalDischargeData}, #{chargeData}, #{dischargeData}, + #{totalRevenue}, + #{dayRevenue}, #{createBy}, #{createTime}, #{updateBy}, @@ -78,12 +91,13 @@ update ems_daily_charge_data site_id = #{siteId}, - device_id = #{deviceId}, date_time = #{dateTime}, total_charge_Data = #{totalChargeData}, total_discharge_Data = #{totalDischargeData}, charge_data = #{chargeData}, discharge_data = #{dischargeData}, + total_revenue = #{totalRevenue}, + day_revenue = #{dayRevenue}, create_by = #{createBy}, create_time = #{createTime}, update_by = #{updateBy}, @@ -108,26 +122,28 @@ INSERT into ems_daily_charge_data ( id, site_id, - device_id, date_time, total_charge_Data, total_discharge_Data, charge_data, discharge_data, + total_revenue, + day_revenue, create_by, create_time, update_by, update_time, remark - ) values ( + ) values ( #{id}, #{siteId}, - #{deviceId}, #{dateTime}, #{totalChargeData}, #{totalDischargeData}, #{chargeData}, #{dischargeData}, + #{totalRevenue}, + #{dayRevenue}, #{createBy}, #{createTime}, #{updateBy}, @@ -139,9 +155,21 @@ total_discharge_Data = #{totalDischargeData}, charge_data = #{chargeData}, discharge_data = #{dischargeData}, + total_revenue = IFNULL(#{totalRevenue}, total_revenue), + day_revenue = IFNULL(#{dayRevenue}, day_revenue), update_time = NOW() + + update ems_daily_charge_data + set total_revenue = #{totalRevenue}, + day_revenue = #{dayRevenue}, + update_by = #{updateBy}, + update_time = NOW() + where site_id = #{siteId} + and date(date_time) = date(#{dateTime}) + + - \ No newline at end of file + diff --git a/ems-system/src/main/resources/mapper/ems/EmsDailyEnergyDataMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsDailyEnergyDataMapper.xml index 3b34ab5..dbcadb5 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsDailyEnergyDataMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsDailyEnergyDataMapper.xml @@ -8,8 +8,7 @@ - - + @@ -27,7 +26,11 @@ - select id, site_id, data_date, total_revenue, day_revenue, peak_charge_diff, peak_discharge_diff, high_charge_diff, high_discharge_diff, flat_charge_diff, flat_discharge_diff, valley_charge_diff, valley_discharge_diff, calc_time, create_by, create_time, update_by, update_time, remark from ems_daily_energy_data + select id, site_id, data_date, data_hour, + peak_charge_diff, peak_discharge_diff, high_charge_diff, high_discharge_diff, + flat_charge_diff, flat_discharge_diff, valley_charge_diff, valley_discharge_diff, + calc_time, create_by, create_time, update_by, update_time, remark + from ems_daily_energy_data + + INSERT INTO ems_daily_energy_data ( id, site_id, data_date, - total_revenue, - day_revenue, + data_hour, peak_charge_diff, peak_discharge_diff, high_charge_diff, @@ -164,8 +172,7 @@ #{id}, #{siteId}, #{dataDate}, - #{totalRevenue}, - #{dayRevenue}, + #{dataHour}, #{peakChargeDiff}, #{peakDischargeDiff}, #{highChargeDiff}, @@ -181,8 +188,7 @@ NOW(), #{remark} ) ON DUPLICATE KEY UPDATE - total_revenue = #{totalRevenue}, - day_revenue = #{dayRevenue}, + data_hour = #{dataHour}, peak_charge_diff = #{peakChargeDiff}, peak_discharge_diff = #{peakDischargeDiff}, high_charge_diff = #{highChargeDiff}, @@ -196,67 +202,138 @@ - \ No newline at end of file + 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/EmsPcsSettingMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPcsSettingMapper.xml index 287ab57..7a69e04 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPcsSettingMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPcsSettingMapper.xml @@ -13,6 +13,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -23,7 +24,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - select id, device_setting_id, point_address, power_address, start_command, stop_command, start_power, stop_power, cluster_num, cluster_point_address, create_by, create_time, update_by, update_time, remark from ems_pcs_setting + select id, device_setting_id, point_address, power_address, start_command, stop_command, start_power, stop_power, power_multiplier, cluster_num, cluster_point_address, create_by, create_time, update_by, update_time, remark from ems_pcs_setting + + where id = #{id} + + + + + + insert into ems_point_calc_config + + site_id, + device_category, + point_name, + data_key, + point_desc, + data_unit, + calc_expression, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{siteId}, + #{deviceCategory}, + #{pointName}, + #{dataKey}, + #{pointDesc}, + #{dataUnit}, + #{calcExpression}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update ems_point_calc_config + + site_id = #{siteId}, + device_category = #{deviceCategory}, + point_name = #{pointName}, + data_key = #{dataKey}, + point_desc = #{pointDesc}, + data_unit = #{dataUnit}, + calc_expression = #{calcExpression}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + delete from ems_point_calc_config where id = #{id} + + + + delete from ems_point_calc_config where id in + + #{id} + + + + + + + delete from ems_point_calc_config + where site_id = #{siteId} + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml new file mode 100644 index 0000000..f3d1d68 --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsPointConfigMapper.xml @@ -0,0 +1,373 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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, + collect_enabled, collect_source, modbus_register_type, modbus_data_type, modbus_read_order, modbus_group, + create_by, create_time, update_by, update_time, remark + from ems_point_config + + + + + + + + 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, + collect_enabled, + collect_source, + modbus_register_type, + modbus_data_type, + modbus_read_order, + modbus_group, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{pointId}, + #{siteId}, + #{deviceCategory}, + #{deviceId}, + #{pointName}, + #{dataKey}, + #{pointDesc}, + #{registerAddress}, + #{dataUnit}, + #{dataA}, + #{dataK}, + #{dataB}, + #{dataBit}, + #{isAlarm}, + #{pointType}, + #{calcExpression}, + #{collectEnabled}, + #{collectSource}, + #{modbusRegisterType}, + #{modbusDataType}, + #{modbusReadOrder}, + #{modbusGroup}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + 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, + collect_enabled, collect_source, modbus_register_type, modbus_data_type, modbus_read_order, modbus_group, + create_by, create_time, update_by, update_time, remark + ) + values + + ( + #{item.pointId}, + #{item.siteId}, + #{item.deviceCategory}, + #{item.deviceId}, + #{item.pointName}, + #{item.dataKey}, + #{item.pointDesc}, + #{item.registerAddress}, + #{item.dataUnit}, + #{item.dataA}, + #{item.dataK}, + #{item.dataB}, + #{item.dataBit}, + #{item.isAlarm}, + #{item.pointType}, + #{item.calcExpression}, + #{item.collectEnabled}, + #{item.collectSource}, + #{item.modbusRegisterType}, + #{item.modbusDataType}, + #{item.modbusReadOrder}, + #{item.modbusGroup}, + #{item.createBy}, + now(), + #{item.updateBy}, + now(), + #{item.remark} + ) + + + + + update ems_point_config + + point_id = #{pointId}, + site_id = #{siteId}, + device_category = #{deviceCategory}, + device_id = #{deviceId}, + point_name = #{pointName}, + data_key = #{dataKey}, + point_desc = #{pointDesc}, + register_address = #{registerAddress}, + data_unit = #{dataUnit}, + data_a = #{dataA}, + data_k = #{dataK}, + data_b = #{dataB}, + data_bit = #{dataBit}, + is_alarm = #{isAlarm}, + point_type = #{pointType}, + calc_expression = #{calcExpression}, + collect_enabled = #{collectEnabled}, + collect_source = #{collectSource}, + modbus_register_type = #{modbusRegisterType}, + modbus_data_type = #{modbusDataType}, + modbus_read_order = #{modbusReadOrder}, + modbus_group = #{modbusGroup}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where id = #{id} + + + + update ems_point_config + set point_id = #{pointId}, + site_id = #{siteId}, + device_category = #{deviceCategory}, + device_id = #{deviceId}, + point_name = #{pointName}, + data_key = #{dataKey}, + point_desc = #{pointDesc}, + register_address = #{registerAddress}, + data_unit = #{dataUnit}, + data_a = #{dataA}, + data_k = #{dataK}, + data_b = #{dataB}, + data_bit = #{dataBit}, + is_alarm = #{isAlarm}, + point_type = #{pointType}, + calc_expression = #{calcExpression}, + collect_enabled = #{collectEnabled}, + collect_source = #{collectSource}, + modbus_register_type = #{modbusRegisterType}, + modbus_data_type = #{modbusDataType}, + modbus_read_order = #{modbusReadOrder}, + modbus_group = #{modbusGroup}, + update_by = #{updateBy}, + update_time = now(), + remark = #{remark} + where id = #{id} + + + + delete from ems_point_config where id = #{id} + + + + delete from ems_point_config where id in + + #{id} + + + + + + + delete from ems_point_config + where site_id = #{siteId} + + + + 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, + collect_enabled, collect_source, modbus_register_type, modbus_data_type, modbus_read_order, modbus_group, + create_by, create_time, update_by, update_time, remark + ) + select + 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, + collect_enabled, collect_source, modbus_register_type, modbus_data_type, modbus_read_order, modbus_group, + #{operName}, now(), #{operName}, now(), remark + from ems_point_config + where site_id = #{templateSiteId} + + + + + + + + + + + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsPointEnumMatchMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPointEnumMatchMapper.xml index 7ba0254..c3d6790 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPointEnumMatchMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPointEnumMatchMapper.xml @@ -119,4 +119,54 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - \ No newline at end of file + + + + delete from ems_point_enum_match + where site_id = #{siteId} + + + + insert into ems_point_enum_match ( + match_field, + site_id, + device_category, + enum_code, + enum_name, + enum_desc, + data_enum_code, + create_by, + create_time, + update_by, + update_time, + remark + ) + select + match_field, + #{targetSiteId}, + device_category, + enum_code, + enum_name, + enum_desc, + data_enum_code, + #{operName}, + now(), + #{operName}, + now(), + remark + from ems_point_enum_match + where site_id = #{templateSiteId} + + + + delete from ems_point_enum_match + where site_id = #{siteId} + and device_category = #{deviceCategory} + and match_field = #{matchField} + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml index e0b49ee..289695f 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsPointMatchMapper.xml @@ -577,4 +577,74 @@ - \ No newline at end of file + + + + delete from ems_point_match + where site_id = #{siteId} + + + + insert into ems_point_match ( + point_name, + match_table, + match_field, + site_id, + device_category, + data_point, + data_point_name, + data_device, + data_unit, + ip_address, + ip_port, + data_type, + need_diff_device_id, + create_by, + create_time, + update_by, + update_time, + remark, + is_alarm, + device_id, + a, + k, + b + ) + select + point_name, + match_table, + match_field, + #{targetSiteId}, + device_category, + data_point, + data_point_name, + data_device, + data_unit, + ip_address, + ip_port, + data_type, + need_diff_device_id, + #{operName}, + now(), + #{operName}, + now(), + remark, + is_alarm, + device_id, + a, + k, + b + from ems_point_match + where site_id = #{templateSiteId} + + + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorDataMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorDataMapper.xml new file mode 100644 index 0000000..4b668d5 --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorDataMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + insert into ${tableName} ( + site_id, statis_minute, data_json, + hot_soc, hot_total_active_power, hot_total_reactive_power, hot_day_charged_cap, hot_day_dis_charged_cap, + create_by, create_time, update_by, update_time + ) values ( + #{siteId}, #{statisMinute}, #{dataJson}, + #{hotSoc}, #{hotTotalActivePower}, #{hotTotalReactivePower}, #{hotDayChargedCap}, #{hotDayDisChargedCap}, + #{operName}, now(), #{operName}, now() + ) + on duplicate key update + data_json = values(data_json), + hot_soc = values(hot_soc), + hot_total_active_power = values(hot_total_active_power), + hot_total_reactive_power = values(hot_total_reactive_power), + hot_day_charged_cap = values(hot_day_charged_cap), + hot_day_dis_charged_cap = values(hot_day_dis_charged_cap), + update_by = values(update_by), + update_time = now() + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorItemMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorItemMapper.xml new file mode 100644 index 0000000..14635c6 --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorItemMapper.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml new file mode 100644 index 0000000..e01124b --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteMonitorPointMatchMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + delete from ems_site_monitor_point_match + 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) + values + + (#{item.siteId}, #{item.fieldCode}, #{item.deviceId}, #{item.dataPoint}, #{item.fixedDataPoint}, #{item.useFixedDisplay}, #{item.createBy}, now(), #{item.updateBy}, now()) + + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteSettingMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteSettingMapper.xml index c7e0fa9..35df233 100644 --- a/ems-system/src/main/resources/mapper/ems/EmsSiteSettingMapper.xml +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteSettingMapper.xml @@ -7,6 +7,7 @@ + @@ -22,13 +23,14 @@ - select id, site_name, site_address, running_time, latitude, longitude, install_capacity, install_power, remark, create_by, update_by, create_time, update_time, site_id from ems_site_setting + select id, site_name, site_short_name, site_address, running_time, latitude, longitude, install_capacity, install_power, remark, create_by, update_by, create_time, update_time, site_id from ems_site_setting select distinct site_id from ems_site_setting - \ No newline at end of file + diff --git a/ems-system/src/main/resources/mapper/ems/EmsSiteWeatherDayMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsSiteWeatherDayMapper.xml new file mode 100644 index 0000000..93acc0f --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsSiteWeatherDayMapper.xml @@ -0,0 +1,28 @@ + + + + + + update ems_site_weather_day + set weather_desc = #{weatherDesc}, + weather_code = #{weatherCode}, + update_time = now() + where site_id = #{siteId} + and calendar_date = #{calendarDate} + + + + + + insert into ems_site_weather_day(site_id, calendar_date, weather_desc, weather_code, source, create_time, update_time) + values (#{siteId}, #{calendarDate}, #{weatherDesc}, #{weatherCode}, #{source}, now(), now()) + + + diff --git a/ems-system/src/main/resources/mapper/ems/EmsStrategyRuntimeConfigMapper.xml b/ems-system/src/main/resources/mapper/ems/EmsStrategyRuntimeConfigMapper.xml new file mode 100644 index 0000000..cacdc13 --- /dev/null +++ b/ems-system/src/main/resources/mapper/ems/EmsStrategyRuntimeConfigMapper.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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, + power_set_multiplier, + protect_intervene_enable, + protect_l1_derate_percent, + protect_recovery_stable_seconds, + protect_l3_latch_enable, + protect_conflict_policy, + create_by, + create_time, + update_by, + update_time, + remark + from ems_strategy_runtime_config + + + + + + insert into ems_strategy_runtime_config + + site_id, + soc_down, + soc_up, + anti_reverse_threshold, + anti_reverse_range_percent, + anti_reverse_up, + anti_reverse_power_down_percent, + anti_reverse_hard_stop_threshold, + power_set_multiplier, + protect_intervene_enable, + protect_l1_derate_percent, + protect_recovery_stable_seconds, + protect_l3_latch_enable, + protect_conflict_policy, + create_by, + create_time, + update_by, + update_time, + remark, + + + #{siteId}, + #{socDown}, + #{socUp}, + #{antiReverseThreshold}, + #{antiReverseRangePercent}, + #{antiReverseUp}, + #{antiReversePowerDownPercent}, + #{antiReverseHardStopThreshold}, + #{powerSetMultiplier}, + #{protectInterveneEnable}, + #{protectL1DeratePercent}, + #{protectRecoveryStableSeconds}, + #{protectL3LatchEnable}, + #{protectConflictPolicy}, + #{createBy}, + #{createTime}, + #{updateBy}, + #{updateTime}, + #{remark}, + + + + + update ems_strategy_runtime_config + + soc_down = #{socDown}, + soc_up = #{socUp}, + anti_reverse_threshold = #{antiReverseThreshold}, + anti_reverse_range_percent = #{antiReverseRangePercent}, + anti_reverse_up = #{antiReverseUp}, + anti_reverse_power_down_percent = #{antiReversePowerDownPercent}, + anti_reverse_hard_stop_threshold = #{antiReverseHardStopThreshold}, + power_set_multiplier = #{powerSetMultiplier}, + protect_intervene_enable = #{protectInterveneEnable}, + protect_l1_derate_percent = #{protectL1DeratePercent}, + protect_recovery_stable_seconds = #{protectRecoveryStableSeconds}, + protect_l3_latch_enable = #{protectL3LatchEnable}, + protect_conflict_policy = #{protectConflictPolicy}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark}, + + where site_id = #{siteId} + +