Compare commits

..

16 Commits

Author SHA1 Message Date
7153c00d0c 重构 2026-04-16 23:11:25 +08:00
2b3be8636f 1.单体电池批量新增修改 2026-04-16 19:49:56 +08:00
5eb9b455a5 1.单体电池批量新增修改 2026-04-15 22:47:17 +08:00
4947f085c7 增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改 2026-04-14 16:44:31 +08:00
5460aadf6c 增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改 2026-04-14 16:11:23 +08:00
79a8040446 1.一周充放曲线改为了时间聚合柱状图。
2.PCS最高温度修复bug展示多PCS设备的数据
3.PCS的状态根据状态枚举映射配置的内容显示
4.BMS的总览,增加工作状态、与PCS通讯、与EMS通讯的配置,及关联展示
5.增加批量导入单体电池点位的功能
6.修复计算点可能会出现id与code在一个池子内的问题,再次计算后数据正常
7.计算点增加小数位限制的功能,实时计算与7天历史接口都已经按照配置的小数位进行限制
8.统计报表中的功率曲线改为了按照分钟显示
9.功率曲线出现断点的问题是因为数据计算太密集了导致的,增加了前端连线不断的显示
10.PCS和电池堆的曲线与配置增加了关联设备显示
11.点位映射中的电池温度,增加了多设备
12.收益报表增加升序排列,合并当月所有合计
13.增加业务报表备注功能,可以根据业务设计开发,目前电表报表与收益报表均有备注列可以修改
2026-04-12 15:16:57 +08:00
ed36292c94 Merge pull request 'dev' (#3) from dev into waibao
Reviewed-on: #3
2026-04-09 01:31:05 +00:00
515f440298 重构 2026-03-23 13:47:20 +08:00
e4cfd15cb4 重构 2026-03-18 10:06:42 +08:00
5ab2cb8f90 重构 2026-02-22 18:59:40 +08:00
4e7d387edf 重构 2026-02-17 21:42:59 +08:00
49ed5f218a 重构 2026-02-16 14:46:27 +08:00
8806473080 重构 2026-02-16 13:41:35 +08:00
71d0b0f609 重构 2026-02-15 16:02:06 +08:00
6253fb6b2d 临时修改 2026-02-13 21:41:23 +08:00
5d5a7137fc Merge pull request 'dev' (#2) from dev into main
Reviewed-on: #2
2026-02-11 01:55:45 +00:00
111 changed files with 12307 additions and 3807 deletions

View File

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

View File

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

View File

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

View File

@ -0,0 +1,51 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.annotation.Log;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.ems.domain.EmsPointCalcConfig;
import com.xzzn.ems.service.IEmsPointCalcConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/ems/pointCalcConfig")
public class EmsPointCalcConfigController extends BaseController {
@Autowired
private IEmsPointCalcConfigService pointCalcConfigService;
@GetMapping("/list")
public TableDataInfo list(EmsPointCalcConfig pointCalcConfig) {
startPage();
List<EmsPointCalcConfig> list = pointCalcConfigService.selectPointCalcConfigList(pointCalcConfig);
return getDataTable(list);
}
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
return success(pointCalcConfigService.selectPointCalcConfigById(id));
}
@Log(title = "计算点配置", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody EmsPointCalcConfig pointCalcConfig) {
return toAjax(pointCalcConfigService.insertPointCalcConfig(pointCalcConfig, getUsername()));
}
@Log(title = "计算点配置", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody EmsPointCalcConfig pointCalcConfig) {
return toAjax(pointCalcConfigService.updatePointCalcConfig(pointCalcConfig, getUsername()));
}
@Log(title = "计算点配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(pointCalcConfigService.deletePointCalcConfigByIds(ids));
}
}

View File

@ -8,6 +8,7 @@ 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;
@ -93,4 +94,10 @@ public class EmsPointConfigController extends BaseController {
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));
}
}

View File

