From 38f910fccd3f4ee505627cf56382c9175620da03 Mon Sep 17 00:00:00 2001 From: yexb <553699424@qq.com> Date: Mon, 18 May 2026 08:45:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(event):=20=E6=B7=BB=E5=8A=A0=E6=9A=82?= =?UTF-8?q?=E6=80=81=E4=BA=8B=E4=BB=B6=E6=B3=A2=E5=BD=A2=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E4=B8=8E=E5=AF=BC=E5=87=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 getTransientEventWave 接口用于查看暂态事件波形 - 新增 exportTransientEventWaves 接口用于批量导出暂态事件波形 - 添加 EventWaveExportParam 参数类支持波形导出 - 在 EventListMapper 中增加 selectTransientDetailsByIds 查询方法 - 更新事件列表查询参数支持毫秒级时间格式 - 移除事件描述模糊查询条件优化查询性能 - 添加波形导出相关的常量和工具类集成 --- AGENTS.md | 3 + entrance/src/main/resources/application.yml | 10 + event/event-list/pom.xml | 21 + ...va => EventMillisecondTimeSerializer.java} | 6 +- .../controller/EventListController.java | 19 + .../eventlist/mapper/EventListMapper.java | 2 + .../mapper/mapping/EventListMapper.xml | 13 +- .../pojo/param/EventListQueryParam.java | 7 +- .../pojo/param/EventWaveExportParam.java | 19 + .../event/eventlist/pojo/vo/EventListVO.java | 45 +- .../eventlist/service/EventListService.java | 6 + .../service/impl/EventListServiceImpl.java | 471 +++++++++++++++++- .../service/impl/EventWavePathResolver.java | 81 +++ .../service/impl/EventListTimeFormatTest.java | 44 ++ .../impl/EventWavePathResolverTest.java | 40 ++ .../component/SteadyTrendFieldResolver.java | 24 +- .../controller/SteadyDataViewController.java | 12 - .../datavie/mapper/SteadyDataViewMapper.java | 7 - .../mapper/mapping/SteadyDataViewMapper.xml | 33 -- .../pojo/param/SteadyDataViewQueryParam.java | 49 -- .../pojo/param/SteadyTrendQueryParam.java | 3 - .../service/SteadyDataViewService.java | 4 - .../impl/SteadyDataViewServiceImpl.java | 120 ----- .../impl/SteadyDataViewTrendServiceImpl.java | 1 - .../SteadyInfluxQueryComponentTest.java | 51 -- .../SteadyTrendFieldResolverTest.java | 28 +- .../SteadyDataViewControllerTest.java | 19 + .../pojo/param/SteadyTrendQueryParamTest.java | 19 + ...teadyDataViewIndicatorServiceImplTest.java | 27 - .../SteadyDataViewTrendServiceImplTest.java | 123 ----- .../cfg/controller/SysConfigController.java | 61 +++ .../system/cfg/mapper/SysConfigMapper.java | 10 + .../cfg/mapper/mapping/SysConfigMapper.xml | 5 + .../system/cfg/pojo/param/SysConfigParam.java | 26 + .../gather/system/cfg/pojo/po/SysConfig.java | 36 ++ .../system/cfg/service/ISysConfigService.java | 33 ++ .../service/impl/SysConfigServiceImpl.java | 59 +++ .../service/impl/DictDataServiceImpl.java | 3 +- .../service/impl/DictTypeServiceImpl.java | 3 +- .../service/impl/SysLogAuditServiceImpl.java | 3 +- .../njcn/gather/system/log/util/CSVUtil.java | 5 +- .../system/pojo/constant/DictConst.java | 5 + .../system/util/ExportFileNameUtil.java | 32 ++ .../main/resources/sql/system/sys-config.sql | 34 ++ .../DiskMonitorNotificationComponent.java | 3 +- .../disk/util/GeneratedFileNameUtil.java | 32 ++ .../component/AddDataTableRegistryTest.java | 28 -- .../AddDataTaskStatusHolderTest.java | 33 -- .../AddDataTemplateRegistryTest.java | 33 -- .../AddDataTimeSlotCalculatorTest.java | 61 --- .../component/AddDataValueGeneratorTest.java | 32 -- .../mapper/mapping/AddLedgerLineMapper.xml | 2 + .../pojo/vo/AddLedgerLinePathVO.java | 2 + .../sql/add-ledger/add-ledger-dict-init.sql | 224 --------- .../component/AddLedgerTreeBuilderTest.java | 43 -- .../util/AddLedgerLineNoUtilTest.java | 33 -- tools/mms-mapping/README.md | 2 +- .../mapping/component/FileStorageService.java | 3 +- .../component/IcdToXmlResponseConverter.java | 7 +- .../component/JsonToXmlConversionService.java | 8 +- .../component/RuleBasedXmlMappingService.java | 8 +- .../mapping/utils/GeneratedFileNameUtil.java | 33 ++ .../debug/GetIcdMmsJsonDebugRunner.java | 232 --------- .../component/WaveFileComponentTestMain.java | 265 ---------- .../wave/component/WaveFileComponentTest.java | 22 - .../component/WaveVectorComponentTest.java | 73 --- .../service/impl/WaveServiceImplTest.java | 162 ------ 67 files changed, 1203 insertions(+), 1760 deletions(-) rename event/event-list/src/main/java/com/njcn/gather/event/eventlist/config/{EventSecondTimeSerializer.java => EventMillisecondTimeSerializer.java} (74%) create mode 100644 event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/param/EventWaveExportParam.java create mode 100644 event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/impl/EventWavePathResolver.java create mode 100644 event/event-list/src/test/java/com/njcn/gather/event/eventlist/service/impl/EventListTimeFormatTest.java create mode 100644 event/event-list/src/test/java/com/njcn/gather/event/eventlist/service/impl/EventWavePathResolverTest.java delete mode 100644 steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/pojo/param/SteadyDataViewQueryParam.java delete mode 100644 steady/steady-DataView/src/test/java/com/njcn/gather/steady/datavie/component/SteadyInfluxQueryComponentTest.java create mode 100644 steady/steady-DataView/src/test/java/com/njcn/gather/steady/datavie/controller/SteadyDataViewControllerTest.java create mode 100644 steady/steady-DataView/src/test/java/com/njcn/gather/steady/datavie/pojo/param/SteadyTrendQueryParamTest.java delete mode 100644 steady/steady-DataView/src/test/java/com/njcn/gather/steady/datavie/service/impl/SteadyDataViewIndicatorServiceImplTest.java delete mode 100644 steady/steady-DataView/src/test/java/com/njcn/gather/steady/datavie/service/impl/SteadyDataViewTrendServiceImplTest.java create mode 100644 system/src/main/java/com/njcn/gather/system/cfg/controller/SysConfigController.java create mode 100644 system/src/main/java/com/njcn/gather/system/cfg/mapper/SysConfigMapper.java create mode 100644 system/src/main/java/com/njcn/gather/system/cfg/mapper/mapping/SysConfigMapper.xml create mode 100644 system/src/main/java/com/njcn/gather/system/cfg/pojo/param/SysConfigParam.java create mode 100644 system/src/main/java/com/njcn/gather/system/cfg/pojo/po/SysConfig.java create mode 100644 system/src/main/java/com/njcn/gather/system/cfg/service/ISysConfigService.java create mode 100644 system/src/main/java/com/njcn/gather/system/cfg/service/impl/SysConfigServiceImpl.java create mode 100644 system/src/main/java/com/njcn/gather/system/util/ExportFileNameUtil.java create mode 100644 system/src/main/resources/sql/system/sys-config.sql create mode 100644 systemmonitor/disk-monitor/src/main/java/com/njcn/gather/systemmonitor/disk/util/GeneratedFileNameUtil.java delete mode 100644 tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataTableRegistryTest.java delete mode 100644 tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataTaskStatusHolderTest.java delete mode 100644 tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataTemplateRegistryTest.java delete mode 100644 tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataTimeSlotCalculatorTest.java delete mode 100644 tools/add-data/src/test/java/com/njcn/gather/tool/adddata/component/AddDataValueGeneratorTest.java delete mode 100644 tools/add-ledger/src/main/resources/sql/add-ledger/add-ledger-dict-init.sql delete mode 100644 tools/add-ledger/src/test/java/com/njcn/gather/tool/addledger/component/AddLedgerTreeBuilderTest.java delete mode 100644 tools/add-ledger/src/test/java/com/njcn/gather/tool/addledger/util/AddLedgerLineNoUtilTest.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/utils/GeneratedFileNameUtil.java delete mode 100644 tools/mms-mapping/src/test/java/com/njcn/gather/icd/mapping/debug/GetIcdMmsJsonDebugRunner.java delete mode 100644 tools/wave-tool/src/main/java/com/njcn/gather/tool/wave/component/WaveFileComponentTestMain.java delete mode 100644 tools/wave-tool/src/test/java/com/njcn/gather/tool/wave/component/WaveFileComponentTest.java delete mode 100644 tools/wave-tool/src/test/java/com/njcn/gather/tool/wave/component/WaveVectorComponentTest.java delete mode 100644 tools/wave-tool/src/test/java/com/njcn/gather/tool/wave/service/impl/WaveServiceImplTest.java diff --git a/AGENTS.md b/AGENTS.md index 709a9c5..eaf69db 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,7 @@ - 只清理自己造成的问题:可以删除因本次修改而产生的未使用 `import`、变量或方法;不要删除仓库中原本就存在的死代码,除非用户明确要求。 - 先定义验证方式:执行方案里要写清楚“改哪里、怎么判断改对了”;默认通过代码路径、配置一致性和受影响范围检查进行验证。 - 除非用户明确要求,否则不执行任何 `mvn` 编译、打包、测试或其他构建命令。 +- 所有导出或生成文件名统一追加日期,格式为“原文件名 + `_` + `yyyyMMdd` + 原扩展名”,例如 `暂态事件列表_20260516.xlsx`;新增下载导出、临时生成或落盘保存文件时,优先复用目标模块已有文件名工具,不要在业务代码中散落手写日期拼接。 ## 项目结构与模块划分 `CN_Tool` 是一个 Maven 多模块后端项目,根目录的 [`pom.xml`](C:/code/gitea/cn_tool/CN_Tool/pom.xml) 聚合了 `entrance`、`system`、`user`、`detection` 和 `tools`。 @@ -40,6 +41,8 @@ Java 源码位于 `src/main/java`,配置文件位于 `src/main/resources`,My - 不要假设运行时存在自动数据库迁移;如果代码依赖新表、新字段或新索引,必须同步补齐对应 SQL 与文档说明。 - SQL 脚本应放在目标模块的 `src/main/resources/sql/...` 下,并保持可审阅、可单独执行、语义清晰。 - 变更缓存、日志、审计相关逻辑时,优先沿用现有机制,不要绕开现有登录上下文、缓存约定和审计字段填充方式。 +- 涉及 `sys_dict_type`、`sys_dict_data` 的字典类型编码、字典数据编码或固定字典数据 ID 时,必须统一维护在 `system/src/main/java/com/njcn/gather/system/pojo/constant/DictConst.java`;后续新增使用点也要先补常量再引用,不要在业务代码、SQL 或文档中散落硬编码。 +- 新增或保留字典初始化 SQL 前,必须先确认对应字典类型在后端代码、配置或明确页面契约中确实被使用;未使用的字典类型和字典数据不要写入 `sys_dict_type`、`sys_dict_data` 初始化脚本。 ## 注释与编码 - 新增或修改代码时,关键字段、关键分支、关键约束和非直观实现应补充简洁中文注释。 diff --git a/entrance/src/main/resources/application.yml b/entrance/src/main/resources/application.yml index b9b7cf4..3120866 100644 --- a/entrance/src/main/resources/application.yml +++ b/entrance/src/main/resources/application.yml @@ -48,6 +48,16 @@ socket: webSocket: port: 7777 +steady: + influxdb: + url: http://192.168.1.103:18086 + database: pqsbase + username: admin + password: ${STEADY_INFLUXDB_PASSWORD:} + ssl: false + connect-timeout-ms: 5000 + read-timeout-ms: 30000 + log: homeDir: D:\logs commonLevel: info diff --git a/event/event-list/pom.xml b/event/event-list/pom.xml index 0ddb213..4ef095b 100644 --- a/event/event-list/pom.xml +++ b/event/event-list/pom.xml @@ -35,5 +35,26 @@ add-ledger 1.0.0 + + com.njcn.gather + system + 1.0.0 + + + com.njcn.gather + wave-tool + 1.0.0 + + + org.apache.poi + poi-ooxml + 4.1.2 + + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/config/EventSecondTimeSerializer.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/config/EventMillisecondTimeSerializer.java similarity index 74% rename from event/event-list/src/main/java/com/njcn/gather/event/eventlist/config/EventSecondTimeSerializer.java rename to event/event-list/src/main/java/com/njcn/gather/event/eventlist/config/EventMillisecondTimeSerializer.java index 261e4e8..fde1d2b 100644 --- a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/config/EventSecondTimeSerializer.java +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/config/EventMillisecondTimeSerializer.java @@ -9,11 +9,11 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** - * 暂态事件时间字段按秒输出,避免接口响应携带毫秒。 + * 暂态事件时间字段按毫秒输出,保持与数据库 datetime(3) 精度一致。 */ -public class EventSecondTimeSerializer extends JsonSerializer { +public class EventMillisecondTimeSerializer extends JsonSerializer { - private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); @Override public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException { diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/controller/EventListController.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/controller/EventListController.java index a370836..c9ba281 100644 --- a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/controller/EventListController.java +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/controller/EventListController.java @@ -8,8 +8,10 @@ import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.utils.LogUtil; import com.njcn.gather.event.eventlist.pojo.param.EventListQueryParam; +import com.njcn.gather.event.eventlist.pojo.param.EventWaveExportParam; import com.njcn.gather.event.eventlist.pojo.vo.EventListVO; import com.njcn.gather.event.eventlist.service.EventListService; +import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO; import com.njcn.web.controller.BaseController; import com.njcn.web.utils.HttpResultUtil; import io.swagger.annotations.Api; @@ -64,4 +66,21 @@ public class EventListController extends BaseController { public void exportTransientEvents(@RequestBody EventListQueryParam param) { eventListService.exportTransientEvents(param); } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("查看暂态事件波形") + @GetMapping("/transient/{eventId}/wave") + public HttpResult getTransientEventWave(@PathVariable("eventId") String eventId) { + String methodDescribe = getMethodDescribe("getTransientEventWave"); + LogUtil.njcnDebug(log, "{},开始查看暂态事件波形,eventId={}", methodDescribe, eventId); + WaveComtradeResultVO result = eventListService.getTransientEventWave(eventId); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DOWNLOAD) + @ApiOperation("导出选中暂态事件波形") + @PostMapping("/transient/wave/export") + public void exportTransientEventWaves(@RequestBody EventWaveExportParam param) { + eventListService.exportTransientEventWaves(param); + } } diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/mapper/EventListMapper.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/mapper/EventListMapper.java index c3909fb..6eb21ac 100644 --- a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/mapper/EventListMapper.java +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/mapper/EventListMapper.java @@ -18,4 +18,6 @@ public interface EventListMapper extends BaseMapper { List selectTransientExportList(@Param("param") EventListQueryParam param, @Param("limit") Integer limit); MpEventDetailPO selectTransientDetail(@Param("eventId") String eventId); + + List selectTransientDetailsByIds(@Param("eventIds") List eventIds); } diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/mapper/mapping/EventListMapper.xml b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/mapper/mapping/EventListMapper.xml index 891bd90..bedeef5 100644 --- a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/mapper/mapping/EventListMapper.xml +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/mapper/mapping/EventListMapper.xml @@ -47,9 +47,6 @@ AND phase = #{param.phase} - - AND event_describe LIKE CONCAT('%', #{param.eventDescribe}, '%') - AND duration >= #{param.durationMin} @@ -101,4 +98,14 @@ WHERE event_id = #{eventId} LIMIT 1 + + diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/param/EventListQueryParam.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/param/EventListQueryParam.java index cfb2f82..f67e944 100644 --- a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/param/EventListQueryParam.java +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/param/EventListQueryParam.java @@ -18,10 +18,10 @@ import java.util.List; @ApiModel("暂态事件列表查询参数") public class EventListQueryParam extends BaseParam { - @ApiModelProperty("发生时刻开始,格式 yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("发生时刻开始,格式 yyyy-MM-dd HH:mm:ss.SSS,兼容 yyyy-MM-dd HH:mm:ss") private String startTimeStart; - @ApiModelProperty("发生时刻结束,格式 yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("发生时刻结束,格式 yyyy-MM-dd HH:mm:ss.SSS,兼容 yyyy-MM-dd HH:mm:ss") private String startTimeEnd; @ApiModelProperty("事件类型") @@ -30,9 +30,6 @@ public class EventListQueryParam extends BaseParam { @ApiModelProperty("相别") private String phase; - @ApiModelProperty("事件描述关键字") - private String eventDescribe; - @ApiModelProperty("持续时间下限,单位秒") private BigDecimal durationMin; diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/param/EventWaveExportParam.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/param/EventWaveExportParam.java new file mode 100644 index 0000000..94d94df --- /dev/null +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/param/EventWaveExportParam.java @@ -0,0 +1,19 @@ +package com.njcn.gather.event.eventlist.pojo.param; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 事件波形批量导出参数。 + */ +@Data +@ApiModel("事件波形批量导出参数") +public class EventWaveExportParam { + + @ApiModelProperty("页面勾选的事件 ID 列表") + private List eventIds = new ArrayList(); +} diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/vo/EventListVO.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/vo/EventListVO.java index 82dbd6a..87de897 100644 --- a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/vo/EventListVO.java +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/pojo/vo/EventListVO.java @@ -2,11 +2,12 @@ package com.njcn.gather.event.eventlist.pojo.vo; import cn.afterturn.easypoi.excel.annotation.Excel; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; -import com.njcn.gather.event.eventlist.config.EventSecondTimeSerializer; +import com.njcn.gather.event.eventlist.config.EventMillisecondTimeSerializer; import lombok.Data; import java.io.Serializable; @@ -25,10 +26,11 @@ public class EventListVO implements Serializable { private String measurementPointId; - private String eventType; - - @Excel(name = "设备名称", width = 25) - private String equipmentName; + @Excel(name = "发生时刻", width = 25, exportFormat = "yyyy-MM-dd HH:mm:ss.SSS") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS") + @JsonDeserialize(using = LocalDateTimeDeserializer.class) + @JsonSerialize(using = EventMillisecondTimeSerializer.class) + private LocalDateTime startTime; @Excel(name = "工程名称", width = 25) private String engineeringName; @@ -36,29 +38,32 @@ public class EventListVO implements Serializable { @Excel(name = "项目名称", width = 25) private String projectName; - @Excel(name = "发生时刻", width = 25, exportFormat = "yyyy-MM-dd HH:mm:ss") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @JsonDeserialize(using = LocalDateTimeDeserializer.class) - @JsonSerialize(using = EventSecondTimeSerializer.class) - private LocalDateTime startTime; + @Excel(name = "设备名称", width = 25) + private String equipmentName; + + private String mac; @Excel(name = "监测点名称", width = 25) private String lineName; - @Excel(name = "事件描述", width = 25) - private String eventDescribe; - - @Excel(name = "事件发生位置", width = 18) - private String sagsource; - - @Excel(name = "相别", width = 15) - private String phase; + @Excel(name = "暂降/暂升幅值(%)", width = 20) + private BigDecimal featureAmplitude; @Excel(name = "持续时间(s)", width = 18) private BigDecimal duration; - @Excel(name = "暂降/暂升幅值(%)", width = 20) - private BigDecimal featureAmplitude; + @Excel(name = "事件类型", width = 18) + private String eventType; + + @Excel(name = "相别", width = 15) + private String phase; + + @Excel(name = "事件描述", width = 25) + @JsonProperty("event_describe") + private String eventDescribe; + + @Excel(name = "事件发生位置", width = 18) + private String sagsource; private String wavePath; diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/EventListService.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/EventListService.java index 74267ba..ece1426 100644 --- a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/EventListService.java +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/EventListService.java @@ -2,7 +2,9 @@ package com.njcn.gather.event.eventlist.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.gather.event.eventlist.pojo.param.EventListQueryParam; +import com.njcn.gather.event.eventlist.pojo.param.EventWaveExportParam; import com.njcn.gather.event.eventlist.pojo.vo.EventListVO; +import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO; /** * 事件列表服务。 @@ -14,4 +16,8 @@ public interface EventListService { EventListVO getTransientEventDetail(String eventId); void exportTransientEvents(EventListQueryParam param); + + WaveComtradeResultVO getTransientEventWave(String eventId); + + void exportTransientEventWaves(EventWaveExportParam param); } diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/impl/EventListServiceImpl.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/impl/EventListServiceImpl.java index 7446e82..56440b9 100644 --- a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/impl/EventListServiceImpl.java +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/impl/EventListServiceImpl.java @@ -1,30 +1,61 @@ package com.njcn.gather.event.eventlist.service.impl; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.njcn.common.pojo.enums.common.DataStateEnum; import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.exception.BusinessException; import com.njcn.gather.event.eventlist.mapper.EventListMapper; import com.njcn.gather.event.eventlist.pojo.param.EventListQueryParam; +import com.njcn.gather.event.eventlist.pojo.param.EventWaveExportParam; import com.njcn.gather.event.eventlist.pojo.po.MpEventDetailPO; import com.njcn.gather.event.eventlist.pojo.vo.EventListVO; import com.njcn.gather.event.eventlist.service.EventListService; +import com.njcn.gather.system.cfg.service.ISysConfigService; +import com.njcn.gather.system.dictionary.pojo.po.DictData; +import com.njcn.gather.system.dictionary.pojo.po.DictType; +import com.njcn.gather.system.dictionary.service.IDictDataService; +import com.njcn.gather.system.dictionary.service.IDictTypeService; +import com.njcn.gather.system.pojo.constant.DictConst; +import com.njcn.gather.system.util.ExportFileNameUtil; import com.njcn.gather.tool.addledger.pojo.param.AddLedgerLinePathQueryParam; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO; import com.njcn.gather.tool.addledger.service.AddLedgerService; +import com.njcn.gather.tool.wave.pojo.param.WaveComtradeParseParam; +import com.njcn.gather.tool.wave.pojo.vo.WaveComtradeResultVO; +import com.njcn.gather.tool.wave.service.WaveService; import com.njcn.web.factory.PageFactory; import com.njcn.web.utils.ExcelUtil; +import com.njcn.web.utils.HttpServletUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.stereotype.Service; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; +import java.net.URLEncoder; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * 事件列表服务实现。 @@ -37,8 +68,12 @@ public class EventListServiceImpl implements EventListService { private static final int LEDGER_LINE_QUERY_LIMIT = 1000; private static final int EVENT_LINE_ID_QUERY_LIMIT = 1000; private static final int EXPORT_LIMIT = 5000; + private static final int WAVE_EXPORT_LIMIT = 500; private static final String EMPTY_TEXT = "-"; - private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final String WAVE_EXPORT_SUCCESS = "成功"; + private static final String WAVE_EXPORT_FAIL = "失败"; + private static final BigDecimal PERCENT_BASE = new BigDecimal("100"); + private static final DateTimeFormatter OUTPUT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); private static final DateTimeFormatter[] INPUT_FORMATTERS = new DateTimeFormatter[]{ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"), @@ -48,6 +83,10 @@ public class EventListServiceImpl implements EventListService { private final EventListMapper eventListMapper; private final AddLedgerService addLedgerService; + private final ISysConfigService sysConfigService; + private final IDictTypeService dictTypeService; + private final IDictDataService dictDataService; + private final WaveService waveService; @Override public Page pageTransientEvents(EventListQueryParam param) { @@ -89,10 +128,298 @@ public class EventListServiceImpl implements EventListService { throw new BusinessException(CommonResponseEnum.FAIL, "导出数据超过 5000 条,请缩小查询条件"); } exportRecords = buildEventList(eventDetails); + fillExportEventTypeNames(exportRecords); } else { exportRecords = Collections.emptyList(); } - ExcelUtil.exportExcel("暂态事件列表.xlsx", "暂态事件列表", EventListVO.class, exportRecords); + ExcelUtil.exportExcel(ExportFileNameUtil.appendToday("暂态事件列表.xlsx"), "暂态事件列表", EventListVO.class, exportRecords); + } + + @Override + public WaveComtradeResultVO getTransientEventWave(String eventId) { + MpEventDetailPO eventDetail = requireEventDetail(eventId); + AddLedgerLinePathVO linePath = requireLinePath(eventDetail); + EventWavePathResolver.WaveFilePath waveFilePath = resolveWaveFilePath(eventDetail, linePath); + if (!Files.exists(waveFilePath.getCfgPath())) { + throw new BusinessException(CommonResponseEnum.FAIL, "cfg 波形文件不存在"); + } + if (!Files.exists(waveFilePath.getDatPath())) { + throw new BusinessException(CommonResponseEnum.FAIL, "dat 波形文件不存在"); + } + WaveComtradeParseParam parseParam = new WaveComtradeParseParam(); + parseParam.setMonitorName(defaultText(linePath.getLineName())); + try (InputStream cfgInputStream = new FileInputStream(waveFilePath.getCfgPath().toFile()); + InputStream datInputStream = new FileInputStream(waveFilePath.getDatPath().toFile())) { + return waveService.parseComtrade(cfgInputStream, datInputStream, parseParam); + } catch (IOException ex) { + log.error("读取暂态事件波形文件失败,eventId={}", eventDetail.getEventId(), ex); + throw new BusinessException(CommonResponseEnum.FAIL, "读取波形文件失败"); + } + } + + @Override + public void exportTransientEventWaves(EventWaveExportParam param) { + List eventIds = normalizeExportEventIds(param); + List exportItems = buildWaveExportItems(eventIds); + writeWaveExportZip(exportItems); + } + + private MpEventDetailPO requireEventDetail(String eventId) { + String normalizedEventId = trimToNull(eventId); + if (normalizedEventId == null) { + throw new BusinessException(CommonResponseEnum.FAIL, "事件 ID 不能为空"); + } + MpEventDetailPO eventDetail = eventListMapper.selectTransientDetail(normalizedEventId); + if (eventDetail == null) { + throw new BusinessException(CommonResponseEnum.FAIL, "暂态事件不存在"); + } + return eventDetail; + } + + private AddLedgerLinePathVO requireLinePath(MpEventDetailPO eventDetail) { + List lineIds = new ArrayList(); + lineIds.add(eventDetail.getMeasurementPointId()); + Map linePathMap = addLedgerService.listLinePathByLineIds(lineIds); + AddLedgerLinePathVO linePath = linePathMap.get(eventDetail.getMeasurementPointId()); + if (linePath == null) { + throw new BusinessException(CommonResponseEnum.FAIL, "监测点台账不存在或已删除"); + } + return linePath; + } + + private List normalizeExportEventIds(EventWaveExportParam param) { + List eventIds = normalizeIds(param == null ? null : param.getEventIds()); + if (eventIds.isEmpty()) { + throw new BusinessException(CommonResponseEnum.FAIL, "请选择需要导出的事件"); + } + if (eventIds.size() > WAVE_EXPORT_LIMIT) { + throw new BusinessException(CommonResponseEnum.FAIL, "波形导出事件数量不能超过 500 条"); + } + return eventIds; + } + + private List buildWaveExportItems(List eventIds) { + List eventDetails = eventListMapper.selectTransientDetailsByIds(eventIds); + Map eventMap = new LinkedHashMap(); + List lineIds = new ArrayList(); + for (MpEventDetailPO eventDetail : eventDetails) { + eventMap.put(eventDetail.getEventId(), eventDetail); + String lineId = trimToNull(eventDetail.getMeasurementPointId()); + if (lineId != null && !lineIds.contains(lineId)) { + lineIds.add(lineId); + } + } + Map linePathMap = addLedgerService.listLinePathByLineIds(lineIds); + List exportItems = new ArrayList(); + for (String eventId : eventIds) { + WaveExportItem item = new WaveExportItem(); + item.setEventId(eventId); + MpEventDetailPO eventDetail = eventMap.get(eventId); + item.setEventDetail(eventDetail); + if (eventDetail == null) { + item.fail("事件不存在"); + exportItems.add(item); + continue; + } + AddLedgerLinePathVO linePath = linePathMap.get(eventDetail.getMeasurementPointId()); + item.setLinePath(linePath); + item.setEventVO(buildEventVO(eventDetail, linePath)); + fillWaveExportFileState(item); + exportItems.add(item); + } + return exportItems; + } + + private void fillWaveExportFileState(WaveExportItem item) { + MpEventDetailPO eventDetail = item.getEventDetail(); + if (item.getLinePath() == null) { + item.fail("监测点台账不存在或已删除"); + return; + } + if (eventDetail.getFileFlag() == null || eventDetail.getFileFlag() != 1) { + item.fail("波形文件未招"); + return; + } + try { + EventWavePathResolver.WaveFilePath waveFilePath = resolveWaveFilePath(eventDetail, item.getLinePath()); + item.setWaveFilePath(waveFilePath); + boolean cfgExists = Files.exists(waveFilePath.getCfgPath()); + boolean datExists = Files.exists(waveFilePath.getDatPath()); + if (cfgExists && datExists) { + item.success(); + return; + } + if (!cfgExists && !datExists) { + item.fail("cfg/dat 文件不存在"); + } else if (!cfgExists) { + item.fail("cfg 文件不存在"); + } else { + item.fail("dat 文件不存在"); + } + } catch (BusinessException ex) { + item.fail(ex.getMessage()); + } + } + + private EventWavePathResolver.WaveFilePath resolveWaveFilePath(MpEventDetailPO eventDetail, AddLedgerLinePathVO linePath) { + try { + return EventWavePathResolver.resolve(sysConfigService.getWaveStoragePath(), linePath.getEquipmentMac(), eventDetail.getWavePath()); + } catch (IllegalArgumentException ex) { + throw new BusinessException(CommonResponseEnum.FAIL, ex.getMessage()); + } + } + + private void writeWaveExportZip(List exportItems) { + HttpServletResponse response = HttpServletUtil.getResponse(); + try { + String fileName = URLEncoder.encode(ExportFileNameUtil.appendToday("选中事件波形导出.zip"), "UTF-8"); + response.reset(); + response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + response.setContentType("application/zip;charset=UTF-8"); + try (ServletOutputStream outputStream = response.getOutputStream(); + ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) { + writeZipEntry(zipOutputStream, ExportFileNameUtil.appendToday("选中事件日志.xlsx"), buildWaveExportWorkbook(exportItems)); + for (int i = 0; i < exportItems.size(); i++) { + WaveExportItem item = exportItems.get(i); + EventWavePathResolver.WaveFilePath waveFilePath = item.getWaveFilePath(); + if (waveFilePath == null) { + continue; + } + String folder = buildZipFolderName(i + 1, item.getEventId()); + writeWaveFileIfExists(zipOutputStream, folder + "/" + waveFilePath.getCfgPath().getFileName(), waveFilePath.getCfgPath()); + writeWaveFileIfExists(zipOutputStream, folder + "/" + waveFilePath.getDatPath().getFileName(), waveFilePath.getDatPath()); + } + zipOutputStream.flush(); + } + } catch (IOException ex) { + log.error("导出暂态事件波形失败", ex); + throw new BusinessException(CommonResponseEnum.FAIL, "导出波形文件失败"); + } + } + + private byte[] buildWaveExportWorkbook(List exportItems) throws IOException { + XSSFWorkbook workbook = new XSSFWorkbook(); + try { + writeEventLogSheet(workbook, exportItems); + writeWaveFileSheet(workbook, exportItems); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + workbook.write(outputStream); + return outputStream.toByteArray(); + } finally { + workbook.close(); + } + } + + private void writeEventLogSheet(XSSFWorkbook workbook, List exportItems) { + XSSFSheet sheet = workbook.createSheet("选中事件列表"); + String[] headers = new String[]{"发生时刻", "工程名称", "项目名称", "设备名称", "监测点名称", "暂降/暂升幅值(%)", + "持续时间(s)", "事件类型", "相别", "事件描述", "事件发生位置"}; + int[] columnWidths = new int[]{25, 25, 25, 25, 25, 20, 18, 18, 15, 25, 18}; + writeHeader(sheet, headers, createHeaderStyle(workbook)); + Map eventTypeNameMap = buildEventTypeNameMap(); + int rowIndex = 1; + for (WaveExportItem item : exportItems) { + EventListVO vo = item.getEventVO(); + if (vo == null) { + continue; + } + Row row = sheet.createRow(rowIndex++); + writeCells(row, new Object[]{formatDateTime(vo.getStartTime()), vo.getEngineeringName(), vo.getProjectName(), + vo.getEquipmentName(), vo.getLineName(), vo.getFeatureAmplitude(), vo.getDuration(), + translateEventType(vo.getEventType(), eventTypeNameMap), vo.getPhase(), vo.getEventDescribe(), + vo.getSagsource()}); + } + applyColumnWidth(sheet, columnWidths); + } + + private void writeWaveFileSheet(XSSFWorkbook workbook, List exportItems) { + XSSFSheet sheet = workbook.createSheet("波形文件清单"); + String[] headers = new String[]{"事件ID", "设备MAC", "wavePath", "cfg文件路径", "dat文件路径", "导出状态", "失败原因"}; + writeHeader(sheet, headers, createHeaderStyle(workbook)); + int rowIndex = 1; + for (WaveExportItem item : exportItems) { + EventWavePathResolver.WaveFilePath waveFilePath = item.getWaveFilePath(); + Row row = sheet.createRow(rowIndex++); + writeCells(row, new Object[]{item.getEventId(), + item.getLinePath() == null ? EMPTY_TEXT : item.getLinePath().getEquipmentMac(), + item.getEventDetail() == null ? EMPTY_TEXT : item.getEventDetail().getWavePath(), + waveFilePath == null ? EMPTY_TEXT : waveFilePath.getCfgPath().toString(), + waveFilePath == null ? EMPTY_TEXT : waveFilePath.getDatPath().toString(), + item.getStatus(), defaultText(item.getFailReason())}); + } + applyColumnWidth(sheet, headers.length); + } + + private CellStyle createHeaderStyle(XSSFWorkbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + return style; + } + + private void writeHeader(XSSFSheet sheet, String[] headers, CellStyle style) { + Row row = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = row.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(style); + } + } + + private void writeCells(Row row, Object[] values) { + for (int i = 0; i < values.length; i++) { + Cell cell = row.createCell(i); + Object value = values[i]; + cell.setCellValue(value == null ? "" : String.valueOf(value)); + } + } + + private void applyColumnWidth(XSSFSheet sheet, int columnCount) { + for (int i = 0; i < columnCount; i++) { + sheet.setColumnWidth(i, 20 * 256); + } + } + + private void applyColumnWidth(XSSFSheet sheet, int[] columnWidths) { + for (int i = 0; i < columnWidths.length; i++) { + sheet.setColumnWidth(i, columnWidths[i] * 256); + } + } + + private void writeZipEntry(ZipOutputStream zipOutputStream, String entryName, byte[] content) throws IOException { + ZipEntry zipEntry = new ZipEntry(entryName); + zipOutputStream.putNextEntry(zipEntry); + zipOutputStream.write(content); + zipOutputStream.closeEntry(); + } + + private void writeWaveFileIfExists(ZipOutputStream zipOutputStream, String entryName, Path filePath) throws IOException { + if (!Files.exists(filePath)) { + return; + } + zipOutputStream.putNextEntry(new ZipEntry(entryName)); + try (InputStream inputStream = new FileInputStream(filePath.toFile())) { + byte[] buffer = new byte[8192]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + zipOutputStream.write(buffer, 0, length); + } + } + zipOutputStream.closeEntry(); + } + + private String buildZipFolderName(int index, String eventId) { + return index + "_" + sanitizeZipSegment(eventId); + } + + private String sanitizeZipSegment(String value) { + String text = trimToNull(value); + return text == null ? "unknown" : text.replaceAll("[\\\\/:*?\"<>|]", "_"); + } + + private String formatDateTime(LocalDateTime value) { + return value == null ? EMPTY_TEXT : OUTPUT_FORMATTER.format(value); } private List buildEventList(List eventDetails) { @@ -121,15 +448,16 @@ public class EventListServiceImpl implements EventListService { vo.setMeasurementPointId(eventDetail.getMeasurementPointId()); vo.setEventType(eventDetail.getEventType()); vo.setEquipmentName(linePath == null ? EMPTY_TEXT : defaultText(linePath.getEquipmentName())); + vo.setMac(linePath == null ? EMPTY_TEXT : defaultText(linePath.getEquipmentMac())); vo.setEngineeringName(linePath == null ? EMPTY_TEXT : defaultText(linePath.getEngineeringName())); vo.setProjectName(linePath == null ? EMPTY_TEXT : defaultText(linePath.getProjectName())); vo.setStartTime(eventDetail.getStartTime()); vo.setLineName(linePath == null ? EMPTY_TEXT : defaultText(linePath.getLineName())); - vo.setEventDescribe(defaultText(eventDetail.getEventDescribe(), eventDetail.getEventType())); + vo.setEventDescribe(trimToNull(eventDetail.getEventDescribe())); vo.setSagsource(defaultText(eventDetail.getSagsource())); vo.setPhase(defaultText(eventDetail.getPhase())); vo.setDuration(eventDetail.getDuration()); - vo.setFeatureAmplitude(eventDetail.getFeatureAmplitude()); + vo.setFeatureAmplitude(toPercent(eventDetail.getFeatureAmplitude())); vo.setWavePath(eventDetail.getWavePath()); vo.setFileFlag(eventDetail.getFileFlag()); vo.setDealFlag(eventDetail.getDealFlag()); @@ -155,11 +483,12 @@ public class EventListServiceImpl implements EventListService { validateRange(queryParam.getFeatureAmplitudeMin(), queryParam.getFeatureAmplitudeMax(), "幅值下限不能大于上限"); validateFlag(queryParam.getFileFlag(), "波形文件状态只能是 0 或 1"); validateDealFlag(queryParam.getDealFlag()); + queryParam.setFeatureAmplitudeMin(fromPercent(queryParam.getFeatureAmplitudeMin())); + queryParam.setFeatureAmplitudeMax(fromPercent(queryParam.getFeatureAmplitudeMax())); queryParam.setStartTimeStart(OUTPUT_FORMATTER.format(startTime)); queryParam.setStartTimeEnd(OUTPUT_FORMATTER.format(endTime)); queryParam.setEventType(trimToNull(queryParam.getEventType())); queryParam.setPhase(trimToNull(queryParam.getPhase())); - queryParam.setEventDescribe(trimToNull(queryParam.getEventDescribe())); queryParam.setEngineeringName(trimToNull(queryParam.getEngineeringName())); queryParam.setProjectName(trimToNull(queryParam.getProjectName())); queryParam.setEquipmentName(trimToNull(queryParam.getEquipmentName())); @@ -222,10 +551,10 @@ public class EventListServiceImpl implements EventListService { try { return LocalDateTime.parse(text, formatter); } catch (DateTimeParseException ignored) { - // 尝试下一个前端可能传入的时间格式。 + // 尝试下一种前端可能传入的时间格式。 } } - throw new BusinessException(CommonResponseEnum.FAIL, "时间格式不正确,仅支持 yyyy-MM-dd HH:mm:ss"); + throw new BusinessException(CommonResponseEnum.FAIL, "时间格式不正确,仅支持 yyyy-MM-dd HH:mm:ss 或 yyyy-MM-dd HH:mm:ss.SSS"); } private List normalizeIds(List ids) { @@ -255,6 +584,59 @@ public class EventListServiceImpl implements EventListService { } } + private BigDecimal toPercent(BigDecimal value) { + return value == null ? null : value.multiply(PERCENT_BASE); + } + + private BigDecimal fromPercent(BigDecimal value) { + return value == null ? null : value.divide(PERCENT_BASE); + } + + private void fillExportEventTypeNames(List exportRecords) { + Map eventTypeNameMap = buildEventTypeNameMap(); + for (EventListVO record : exportRecords) { + record.setEventType(translateEventType(record.getEventType(), eventTypeNameMap)); + } + } + + private Map buildEventTypeNameMap() { + DictType dictType = dictTypeService.getByCode(DictConst.EVENT_TYPE_DICT); + if (dictType == null) { + dictType = dictTypeService.lambdaQuery() + .eq(DictType::getName, DictConst.EVENT_TYPE_DICT) + .eq(DictType::getState, DataStateEnum.ENABLE.getCode()) + .orderByAsc(DictType::getSort) + .orderByDesc(DictType::getId) + .last("LIMIT 1") + .one(); + } + if (dictType == null) { + return Collections.emptyMap(); + } + List dictDataList = dictDataService.listDictDataByTypeId(dictType.getId()); + Map eventTypeNameMap = new LinkedHashMap(); + for (DictData dictData : dictDataList) { + String id = trimToNull(dictData.getId()); + String code = trimToNull(dictData.getCode()); + if (id != null) { + eventTypeNameMap.put(id, defaultText(dictData.getName(), id)); + } + if (code != null) { + eventTypeNameMap.put(code, defaultText(dictData.getName(), code)); + } + } + return eventTypeNameMap; + } + + private String translateEventType(String eventType, Map eventTypeNameMap) { + String normalizedEventType = trimToNull(eventType); + if (normalizedEventType == null) { + return EMPTY_TEXT; + } + String eventTypeName = eventTypeNameMap.get(normalizedEventType); + return eventTypeName == null ? normalizedEventType : eventTypeName; + } + private void validateFlag(Integer flag, String message) { if (flag != null && flag != 0 && flag != 1) { throw new BusinessException(CommonResponseEnum.FAIL, message); @@ -283,4 +665,79 @@ public class EventListServiceImpl implements EventListService { String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } + + private static class WaveExportItem { + + private String eventId; + + private MpEventDetailPO eventDetail; + + private AddLedgerLinePathVO linePath; + + private EventListVO eventVO; + + private EventWavePathResolver.WaveFilePath waveFilePath; + + private String status = WAVE_EXPORT_FAIL; + + private String failReason; + + private String getEventId() { + return eventId; + } + + private void setEventId(String eventId) { + this.eventId = eventId; + } + + private MpEventDetailPO getEventDetail() { + return eventDetail; + } + + private void setEventDetail(MpEventDetailPO eventDetail) { + this.eventDetail = eventDetail; + } + + private AddLedgerLinePathVO getLinePath() { + return linePath; + } + + private void setLinePath(AddLedgerLinePathVO linePath) { + this.linePath = linePath; + } + + private EventListVO getEventVO() { + return eventVO; + } + + private void setEventVO(EventListVO eventVO) { + this.eventVO = eventVO; + } + + private EventWavePathResolver.WaveFilePath getWaveFilePath() { + return waveFilePath; + } + + private void setWaveFilePath(EventWavePathResolver.WaveFilePath waveFilePath) { + this.waveFilePath = waveFilePath; + } + + private String getStatus() { + return status; + } + + private String getFailReason() { + return failReason; + } + + private void success() { + this.status = WAVE_EXPORT_SUCCESS; + this.failReason = null; + } + + private void fail(String failReason) { + this.status = WAVE_EXPORT_FAIL; + this.failReason = failReason; + } + } } diff --git a/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/impl/EventWavePathResolver.java b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/impl/EventWavePathResolver.java new file mode 100644 index 0000000..7ba6414 --- /dev/null +++ b/event/event-list/src/main/java/com/njcn/gather/event/eventlist/service/impl/EventWavePathResolver.java @@ -0,0 +1,81 @@ +package com.njcn.gather.event.eventlist.service.impl; + +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * 暂态事件波形文件路径解析器。 + */ +final class EventWavePathResolver { + + private EventWavePathResolver() { + } + + static WaveFilePath resolve(String storageRoot, String equipmentMac, String wavePath) { + String rootText = requireText(storageRoot, "波形存储路径未配置"); + String mac = requireText(equipmentMac, "设备 MAC 为空"); + String normalizedWavePath = normalizeWavePath(wavePath); + + Path rootPath = Paths.get(rootText).normalize(); + Path rawWavePath = Paths.get(normalizedWavePath).normalize(); + Path eventBasePath; + if (rawWavePath.isAbsolute()) { + eventBasePath = rawWavePath; + } else if (startsWithSegment(rawWavePath, mac)) { + eventBasePath = rootPath.resolve(rawWavePath).normalize(); + } else { + eventBasePath = rootPath.resolve(mac).resolve(rawWavePath).normalize(); + } + + if (!eventBasePath.startsWith(rootPath)) { + throw new IllegalArgumentException("波形路径非法"); + } + + String fileName = eventBasePath.getFileName().toString(); + return new WaveFilePath(eventBasePath.resolveSibling(fileName + ".cfg"), + eventBasePath.resolveSibling(fileName + ".dat")); + } + + private static String normalizeWavePath(String value) { + String wavePath = requireText(value, "事件 wave_path 为空").replace("\\", "/"); + String lowerWavePath = wavePath.toLowerCase(); + if (lowerWavePath.endsWith(".cfg") || lowerWavePath.endsWith(".dat")) { + wavePath = wavePath.substring(0, wavePath.length() - 4); + } + if (wavePath.contains("..")) { + throw new IllegalArgumentException("波形路径非法"); + } + return wavePath; + } + + private static boolean startsWithSegment(Path path, String segment) { + return path.getNameCount() > 0 && path.getName(0).toString().equalsIgnoreCase(segment); + } + + private static String requireText(String value, String message) { + if (value == null || value.trim().isEmpty()) { + throw new IllegalArgumentException(message); + } + return value.trim(); + } + + static class WaveFilePath { + + private final Path cfgPath; + + private final Path datPath; + + private WaveFilePath(Path cfgPath, Path datPath) { + this.cfgPath = cfgPath; + this.datPath = datPath; + } + + Path getCfgPath() { + return cfgPath; + } + + Path getDatPath() { + return datPath; + } + } +} diff --git a/event/event-list/src/test/java/com/njcn/gather/event/eventlist/service/impl/EventListTimeFormatTest.java b/event/event-list/src/test/java/com/njcn/gather/event/eventlist/service/impl/EventListTimeFormatTest.java new file mode 100644 index 0000000..4e6f00b --- /dev/null +++ b/event/event-list/src/test/java/com/njcn/gather/event/eventlist/service/impl/EventListTimeFormatTest.java @@ -0,0 +1,44 @@ +package com.njcn.gather.event.eventlist.service.impl; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.njcn.gather.event.eventlist.pojo.param.EventListQueryParam; +import com.njcn.gather.event.eventlist.pojo.vo.EventListVO; +import org.junit.Test; + +import java.lang.reflect.Method; +import java.time.LocalDateTime; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class EventListTimeFormatTest { + + @Test + public void startTimeJsonKeepsMilliseconds() throws Exception { + EventListVO vo = new EventListVO(); + vo.setStartTime(LocalDateTime.of(2026, 5, 10, 13, 41, 17, 944000000)); + + String json = new ObjectMapper().writeValueAsString(vo); + + assertTrue(json.contains("\"startTime\":\"2026-05-10 13:41:17.944\"")); + } + + @Test + public void queryTimeNormalizationKeepsMilliseconds() throws Exception { + EventListQueryParam param = new EventListQueryParam(); + param.setStartTimeStart("2026-05-10 13:41:17.944"); + param.setStartTimeEnd("2026-05-10 13:41:18.123"); + + normalizeQueryParam(param); + + assertEquals("2026-05-10 13:41:17.944", param.getStartTimeStart()); + assertEquals("2026-05-10 13:41:18.123", param.getStartTimeEnd()); + } + + private void normalizeQueryParam(EventListQueryParam param) throws Exception { + EventListServiceImpl service = new EventListServiceImpl(null, null, null, null, null, null); + Method method = EventListServiceImpl.class.getDeclaredMethod("normalizeQueryParam", EventListQueryParam.class); + method.setAccessible(true); + method.invoke(service, param); + } +} diff --git a/event/event-list/src/test/java/com/njcn/gather/event/eventlist/service/impl/EventWavePathResolverTest.java b/event/event-list/src/test/java/com/njcn/gather/event/eventlist/service/impl/EventWavePathResolverTest.java new file mode 100644 index 0000000..3d4b7e5 --- /dev/null +++ b/event/event-list/src/test/java/com/njcn/gather/event/eventlist/service/impl/EventWavePathResolverTest.java @@ -0,0 +1,40 @@ +package com.njcn.gather.event.eventlist.service.impl; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class EventWavePathResolverTest { + + public static void main(String[] args) { + resolvesAbsoluteWavePath(); + resolvesRelativeWavePathWithMacPrefix(); + resolvesRelativeWavePathAlreadyContainingMac(); + } + + private static void resolvesAbsoluteWavePath() { + EventWavePathResolver.WaveFilePath result = EventWavePathResolver.resolve("D:/", "AA-BB", "D:/wave/event-001.cfg"); + + assertPathEquals(Paths.get("D:/wave/event-001.cfg"), result.getCfgPath(), "absolute cfg"); + assertPathEquals(Paths.get("D:/wave/event-001.dat"), result.getDatPath(), "absolute dat"); + } + + private static void resolvesRelativeWavePathWithMacPrefix() { + EventWavePathResolver.WaveFilePath result = EventWavePathResolver.resolve("D:/wave-root", "AA-BB", "event-001"); + + assertPathEquals(Paths.get("D:/wave-root/AA-BB/event-001.cfg"), result.getCfgPath(), "relative cfg"); + assertPathEquals(Paths.get("D:/wave-root/AA-BB/event-001.dat"), result.getDatPath(), "relative dat"); + } + + private static void resolvesRelativeWavePathAlreadyContainingMac() { + EventWavePathResolver.WaveFilePath result = EventWavePathResolver.resolve("D:/wave-root", "AA-BB", "AA-BB/event-001.dat"); + + assertPathEquals(Paths.get("D:/wave-root/AA-BB/event-001.cfg"), result.getCfgPath(), "relative mac cfg"); + assertPathEquals(Paths.get("D:/wave-root/AA-BB/event-001.dat"), result.getDatPath(), "relative mac dat"); + } + + private static void assertPathEquals(Path expected, Path actual, String label) { + if (!expected.normalize().equals(actual.normalize())) { + throw new AssertionError(label + " expected " + expected + " but got " + actual); + } + } +} diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/component/SteadyTrendFieldResolver.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/component/SteadyTrendFieldResolver.java index 2fc1078..3437661 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/component/SteadyTrendFieldResolver.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/component/SteadyTrendFieldResolver.java @@ -38,7 +38,6 @@ public class SteadyTrendFieldResolver { validateBasicParam(param); List lineIds = normalizeTextList(param.getLineIds()); List indicatorCodes = normalizeTextList(param.getIndicatorCodes()); - List requestPhases = normalizeUpperList(param.getPhases()); List statTypes = normalizeUpperList(param.getStatTypes()); if (statTypes.isEmpty()) { statTypes.add("AVG"); @@ -47,7 +46,7 @@ public class SteadyTrendFieldResolver { for (String lineId : lineIds) { for (String indicatorCode : indicatorCodes) { SteadyTrendIndicatorDefinitionBO indicator = requireIndicator(indicatorCode); - List phases = resolvePhases(indicator, requestPhases); + List phases = resolvePhases(indicator); for (String phase : phases) { for (String statType : statTypes) { validateStatType(indicator, statType); @@ -57,7 +56,7 @@ public class SteadyTrendFieldResolver { } } if (result.size() > MAX_SERIES_COUNT) { - throw fail("趋势曲线数量不能超过 24 条,请缩小监测点、指标、相别或统计类型范围"); + throw fail("趋势曲线数量不能超过 24 条,请缩小监测点、指标或统计类型范围"); } return result; } @@ -165,23 +164,8 @@ public class SteadyTrendFieldResolver { return indicator; } - private List resolvePhases(SteadyTrendIndicatorDefinitionBO indicator, List requestPhases) { - if (requestPhases.isEmpty()) { - return new ArrayList(indicator.getPhaseCodes()); - } - List result = new ArrayList(); - for (String phase : requestPhases) { - if (!"A".equals(phase) && !"B".equals(phase) && !"C".equals(phase) && !"T".equals(phase)) { - throw fail("相别只能是 A、B、C、T"); - } - if (indicator.getPhaseCodes().contains(phase) && !result.contains(phase)) { - result.add(phase); - } - } - if (result.isEmpty()) { - throw fail("指标 " + indicator.getIndicatorCode() + " 不支持当前相别"); - } - return result; + private List resolvePhases(SteadyTrendIndicatorDefinitionBO indicator) { + return new ArrayList(indicator.getPhaseCodes()); } private void validateStatType(SteadyTrendIndicatorDefinitionBO indicator, String statType) { diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/controller/SteadyDataViewController.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/controller/SteadyDataViewController.java index 143d12b..dfdf11a 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/controller/SteadyDataViewController.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/controller/SteadyDataViewController.java @@ -1,13 +1,11 @@ package com.njcn.gather.steady.datavie.controller; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.common.pojo.annotation.OperateInfo; import com.njcn.common.pojo.enums.common.LogEnum; import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.utils.LogUtil; import com.njcn.gather.steady.datavie.pojo.param.SteadyDataViewDetailParam; -import com.njcn.gather.steady.datavie.pojo.param.SteadyDataViewQueryParam; import com.njcn.gather.steady.datavie.pojo.vo.SteadyDataViewTemplateVO; import com.njcn.gather.steady.datavie.pojo.vo.SteadyDataViewVO; import com.njcn.gather.steady.datavie.service.SteadyDataViewService; @@ -40,16 +38,6 @@ public class SteadyDataViewController extends BaseController { /** 稳态数据查看服务。 */ private final SteadyDataViewService steadyDataViewService; - @OperateInfo(info = LogEnum.BUSINESS_COMMON) - @ApiOperation("分页查询稳态数据") - @PostMapping("/page") - public HttpResult> pageSteadyData(@RequestBody SteadyDataViewQueryParam param) { - String methodDescribe = getMethodDescribe("pageSteadyData"); - LogUtil.njcnDebug(log, "{},开始分页查询稳态数据,param={}", methodDescribe, param); - Page result = steadyDataViewService.pageSteadyData(param); - return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); - } - @OperateInfo(info = LogEnum.BUSINESS_COMMON) @ApiOperation("查询稳态数据详情") @PostMapping("/detail") diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/mapper/SteadyDataViewMapper.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/mapper/SteadyDataViewMapper.java index 9791876..4aac3d1 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/mapper/SteadyDataViewMapper.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/mapper/SteadyDataViewMapper.java @@ -1,7 +1,5 @@ package com.njcn.gather.steady.datavie.mapper; -import com.baomidou.mybatisplus.extension.plugins.pagination.Page; -import com.njcn.gather.steady.datavie.pojo.param.SteadyDataViewQueryParam; import org.apache.ibatis.annotations.Param; import java.util.List; @@ -12,11 +10,6 @@ import java.util.Map; */ public interface SteadyDataViewMapper { - Page> selectSteadyPage(Page> page, - @Param("tableName") String tableName, - @Param("columns") List columns, - @Param("param") SteadyDataViewQueryParam param); - Map selectSteadyDetail(@Param("tableName") String tableName, @Param("columns") List columns, @Param("lineId") String lineId, diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/mapper/mapping/SteadyDataViewMapper.xml b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/mapper/mapping/SteadyDataViewMapper.xml index b346fc1..577ba6c 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/mapper/mapping/SteadyDataViewMapper.xml +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/datavie/mapper/mapping/SteadyDataViewMapper.xml @@ -3,39 +3,6 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - AND TIMEID >= #{param.timeStart} - - - AND TIMEID <= #{param.timeEnd} - - - AND PHASIC_TYPE = #{param.phasicType} - - - AND QUALITYFLAG = #{param.qualityFlag} - - - AND LINEID IN - - #{lineId} - - - - - - -