@ -8,20 +8,21 @@ import com.xzzn.common.core.page.TableDataInfo;
import com.xzzn.common.enums.BusinessType;
import com.xzzn.common.utils.file.FileUploadUtils;
import com.xzzn.common.utils.file.MimeTypeUtils;
import com.xzzn.common.utils.poi.ExcelUtil;
import com.xzzn.ems.domain.EmsSiteSetting;
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.SiteMonitorProjectPointMappingSaveRequest;
import com.xzzn.ems.domain.vo.SingleBatteryConfigInitRequest;
import com.xzzn.ems.domain.vo.SingleBatteryConfigInitResultVo;
import com.xzzn.ems.domain.vo.SingleBatteryMonitorImportResultVo;
import com.xzzn.ems.domain.vo.SingleBatteryMonitorImportRowVo;
import com.xzzn.ems.domain.vo.WorkStatusEnumMappingSaveRequest;
import com.xzzn.ems.service.IEmsDeviceSettingService;
import com.xzzn.ems.service.IEmsSiteService;
import java.util.List;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
@ -34,14 +35,17 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
*
* 站点配置
*
* 绔欑偣閰嶇疆
*/
@RestController
@RequestMapping("/ems/siteConfig")
public class EmsSiteConfigController extends BaseController{
public class EmsSiteConfigController extends BaseController {
@Autowired
private IEmsSiteService iEmsSiteService;
@ -49,197 +53,169 @@ public class EmsSiteConfigController extends BaseController{
@Autowired
private IEmsDeviceSettingService iEmsDeviceSettingService;
/**
* 获取站点列表
*/
@GetMapping("/getSiteInfoList")
public TableDataInfo getSiteInfoList(@RequestParam String siteName, @RequestParam String startTime, @RequestParam String endTime)
{
public TableDataInfo getSiteInfoList(@RequestParam String siteName, @RequestParam String startTime, @RequestParam String endTime) {
startPage();
List<EmsSiteSetting> list = iEmsSiteService.getAllSiteInfoList(siteName,startTime,endTime);
List<EmsSiteSetting> list = iEmsSiteService.getAllSiteInfoList(siteName, startTime, endTime);
return getDataTable(list);
}
/**
* 新增站点
*/
@PostMapping("/addSite")
public AjaxResult addSite(@RequestBody EmsSiteSetting emsSiteSetting)
{
public AjaxResult addSite(@RequestBody EmsSiteSetting emsSiteSetting) {
emsSiteSetting.setCreateBy(getUsername());
return toAjax(iEmsSiteService.addSite(emsSiteSetting));
}
/**
* 编辑站点
*/
@PostMapping("/updateSite")
public AjaxResult updateSite(@RequestBody EmsSiteSetting emsSiteSetting)
{
public AjaxResult updateSite(@RequestBody EmsSiteSetting emsSiteSetting) {
emsSiteSetting.setUpdateBy(getUsername());
return toAjax(iEmsSiteService.updateSite(emsSiteSetting));
}
/**
* 获取设备列表-分页
*/
@GetMapping("/getDeviceInfoList")
public TableDataInfo getDeviceInfoList(@RequestParam(value = "siteId", required = false) String siteId,
@RequestParam(value = "deviceCategory", required = false) String deviceCategory)
{
public TableDataInfo getDeviceInfoList(@RequestParam(value = "siteId", required = false) String siteId,
@RequestParam(value = "deviceCategory", required = false) String deviceCategory) {
startPage();
List<SiteDeviceListVo> list = iEmsSiteService.getAllDeviceListNoDisp(siteId, deviceCategory);
return getDataTable(list);
}
/**
* 获取设备详细信息
*/
@GetMapping("/getDeviceDetailInfo")
public AjaxResult getDeviceDetailInfo(@RequestParam Long id)
{
public AjaxResult getDeviceDetailInfo(@RequestParam Long id) {
return success(iEmsDeviceSettingService.getDeviceDetailInfo(id));
}
/**
* 获取设备列表-不分页
*/
@GetMapping("/getDeviceList")
public AjaxResult getDeviceInfoList2(@RequestParam String siteId)
{
public AjaxResult getDeviceInfoList2(@RequestParam String siteId) {
return success(iEmsSiteService.getAllDeviceList(siteId, null));
}
/**
* 获取所有设备类别
*/
@GetMapping("/getDeviceCategory")
public AjaxResult getDeviceCategory()
{
public AjaxResult getDeviceCategory() {
return success(iEmsDeviceSettingService.getDeviceCategory());
}
/**
* 新增设备
*/
@PostMapping("/addDevice")
public AjaxResult addDevice(@RequestBody DevicesSettingVo devicesSetting)
{
public AjaxResult addDevice(@RequestBody DevicesSettingVo devicesSetting) {
int result = iEmsDeviceSettingService.addDevice(devicesSetting);
if (result > 0) {
if (result > 0) {
return AjaxResult.success(result);
} else {
return AjaxResult.error("该设备已存在");
}
return AjaxResult.error("璇ヨ澶囧凡瀛樺湪");
}
/**
* 上传设备图片
*/
@PostMapping("/uploadDeviceImg")
public AjaxResult uploadDeviceImg(@RequestParam("avatarfile") MultipartFile file) throws Exception
{
public AjaxResult uploadDeviceImg(@RequestParam("avatarfile") MultipartFile file) throws Exception {
if (!file.isEmpty()) {
String avatar = FileUploadUtils.upload(RuoYiConfig.getDevicePath(), file, MimeTypeUtils.IMAGE_EXTENSION);
AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", avatar);
return ajax;
}
return error("上传图片异常,请联系管理员");
return error("涓婁紶鍥剧墖寮傚父锛岃鑱旂郴绠$悊鍛?");
}
/**
* 修改Modbus设备配置
*/
@PostMapping("/updateDevice")
public AjaxResult updateDevice(@RequestBody DevicesSettingVo emsDevicesSetting)
{
public AjaxResult updateDevice(@RequestBody DevicesSettingVo emsDevicesSetting) {
int result = iEmsDeviceSettingService.updateDevice(emsDevicesSetting);
if (result > 0) {
if (result > 0) {
return AjaxResult.success(result);
} else if (result == -1) {
return AjaxResult.error("数据不存在");
} else if (result == -2) {
return AjaxResult.error("该设备已存在");
} else if (result == -1) {
return AjaxResult.error("鏁版嵁涓嶅瓨鍦?");
} else if (result == -2) {
return AjaxResult.error("璇ヨ澶囧凡瀛樺湪");
}
return AjaxResult.success(result);
}
/**
* 删除Modbus设备配置
*/
@DeleteMapping("/deleteService/{id}")
public AjaxResult deleteService(@PathVariable Long id)
{
public AjaxResult deleteService(@PathVariable Long id) {
return toAjax(iEmsDeviceSettingService.deleteEmsDevicesSettingById(id));
}
/**
* 单个站点单个设备点位查询-点位清单
*/
@GetMapping("/getDevicePointList")
public TableDataInfo getDevicePointList(@Validated PointDataRequest request)
{
public TableDataInfo getDevicePointList(@Validated PointDataRequest request) {
List<PointQueryResponse> result = iEmsDeviceSettingService.getSingleSiteDevicePoints(request);
return getDataTable2(result);
}
/**
* 获取指定站点下的所有设备类别
*/
@GetMapping("/getSiteAllDeviceCategory")
public AjaxResult getSiteAllDeviceCategory(String siteId)
{
public AjaxResult getSiteAllDeviceCategory(String siteId) {
return success(iEmsDeviceSettingService.getSiteAllDeviceCategory(siteId));
}
/**
* 根据设备类别获取父类的设备id
*/
@GetMapping("/getParentDeviceId")
public AjaxResult getParentDeviceId(@RequestParam String siteId, @RequestParam String deviceCategory)
{
public AjaxResult getParentDeviceId(@RequestParam String siteId, @RequestParam String deviceCategory) {
return success(iEmsSiteService.getParentCategoryDeviceId(siteId, deviceCategory));
}
/**
* 获取指定站点下的指定设备类型的设备
*/
@GetMapping("/getDeviceListBySiteAndCategory")
public AjaxResult getDeviceListBySiteAndCategory(String siteId,String deviceCategory)
{
public AjaxResult getDeviceListBySiteAndCategory(String siteId, String deviceCategory) {
return success(iEmsDeviceSettingService.getDeviceListBySiteAndCategory(siteId, deviceCategory));
}
/**
* 获取单站监控项目点位映射
*/
@GetMapping("/getSingleMonitorProjectPointMapping")
public AjaxResult getSingleMonitorProjectPointMapping(@RequestParam String siteId)
{
public AjaxResult getSingleMonitorProjectPointMapping(@RequestParam String siteId) {
return success(iEmsDeviceSettingService.getSiteMonitorProjectPointMapping(siteId));
}
/**
* 保存单站监控项目点位映射
*/
@PostMapping("/saveSingleMonitorProjectPointMapping")
public AjaxResult saveSingleMonitorProjectPointMapping(@RequestBody SiteMonitorProjectPointMappingSaveRequest request)
{
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));
}
@PostMapping("/saveSingleMonitorWorkStatusEnumMappings")
public AjaxResult saveSingleMonitorWorkStatusEnumMappings(@RequestBody WorkStatusEnumMappingSaveRequest request) {
int rows = iEmsDeviceSettingService.saveSiteWorkStatusEnumMappings(request.getSiteId(), request.getMappings(), getUsername());
return AjaxResult.success(rows);
}
@GetMapping("/downloadSingleBatteryMonitorImportTemplate")
public void downloadSingleBatteryMonitorImportTemplate(HttpServletResponse response) {
ExcelUtil<SingleBatteryMonitorImportRowVo> util = new ExcelUtil<>(SingleBatteryMonitorImportRowVo.class);
util.importTemplateExcel(response, "单体电池导入模板", "单体电池导入模板");
}
@PostMapping("/importSingleBatteryMonitorMappings")
public AjaxResult importSingleBatteryMonitorMappings(@RequestParam("siteId") String siteId,
@RequestParam("file") MultipartFile file) throws Exception {
ExcelUtil<SingleBatteryMonitorImportRowVo> util = new ExcelUtil<>(SingleBatteryMonitorImportRowVo.class);
List<SingleBatteryMonitorImportRowVo> rowList = util.importExcel(file.getInputStream(), 1);
if (rowList == null) {
return AjaxResult.error("导入失败:未读取到任何数据,请检查 Excel 文件内容");
}
List<SingleBatteryMonitorImportRowVo> validRowList = new ArrayList<>();
for (int i = 0; i < rowList.size(); i++) {
SingleBatteryMonitorImportRowVo row = rowList.get(i);
if (row == null) {
continue;
}
row.setRowNum(i + 3);
validRowList.add(row);
}
if (validRowList.isEmpty()) {
return AjaxResult.error("导入失败:未识别到有效数据,请使用系统导出的最新模板,并保持表头名称不变");
}
SingleBatteryMonitorImportResultVo result = iEmsDeviceSettingService.importSingleBatteryMonitorMappings(siteId, validRowList, getUsername());
return AjaxResult.success(result);
}
@PostMapping("/initializeSingleBatteryMonitorMappings")
public AjaxResult initializeSingleBatteryMonitorMappings(@RequestBody SingleBatteryConfigInitRequest request) {
SingleBatteryConfigInitResultVo result = iEmsDeviceSettingService.initializeSingleBatteryMonitorMappings(request, getUsername());
return AjaxResult.success(result);
}
// @PreAuthorize("@ss.hasPermi('system:device:onAndOff')")
@Log(title = "开关机", businessType = BusinessType.UPDATE)
@Log(title = "寮€鍏虫満", businessType = BusinessType.UPDATE)
@PostMapping("/updateDeviceStatus")
public AjaxResult updateDeviceStatus(@Valid @RequestBody DeviceUpdateRequest request)
{
public AjaxResult updateDeviceStatus(@Valid @RequestBody DeviceUpdateRequest request) {
return success(iEmsDeviceSettingService.updateDeviceStatus(request));
}
}

View File

@ -10,10 +10,13 @@ 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.*;
@ -28,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;
@ -47,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));
}
/**
* 单站监控-设备监控-实时运行头部数据
*/
@ -60,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);
}
/**
@ -155,9 +217,7 @@ public class EmsSiteMonitorController extends BaseController{
{
startPage();
SiteBatteryDataList siteBatteryDataList = new SiteBatteryDataList();
// 簇最大最小单体id数据
List<BMSBatteryDataList> clusterBatteryDataList = iSingleSiteService.getClusterBatteryList(siteId,stackDeviceId,clusterDeviceId);
siteBatteryDataList.setClusterList(clusterBatteryDataList);
// 单体电池数据
List<BatteryDataStatsListVo> List = iSingleSiteService.getClusterDataInfoList(clusterDeviceId,siteId,stackDeviceId,batteryId);
// 对batteryList进行分页处理

View File

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

View File

@ -0,0 +1,48 @@
package com.xzzn.web.controller.ems;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
import com.xzzn.ems.service.IEmsStrategyRuntimeConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 策略运行参数配置Controller
*/
@RestController
@RequestMapping("/system/strategyRuntimeConfig")
public class EmsStrategyRuntimeConfigController extends BaseController {
@Autowired
private IEmsStrategyRuntimeConfigService runtimeConfigService;
/**
* 按站点ID获取策略运行参数
*/
@GetMapping("/getBySiteId")
public AjaxResult getBySiteId(String siteId) {
if (StringUtils.isEmpty(siteId)) {
return error("缺少必填字段siteId");
}
return success(runtimeConfigService.getBySiteId(siteId));
}
/**
* 保存策略运行参数按siteId新增/更新)
*/
@PostMapping("/save")
public AjaxResult save(@RequestBody EmsStrategyRuntimeConfig config) {
if (config == null || StringUtils.isEmpty(config.getSiteId())) {
return error("缺少必填字段siteId");
}
config.setCreateBy(getUsername());
config.setUpdateBy(getUsername());
return toAjax(runtimeConfigService.saveBySiteId(config));
}
}

View File

@ -6,11 +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.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;
@ -38,22 +34,13 @@ public class MqttMessageController implements MqttPublisher, MqttSubscriber {
private final MqttLifecycleManager mqttLifecycleManager;
@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

View File

@ -0,0 +1,32 @@
package com.xzzn.web.controller.system;
import com.xzzn.common.core.controller.BaseController;
import com.xzzn.common.core.domain.AjaxResult;
import com.xzzn.ems.domain.vo.BizRemarkBatchGetRequest;
import com.xzzn.ems.domain.vo.BizRemarkSaveRequest;
import com.xzzn.ems.service.ISysBizRemarkService;
import org.springframework.beans.factory.annotation.Autowired;
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;
@RestController
@RequestMapping("/system/bizRemark")
public class SysBizRemarkController extends BaseController
{
@Autowired
private ISysBizRemarkService sysBizRemarkService;
@PostMapping("/batchGet")
public AjaxResult batchGet(@RequestBody BizRemarkBatchGetRequest request)
{
return success(sysBizRemarkService.getRemarkMap(request.getBizType(), request.getBizKey1(), request.getBizKey2List()));
}
@PostMapping("/save")
public AjaxResult save(@RequestBody BizRemarkSaveRequest request)
{
return toAjax(sysBizRemarkService.saveRemark(request, getUsername()));
}
}

View File

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

View File

@ -205,3 +205,24 @@ 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
influxdb:
enabled: true
url: http://172.17.0.7:8086/
api-token: B_1HvHbUhubQQhLdI0XtNVw7maWS1aIjVZQ1a3PGD6b-VNg3_JUo_jHgZmjeBKYXnGATNdIqfpl-FAVbJ4UIPg==
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

View File

@ -221,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

View File

@ -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";
@ -126,6 +129,15 @@ public class RedisKeyConstants
/** 点位配置缓存(按站点+设备) */
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_";
}

View File

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

View File

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

View File

@ -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<String, Integer> 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<PollingTask> pollingTasks = buildPollingTasks();
if (CollectionUtils.isEmpty(pollingTasks)) {
log.warn("未查询到可用的Modbus采集点位配置跳过本轮轮询");
polling.set(false);
return;
}
List<Path> 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<String, List<PollingTask>> groupedByHost = pollingTasks.stream()
.collect(Collectors.groupingBy(
task -> task.getDeviceConfig().getHost(),
HashMap::new,
Collectors.toList()));
// 按主机IP分组同一网关的不同端口也归为一组避免并发访问导致Connection Reset
Map<String, List<DeviceConfig>> 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<String, List<DeviceConfig>> entry : groupedByHost.entrySet()) {
for (Map.Entry<String, List<PollingTask>> entry : groupedByHost.entrySet()) {
String hostKey = entry.getKey();
List<DeviceConfig> configs = entry.getValue();
for (DeviceConfig config : configs) {
List<PollingTask> 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<PollingTask> buildPollingTasks() {
List<EmsPointConfig> pointConfigs = emsPointConfigMapper.selectModbusCollectPointConfigs(null);
if (CollectionUtils.isEmpty(pointConfigs)) {
return Collections.emptyList();
}
List<EmsDevicesSetting> allDevices = emsDevicesSettingMapper.selectEmsDevicesSettingList(null);
Map<String, EmsDevicesSetting> 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<String, List<EmsPointConfig>> 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<PollingTask> tasks = new ArrayList<>();
for (Map.Entry<String, List<EmsPointConfig>> 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<EmsPointConfig> 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<TagConfig> 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<String, Object> data, String deviceNumber) {
private void processingData(String siteId, Map<String, Object> 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<String, Object> 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<String, Object> data, String deviceNumber, Long timestamp) {
deviceDataProcessServiceImpl.processingDeviceData(siteId, deviceNumber, JSON.toJSONString(data), DateUtils.convertUpdateTime(timestamp));
private void saveDataToDatabase(String siteId, Map<String, Object> 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<SysJob> 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;
}
}
}

View File

@ -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<EmsFaultProtectionPlan> 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<ProtectionSettingVo> protSettings = objectMapper.readValue(
protectionSettings,
new TypeReference<List<ProtectionSettingVo>>() {}
);
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<ProtectionSettingVo> 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<ProtectionSettingVo> faultSettings = settingGroup.getFaultSettings();
List<ProtectionSettingVo> 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<ProtectionPlanVo> protPlanList;
try {
protPlanList = objectMapper.readValue(
protPlanJson,
new TypeReference<List<ProtectionPlanVo>>() {}
);
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<WriteTagConfig> 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<ProtectionSettingVo> 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<ProtectionSettingVo> legacy = objectMapper.readValue(
settingsJson,
new TypeReference<List<ProtectionSettingVo>>() {}
);
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<ProtectionPlanVo> parseProtectionPlan(String planJson) {
if (StringUtils.isEmpty(planJson)) {
return new ArrayList<>();
}
try {
if (planJson.trim().startsWith("[")) {
List<ProtectionPlanVo> plans = objectMapper.readValue(
planJson,
new TypeReference<List<ProtectionPlanVo>>() {}
);
return plans == null ? new ArrayList<>() : plans;
}
ProtectionPlanVo plan = objectMapper.readValue(planJson, ProtectionPlanVo.class);
List<ProtectionPlanVo> 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<EmsFaultProtectionPlan> allPlans) {
Map<String, List<EmsFaultProtectionPlan>> 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<String, List<EmsFaultProtectionPlan>> 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<EmsFaultProtectionPlan> sitePlans = iEmsFaultProtectionPlanService.selectEmsFaultProtectionPlanList(query);
writeSiteProtectionConstraint(siteId, sitePlans);
}
private void writeSiteProtectionConstraint(String siteId, List<EmsFaultProtectionPlan> sitePlans) {
List<EmsFaultProtectionPlan> 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<ProtectionPlanVo> protectionPlans = parseProtectionPlan(plan.getProtectionPlan());
applyCapabilityByProtectionPlan(vo, protectionPlans);
return vo;
}
private void applyCapabilityByProtectionPlan(ProtectionConstraintVo vo, List<ProtectionPlanVo> 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<ProtectionSettingVo> 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<ProtectionSettingVo> 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<String, Object> obj = JSON.parseObject(jsonData, new com.alibaba.fastjson2.TypeReference<Map<String, Object>>() {});
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<EmsStrategyRunning> 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<String> 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<Boolean> 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)) {

View File

@ -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<Long, Boolean> strategyLocks = new ConcurrentHashMap<>();
// SOC 上下限值默认为0%-100%
private static final BigDecimal SOC_DOWN = new BigDecimal(0);
private static final BigDecimal SOC_UP = new BigDecimal(100);
private static final BigDecimal DEFAULT_SOC_DOWN = BigDecimal.ZERO;
private static final BigDecimal DEFAULT_SOC_UP = new BigDecimal(100);
// 逆变器功率下限值默认为30kW
private static final BigDecimal ANTI_REVERSE_THRESHOLD = new BigDecimal(30);
private static final BigDecimal DEFAULT_ANTI_REVERSE_THRESHOLD = new BigDecimal(30);
// 逆变器下限值范围默认为20%
private static final BigDecimal ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20);
private static final BigDecimal DEFAULT_ANTI_REVERSE_RANGE_PERCENT = new BigDecimal(20);
// 逆变器功率上限值默认为100kW
private static final BigDecimal ANTI_REVERSE_UP = new BigDecimal(100);
private static final BigDecimal DEFAULT_ANTI_REVERSE_UP = new BigDecimal(100);
// PCS功率降幅默认为10%
private static final BigDecimal ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10);
private static final BigDecimal DEFAULT_ANTI_REVERSE_POWER_DOWN_PERCENT = new BigDecimal(10);
// 电网有功功率低于20kW时强制待机
private static final BigDecimal DEFAULT_ANTI_REVERSE_HARD_STOP_THRESHOLD = new BigDecimal(20);
// 设定功率倍率默认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<Map<String, String>> 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<Long, EmsPcsSetting> pcsSettingCache = new HashMap<>();
BigDecimal avgChargeDischargePower = emsStrategyTemp.getChargeDischargePower()
.divide(new BigDecimal(pcsDeviceList.size()), POWER_SCALE, RoundingMode.HALF_UP);
BigDecimal totalActivePower = null;
if (ChargeStatus.DISCHARGING.getCode().equals(emsStrategyTemp.getChargeStatus())) {
// 同一站点同一轮执行只读取一次电网电表,降低重复查库和数据抖动
EmsAmmeterData emsAmmeterData = emsAmmeterDataMapper.getLastData(emsStrategyTemp.getSiteId(), SiteDevice.LOAD.name());
if (emsAmmeterData != null) {
totalActivePower = emsAmmeterData.getTotalActivePower();
}
}
for (EmsDevicesSetting pcsDevice : pcsDeviceList) {
EmsPcsSetting pcsSetting = emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(pcsDevice.getId());
EmsPcsSetting pcsSetting = pcsSettingCache.computeIfAbsent(
pcsDevice.getId(),
id -> emsPcsSettingMapper.selectEmsPcsSettingByDeviceId(id)
);
if (pcsSetting == null || pcsSetting.getClusterNum() < 1) {
logger.info("当前站点: {}, PCS设备: {} 未获取电池簇数量", siteId, pcsDevice.getDeviceId());
continue;
}
// 功率默认放大10倍平均功率值根据电池簇数量进行平均分配
avgChargeDischargePower = avgChargeDischargePower.multiply(new BigDecimal(10)).divide(new BigDecimal(pcsSetting.getClusterNum()));
// 平均功率值根据倍率放大后,再按电池簇数量平均分配
BigDecimal strategyPower = avgChargeDischargePower.multiply(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<String, Object> 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;
}
}

View File

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

View File

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

View File

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

View File

@ -49,6 +49,10 @@ public class EmsPcsSetting extends BaseEntity
@Excel(name = "关机目标功率")
private BigDecimal stopPower;
/** 目标功率倍率 */
@Excel(name = "目标功率倍率")
private BigDecimal powerMultiplier;
/** 电池簇数 */
@Excel(name = "电池簇数")
private Integer clusterNum;
@ -135,6 +139,16 @@ public class EmsPcsSetting extends BaseEntity
return stopPower;
}
public BigDecimal getPowerMultiplier()
{
return powerMultiplier;
}
public void setPowerMultiplier(BigDecimal powerMultiplier)
{
this.powerMultiplier = powerMultiplier;
}
public void setClusterNum(Integer clusterNum)
{
this.clusterNum = clusterNum;
@ -165,6 +179,7 @@ public class EmsPcsSetting extends BaseEntity
.append("stopCommand", getStopCommand())
.append("startPower", getStartPower())
.append("stopPower", getStopPower())
.append("powerMultiplier", getPowerMultiplier())
.append("clusterNum", getClusterNum())
.append("clusterPointAddress", getClusterPointAddress())
.append("createBy", getCreateBy())

View File

@ -0,0 +1,128 @@
package com.xzzn.ems.domain;
import com.xzzn.common.annotation.Excel;
import com.xzzn.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
public class EmsPointCalcConfig extends BaseEntity {
private static final long serialVersionUID = 1L;
private Long id;
@Excel(name = "点位ID")
private String pointId;
@Excel(name = "站点ID")
private String siteId;
@Excel(name = "设备类型")
private String deviceCategory;
@Excel(name = "点位名称")
private String pointName;
@Excel(name = "数据键")
private String dataKey;
@Excel(name = "点位描述")
private String pointDesc;
@Excel(name = "单位")
private String dataUnit;
@Excel(name = "计算表达式")
private String calcExpression;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getPointName() {
return pointName;
}
public void setPointName(String pointName) {
this.pointName = pointName;
}
public String getDeviceCategory() {
return deviceCategory;
}
public void setDeviceCategory(String deviceCategory) {
this.deviceCategory = deviceCategory;
}
public String getDataKey() {
return dataKey;
}
public void setDataKey(String dataKey) {
this.dataKey = dataKey;
}
public String getPointDesc() {
return pointDesc;
}
public void setPointDesc(String pointDesc) {
this.pointDesc = pointDesc;
}
public String getDataUnit() {
return dataUnit;
}
public void setDataUnit(String dataUnit) {
this.dataUnit = dataUnit;
}
public String getCalcExpression() {
return calcExpression;
}
public void setCalcExpression(String calcExpression) {
this.calcExpression = calcExpression;
}
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
.append("id", getId())
.append("pointId", getPointId())
.append("siteId", getSiteId())
.append("deviceCategory", getDeviceCategory())
.append("pointName", getPointName())
.append("dataKey", getDataKey())
.append("pointDesc", getPointDesc())
.append("dataUnit", getDataUnit())
.append("calcExpression", getCalcExpression())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.toString();
}
}

View File

@ -15,6 +15,9 @@ public class EmsPointConfig extends BaseEntity {
private Long id;
@Excel(name = "点位ID")
private String pointId;
@Excel(name = "站点ID")
private String siteId;
@ -60,6 +63,27 @@ public class EmsPointConfig extends BaseEntity {
@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;
@Excel(name = "小数位数")
private Integer decimalScale;
public Long getId() {
return id;
}
@ -68,6 +92,14 @@ public class EmsPointConfig extends BaseEntity {
this.id = id;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getSiteId() {
return siteId;
}
@ -188,10 +220,67 @@ public class EmsPointConfig extends BaseEntity {
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;
}
public Integer getDecimalScale() {
return decimalScale;
}
public void setDecimalScale(Integer decimalScale) {
this.decimalScale = decimalScale;
}
@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())
@ -207,6 +296,13 @@ public class EmsPointConfig extends BaseEntity {
.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("decimalScale", getDecimalScale())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())

View File

@ -11,7 +11,10 @@ public class EmsSiteMonitorPointMatch extends BaseEntity {
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;
@ -44,4 +47,28 @@ public class EmsSiteMonitorPointMatch extends BaseEntity {
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;
}
}

View File

@ -25,6 +25,10 @@ public class EmsSiteSetting extends BaseEntity
@Excel(name = "站点名称")
private String siteName;
/** 站点简称 */
@Excel(name = "站点简称")
private String siteShortName;
/** 站点地址 */
@Excel(name = "站点地址")
private String siteAddress;
@ -77,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;
@ -162,6 +176,7 @@ public class EmsSiteSetting extends BaseEntity
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())

View File

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

View File

@ -35,6 +35,14 @@ public class EmsStrategyTempTimeConfig extends BaseEntity
@Excel(name = "充放功率 (kW)")
private BigDecimal chargeDischargePower;
/** SDC下限 (%) */
@Excel(name = "SDC下限 (%)")
private BigDecimal sdcDown;
/** SDC上限 (%) */
@Excel(name = "SDC上限 (%)")
private BigDecimal sdcUp;
/** 充电状态如“1-充电”、“2-待机” */
@Excel(name = "充电状态如“1-充电”、“2-待机”")
private String chargeStatus;
@ -83,6 +91,26 @@ public class EmsStrategyTempTimeConfig extends BaseEntity
return chargeDischargePower;
}
public void setSdcDown(BigDecimal sdcDown)
{
this.sdcDown = sdcDown;
}
public BigDecimal getSdcDown()
{
return sdcDown;
}
public void setSdcUp(BigDecimal sdcUp)
{
this.sdcUp = sdcUp;
}
public BigDecimal getSdcUp()
{
return sdcUp;
}
public void setChargeStatus(String chargeStatus)
{
this.chargeStatus = chargeStatus;
@ -110,6 +138,8 @@ public class EmsStrategyTempTimeConfig extends BaseEntity
.append("startTime", getStartTime())
.append("endTime", getEndTime())
.append("chargeDischargePower", getChargeDischargePower())
.append("sdcDown", getSdcDown())
.append("sdcUp", getSdcUp())
.append("chargeStatus", getChargeStatus())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())

View File

@ -0,0 +1,68 @@
package com.xzzn.ems.domain;
import com.xzzn.common.core.domain.BaseEntity;
public class SysBizRemark extends BaseEntity
{
private static final long serialVersionUID = 1L;
private Long id;
private String bizType;
private String bizKey1;
private String bizKey2;
private String delFlag;
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getBizType()
{
return bizType;
}
public void setBizType(String bizType)
{
this.bizType = bizType;
}
public String getBizKey1()
{
return bizKey1;
}
public void setBizKey1(String bizKey1)
{
this.bizKey1 = bizKey1;
}
public String getBizKey2()
{
return bizKey2;
}
public void setBizKey2(String bizKey2)
{
this.bizKey2 = bizKey2;
}
public String getDelFlag()
{
return delFlag;
}
public void setDelFlag(String delFlag)
{
this.delFlag = delFlag;
}
}

View File

@ -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;
@ -44,6 +53,8 @@ public class AmmeterRevenueStatisListVo {
/** 实际收益 */
private BigDecimal actualRevenue = BigDecimal.ZERO;
private String remark;
public String getDataTime() {
return dataTime;
}
@ -52,6 +63,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;
}
@ -139,4 +174,12 @@ public class AmmeterRevenueStatisListVo {
public void setActualRevenue(BigDecimal actualRevenue) {
this.actualRevenue = actualRevenue;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

@ -46,6 +46,8 @@ public class AmmeterStatisListVo {
/** 效率-有功总/无功总 */
private BigDecimal effect = BigDecimal.ZERO;
private String remark;
public String getDataTime() {
return dataTime;
}
@ -149,4 +151,12 @@ public class AmmeterStatisListVo {
public void setEffect(BigDecimal effect) {
this.effect = effect;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}

View File

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

View File

@ -0,0 +1,42 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
public class BizRemarkBatchGetRequest
{
private String bizType;
private String bizKey1;
private List<String> bizKey2List;
public String getBizType()
{
return bizType;
}
public void setBizType(String bizType)
{
this.bizType = bizType;
}
public String getBizKey1()
{
return bizKey1;
}
public void setBizKey1(String bizKey1)
{
this.bizKey1 = bizKey1;
}
public List<String> getBizKey2List()
{
return bizKey2List;
}
public void setBizKey2List(List<String> bizKey2List)
{
this.bizKey2List = bizKey2List;
}
}

View File

@ -0,0 +1,52 @@
package com.xzzn.ems.domain.vo;
public class BizRemarkSaveRequest
{
private String bizType;
private String bizKey1;
private String bizKey2;
private String remark;
public String getBizType()
{
return bizType;
}
public void setBizType(String bizType)
{
this.bizType = bizType;
}
public String getBizKey1()
{
return bizKey1;
}
public void setBizKey1(String bizKey1)
{
this.bizKey1 = bizKey1;
}
public String getBizKey2()
{
return bizKey2;
}
public void setBizKey2(String bizKey2)
{
this.bizKey2 = bizKey2;
}
public String getRemark()
{
return remark;
}
public void setRemark(String remark)
{
this.remark = remark;
}
}

View File

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

View File

@ -1,10 +1,19 @@
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;
}

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package com.xzzn.ems.domain.vo;
public class PointConfigLatestValueItemVo {
private String siteId;
private String pointId;
private String deviceId;
private String dataKey;
@ -13,6 +14,14 @@ public class PointConfigLatestValueItemVo {
this.siteId = siteId;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getDeviceId() {
return deviceId;
}

View File

@ -4,6 +4,7 @@ import java.util.Date;
public class PointConfigLatestValueVo {
private String siteId;
private String pointId;
private String deviceId;
private String dataKey;
private Object pointValue;
@ -17,6 +18,14 @@ public class PointConfigLatestValueVo {
this.siteId = siteId;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public String getDeviceId() {
return deviceId;
}

View File

@ -15,6 +15,8 @@ public class PointNameRequest {
private String pointName;
private List<String> pointNames;
private String pointId;
private List<String> pointIds;
/** 数据分组 1-分钟 2-小时 3-天 */
private int dataUnit;
@ -59,6 +61,22 @@ public class PointNameRequest {
this.pointNames = pointNames;
}
public String getPointId() {
return pointId;
}
public void setPointId(String pointId) {
this.pointId = pointId;
}
public List<String> getPointIds() {
return pointIds;
}
public void setPointIds(List<String> pointIds) {
this.pointIds = pointIds;
}
public int getDataUnit() {
return dataUnit;
}

View File

@ -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<Long> 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<Long> getSourcePlanIds() {
return sourcePlanIds;
}
public void setSourcePlanIds(List<Long> 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;
}
}

View File

@ -0,0 +1,38 @@
package com.xzzn.ems.domain.vo;
import java.util.ArrayList;
import java.util.List;
/**
* 告警保护方案-保护前提分组
*/
public class ProtectionSettingsGroupVo {
/** 故障保护前提 */
private List<ProtectionSettingVo> faultSettings;
/** 释放保护前提 */
private List<ProtectionSettingVo> releaseSettings;
public static ProtectionSettingsGroupVo empty() {
ProtectionSettingsGroupVo vo = new ProtectionSettingsGroupVo();
vo.setFaultSettings(new ArrayList<>());
vo.setReleaseSettings(new ArrayList<>());
return vo;
}
public List<ProtectionSettingVo> getFaultSettings() {
return faultSettings;
}
public void setFaultSettings(List<ProtectionSettingVo> faultSettings) {
this.faultSettings = faultSettings;
}
public List<ProtectionSettingVo> getReleaseSettings() {
return releaseSettings;
}
public void setReleaseSettings(List<ProtectionSettingVo> releaseSettings) {
this.releaseSettings = releaseSettings;
}
}

View File

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

View File

@ -0,0 +1,41 @@
package com.xzzn.ems.domain.vo;
public class SingleBatteryConfigInitRequest {
private String siteId;
private String scopeType;
private String scopeDeviceId;
private Integer targetCount;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getScopeType() {
return scopeType;
}
public void setScopeType(String scopeType) {
this.scopeType = scopeType;
}
public String getScopeDeviceId() {
return scopeDeviceId;
}
public void setScopeDeviceId(String scopeDeviceId) {
this.scopeDeviceId = scopeDeviceId;
}
public Integer getTargetCount() {
return targetCount;
}
public void setTargetCount(Integer targetCount) {
this.targetCount = targetCount;
}
}

View File

@ -0,0 +1,122 @@
package com.xzzn.ems.domain.vo;
public class SingleBatteryConfigInitResultVo {
private Boolean committed;
private String siteId;
private String scopeType;
private String scopeDeviceId;
private Integer targetCount;
private Integer existingBatteryCount;
private Integer initializedBatteryCount;
private Integer insertedBatteryCount;
private Integer pointHitCount;
private Integer fixedValueFallbackCount;
private Integer insertedMappingCount;
private Integer updatedMappingCount;
private String message;
public Boolean getCommitted() {
return committed;
}
public void setCommitted(Boolean committed) {
this.committed = committed;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getScopeType() {
return scopeType;
}
public void setScopeType(String scopeType) {
this.scopeType = scopeType;
}
public String getScopeDeviceId() {
return scopeDeviceId;
}
public void setScopeDeviceId(String scopeDeviceId) {
this.scopeDeviceId = scopeDeviceId;
}
public Integer getTargetCount() {
return targetCount;
}
public void setTargetCount(Integer targetCount) {
this.targetCount = targetCount;
}
public Integer getExistingBatteryCount() {
return existingBatteryCount;
}
public void setExistingBatteryCount(Integer existingBatteryCount) {
this.existingBatteryCount = existingBatteryCount;
}
public Integer getInitializedBatteryCount() {
return initializedBatteryCount;
}
public void setInitializedBatteryCount(Integer initializedBatteryCount) {
this.initializedBatteryCount = initializedBatteryCount;
}
public Integer getInsertedBatteryCount() {
return insertedBatteryCount;
}
public void setInsertedBatteryCount(Integer insertedBatteryCount) {
this.insertedBatteryCount = insertedBatteryCount;
}
public Integer getPointHitCount() {
return pointHitCount;
}
public void setPointHitCount(Integer pointHitCount) {
this.pointHitCount = pointHitCount;
}
public Integer getFixedValueFallbackCount() {
return fixedValueFallbackCount;
}
public void setFixedValueFallbackCount(Integer fixedValueFallbackCount) {
this.fixedValueFallbackCount = fixedValueFallbackCount;
}
public Integer getInsertedMappingCount() {
return insertedMappingCount;
}
public void setInsertedMappingCount(Integer insertedMappingCount) {
this.insertedMappingCount = insertedMappingCount;
}
public Integer getUpdatedMappingCount() {
return updatedMappingCount;
}
public void setUpdatedMappingCount(Integer updatedMappingCount) {
this.updatedMappingCount = updatedMappingCount;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,59 @@
package com.xzzn.ems.domain.vo;
public class SingleBatteryMonitorImportFailureVo {
private Integer rowNum;
private String siteId;
private String stackDeviceId;
private String clusterDeviceId;
private String batteryDeviceId;
private String errorMessage;
public Integer getRowNum() {
return rowNum;
}
public void setRowNum(Integer rowNum) {
this.rowNum = rowNum;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getStackDeviceId() {
return stackDeviceId;
}
public void setStackDeviceId(String stackDeviceId) {
this.stackDeviceId = stackDeviceId;
}
public String getClusterDeviceId() {
return clusterDeviceId;
}
public void setClusterDeviceId(String clusterDeviceId) {
this.clusterDeviceId = clusterDeviceId;
}
public String getBatteryDeviceId() {
return batteryDeviceId;
}
public void setBatteryDeviceId(String batteryDeviceId) {
this.batteryDeviceId = batteryDeviceId;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
}

View File

@ -0,0 +1,98 @@
package com.xzzn.ems.domain.vo;
import java.util.ArrayList;
import java.util.List;
public class SingleBatteryMonitorImportResultVo {
private Boolean committed;
private Integer totalRows;
private Integer successRows;
private Integer failureRows;
private Integer insertedBatteryCount;
private Integer updatedBatteryCount;
private Integer insertedMappingCount;
private Integer updatedMappingCount;
private String message;
private List<SingleBatteryMonitorImportFailureVo> failureDetails = new ArrayList<>();
public Boolean getCommitted() {
return committed;
}
public void setCommitted(Boolean committed) {
this.committed = committed;
}
public Integer getTotalRows() {
return totalRows;
}
public void setTotalRows(Integer totalRows) {
this.totalRows = totalRows;
}
public Integer getSuccessRows() {
return successRows;
}
public void setSuccessRows(Integer successRows) {
this.successRows = successRows;
}
public Integer getFailureRows() {
return failureRows;
}
public void setFailureRows(Integer failureRows) {
this.failureRows = failureRows;
}
public Integer getInsertedBatteryCount() {
return insertedBatteryCount;
}
public void setInsertedBatteryCount(Integer insertedBatteryCount) {
this.insertedBatteryCount = insertedBatteryCount;
}
public Integer getUpdatedBatteryCount() {
return updatedBatteryCount;
}
public void setUpdatedBatteryCount(Integer updatedBatteryCount) {
this.updatedBatteryCount = updatedBatteryCount;
}
public Integer getInsertedMappingCount() {
return insertedMappingCount;
}
public void setInsertedMappingCount(Integer insertedMappingCount) {
this.insertedMappingCount = insertedMappingCount;
}
public Integer getUpdatedMappingCount() {
return updatedMappingCount;
}
public void setUpdatedMappingCount(Integer updatedMappingCount) {
this.updatedMappingCount = updatedMappingCount;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public List<SingleBatteryMonitorImportFailureVo> getFailureDetails() {
return failureDetails;
}
public void setFailureDetails(List<SingleBatteryMonitorImportFailureVo> failureDetails) {
this.failureDetails = failureDetails;
}
}

View File

@ -0,0 +1,115 @@
package com.xzzn.ems.domain.vo;
import com.xzzn.common.annotation.Excel;
public class SingleBatteryMonitorImportRowVo {
private Integer rowNum;
@Excel(name = "站点ID")
private String siteId;
@Excel(name = "电池堆编号")
private String stackDeviceId;
@Excel(name = "电池簇编号")
private String clusterDeviceId;
@Excel(name = "单体编号")
private String batteryDeviceId;
@Excel(name = "单体名称")
private String batteryDeviceName;
@Excel(name = "电压点位ID")
private String voltagePointId;
@Excel(name = "温度点位ID")
private String temperaturePointId;
@Excel(name = "SOC点位ID")
private String socPointId;
@Excel(name = "SOH点位ID")
private String sohPointId;
public Integer getRowNum() {
return rowNum;
}
public void setRowNum(Integer rowNum) {
this.rowNum = rowNum;
}
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public String getStackDeviceId() {
return stackDeviceId;
}
public void setStackDeviceId(String stackDeviceId) {
this.stackDeviceId = stackDeviceId;
}
public String getClusterDeviceId() {
return clusterDeviceId;
}
public void setClusterDeviceId(String clusterDeviceId) {
this.clusterDeviceId = clusterDeviceId;
}
public String getBatteryDeviceId() {
return batteryDeviceId;
}
public void setBatteryDeviceId(String batteryDeviceId) {
this.batteryDeviceId = batteryDeviceId;
}
public String getBatteryDeviceName() {
return batteryDeviceName;
}
public void setBatteryDeviceName(String batteryDeviceName) {
this.batteryDeviceName = batteryDeviceName;
}
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;
}
}

View File

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

View File

@ -16,8 +16,16 @@ public class SiteMonitorProjectPointMappingVo {
private String fieldName;
private String deviceId;
private String deviceName;
private String dataPoint;
private String fixedDataPoint;
private Integer useFixedDisplay;
public String getModuleCode() {
return moduleCode;
}
@ -81,4 +89,36 @@ public class SiteMonitorProjectPointMappingVo {
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;
}
}

View File

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

View File

@ -0,0 +1,26 @@
package com.xzzn.ems.domain.vo;
import java.util.List;
public class WorkStatusEnumMappingSaveRequest {
private String siteId;
private List<WorkStatusEnumMappingVo> mappings;
public String getSiteId() {
return siteId;
}
public void setSiteId(String siteId) {
this.siteId = siteId;
}
public List<WorkStatusEnumMappingVo> getMappings() {
return mappings;
}
public void setMappings(List<WorkStatusEnumMappingVo> mappings) {
this.mappings = mappings;
}
}

View File

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

View File

@ -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<String, BigDecimal> getAllSiteChargeData(@Param("nowData")String nowData, @Param("siteId")String siteId);

View File

@ -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);
// 电表报表

View File

@ -0,0 +1,24 @@
package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsPointCalcConfig;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface EmsPointCalcConfigMapper {
EmsPointCalcConfig selectEmsPointCalcConfigById(Long id);
List<EmsPointCalcConfig> selectEmsPointCalcConfigList(EmsPointCalcConfig emsPointCalcConfig);
int insertEmsPointCalcConfig(EmsPointCalcConfig emsPointCalcConfig);
int updateEmsPointCalcConfig(EmsPointCalcConfig emsPointCalcConfig);
int deleteEmsPointCalcConfigById(Long id);
int deleteEmsPointCalcConfigByIds(Long[] ids);
int countBySiteId(@Param("siteId") String siteId);
int deleteBySiteId(@Param("siteId") String siteId);
}

View File

@ -13,8 +13,12 @@ public interface EmsPointConfigMapper {
int insertEmsPointConfig(EmsPointConfig emsPointConfig);
int insertBatchEmsPointConfig(@Param("list") List<EmsPointConfig> list);
int updateEmsPointConfig(EmsPointConfig emsPointConfig);
int updateEmsPointConfigForImport(EmsPointConfig emsPointConfig);
int deleteEmsPointConfigById(Long id);
int deleteEmsPointConfigByIds(Long[] ids);
@ -39,6 +43,12 @@ public interface EmsPointConfigMapper {
List<EmsPointConfig> getConfigListForGeneralQuery(@Param("siteIds") List<String> siteIds,
@Param("deviceCategory") String deviceCategory,
@Param("pointIds") List<String> pointIds,
@Param("pointNames") List<String> pointNames,
@Param("deviceIds") List<String> deviceIds);
List<EmsPointConfig> selectBySiteIdAndPointIds(@Param("siteId") String siteId,
@Param("pointIds") List<String> pointIds);
List<EmsPointConfig> selectModbusCollectPointConfigs(@Param("siteId") String siteId);
}

View File

@ -75,4 +75,8 @@ public interface EmsPointEnumMatchMapper
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);
}

View File

@ -14,15 +14,10 @@ public interface EmsSiteMonitorDataMapper {
@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);
int updateHistoryHotColumns(@Param("tableName") String tableName,
@Param("siteId") String siteId,
@Param("statisMinute") java.util.Date statisMinute,
@Param("hotSoc") String hotSoc,
@Param("hotTotalActivePower") String hotTotalActivePower,
@Param("hotTotalReactivePower") String hotTotalReactivePower,
@Param("hotDayChargedCap") String hotDayChargedCap,
@Param("hotDayDisChargedCap") String hotDayDisChargedCap,
@Param("operName") String operName);
}

View File

@ -9,9 +9,15 @@ import java.util.List;
* 单站监控字段点位映射 Mapper
*/
public interface EmsSiteMonitorPointMatchMapper {
List<String> selectDistinctSiteIds();
List<EmsSiteMonitorPointMatch> 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<EmsSiteMonitorPointMatch> list);
int updateEmsSiteMonitorPointMatch(EmsSiteMonitorPointMatch pointMatch);
}

View File

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

View File

@ -0,0 +1,36 @@
package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
/**
* 策略运行参数配置Mapper接口
*
* @author xzzn
* @date 2026-02-13
*/
public interface EmsStrategyRuntimeConfigMapper {
/**
* 根据站点ID查询参数配置
*
* @param siteId 站点ID
* @return 参数配置
*/
EmsStrategyRuntimeConfig selectBySiteId(String siteId);
/**
* 新增参数配置
*
* @param config 参数配置
* @return 结果
*/
int insert(EmsStrategyRuntimeConfig config);
/**
* 按站点ID更新参数配置
*
* @param config 参数配置
* @return 结果
*/
int updateBySiteId(EmsStrategyRuntimeConfig config);
}

View File

@ -0,0 +1,21 @@
package com.xzzn.ems.mapper;
import com.xzzn.ems.domain.SysBizRemark;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface SysBizRemarkMapper
{
SysBizRemark selectByBizKeys(@Param("bizType") String bizType,
@Param("bizKey1") String bizKey1,
@Param("bizKey2") String bizKey2);
List<SysBizRemark> selectByBizKey2List(@Param("bizType") String bizType,
@Param("bizKey1") String bizKey1,
@Param("bizKey2List") List<String> bizKey2List);
int insertSysBizRemark(SysBizRemark bizRemark);
int updateSysBizRemark(SysBizRemark bizRemark);
}

View File

@ -1,7 +0,0 @@
package com.xzzn.ems.service;
public interface IDDSDataProcessService {
public void handleDdsData(String message);
}

View File

@ -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);
// 订阅成功-处理告警

View File

@ -0,0 +1,21 @@
package com.xzzn.ems.service;
import com.xzzn.ems.domain.EmsDailyChargeData;
import java.util.List;
/**
* 站点每日充放电数据Service接口
*/
public interface IEmsDailyChargeDataService {
List<EmsDailyChargeData> selectEmsDailyChargeDataList(EmsDailyChargeData emsDailyChargeData);
EmsDailyChargeData selectEmsDailyChargeDataById(Long id);
int insertEmsDailyChargeData(EmsDailyChargeData emsDailyChargeData, String operName);
int updateEmsDailyChargeData(EmsDailyChargeData emsDailyChargeData, String operName);
int deleteEmsDailyChargeDataByIds(Long[] ids);
}

View File

@ -0,0 +1,21 @@
package com.xzzn.ems.service;
import com.xzzn.ems.domain.EmsDailyEnergyData;
import java.util.List;
/**
* 站点每日收益数据Service接口
*/
public interface IEmsDailyEnergyDataService {
List<EmsDailyEnergyData> selectEmsDailyEnergyDataList(EmsDailyEnergyData emsDailyEnergyData);
EmsDailyEnergyData selectEmsDailyEnergyDataById(Long id);
int insertEmsDailyEnergyData(EmsDailyEnergyData emsDailyEnergyData, String operName);
int updateEmsDailyEnergyData(EmsDailyEnergyData emsDailyEnergyData, String operName);
int deleteEmsDailyEnergyDataByIds(Long[] ids);
}

View File

@ -10,6 +10,11 @@ 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.SingleBatteryConfigInitRequest;
import com.xzzn.ems.domain.vo.SingleBatteryConfigInitResultVo;
import com.xzzn.ems.domain.vo.SingleBatteryMonitorImportResultVo;
import com.xzzn.ems.domain.vo.SingleBatteryMonitorImportRowVo;
import com.xzzn.ems.domain.vo.WorkStatusEnumMappingVo;
import java.util.Date;
import java.util.List;
@ -46,9 +51,17 @@ public interface IEmsDeviceSettingService
public int saveSiteMonitorProjectPointMapping(SiteMonitorProjectPointMappingSaveRequest request, String operName);
public List<WorkStatusEnumMappingVo> getSiteWorkStatusEnumMappings(String siteId);
public int saveSiteWorkStatusEnumMappings(String siteId, List<WorkStatusEnumMappingVo> mappings, String operName);
public List<SiteMonitorProjectDisplayVo> getSiteMonitorProjectDisplay(String siteId);
public int saveSiteMonitorProjectData(SiteMonitorDataSaveRequest request, String operName);
public int syncSiteMonitorDataByMqtt(String siteId, String deviceId, String jsonData, Date valueTime);
public SingleBatteryMonitorImportResultVo importSingleBatteryMonitorMappings(String siteId, List<SingleBatteryMonitorImportRowVo> rows, String operName);
public SingleBatteryConfigInitResultVo initializeSingleBatteryMonitorMappings(SingleBatteryConfigInitRequest request, String operName);
}

View File

@ -0,0 +1,19 @@
package com.xzzn.ems.service;
import com.xzzn.ems.domain.EmsPointCalcConfig;
import java.util.List;
public interface IEmsPointCalcConfigService {
List<EmsPointCalcConfig> selectPointCalcConfigList(EmsPointCalcConfig pointCalcConfig);
EmsPointCalcConfig selectPointCalcConfigById(Long id);
int insertPointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName);
int updatePointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName);
int deletePointCalcConfigByIds(Long[] ids);
int deleteBySiteId(String siteId);
}

View File

@ -4,6 +4,7 @@ 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;
@ -30,4 +31,6 @@ public interface IEmsPointConfigService {
List<PointConfigLatestValueVo> getLatestValues(PointConfigLatestValueRequest request);
List<PointConfigCurveValueVo> getCurveData(PointConfigCurveRequest request);
String generateRecent7DaysData(PointConfigGenerateRecentRequest request);
}

View File

@ -0,0 +1,28 @@
package com.xzzn.ems.service;
import com.xzzn.ems.domain.EmsStrategyRuntimeConfig;
/**
* 策略运行参数配置Service接口
*
* @author xzzn
* @date 2026-02-13
*/
public interface IEmsStrategyRuntimeConfigService {
/**
* 按站点ID获取参数配置不存在时返回默认值
*
* @param siteId 站点ID
* @return 参数配置
*/
EmsStrategyRuntimeConfig getBySiteId(String siteId);
/**
* 保存参数配置按siteId新增或更新
*
* @param config 参数配置
* @return 结果
*/
int saveBySiteId(EmsStrategyRuntimeConfig config);
}

View File

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

View File

@ -1,6 +0,0 @@
package com.xzzn.ems.service;
public interface IFXXDataProcessService {
public void handleFxData(String message);
}

View File

@ -13,6 +13,8 @@ public interface ISingleSiteService
public SiteMonitorHomeVo getSiteMonitorDataVo(String siteId);
public SiteMonitorHomeVo getSiteMonitorTotalDataVo(String siteId);
public SiteMonitorRunningHeadInfoVo getSiteRunningHeadInfo(String siteId);

View File

@ -0,0 +1,13 @@
package com.xzzn.ems.service;
import com.xzzn.ems.domain.vo.BizRemarkSaveRequest;
import java.util.List;
import java.util.Map;
public interface ISysBizRemarkService
{
Map<String, String> getRemarkMap(String bizType, String bizKey1, List<String> bizKey2List);
int saveRemark(BizRemarkSaveRequest request, String username);
}

View File

@ -181,6 +181,85 @@ public class InfluxPointDataWriter {
}
}
public List<PointValue> 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<PointValue> 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();
@ -488,6 +567,7 @@ public class InfluxPointDataWriter {
return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'");
}
private String escapeRegex(String value) {
if (value == null) {
return "";
@ -546,6 +626,7 @@ public class InfluxPointDataWriter {
return values;
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}

View File

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

View File

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

View File

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

View File

@ -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<String, BigDecimal> 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<String, BigDecimal> 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();

View File

@ -0,0 +1,144 @@
package com.xzzn.ems.service.impl;
import com.xzzn.common.exception.ServiceException;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.EmsPointConfig;
import com.xzzn.ems.domain.EmsPointCalcConfig;
import com.xzzn.ems.service.IEmsPointConfigService;
import com.xzzn.ems.service.IEmsPointCalcConfigService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Service
public class EmsPointCalcConfigServiceImpl implements IEmsPointCalcConfigService {
private static final Pattern CALC_EXPRESSION_PATTERN = Pattern.compile("^[0-9A-Za-z_+\\-*/().,?:<>=!&|\\s]+$");
private static final String CALC_POINT_TYPE = "calc";
@Autowired
private IEmsPointConfigService pointConfigService;
@Override
public List<EmsPointCalcConfig> selectPointCalcConfigList(EmsPointCalcConfig pointCalcConfig) {
EmsPointConfig query = toPointConfigQuery(pointCalcConfig);
List<EmsPointConfig> configList = pointConfigService.selectPointConfigList(query);
if (configList == null || configList.isEmpty()) {
return new ArrayList<>();
}
return configList.stream().map(this::toCalcConfig).collect(Collectors.toList());
}
@Override
public EmsPointCalcConfig selectPointCalcConfigById(Long id) {
EmsPointConfig config = pointConfigService.selectPointConfigById(id);
if (config == null || !CALC_POINT_TYPE.equalsIgnoreCase(config.getPointType())) {
return null;
}
return toCalcConfig(config);
}
@Override
public int insertPointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName) {
if (pointCalcConfig == null || StringUtils.isBlank(pointCalcConfig.getSiteId())) {
throw new ServiceException("站点ID不能为空");
}
if (StringUtils.isBlank(pointCalcConfig.getPointId())) {
throw new ServiceException("点位ID不能为空");
}
validateCalcExpression(pointCalcConfig.getCalcExpression());
return pointConfigService.insertPointConfig(toPointConfig(pointCalcConfig), operName);
}
@Override
public int updatePointCalcConfig(EmsPointCalcConfig pointCalcConfig, String operName) {
if (pointCalcConfig == null || StringUtils.isBlank(pointCalcConfig.getSiteId())) {
throw new ServiceException("站点ID不能为空");
}
if (StringUtils.isBlank(pointCalcConfig.getPointId())) {
throw new ServiceException("点位ID不能为空");
}
validateCalcExpression(pointCalcConfig.getCalcExpression());
return pointConfigService.updatePointConfig(toPointConfig(pointCalcConfig), operName);
}
@Override
public int deletePointCalcConfigByIds(Long[] ids) {
return pointConfigService.deletePointConfigByIds(ids);
}
@Override
public int deleteBySiteId(String siteId) {
if (StringUtils.isBlank(siteId)) {
return 0;
}
EmsPointConfig query = new EmsPointConfig();
query.setSiteId(siteId);
query.setPointType(CALC_POINT_TYPE);
List<EmsPointConfig> configList = pointConfigService.selectPointConfigList(query);
if (configList == null || configList.isEmpty()) {
return 0;
}
Long[] ids = configList.stream().map(EmsPointConfig::getId).toArray(Long[]::new);
return pointConfigService.deletePointConfigByIds(ids);
}
private void validateCalcExpression(String expression) {
if (StringUtils.isBlank(expression)) {
throw new ServiceException("计算表达式不能为空");
}
if (!CALC_EXPRESSION_PATTERN.matcher(expression).matches()) {
throw new ServiceException("计算表达式仅支持数字、字母、下划线、空格、运算符和函数语法");
}
}
private EmsPointConfig toPointConfig(EmsPointCalcConfig calcConfig) {
EmsPointConfig pointConfig = new EmsPointConfig();
pointConfig.setId(calcConfig.getId());
pointConfig.setPointId(calcConfig.getPointId());
pointConfig.setSiteId(calcConfig.getSiteId());
pointConfig.setPointType(CALC_POINT_TYPE);
pointConfig.setDeviceCategory(calcConfig.getDeviceCategory());
pointConfig.setDeviceId("");
pointConfig.setRegisterAddress("");
pointConfig.setPointName(calcConfig.getPointName());
pointConfig.setDataKey(calcConfig.getDataKey());
pointConfig.setPointDesc(calcConfig.getPointDesc());
pointConfig.setDataUnit(calcConfig.getDataUnit());
pointConfig.setCalcExpression(calcConfig.getCalcExpression());
pointConfig.setRemark(calcConfig.getRemark());
return pointConfig;
}
private EmsPointConfig toPointConfigQuery(EmsPointCalcConfig calcConfig) {
EmsPointConfig query = new EmsPointConfig();
query.setSiteId(calcConfig == null ? null : calcConfig.getSiteId());
query.setDeviceCategory(calcConfig == null ? null : calcConfig.getDeviceCategory());
query.setDataKey(calcConfig == null ? null : calcConfig.getDataKey());
query.setPointDesc(calcConfig == null ? null : calcConfig.getPointDesc());
query.setPointType(CALC_POINT_TYPE);
return query;
}
private EmsPointCalcConfig toCalcConfig(EmsPointConfig pointConfig) {
EmsPointCalcConfig calcConfig = new EmsPointCalcConfig();
calcConfig.setId(pointConfig.getId());
calcConfig.setPointId(pointConfig.getPointId());
calcConfig.setSiteId(pointConfig.getSiteId());
calcConfig.setDeviceCategory(pointConfig.getDeviceCategory());
calcConfig.setPointName(pointConfig.getPointName());
calcConfig.setDataKey(pointConfig.getDataKey());
calcConfig.setPointDesc(pointConfig.getPointDesc());
calcConfig.setDataUnit(pointConfig.getDataUnit());
calcConfig.setCalcExpression(pointConfig.getCalcExpression());
calcConfig.setRemark(pointConfig.getRemark());
calcConfig.setCreateBy(pointConfig.getCreateBy());
calcConfig.setCreateTime(pointConfig.getCreateTime());
calcConfig.setUpdateBy(pointConfig.getUpdateBy());
calcConfig.setUpdateTime(pointConfig.getUpdateTime());
return calcConfig;
}
}

View File

@ -28,6 +28,7 @@ import com.xzzn.ems.mapper.EmsDevicesSettingMapper;
import com.xzzn.ems.mapper.EmsEnergyPriceConfigMapper;
import com.xzzn.ems.mapper.EmsPcsDataMapper;
import com.xzzn.ems.service.IEmsStatsReportService;
import com.xzzn.ems.service.ISysBizRemarkService;
import java.math.BigDecimal;
import java.math.RoundingMode;
@ -99,6 +100,40 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService
private EmsDailyEnergyDataMapper emsDailyEnergyDataMapper;
@Autowired
private EmsEnergyPriceConfigMapper emsEnergyPriceConfigMapper;
@Autowired
private ISysBizRemarkService sysBizRemarkService;
private static final String STATS_REPORT_BIZ_TYPE = "stats_report";
private static final String AMMETER_REPORT_KEY = "DBBB";
private static final String REVENUE_REPORT_KEY = "SYBB";
private String buildRemarkBizKey(String siteId, String dataTime) {
String safeSiteId = siteId == null ? "" : siteId.trim();
String safeDate = dataTime == null ? "" : dataTime.trim();
return safeSiteId + "_" + safeDate;
}
private void fillAmmeterRemarks(String siteId, String reportKey, List<AmmeterStatisListVo> rows) {
if (CollectionUtils.isEmpty(rows)) {
return;
}
List<String> bizKeys = rows.stream()
.map(row -> buildRemarkBizKey(siteId, row.getDataTime()))
.collect(Collectors.toList());
Map<String, String> remarkMap = sysBizRemarkService.getRemarkMap(STATS_REPORT_BIZ_TYPE, reportKey, bizKeys);
rows.forEach(row -> row.setRemark(remarkMap.getOrDefault(buildRemarkBizKey(siteId, row.getDataTime()), "")));
}
private void fillRevenueRemarks(String siteId, String reportKey, List<AmmeterRevenueStatisListVo> rows) {
if (CollectionUtils.isEmpty(rows)) {
return;
}
List<String> bizKeys = rows.stream()
.map(row -> buildRemarkBizKey(siteId, row.getDataTime()))
.collect(Collectors.toList());
Map<String, String> remarkMap = sysBizRemarkService.getRemarkMap(STATS_REPORT_BIZ_TYPE, reportKey, bizKeys);
rows.forEach(row -> row.setRemark(remarkMap.getOrDefault(buildRemarkBizKey(siteId, row.getDataTime()), "")));
}
// 电量指标
@Override
@ -374,25 +409,92 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService
}
dataList.add(totalVo);*/
fillAmmeterRemarks(requestVo.getSiteId(), AMMETER_REPORT_KEY, dataList);
return dataList;
}
@Override
public List<AmmeterRevenueStatisListVo> getAmmeterRevenueDataResult(StatisAmmeterDateRequest requestVo) {
List<AmmeterRevenueStatisListVo> resultList = emsDailyEnergyDataMapper.getRevenueDataBySiteId(requestVo.getSiteId(),requestVo.getStartTime(),requestVo.getEndTime());
log.info("收益报表查询开始, siteId: {}, startTime: {}, endTime: {}",
requestVo.getSiteId(), requestVo.getStartTime(), requestVo.getEndTime());
List<AmmeterRevenueStatisListVo> 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);
fillRevenueRemarks(requestVo.getSiteId(), REVENUE_REPORT_KEY, resultList);
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<EnergyPriceConfigVo> priceList) {
AmmeterRevenueStatisListVo ammeterRevenue = new AmmeterRevenueStatisListVo();
ammeterRevenue.setDataTime(ammeter.getDataTime());
@ -642,6 +744,8 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService
cell3.setCellValue("放电量");
Cell cell4 = row1.createCell(11);
cell4.setCellValue("效率");
Cell cell5Top = row1.createCell(12);
cell5Top.setCellValue("备注");
// 合并充电量列
CellRangeAddress mergeRegion1 = new CellRangeAddress(0, 0, 1, 5);
@ -677,6 +781,8 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService
cell15.setCellValue("");
Cell cell16 = row2.createCell(11);
cell16.setCellValue("");
Cell cell17 = row2.createCell(12);
cell17.setCellValue("备注");
// 设置背景颜色
CellStyle headerStyle = workbook.createCellStyle();
@ -756,6 +862,8 @@ public class EmsStatsReportServiceImpl implements IEmsStatsReportService
dataCell11.setCellValue(ammeterStatisVo.getReActiveTotalKwh().doubleValue());
Cell dataCell12 = dataRow.createCell(11);
dataCell12.setCellValue(ammeterStatisVo.getEffect().doubleValue());
Cell dataCell13 = dataRow.createCell(12);
dataCell13.setCellValue(ammeterStatisVo.getRemark() == null ? "" : ammeterStatisVo.getRemark());
// 根据行号设置背景色
if (i % 2 == 0) {
@ -798,19 +906,21 @@ 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);
cell4.setCellValue("");
Cell cell4 = row1.createCell(13);
cell4.setCellValue("实际收益");
Cell cell5Top = row1.createCell(14);
cell5Top.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 +928,33 @@ 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("实际收益");
Cell cell19 = row2.createCell(14);
cell19.setCellValue("备注");
// 设置背景颜色
CellStyle headerStyle = workbook.createCellStyle();
@ -908,27 +1024,33 @@ 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());
Cell dataCell15 = dataRow.createCell(14);
dataCell15.setCellValue(ammeterRevenueStatisVo.getRemark() == null ? "" : ammeterRevenueStatisVo.getRemark());
// 根据行号设置背景色
if (i % 2 == 0) {
@ -961,27 +1083,33 @@ 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());
Cell lastRowCell15 = lastRow.createCell(14);
lastRowCell15.setCellValue("");
Iterator<Cell> lastRowCellIterator = lastRow.cellIterator();
while (lastRowCellIterator.hasNext()) {
int i = lastRowCellIterator.next().getColumnIndex();

View File

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

View File

@ -92,6 +92,9 @@ public class EmsStrategyTempServiceImpl implements IEmsStrategyTempService
temp.setStartTime(timeConfig.getStartTime());
temp.setEndTime(timeConfig.getEndTime());
temp.setChargeDischargePower(timeConfig.getChargeDischargePower());
// 每个时间段可独立配置SOC上下限为空时兼容旧的模板级配置
temp.setSdcDown(timeConfig.getSdcDown() != null ? timeConfig.getSdcDown() : publicTemp.getSdcDown());
temp.setSdcUp(timeConfig.getSdcUp() != null ? timeConfig.getSdcUp() : publicTemp.getSdcUp());
temp.setChargeStatus(timeConfig.getChargeStatus());
emsStrategyTempMapper.insertEmsStrategyTemp(temp);
}

View File

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

View File

@ -49,15 +49,12 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
if (siteIds == null || siteIds.isEmpty()) {
return Collections.emptyList();
}
String deviceCategory = request.getDeviceCategory();
String deviceId = request.getDeviceId();
if ((deviceCategory == null || "".equals(deviceCategory.trim()))
&& (deviceId == null || "".equals(deviceId.trim()))) {
return Collections.emptyList();
}
return emsPointConfigMapper.getPointNameList(siteIds, deviceCategory, deviceId, request.getPointName());
return emsPointConfigMapper.getPointNameList(
siteIds,
request.getDeviceCategory(),
request.getDeviceId(),
request.getPointName()
);
}
@Override
@ -94,15 +91,10 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
}
String deviceCategory = request.getDeviceCategory();
String requestDeviceId = request.getDeviceId();
if ((deviceCategory == null || "".equals(deviceCategory.trim()))
&& (requestDeviceId == null || "".equals(requestDeviceId.trim()))
) {
return Collections.emptyList();
}
List<String> pointIds = resolvePointIds(request);
List<String> pointNames = resolvePointNames(request);
if (pointNames.isEmpty()) {
if (pointIds.isEmpty() && pointNames.isEmpty()) {
return Collections.emptyList();
}
@ -114,17 +106,19 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
endDate = DateUtils.adjustToEndOfDay(request.getEndDate());
}
List<String> selectedDeviceIds = resolveSelectedDeviceIds(request);
List<String> selectedDeviceIds = pointIds.isEmpty() ? resolveSelectedDeviceIds(request) : Collections.emptyList();
List<EmsPointConfig> pointConfigs = emsPointConfigMapper.getConfigListForGeneralQuery(
siteIds, deviceCategory, pointNames, selectedDeviceIds
siteIds, deviceCategory, pointIds, pointNames, selectedDeviceIds
);
if (pointConfigs == null || pointConfigs.isEmpty()) {
return Collections.emptyList();
}
Map<String, String> selectedPointNameById = buildSelectedPointNameById(request);
List<GeneralQueryDataVo> dataVoList = new ArrayList<>();
for (EmsPointConfig pointConfig : pointConfigs) {
dataVoList.addAll(queryPointCurve(pointConfig, request.getDataUnit(), startDate, endDate));
String selectedPointName = selectedPointNameById.get(resolveInfluxPointKey(pointConfig));
dataVoList.addAll(queryPointCurve(pointConfig, request.getDataUnit(), startDate, endDate, selectedPointName));
}
if (dataVoList.isEmpty()) {
@ -154,6 +148,19 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return names.stream().distinct().collect(Collectors.toList());
}
private List<String> resolvePointIds(PointNameRequest request) {
List<String> 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<String> resolveSelectedDeviceIds(PointNameRequest request) {
List<String> selected = new ArrayList<>();
if (request.getDeviceId() != null && !"".equals(request.getDeviceId().trim())) {
@ -175,12 +182,18 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return selected.stream().distinct().collect(Collectors.toList());
}
private List<GeneralQueryDataVo> queryPointCurve(EmsPointConfig config, int dataUnit, Date startDate, Date endDate) {
if (config == null || config.getSiteId() == null || config.getDeviceId() == null || config.getDataKey() == null) {
private List<GeneralQueryDataVo> queryPointCurve(EmsPointConfig config, int dataUnit, Date startDate, Date endDate,
String selectedPointName) {
if (config == null || config.getSiteId() == null) {
return Collections.emptyList();
}
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveData(
config.getSiteId(), config.getDeviceId(), config.getDataKey(), startDate, endDate
String influxPointKey = resolveInfluxPointKey(config);
if (influxPointKey == null) {
return Collections.emptyList();
}
// 与点位列表曲线保持一致:按 siteId + pointKey 查询,避免 deviceId 维度导致综合查询漏数
List<InfluxPointDataWriter.PointValue> values = influxPointDataWriter.queryCurveDataByPointKey(
config.getSiteId(), influxPointKey, startDate, endDate
);
if (values == null || values.isEmpty()) {
return Collections.emptyList();
@ -193,7 +206,7 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
}
List<GeneralQueryDataVo> result = new ArrayList<>();
String displayDeviceId = buildDisplayDeviceId(config);
String displayDeviceId = buildDisplayDeviceId(config, selectedPointName);
for (Map.Entry<String, Object> entry : latestByBucket.entrySet()) {
GeneralQueryDataVo vo = new GeneralQueryDataVo();
vo.setSiteId(config.getSiteId());
@ -205,10 +218,42 @@ public class GeneralQueryServiceImpl implements IGeneralQueryService
return result;
}
private String buildDisplayDeviceId(EmsPointConfig config) {
private String resolveInfluxPointKey(EmsPointConfig config) {
if (config == null) {
return null;
}
if (config.getPointId() != null && !"".equals(config.getPointId().trim())) {
return config.getPointId().trim();
}
return null;
}
private String buildDisplayDeviceId(EmsPointConfig config, String 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 config.getDeviceId() + "-" + pointName;
return pointName;
}
private Map<String, String> buildSelectedPointNameById(PointNameRequest request) {
Map<String, String> selectedNameById = new HashMap<>();
if (request == null || request.getPointIds() == null || request.getPointNames() == null) {
return selectedNameById;
}
List<String> pointIds = request.getPointIds();
List<String> 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) {
@ -432,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
@ -468,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());
@ -491,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<ValueTimePair> maxPair = validPairs.stream().max((p1, p2) -> p1.value.compareTo(p2.value));
@ -501,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<BigDecimal> 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) {
@ -755,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;
}
}

View File

@ -0,0 +1,69 @@
package com.xzzn.ems.service.impl;
import com.xzzn.common.utils.DateUtils;
import com.xzzn.common.utils.StringUtils;
import com.xzzn.ems.domain.SysBizRemark;
import com.xzzn.ems.domain.vo.BizRemarkSaveRequest;
import com.xzzn.ems.mapper.SysBizRemarkMapper;
import com.xzzn.ems.service.ISysBizRemarkService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Service
public class SysBizRemarkServiceImpl implements ISysBizRemarkService
{
@Autowired
private SysBizRemarkMapper sysBizRemarkMapper;
@Override
public Map<String, String> getRemarkMap(String bizType, String bizKey1, List<String> bizKey2List)
{
if (StringUtils.isAnyBlank(bizType, bizKey1) || bizKey2List == null || bizKey2List.isEmpty())
{
return Collections.emptyMap();
}
List<SysBizRemark> remarkList = sysBizRemarkMapper.selectByBizKey2List(bizType, bizKey1, bizKey2List);
Map<String, String> result = new LinkedHashMap<>();
for (SysBizRemark item : remarkList)
{
result.put(item.getBizKey2(), StringUtils.nvl(item.getRemark(), ""));
}
return result;
}
@Override
public int saveRemark(BizRemarkSaveRequest request, String username)
{
if (request == null || StringUtils.isAnyBlank(request.getBizType(), request.getBizKey1(), request.getBizKey2()))
{
return 0;
}
SysBizRemark existed = sysBizRemarkMapper.selectByBizKeys(request.getBizType(), request.getBizKey1(), request.getBizKey2());
if (existed == null)
{
SysBizRemark bizRemark = new SysBizRemark();
bizRemark.setBizType(request.getBizType());
bizRemark.setBizKey1(request.getBizKey1());
bizRemark.setBizKey2(request.getBizKey2());
bizRemark.setRemark(StringUtils.nvl(request.getRemark(), ""));
bizRemark.setCreateBy(username);
bizRemark.setUpdateBy(username);
bizRemark.setCreateTime(DateUtils.getNowDate());
bizRemark.setUpdateTime(DateUtils.getNowDate());
bizRemark.setDelFlag("0");
return sysBizRemarkMapper.insertSysBizRemark(bizRemark);
}
existed.setRemark(StringUtils.nvl(request.getRemark(), ""));
existed.setUpdateBy(username);
existed.setUpdateTime(DateUtils.getNowDate());
existed.setDelFlag("0");
return sysBizRemarkMapper.updateSysBizRemark(existed);
}
}

View File

@ -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<String, List<EmsPointEnumMatch>> 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<EmsPointEnumMatch> 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();
}

View File

@ -512,10 +512,16 @@
<select id="getStackPointByMinute" parameterType="com.xzzn.ems.domain.vo.DateSearchRequest" resultType="com.xzzn.ems.domain.vo.StackPointVo">
WITH ranked AS (
SELECT
*,
DATE_FORMAT(DATE_ADD(DATE_FORMAT(p.update_time, '%Y-%m-%d %H:00:00'), INTERVAL CEIL(MINUTE(p.update_time) / 5) * 5 MINUTE)
, '%Y-%m-%d %H:%i:%s') AS group_time,
ROW_NUMBER() OVER (PARTITION BY p.device_id, date_format(p.update_time, '%Y-%m-%d %H:00:00'), CEIL(MINUTE(p.update_time) / 5) ORDER BY p.data_update_time DESC) as rn
p.site_id,
p.device_id,
p.stack_soc,
p.stack_soh,
p.avg_temperature,
FROM_UNIXTIME(((UNIX_TIMESTAMP(p.update_time) + 299) DIV 300) * 300) AS group_time,
ROW_NUMBER() OVER (
PARTITION BY p.device_id, ((UNIX_TIMESTAMP(p.update_time) + 299) DIV 300)
ORDER BY p.data_update_time DESC
) as rn
FROM
ems_battery_stack p
<include refid="statisCommonFilter"/>
@ -528,16 +534,27 @@
avg(t.avg_temperature) as avgTemp
FROM
ranked as t
where t.rn = 1
GROUP BY t.site_id, t.group_time
</select>
<select id="getStackDataByMinute" parameterType="com.xzzn.ems.domain.vo.DateSearchRequest" resultType="com.xzzn.ems.domain.vo.StackStatisListVo">
WITH ranked AS (
SELECT
*,
DATE_FORMAT(DATE_ADD(DATE_FORMAT(p.update_time, '%Y-%m-%d %H:00:00'), INTERVAL CEIL(MINUTE(p.update_time) / 5) * 5 MINUTE)
, '%Y-%m-%d %H:%i:%s') AS group_time,
ROW_NUMBER() OVER (PARTITION BY p.device_id, date_format(p.update_time, '%Y-%m-%d %H:00:00'), CEIL(MINUTE(p.update_time) / 5) ORDER BY p.data_update_time DESC) as rn
p.site_id,
p.device_id,
p.stack_soc,
p.stack_soh,
p.avg_temperature,
p.operating_temp,
p.stack_current,
p.stack_voltage,
p.data_update_time,
FROM_UNIXTIME(((UNIX_TIMESTAMP(p.update_time) + 299) DIV 300) * 300) AS group_time,
ROW_NUMBER() OVER (
PARTITION BY p.device_id, ((UNIX_TIMESTAMP(p.update_time) + 299) DIV 300)
ORDER BY p.data_update_time DESC
) as rn
FROM
ems_battery_stack p
<include refid="statisCommonFilter"/>
@ -574,4 +591,4 @@
ranked
GROUP BY site_id,groupTime
</select>
</mapper>
</mapper>

View File

@ -7,12 +7,13 @@
<resultMap type="EmsDailyChargeData" id="EmsDailyChargeDataResult">
<result property="id" column="id" />
<result property="siteId" column="site_id" />
<result property="deviceId" column="device_id" />
<result property="dateTime" column="date_time" />
<result property="totalChargeData" column="total_charge_Data" />
<result property="totalDischargeData" column="total_discharge_Data" />
<result property="chargeData" column="charge_data" />
<result property="dischargeData" column="discharge_data" />
<result property="totalRevenue" column="total_revenue" />
<result property="dayRevenue" column="day_revenue" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
@ -21,20 +22,22 @@
</resultMap>
<sql id="selectEmsDailyChargeDataVo">
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
</sql>
<select id="selectEmsDailyChargeDataList" parameterType="EmsDailyChargeData" resultMap="EmsDailyChargeDataResult">
<include refid="selectEmsDailyChargeDataVo"/>
<where>
<if test="siteId != null and siteId != ''"> and site_id = #{siteId}</if>
<if test="deviceId != null and deviceId != ''"> and device_id = #{deviceId}</if>
<if test="dateTime != null "> and date_time = #{dateTime}</if>
<if test="totalChargeData != null "> and total_charge_Data = #{totalChargeData}</if>
<if test="totalDischargeData != null "> and total_discharge_Data = #{totalDischargeData}</if>
<if test="chargeData != null "> and charge_data = #{chargeData}</if>
<if test="dischargeData != null "> and discharge_data = #{dischargeData}</if>
<if test="totalRevenue != null "> and total_revenue = #{totalRevenue}</if>
<if test="dayRevenue != null "> and day_revenue = #{dayRevenue}</if>
</where>
order by date_time desc
</select>
<select id="selectEmsDailyChargeDataById" parameterType="Long" resultMap="EmsDailyChargeDataResult">
@ -42,16 +45,25 @@
where id = #{id}
</select>
<select id="selectBySiteIdAndDateTime" resultMap="EmsDailyChargeDataResult">
<include refid="selectEmsDailyChargeDataVo"/>
where site_id = #{siteId}
and date(date_time) = date(#{dateTime})
order by date_time desc
limit 1
</select>
<insert id="insertEmsDailyChargeData" parameterType="EmsDailyChargeData" useGeneratedKeys="true" keyProperty="id">
insert into ems_daily_charge_data
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="siteId != null">site_id,</if>
<if test="deviceId != null and deviceId != ''">device_id,</if>
<if test="dateTime != null">date_time,</if>
<if test="totalChargeData != null">total_charge_Data,</if>
<if test="totalDischargeData != null">total_discharge_Data,</if>
<if test="chargeData != null">charge_data,</if>
<if test="dischargeData != null">discharge_data,</if>
<if test="totalRevenue != null">total_revenue,</if>
<if test="dayRevenue != null">day_revenue,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
@ -60,12 +72,13 @@
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="siteId != null">#{siteId},</if>
<if test="deviceId != null and deviceId != ''">#{deviceId},</if>
<if test="dateTime != null">#{dateTime},</if>
<if test="totalChargeData != null">#{totalChargeData},</if>
<if test="totalDischargeData != null">#{totalDischargeData},</if>
<if test="chargeData != null">#{chargeData},</if>
<if test="dischargeData != null">#{dischargeData},</if>
<if test="totalRevenue != null">#{totalRevenue},</if>
<if test="dayRevenue != null">#{dayRevenue},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
@ -78,12 +91,13 @@
update ems_daily_charge_data
<trim prefix="SET" suffixOverrides=",">
<if test="siteId != null">site_id = #{siteId},</if>
<if test="deviceId != null and deviceId != ''">device_id = #{deviceId},</if>
<if test="dateTime != null">date_time = #{dateTime},</if>
<if test="totalChargeData != null">total_charge_Data = #{totalChargeData},</if>
<if test="totalDischargeData != null">total_discharge_Data = #{totalDischargeData},</if>
<if test="chargeData != null">charge_data = #{chargeData},</if>
<if test="dischargeData != null">discharge_data = #{dischargeData},</if>
<if test="totalRevenue != null">total_revenue = #{totalRevenue},</if>
<if test="dayRevenue != null">day_revenue = #{dayRevenue},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
@ -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()
</insert>
<update id="updateRevenueBySiteAndDate">
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})
</update>
<select id="getAllSiteChargeData" resultType="map">
SELECT
SUM(t.total_charge_data) AS totalChargedCap,
@ -207,4 +235,4 @@
GROUP BY dateMonth
ORDER BY dateMonth
</select>
</mapper>
</mapper>

View File

@ -8,8 +8,7 @@
<result property="id" column="id" />
<result property="siteId" column="site_id" />
<result property="dataDate" column="data_date" />
<result property="totalRevenue" column="total_revenue" />
<result property="dayRevenue" column="day_revenue" />
<result property="dataHour" column="data_hour" />
<result property="peakChargeDiff" column="peak_charge_diff" />
<result property="peakDischargeDiff" column="peak_discharge_diff" />
<result property="highChargeDiff" column="high_charge_diff" />
@ -27,7 +26,11 @@
</resultMap>
<sql id="selectEmsDailyEnergyDataVo">
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
</sql>
<select id="selectEmsDailyEnergyDataList" parameterType="EmsDailyEnergyData" resultMap="EmsDailyEnergyDataResult">
@ -35,8 +38,7 @@
<where>
<if test="siteId != null and siteId != ''"> and site_id = #{siteId}</if>
<if test="dataDate != null "> and data_date = #{dataDate}</if>
<if test="totalRevenue != null "> and total_revenue = #{totalRevenue}</if>
<if test="dayRevenue != null "> and day_revenue = #{dayRevenue}</if>
<if test="dataHour != null "> and data_hour = #{dataHour}</if>
<if test="peakChargeDiff != null "> and peak_charge_diff = #{peakChargeDiff}</if>
<if test="peakDischargeDiff != null "> and peak_discharge_diff = #{peakDischargeDiff}</if>
<if test="highChargeDiff != null "> and high_charge_diff = #{highChargeDiff}</if>
@ -47,6 +49,7 @@
<if test="valleyDischargeDiff != null "> and valley_discharge_diff = #{valleyDischargeDiff}</if>
<if test="calcTime != null "> and calc_time = #{calcTime}</if>
</where>
order by data_date desc, calc_time desc, id desc
</select>
<select id="selectEmsDailyEnergyDataById" parameterType="Long" resultMap="EmsDailyEnergyDataResult">
@ -59,8 +62,7 @@
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="siteId != null">site_id,</if>
<if test="dataDate != null">data_date,</if>
<if test="totalRevenue != null">total_revenue,</if>
<if test="dayRevenue != null">day_revenue,</if>
<if test="dataHour != null">data_hour,</if>
<if test="peakChargeDiff != null">peak_charge_diff,</if>
<if test="peakDischargeDiff != null">peak_discharge_diff,</if>
<if test="highChargeDiff != null">high_charge_diff,</if>
@ -79,8 +81,7 @@
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="siteId != null">#{siteId},</if>
<if test="dataDate != null">#{dataDate},</if>
<if test="totalRevenue != null">#{totalRevenue},</if>
<if test="dayRevenue != null">#{dayRevenue},</if>
<if test="dataHour != null">#{dataHour},</if>
<if test="peakChargeDiff != null">#{peakChargeDiff},</if>
<if test="peakDischargeDiff != null">#{peakDischargeDiff},</if>
<if test="highChargeDiff != null">#{highChargeDiff},</if>
@ -103,8 +104,7 @@
<trim prefix="SET" suffixOverrides=",">
<if test="siteId != null">site_id = #{siteId},</if>
<if test="dataDate != null">data_date = #{dataDate},</if>
<if test="totalRevenue != null">total_revenue = #{totalRevenue},</if>
<if test="dayRevenue != null">day_revenue = #{dayRevenue},</if>
<if test="dataHour != null">data_hour = #{dataHour},</if>
<if test="peakChargeDiff != null">peak_charge_diff = #{peakChargeDiff},</if>
<if test="peakDischargeDiff != null">peak_discharge_diff = #{peakDischargeDiff},</if>
<if test="highChargeDiff != null">high_charge_diff = #{highChargeDiff},</if>
@ -138,14 +138,22 @@
<include refid="selectEmsDailyEnergyDataVo"/>
where data_date = #{today}
AND site_id = #{siteId}
AND data_hour IS NULL
</select>
<select id="selectBySiteIdAndDateHour" resultMap="EmsDailyEnergyDataResult">
<include refid="selectEmsDailyEnergyDataVo"/>
where site_id = #{siteId}
and data_date = DATE(#{dataDate})
and data_hour = #{dataHour}
limit 1
</select>
<insert id="insertOrUpdateData" parameterType="com.xzzn.ems.domain.EmsDailyEnergyData"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO ems_daily_energy_data (
id, site_id, data_date,
<if test="totalRevenue != null">total_revenue,</if>
<if test="dayRevenue != null">day_revenue,</if>
<if test="dataHour != null">data_hour,</if>
<if test="peakChargeDiff != null">peak_charge_diff,</if>
<if test="peakDischargeDiff != null">peak_discharge_diff,</if>
<if test="highChargeDiff != null">high_charge_diff,</if>
@ -164,8 +172,7 @@
#{id},
#{siteId},
#{dataDate},
<if test="totalRevenue != null">#{totalRevenue},</if>
<if test="dayRevenue != null">#{dayRevenue},</if>
<if test="dataHour != null">#{dataHour},</if>
<if test="peakChargeDiff != null">#{peakChargeDiff},</if>
<if test="peakDischargeDiff != null">#{peakDischargeDiff},</if>
<if test="highChargeDiff != null">#{highChargeDiff},</if>
@ -181,8 +188,7 @@
NOW(),
#{remark}
) ON DUPLICATE KEY UPDATE
<if test="totalRevenue != null">total_revenue = #{totalRevenue},</if>
<if test="dayRevenue != null">day_revenue = #{dayRevenue},</if>
<if test="dataHour != null">data_hour = #{dataHour},</if>
<if test="peakChargeDiff != null">peak_charge_diff = #{peakChargeDiff},</if>
<if test="peakDischargeDiff != null">peak_discharge_diff = #{peakDischargeDiff},</if>
<if test="highChargeDiff != null">high_charge_diff = #{highChargeDiff},</if>
@ -196,67 +202,138 @@
</insert>
<select id="getDataBySiteId" resultType="com.xzzn.ems.domain.vo.AmmeterStatisListVo">
select t.data_date as dataTime,
t.peak_charge_diff as activePeakKwh,
t.peak_discharge_diff as reActivePeakKwh,
t.high_charge_diff as activeHighKwh,
t.high_discharge_diff as reActiveHighKwh,
t.flat_charge_diff as activeFlatKwh,
t.flat_discharge_diff as reActiveFlatKwh,
t.valley_charge_diff as activeValleyKwh,
t.valley_discharge_diff as reActiveValleyKwh
from ems_daily_energy_data t
where 1=1
<if test="siteId != null">
and t.site_id = #{siteId}
</if>
<if test="startTime != null">
and t.data_date &gt;= #{startTime}
</if>
<if test="endTime != null">
and t.data_date &lt;= #{endTime}
</if>
select DATE_FORMAT(r.dataDate, '%Y-%m-%d') as dataTime,
r.activePeakKwh,
r.reActivePeakKwh,
r.activeHighKwh,
r.reActiveHighKwh,
r.activeFlatKwh,
r.reActiveFlatKwh,
r.activeValleyKwh,
r.reActiveValleyKwh
from (
-- 优先使用按天汇总记录data_hour is null
select DATE(t.data_date) as dataDate,
t.peak_charge_diff as activePeakKwh,
t.peak_discharge_diff as reActivePeakKwh,
t.high_charge_diff as activeHighKwh,
t.high_discharge_diff as reActiveHighKwh,
t.flat_charge_diff as activeFlatKwh,
t.flat_discharge_diff as reActiveFlatKwh,
t.valley_charge_diff as activeValleyKwh,
t.valley_discharge_diff as reActiveValleyKwh
from ems_daily_energy_data t
where t.data_hour is null
<if test="siteId != null and siteId != ''">
and t.site_id = #{siteId}
</if>
<if test="startTime != null and startTime != ''">
and t.data_date &gt;= #{startTime}
</if>
<if test="endTime != null and endTime != ''">
and t.data_date &lt; DATE_ADD(#{endTime}, INTERVAL 1 DAY)
</if>
union all
-- 若某天没有日汇总记录,则按小时数据汇总兜底
select DATE(h.data_date) as dataDate,
SUM(IFNULL(h.peak_charge_diff, 0)) as activePeakKwh,
SUM(IFNULL(h.peak_discharge_diff, 0)) as reActivePeakKwh,
SUM(IFNULL(h.high_charge_diff, 0)) as activeHighKwh,
SUM(IFNULL(h.high_discharge_diff, 0)) as reActiveHighKwh,
SUM(IFNULL(h.flat_charge_diff, 0)) as activeFlatKwh,
SUM(IFNULL(h.flat_discharge_diff, 0)) as reActiveFlatKwh,
SUM(IFNULL(h.valley_charge_diff, 0)) as activeValleyKwh,
SUM(IFNULL(h.valley_discharge_diff, 0)) as reActiveValleyKwh
from ems_daily_energy_data h
where h.data_hour is not null
<if test="siteId != null and siteId != ''">
and h.site_id = #{siteId}
</if>
<if test="startTime != null and startTime != ''">
and h.data_date &gt;= #{startTime}
</if>
<if test="endTime != null and endTime != ''">
and h.data_date &lt; DATE_ADD(#{endTime}, INTERVAL 1 DAY)
</if>
and not exists (
select 1
from ems_daily_energy_data d
where d.site_id = h.site_id
and DATE(d.data_date) = DATE(h.data_date)
and d.data_hour is null
)
group by h.site_id, DATE(h.data_date)
) r
order by r.dataDate desc
</select>
<select id="getLastTotalRevenue" resultType="java.math.BigDecimal">
select t.total_revenue
from ems_daily_energy_data t
where t.site_id = #{siteId}
and t.data_date &lt; CURDATE()
order by t.data_date desc limit 1
from ems_daily_charge_data t
where t.site_id = #{siteId}
and date(t.date_time) &lt; CURDATE()
order by t.date_time desc
limit 1
</select>
<select id="getRealTimeRevenue" resultType="java.util.Map">
select t.total_revenue as totalRevenue, t.day_revenue as dayRevenue
from ems_daily_energy_data t
where t.site_id = #{siteId}
order by t.data_date desc limit 1
from ems_daily_charge_data t
where t.site_id = #{siteId}
order by t.date_time desc
limit 1
</select>
<select id="getRevenueDataBySiteId" resultType="com.xzzn.ems.domain.vo.AmmeterRevenueStatisListVo">
select
t.data_date as dataTime,
ROUND(t.peak_charge_diff * pc.peak, 3) as activePeakPrice,
ROUND(t.peak_discharge_diff * pc.peak, 3) as reActivePeakPrice,
ROUND(t.high_charge_diff * pc.high, 3) as activeHighPrice,
ROUND(t.high_discharge_diff * pc.high, 3) as reActiveHighPrice,
ROUND(t.flat_charge_diff * pc.flat, 3) as activeFlatPrice,
ROUND(t.flat_discharge_diff * pc.flat, 3) as reActiveFlatPrice,
ROUND(t.valley_charge_diff * pc.valley, 3) as activeValleyPrice,
ROUND(t.valley_discharge_diff * pc.valley, 3) as reActiveValleyPrice
DATE_FORMAT(t.data_date, '%Y-%m-%d') as dataTime,
COALESCE(c.is_workday, CASE WHEN WEEKDAY(t.data_date) &lt; 5 THEN 1 ELSE 0 END) as isWorkday,
CASE
WHEN COALESCE(c.is_workday, CASE WHEN WEEKDAY(t.data_date) &lt; 5 THEN 1 ELSE 0 END) = 1 THEN '工作日'
ELSE '节假日'
END as dayType,
COALESCE(NULLIF(TRIM(w.weather_desc), ''), '--') as weatherDesc,
ROUND(SUM(IFNULL(t.peak_charge_diff, 0) * IFNULL(pc.peak, 0)), 3) as activePeakPrice,
ROUND(SUM(IFNULL(t.peak_discharge_diff, 0) * IFNULL(pc.peak, 0)), 3) as reActivePeakPrice,
ROUND(SUM(IFNULL(t.high_charge_diff, 0) * IFNULL(pc.high, 0)), 3) as activeHighPrice,
ROUND(SUM(IFNULL(t.high_discharge_diff, 0) * IFNULL(pc.high, 0)), 3) as reActiveHighPrice,
ROUND(SUM(IFNULL(t.flat_charge_diff, 0) * IFNULL(pc.flat, 0)), 3) as activeFlatPrice,
ROUND(SUM(IFNULL(t.flat_discharge_diff, 0) * IFNULL(pc.flat, 0)), 3) as reActiveFlatPrice,
ROUND(SUM(IFNULL(t.valley_charge_diff, 0) * IFNULL(pc.valley, 0)), 3) as activeValleyPrice,
ROUND(SUM(IFNULL(t.valley_discharge_diff, 0) * IFNULL(pc.valley, 0)), 3) as reActiveValleyPrice,
ROUND(
SUM(
(IFNULL(t.peak_discharge_diff, 0) - IFNULL(t.peak_charge_diff, 0)) * IFNULL(pc.peak, 0)
+ (IFNULL(t.high_discharge_diff, 0) - IFNULL(t.high_charge_diff, 0)) * IFNULL(pc.high, 0)
+ (IFNULL(t.flat_discharge_diff, 0) - IFNULL(t.flat_charge_diff, 0)) * IFNULL(pc.flat, 0)
+ (IFNULL(t.valley_discharge_diff, 0) - IFNULL(t.valley_charge_diff, 0)) * IFNULL(pc.valley, 0)
),
3
) as actualRevenue
from ems_daily_energy_data t
left join (
select
id, site_id, peak, high, flat, valley,
CONCAT(year, '-', LPAD(month, 2, '0')) as yearMonth
from ems_energy_price_config
where 1=1
<if test="siteId != null">
and site_id = #{siteId}
</if>
order by year, month
) pc on pc.yearMonth = DATE_FORMAT(t.data_date, '%Y-%m')
left join ems_calendar_day c on c.calendar_date = t.data_date
left join ems_site_weather_day w on w.site_id = t.site_id and w.calendar_date = t.data_date
left join ems_energy_price_config pc on pc.id = COALESCE(
(
select p.id
from ems_energy_price_config p
where p.site_id = t.site_id
and STR_TO_DATE(CONCAT(p.year, '-', LPAD(p.month, 2, '0'), '-01'), '%Y-%m-%d') &lt;= DATE_FORMAT(t.data_date, '%Y-%m-01')
order by STR_TO_DATE(CONCAT(p.year, '-', LPAD(p.month, 2, '0'), '-01'), '%Y-%m-%d') desc
limit 1
),
(
select p2.id
from ems_energy_price_config p2
where p2.site_id = t.site_id
order by STR_TO_DATE(CONCAT(p2.year, '-', LPAD(p2.month, 2, '0'), '-01'), '%Y-%m-%d') asc
limit 1
)
)
where 1=1
and t.data_hour is not null
<if test="siteId != null">
and t.site_id = #{siteId}
</if>
@ -266,6 +343,10 @@
<if test="endTime != null">
and t.data_date &lt;= #{endTime}
</if>
group by t.data_date,
COALESCE(c.is_workday, CASE WHEN WEEKDAY(t.data_date) &lt; 5 THEN 1 ELSE 0 END),
COALESCE(NULLIF(TRIM(w.weather_desc), ''), '--')
order by t.data_date asc
</select>
</mapper>
</mapper>

Some files were not shown because too many files have changed in this diff Show More