From 212b69060c40952e37b667e1b8466c5f898f0d3a Mon Sep 17 00:00:00 2001 From: yexb <553699424@qq.com> Date: Fri, 12 Jun 2026 08:41:11 +0800 Subject: [PATCH] =?UTF-8?q?refactor(steady):=20=E9=87=8D=E6=9E=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=A0=A1=E9=AA=8C=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=96=B0?= =?UTF-8?q?=E5=A2=9EPQDIF=E8=A7=A3=E6=9E=90=E9=A2=84=E7=95=99=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将数据校验中的缺失率相关字段替换为数据完整性字段 - 新增数据校验任务删除功能及相应测试 - 在tools模块中添加parse-pqdif子模块作为PQDIF文件解析预留 - 更新README文档以反映新的模块结构和依赖关系 - 优化数据校验统计汇总逻辑和测试覆盖 - 在entrance模块中集成parse-pqdif依赖 - 重构数据校验服务层实现和数据对象映射 --- README.md | 5 +- entrance/pom.xml | 5 + .../SteadyChecksquareCalculator.java | 13 - .../SteadyChecksquareController.java | 12 + .../pojo/po/SteadyChecksquareItemPO.java | 10 +- .../po/SteadyChecksquareStatSummaryPO.java | 10 +- .../pojo/po/SteadyChecksquareTaskPO.java | 4 +- .../pojo/vo/SteadyChecksquareItemVO.java | 11 +- .../vo/SteadyChecksquareStatSummaryVO.java | 11 +- .../pojo/vo/SteadyChecksquareTaskVO.java | 4 +- .../service/SteadyChecksquareService.java | 4 + .../impl/SteadyChecksquareServiceImpl.java | 148 ++--- .../steady-checksquare-result-init.sql | 102 ++++ ...dy-checksquare-result-upgrade_20260611.sql | 14 + .../SteadyChecksquareControllerTest.java | 5 + .../SteadyChecksquareServiceImplTest.java | 172 +++++- .../steady-checksquare-api-debug_20260610.md | 522 ++++++++++++++++++ tools/README.md | 16 +- tools/mms-mapping/pom.xml | 6 + .../controller/MmsDeviceTypeController.java | 72 +++ .../icd/mapping/mapper/CsDevTypeMapper.java | 15 + .../icd/mapping/mapper/CsIcdPathMapper.java | 10 + .../mapper/mapping/CsDevTypeMapper.xml | 25 + .../pojo/param/IcdCheckResultSaveParam.java | 25 + .../icd/mapping/pojo/po/CsDevTypePO.java | 61 ++ .../icd/mapping/pojo/po/CsIcdPathPO.java | 67 +++ .../icd/mapping/pojo/vo/MmsDeviceTypeVO.java | 43 ++ .../pojo/vo/PqdifCheckPlaceholderVO.java | 19 + .../mapping/service/MmsDeviceTypeService.java | 19 + .../impl/MmsDeviceTypeServiceImpl.java | 141 +++++ tools/parse-pqdif/pom.xml | 55 ++ .../controller/ParsePqdifController.java | 46 ++ .../pojo/vo/PqdifParseResponse.java | 22 + .../parsepqdif/service/ParsePqdifService.java | 12 + .../service/impl/ParsePqdifServiceImpl.java | 24 + tools/parse-pqdif/src/main/resources/.gitkeep | 1 + tools/pom.xml | 1 + 37 files changed, 1606 insertions(+), 126 deletions(-) create mode 100644 steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql create mode 100644 steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql create mode 100644 steady/steady-DataView/steady-checksquare-api-debug_20260610.md create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsIcdPathMapper.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdCheckResultSaveParam.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsIcdPathPO.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java create mode 100644 tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java create mode 100644 tools/parse-pqdif/pom.xml create mode 100644 tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/controller/ParsePqdifController.java create mode 100644 tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/pojo/vo/PqdifParseResponse.java create mode 100644 tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/ParsePqdifService.java create mode 100644 tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/impl/ParsePqdifServiceImpl.java create mode 100644 tools/parse-pqdif/src/main/resources/.gitkeep diff --git a/README.md b/README.md index 7203266..3fedf8d 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓 - `add-data` - `add-ledger` - `mms-mapping` +- `parse-pqdif` - `wave-tool` ## 启动入口 @@ -43,7 +44,7 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓 - `entrance/src/main/java/com/njcn/gather/EntranceApplication.java` -`entrance` 模块聚合了 `system`、`disk-monitor`、`dbms`、`deploy`、`user`、`detection`、`activate-tool`、`add-data`、`add-ledger`、`wave-tool`、`mms-mapping`,是当前运行时主入口。 +`entrance` 模块聚合了 `system`、`disk-monitor`、`dbms`、`deploy`、`user`、`detection`、`activate-tool`、`add-data`、`add-ledger`、`wave-tool`、`mms-mapping`、`parse-pqdif`,是当前运行时主入口。 ## 技术基线 @@ -100,6 +101,8 @@ P0 已补齐基线文档,建议按以下顺序阅读: - 当前为数据台账工具预留空模块 - `tools/mms-mapping` - 负责 ICD 文件解析与 MMS 映射数据生成能力 +- `tools/parse-pqdif` + - 当前为 PQDIF 文件解析能力预留空方法骨架 - `tools/wave-tool` - 负责波形文本解析与查看数据组装能力 diff --git a/entrance/pom.xml b/entrance/pom.xml index 94f3867..c11af63 100644 --- a/entrance/pom.xml +++ b/entrance/pom.xml @@ -58,6 +58,11 @@ mms-mapping 1.0.0 + + com.njcn.gather + parse-pqdif + 1.0.0 + com.njcn.gather add-data diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java index 36e5aed..64cc441 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java @@ -45,19 +45,6 @@ public class SteadyChecksquareCalculator { return result; } - public int maxContinuousMissingMinutes(List segments) { - int result = 0; - if (segments == null) { - return result; - } - for (SteadyChecksquareSegmentVO segment : segments) { - if (segment != null && STATUS_MISSING.equals(segment.getStatus()) && segment.getDurationMinutes() != null) { - result = Math.max(result, segment.getDurationMinutes()); - } - } - return result; - } - private SteadyChecksquareSegmentVO buildSegment(LocalDateTime startTime, LocalDateTime endTime, String status, int pointCount, int intervalMinutes) { SteadyChecksquareSegmentVO segment = new SteadyChecksquareSegmentVO(); diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java index c0bfa9a..d3f0c71 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java @@ -28,6 +28,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + /** * 数据校验接口。 */ @@ -60,6 +62,16 @@ public class SteadyChecksquareController extends BaseController { return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); } + @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE) + @ApiOperation("删除数据校验任务") + @PostMapping("/delete") + public HttpResult delete(@RequestBody List taskIds) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},开始删除数据校验任务,taskIds={}", methodDescribe, taskIds); + boolean result = checksquareService.delete(taskIds); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + @OperateInfo(info = LogEnum.BUSINESS_COMMON) @ApiOperation("查询数据校验任务详情") @GetMapping("/detail") diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java index 6a56ea7..5612589 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java @@ -40,12 +40,10 @@ public class SteadyChecksquareItemPO implements Serializable { private Integer actualPointCount; @TableField("missing_point_count") private Integer missingPointCount; - @TableField("missing_rate") - private BigDecimal missingRate; - @TableField("missing_rate_text") - private String missingRateText; - @TableField("max_continuous_missing_minutes") - private Integer maxContinuousMissingMinutes; + @TableField("data_integrity") + private BigDecimal dataIntegrity; + @TableField("data_integrity_text") + private String dataIntegrityText; @TableField("abnormal") private Integer abnormal; @TableField("abnormal_point_count") diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java index 5f4697c..685ab20 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java @@ -34,12 +34,10 @@ public class SteadyChecksquareStatSummaryPO implements Serializable { private Integer actualPointCount; @TableField("missing_point_count") private Integer missingPointCount; - @TableField("missing_rate") - private BigDecimal missingRate; - @TableField("missing_rate_text") - private String missingRateText; - @TableField("max_continuous_missing_minutes") - private Integer maxContinuousMissingMinutes; + @TableField("data_integrity") + private BigDecimal dataIntegrity; + @TableField("data_integrity_text") + private String dataIntegrityText; @TableField("create_time") private LocalDateTime createTime; } diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java index bcc75b2..06c25f9 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java @@ -42,8 +42,8 @@ public class SteadyChecksquareTaskPO implements Serializable { private Integer itemCount; @TableField("abnormal_item_count") private Integer abnormalItemCount; - @TableField("max_missing_rate") - private BigDecimal maxMissingRate; + @TableField("min_data_integrity") + private BigDecimal minDataIntegrity; @TableField("result_message") private String resultMessage; @TableField("state") diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java index 400e37a..c1c11c4 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java @@ -48,14 +48,11 @@ public class SteadyChecksquareItemVO implements Serializable { @ApiModelProperty("缺失点数") private Integer missingPointCount; - @ApiModelProperty("缺失率") - private BigDecimal missingRate; + @ApiModelProperty("数据完整性") + private BigDecimal dataIntegrity; - @ApiModelProperty("缺失率文本") - private String missingRateText; - - @ApiModelProperty("最大连续缺失时长,单位分钟") - private Integer maxContinuousMissingMinutes; + @ApiModelProperty("数据完整性文本") + private String dataIntegrityText; @ApiModelProperty("指标值大小关系是否异常") private Boolean abnormal; diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java index 0cb2b36..8f85735 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java @@ -34,12 +34,9 @@ public class SteadyChecksquareStatSummaryVO implements Serializable { @ApiModelProperty("缺失点数") private Integer missingPointCount; - @ApiModelProperty("缺失率") - private BigDecimal missingRate; + @ApiModelProperty("数据完整性") + private BigDecimal dataIntegrity; - @ApiModelProperty("缺失率文本") - private String missingRateText; - - @ApiModelProperty("最大连续缺失时长,单位分钟") - private Integer maxContinuousMissingMinutes; + @ApiModelProperty("数据完整性文本") + private String dataIntegrityText; } diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java index c8904ec..571e979 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java @@ -46,8 +46,8 @@ public class SteadyChecksquareTaskVO implements Serializable { @ApiModelProperty("异常检测项数量") private Integer abnormalItemCount; - @ApiModelProperty("最大缺失率") - private BigDecimal maxMissingRate; + @ApiModelProperty("最低数据完整性") + private BigDecimal minDataIntegrity; @ApiModelProperty("创建时间") private String createTime; diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java index f5a34d6..2e7ae00 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java @@ -8,6 +8,8 @@ import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO; +import java.util.List; + /** * 数据校验服务。 */ @@ -17,6 +19,8 @@ public interface SteadyChecksquareService { SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param); + boolean delete(List taskIds); + SteadyChecksquareQueryVO detail(String taskId); SteadyChecksquareItemDetailVO itemDetail(String itemId, String detailType, String statType); diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java index c62f42c..b694172 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java +++ b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java @@ -1,6 +1,7 @@ package com.njcn.gather.steady.checksquare.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.databind.ObjectMapper; import com.njcn.common.pojo.enums.response.CommonResponseEnum; @@ -127,6 +128,25 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return toCreateVO(task); } + @Override + public boolean delete(List taskIds) { + List ids = normalizeTextList(taskIds); + if (ids.isEmpty()) { + throw fail("数据校验任务 ID 不能为空"); + } + List tasks = taskService.lambdaQuery() + .in(SteadyChecksquareTaskPO::getId, ids) + .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED) + .list(); + if (tasks == null || tasks.size() != ids.size()) { + throw fail("数据校验任务不存在或已删除"); + } + if (transactionTemplate != null) { + return Boolean.TRUE.equals(transactionTemplate.execute(status -> deleteTasksAndItems(ids))); + } + return deleteTasksAndItems(ids); + } + @Override public SteadyChecksquareQueryVO detail(String taskId) { SteadyChecksquareTaskPO task = requireTask(taskId); @@ -198,6 +218,21 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return result; } + private boolean deleteTasksAndItems(List taskIds) { + LambdaUpdateWrapper taskWrapper = new LambdaUpdateWrapper() + .set(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_DELETED) + .in(SteadyChecksquareTaskPO::getId, taskIds) + .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED); + boolean taskResult = taskService.update(taskWrapper); + // 检测项同步置为删除态,避免已删任务下的 item-detail 被继续访问。 + LambdaUpdateWrapper itemWrapper = new LambdaUpdateWrapper() + .set(SteadyChecksquareItemPO::getState, SteadyChecksquareConst.STATE_DELETED) + .in(SteadyChecksquareItemPO::getTaskId, taskIds) + .eq(SteadyChecksquareItemPO::getState, SteadyChecksquareConst.STATE_ENABLED); + itemService.update(itemWrapper); + return taskResult; + } + private SteadyChecksquareQueryVO calculate(SteadyChecksquareQueryParam param) { validateParam(param); String lineId = trimToNull(param.getLineId()); @@ -256,7 +291,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { task.setTaskStatus(SteadyChecksquareConst.TASK_STATUS_SUCCESS); task.setItemCount(result.getItems().size()); task.setAbnormalItemCount(countAbnormalItems(result.getItems())); - task.setMaxMissingRate(maxMissingRate(result.getItems())); + task.setMinDataIntegrity(minDataIntegrity(result.getItems())); task.setResultMessage("数据校验完成"); task.setState(SteadyChecksquareConst.STATE_ENABLED); task.setCreateTime(now); @@ -298,9 +333,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { po.setExpectedPointCount(nullToZero(item.getExpectedPointCount())); po.setActualPointCount(nullToZero(item.getActualPointCount())); po.setMissingPointCount(nullToZero(item.getMissingPointCount())); - po.setMissingRate(item.getMissingRate() == null ? BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP) : item.getMissingRate()); - po.setMissingRateText(item.getMissingRateText()); - po.setMaxContinuousMissingMinutes(nullToZero(item.getMaxContinuousMissingMinutes())); + po.setDataIntegrity(item.getDataIntegrity() == null ? BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP) : item.getDataIntegrity()); + po.setDataIntegrityText(item.getDataIntegrityText()); po.setAbnormal(toFlag(item.getAbnormal())); po.setAbnormalPointCount(nullToZero(item.getAbnormalPointCount())); po.setHarmonicParityAbnormal(toFlag(item.getHarmonicParityAbnormal())); @@ -323,9 +357,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { po.setExpectedPointCount(nullToZero(summary.getExpectedPointCount())); po.setActualPointCount(nullToZero(summary.getActualPointCount())); po.setMissingPointCount(nullToZero(summary.getMissingPointCount())); - po.setMissingRate(summary.getMissingRate() == null ? BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP) : summary.getMissingRate()); - po.setMissingRateText(summary.getMissingRateText()); - po.setMaxContinuousMissingMinutes(nullToZero(summary.getMaxContinuousMissingMinutes())); + po.setDataIntegrity(summary.getDataIntegrity() == null ? BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP) : summary.getDataIntegrity()); + po.setDataIntegrityText(summary.getDataIntegrityText()); po.setCreateTime(now); result.add(po); } @@ -463,13 +496,12 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { result.setIndicatorName(indicator.getName()); result.setHarmonicOrder(null); result.setIntervalMinutes(intervalMinutes); - result.setHasData(anyHasData(orderItems)); result.setExpectedPointCount(averageInteger(orderItems, "expectedPointCount")); result.setActualPointCount(averageInteger(orderItems, "actualPointCount")); result.setMissingPointCount(averageInteger(orderItems, "missingPointCount")); - result.setMissingRate(averageRate(orderItems)); - result.setMissingRateText(formatRateText(result.getMissingRate())); - result.setMaxContinuousMissingMinutes(averageInteger(orderItems, "maxContinuousMissingMinutes")); + result.setDataIntegrity(averageDataIntegrity(orderItems)); + result.setHasData(hasDataByIntegrity(result.getDataIntegrity())); + result.setDataIntegrityText(formatRateText(result.getDataIntegrity())); result.setAbnormal(anyAbnormal(orderItems)); result.setAbnormalPointCount(averageAbnormalCount(orderItems, "abnormalPointCount", result.getAbnormal())); result.setHarmonicParityAbnormal(anyHarmonicParityAbnormal(orderItems)); @@ -501,13 +533,12 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO(); summary.setStatType(entry.getKey()); summary.setSupported(true); - summary.setHasData(anySummaryHasData(entry.getValue())); summary.setExpectedPointCount(averageSummaryInteger(entry.getValue(), "expectedPointCount")); summary.setActualPointCount(averageSummaryInteger(entry.getValue(), "actualPointCount")); summary.setMissingPointCount(averageSummaryInteger(entry.getValue(), "missingPointCount")); - summary.setMissingRate(averageSummaryRate(entry.getValue())); - summary.setMissingRateText(formatRateText(summary.getMissingRate())); - summary.setMaxContinuousMissingMinutes(averageSummaryInteger(entry.getValue(), "maxContinuousMissingMinutes")); + summary.setDataIntegrity(averageSummaryDataIntegrity(entry.getValue())); + summary.setHasData(hasDataByIntegrity(summary.getDataIntegrity())); + summary.setDataIntegrityText(formatRateText(summary.getDataIntegrity())); result.add(summary); } return result; @@ -549,8 +580,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { int totalExpected = 0; int totalActual = 0; - int maxContinuousMissingMinutes = 0; - boolean hasData = false; for (String statType : indicator.getSupportStats()) { Set actualSlots = queryMergedActualSlots(lineId, indicator, harmonicOrder, statType, startTime, endTime, intervalMinutes); Set effectiveActualSlots = retainExpectedSlots(slots, actualSlots); @@ -561,17 +590,14 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { item.getStatDetails().add(detail); totalExpected += summary.getExpectedPointCount(); totalActual += summary.getActualPointCount(); - maxContinuousMissingMinutes = Math.max(maxContinuousMissingMinutes, summary.getMaxContinuousMissingMinutes()); - hasData = hasData || Boolean.TRUE.equals(summary.getHasData()); } - item.setHasData(hasData); item.setExpectedPointCount(totalExpected); item.setActualPointCount(totalActual); item.setMissingPointCount(Math.max(0, totalExpected - totalActual)); - item.setMissingRate(calculateRate(item.getMissingPointCount(), totalExpected)); - item.setMissingRateText(formatRateText(item.getMissingRate())); - item.setMaxContinuousMissingMinutes(maxContinuousMissingMinutes); + item.setDataIntegrity(calculateDataIntegrity(totalActual, totalExpected)); + item.setHasData(hasDataByIntegrity(item.getDataIntegrity())); + item.setDataIntegrityText(formatRateText(item.getDataIntegrity())); fillValueOrderRuleResult(item, lineId, indicator, harmonicOrder, startTime, endTime, intervalMinutes); return item; } @@ -676,13 +702,12 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO(); summary.setStatType(statType); summary.setSupported(true); - summary.setHasData(actualCount > 0); summary.setExpectedPointCount(expectedCount); summary.setActualPointCount(actualCount); summary.setMissingPointCount(Math.max(0, expectedCount - actualCount)); - summary.setMissingRate(calculateRate(summary.getMissingPointCount(), expectedCount)); - summary.setMissingRateText(formatRateText(summary.getMissingRate())); - summary.setMaxContinuousMissingMinutes(calculator.maxContinuousMissingMinutes(segments)); + summary.setDataIntegrity(calculateDataIntegrity(actualCount, expectedCount)); + summary.setHasData(hasDataByIntegrity(summary.getDataIntegrity())); + summary.setDataIntegrityText(formatRateText(summary.getDataIntegrity())); return summary; } @@ -884,7 +909,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { vo.setTaskStatus(task.getTaskStatus()); vo.setItemCount(task.getItemCount()); vo.setAbnormalItemCount(task.getAbnormalItemCount()); - vo.setMaxMissingRate(task.getMaxMissingRate()); + vo.setMinDataIntegrity(task.getMinDataIntegrity()); vo.setCreateTime(formatTime(task.getCreateTime())); return vo; } @@ -915,9 +940,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { vo.setExpectedPointCount(item.getExpectedPointCount()); vo.setActualPointCount(item.getActualPointCount()); vo.setMissingPointCount(item.getMissingPointCount()); - vo.setMissingRate(item.getMissingRate()); - vo.setMissingRateText(item.getMissingRateText()); - vo.setMaxContinuousMissingMinutes(item.getMaxContinuousMissingMinutes()); + vo.setDataIntegrity(item.getDataIntegrity()); + vo.setDataIntegrityText(item.getDataIntegrityText()); vo.setAbnormal(toBoolean(item.getAbnormal())); vo.setAbnormalPointCount(item.getAbnormalPointCount()); vo.setHarmonicParityAbnormal(toBoolean(item.getHarmonicParityAbnormal())); @@ -933,39 +957,34 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { vo.setExpectedPointCount(summary.getExpectedPointCount()); vo.setActualPointCount(summary.getActualPointCount()); vo.setMissingPointCount(summary.getMissingPointCount()); - vo.setMissingRate(summary.getMissingRate()); - vo.setMissingRateText(summary.getMissingRateText()); - vo.setMaxContinuousMissingMinutes(summary.getMaxContinuousMissingMinutes()); + vo.setDataIntegrity(summary.getDataIntegrity()); + vo.setDataIntegrityText(summary.getDataIntegrityText()); return vo; } private int countAbnormalItems(List items) { int count = 0; for (SteadyChecksquareItemVO item : items) { - if (Boolean.TRUE.equals(item.getAbnormal()) || Boolean.TRUE.equals(item.getHarmonicParityAbnormal())) { + if (!Boolean.TRUE.equals(item.getHasData()) + || Boolean.TRUE.equals(item.getAbnormal()) + || Boolean.TRUE.equals(item.getHarmonicParityAbnormal())) { count++; } } return count; } - private BigDecimal maxMissingRate(List items) { - BigDecimal max = BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP); + private BigDecimal minDataIntegrity(List items) { + BigDecimal min = BigDecimal.ONE.setScale(6, RoundingMode.HALF_UP); + if (items == null || items.isEmpty()) { + return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP); + } for (SteadyChecksquareItemVO item : items) { - if (item.getMissingRate() != null && item.getMissingRate().compareTo(max) > 0) { - max = item.getMissingRate(); + if (item.getDataIntegrity() != null && item.getDataIntegrity().compareTo(min) < 0) { + min = item.getDataIntegrity(); } } - return max; - } - - private Boolean anyHasData(List items) { - for (SteadyChecksquareItemVO item : items) { - if (Boolean.TRUE.equals(item.getHasData())) { - return true; - } - } - return false; + return min; } private Boolean anyAbnormal(List items) { @@ -986,15 +1005,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return false; } - private Boolean anySummaryHasData(List summaries) { - for (SteadyChecksquareStatSummaryVO summary : summaries) { - if (Boolean.TRUE.equals(summary.getHasData())) { - return true; - } - } - return false; - } - private Integer averageInteger(List items, String fieldName) { if (items == null || items.isEmpty()) { return 0; @@ -1025,24 +1035,24 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return new BigDecimal(total).divide(new BigDecimal(summaries.size()), 0, RoundingMode.HALF_UP).intValue(); } - private BigDecimal averageRate(List items) { + private BigDecimal averageDataIntegrity(List items) { if (items == null || items.isEmpty()) { return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP); } BigDecimal total = BigDecimal.ZERO; for (SteadyChecksquareItemVO item : items) { - total = total.add(item.getMissingRate() == null ? BigDecimal.ZERO : item.getMissingRate()); + total = total.add(item.getDataIntegrity() == null ? BigDecimal.ZERO : item.getDataIntegrity()); } return total.divide(new BigDecimal(items.size()), 6, RoundingMode.HALF_UP); } - private BigDecimal averageSummaryRate(List summaries) { + private BigDecimal averageSummaryDataIntegrity(List summaries) { if (summaries == null || summaries.isEmpty()) { return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP); } BigDecimal total = BigDecimal.ZERO; for (SteadyChecksquareStatSummaryVO summary : summaries) { - total = total.add(summary.getMissingRate() == null ? BigDecimal.ZERO : summary.getMissingRate()); + total = total.add(summary.getDataIntegrity() == null ? BigDecimal.ZERO : summary.getDataIntegrity()); } return total.divide(new BigDecimal(summaries.size()), 6, RoundingMode.HALF_UP); } @@ -1057,9 +1067,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { if ("missingPointCount".equals(fieldName)) { return nullToZero(item.getMissingPointCount()); } - if ("maxContinuousMissingMinutes".equals(fieldName)) { - return nullToZero(item.getMaxContinuousMissingMinutes()); - } if ("abnormalPointCount".equals(fieldName)) { return nullToZero(item.getAbnormalPointCount()); } @@ -1079,9 +1086,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { if ("missingPointCount".equals(fieldName)) { return nullToZero(summary.getMissingPointCount()); } - if ("maxContinuousMissingMinutes".equals(fieldName)) { - return nullToZero(summary.getMaxContinuousMissingMinutes()); - } return 0; } @@ -1089,11 +1093,15 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return value == null ? 0 : value; } - private BigDecimal calculateRate(int missingCount, int expectedCount) { + private BigDecimal calculateDataIntegrity(int actualCount, int expectedCount) { if (expectedCount <= 0) { return BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP); } - return new BigDecimal(missingCount).divide(new BigDecimal(expectedCount), 6, RoundingMode.HALF_UP); + return new BigDecimal(actualCount).divide(new BigDecimal(expectedCount), 6, RoundingMode.HALF_UP); + } + + private boolean hasDataByIntegrity(BigDecimal dataIntegrity) { + return dataIntegrity != null && dataIntegrity.compareTo(BigDecimal.ZERO) > 0; } private String formatRateText(BigDecimal rate) { diff --git a/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql b/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql new file mode 100644 index 0000000..0aa1a77 --- /dev/null +++ b/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql @@ -0,0 +1,102 @@ +CREATE TABLE IF NOT EXISTS `steady_checksquare_task` ( + `id` VARCHAR(64) NOT NULL COMMENT '主键', + `task_no` VARCHAR(64) NOT NULL COMMENT '检测任务编号', + `line_id` VARCHAR(64) NOT NULL COMMENT '监测点ID', + `line_name` VARCHAR(255) NULL COMMENT '监测点名称', + `time_start` DATETIME NOT NULL COMMENT '检测开始时间', + `time_end` DATETIME NOT NULL COMMENT '检测结束时间', + `interval_minutes` INT NULL COMMENT '默认统计间隔,单位分钟', + `indicator_codes_json` JSON NULL COMMENT '请求指标编码列表', + `indicator_codes_text` VARCHAR(2000) NULL COMMENT '请求指标编码检索文本,格式 |code1|code2|', + `task_status` VARCHAR(32) NOT NULL DEFAULT 'SUCCESS' COMMENT '任务状态:SUCCESS/FAIL', + `item_count` INT NOT NULL DEFAULT 0 COMMENT '检测项数量', + `abnormal_item_count` INT NOT NULL DEFAULT 0 COMMENT '异常检测项数量', + `min_data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '最低数据完整性', + `result_message` VARCHAR(2000) NULL COMMENT '执行结果说明', + `state` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-删除,1-正常', + `create_by` VARCHAR(64) NULL COMMENT '创建人', + `create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` VARCHAR(64) NULL COMMENT '更新人', + `update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_steady_checksquare_task_no` (`task_no`), + KEY `idx_steady_checksquare_task_line_time` (`line_id`, `time_start`, `time_end`), + KEY `idx_steady_checksquare_task_status` (`task_status`), + KEY `idx_steady_checksquare_task_indicator_text` (`indicator_codes_text`(255)), + KEY `idx_steady_checksquare_task_create_time` (`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验任务表'; + +CREATE TABLE IF NOT EXISTS `steady_checksquare_item` ( + `id` VARCHAR(64) NOT NULL COMMENT '主键', + `task_id` VARCHAR(64) NOT NULL COMMENT '检测任务ID', + `item_key` VARCHAR(255) NOT NULL COMMENT '检测项唯一键', + `indicator_code` VARCHAR(64) NOT NULL COMMENT '指标编码', + `indicator_name` VARCHAR(255) NULL COMMENT '指标名称', + `harmonic_order` INT NULL COMMENT '谐波次数;聚合项为空', + `interval_minutes` INT NULL COMMENT '当前检测项统计间隔,单位分钟', + `has_data` TINYINT NOT NULL DEFAULT 0 COMMENT '是否存在任意数据:0-否,1-是', + `expected_point_count` INT NOT NULL DEFAULT 0 COMMENT '期望点数', + `actual_point_count` INT NOT NULL DEFAULT 0 COMMENT '实际点数', + `missing_point_count` INT NOT NULL DEFAULT 0 COMMENT '缺失点数', + `data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '数据完整性', + `data_integrity_text` VARCHAR(32) NULL COMMENT '数据完整性文本', + `abnormal` TINYINT NOT NULL DEFAULT 0 COMMENT '指标值大小关系是否异常', + `abnormal_point_count` INT NOT NULL DEFAULT 0 COMMENT '大小关系异常点数', + `harmonic_parity_abnormal` TINYINT NOT NULL DEFAULT 0 COMMENT '谐波奇偶关系是否异常', + `harmonic_parity_abnormal_point_count` INT NOT NULL DEFAULT 0 COMMENT '谐波奇偶关系异常点数', + `state` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-删除,1-正常', + `create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_steady_checksquare_item` (`task_id`, `item_key`), + KEY `idx_steady_checksquare_item_indicator` (`indicator_code`), + KEY `idx_steady_checksquare_item_abnormal` (`abnormal`, `harmonic_parity_abnormal`), + KEY `idx_steady_checksquare_item_data_integrity` (`data_integrity`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验检测项表'; + +CREATE TABLE IF NOT EXISTS `steady_checksquare_stat_summary` ( + `id` VARCHAR(64) NOT NULL COMMENT '主键', + `item_id` VARCHAR(64) NOT NULL COMMENT '检测项ID', + `stat_type` VARCHAR(16) NOT NULL COMMENT '统计类型:AVG/MAX/MIN/CP95', + `supported` TINYINT NOT NULL DEFAULT 1 COMMENT '是否支持', + `has_data` TINYINT NOT NULL DEFAULT 0 COMMENT '是否存在数据', + `expected_point_count` INT NOT NULL DEFAULT 0 COMMENT '期望点数', + `actual_point_count` INT NOT NULL DEFAULT 0 COMMENT '实际点数', + `missing_point_count` INT NOT NULL DEFAULT 0 COMMENT '缺失点数', + `data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '数据完整性', + `data_integrity_text` VARCHAR(32) NULL COMMENT '数据完整性文本', + `create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_steady_checksquare_stat` (`item_id`, `stat_type`), + KEY `idx_steady_checksquare_stat_type` (`stat_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验统计摘要表'; + +CREATE TABLE IF NOT EXISTS `steady_checksquare_detail` ( + `id` VARCHAR(64) NOT NULL COMMENT '主键', + `item_id` VARCHAR(64) NOT NULL COMMENT '检测项ID', + `detail_type` VARCHAR(32) NOT NULL COMMENT '明细类型:SEGMENT/VALUE_ORDER/HARMONIC_PARITY', + `stat_type` VARCHAR(16) NULL COMMENT '统计类型', + `start_time` DATETIME NULL COMMENT '区间开始时间', + `end_time` DATETIME NULL COMMENT '区间结束时间', + `point_time` DATETIME NULL COMMENT '异常点时间', + `segment_status` VARCHAR(16) NULL COMMENT '区间状态:NORMAL/MISSING', + `missing_point_count` INT NULL COMMENT '缺失点数', + `duration_minutes` INT NULL COMMENT '持续时长,单位分钟', + `phase` VARCHAR(16) NULL COMMENT '相别', + `harmonic_order` INT NULL COMMENT '谐波次数', + `max_value` DECIMAL(24,8) NULL COMMENT '最大值', + `min_value` DECIMAL(24,8) NULL COMMENT '最小值', + `avg_value` DECIMAL(24,8) NULL COMMENT '平均值', + `cp95_value` DECIMAL(24,8) NULL COMMENT 'CP95值', + `even_harmonic_order` INT NULL COMMENT '偶次谐波次数', + `even_value` DECIMAL(24,8) NULL COMMENT '偶次谐波值', + `odd_harmonic_orders_json` JSON NULL COMMENT '参与比较的奇次谐波次数', + `odd_values_json` JSON NULL COMMENT '参与比较的奇次谐波值', + `odd_median_value` DECIMAL(24,8) NULL COMMENT '奇次谐波中位数', + `threshold_multiplier` DECIMAL(12,6) NULL COMMENT '异常阈值倍数', + `create_time` DATETIME NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + PRIMARY KEY (`id`), + KEY `idx_steady_checksquare_detail_item_type` (`item_id`, `detail_type`), + KEY `idx_steady_checksquare_detail_point_time` (`point_time`), + KEY `idx_steady_checksquare_detail_segment` (`start_time`, `end_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验明细表'; diff --git a/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql b/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql new file mode 100644 index 0000000..46f5604 --- /dev/null +++ b/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql @@ -0,0 +1,14 @@ +ALTER TABLE `steady_checksquare_task` + CHANGE COLUMN `max_missing_rate` `min_data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '最低数据完整性'; + +ALTER TABLE `steady_checksquare_item` + DROP INDEX `idx_steady_checksquare_item_missing_rate`, + CHANGE COLUMN `missing_rate` `data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '数据完整性', + CHANGE COLUMN `missing_rate_text` `data_integrity_text` VARCHAR(32) NULL COMMENT '数据完整性文本', + DROP COLUMN `max_continuous_missing_minutes`, + ADD KEY `idx_steady_checksquare_item_data_integrity` (`data_integrity`); + +ALTER TABLE `steady_checksquare_stat_summary` + CHANGE COLUMN `missing_rate` `data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '数据完整性', + CHANGE COLUMN `missing_rate_text` `data_integrity_text` VARCHAR(32) NULL COMMENT '数据完整性文本', + DROP COLUMN `max_continuous_missing_minutes`; diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java index d68542f..37eda9d 100644 --- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java +++ b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java @@ -6,6 +6,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import java.util.List; import java.lang.reflect.Method; /** @@ -34,5 +35,9 @@ class SteadyChecksquareControllerTest { String.class, String.class, String.class, Integer.class, Integer.class); GetMapping itemDetailMapping = itemDetailMethod.getAnnotation(GetMapping.class); Assertions.assertArrayEquals(new String[]{"/item-detail"}, itemDetailMapping.value()); + + Method deleteMethod = SteadyChecksquareController.class.getDeclaredMethod("delete", List.class); + PostMapping deleteMapping = deleteMethod.getAnnotation(PostMapping.class); + Assertions.assertArrayEquals(new String[]{"/delete"}, deleteMapping.value()); } } diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java index 421a010..36a6a72 100644 --- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java +++ b/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java @@ -25,6 +25,7 @@ import com.njcn.gather.steady.checksquare.service.SteadyChecksquareItemService; import com.njcn.gather.steady.checksquare.service.SteadyChecksquareStatSummaryService; import com.njcn.gather.steady.checksquare.service.SteadyChecksquareTaskService; import com.njcn.gather.steady.datavie.component.SteadyTrendIndicatorCatalog; +import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendIndicatorDefinitionBO; import com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO; import com.njcn.gather.tool.adddata.component.AddDataTimeSlotCalculator; import com.njcn.gather.tool.addledger.pojo.vo.AddLedgerLinePathVO; @@ -437,6 +438,34 @@ class SteadyChecksquareServiceImplTest { verify(detailService).page(any(Page.class), any()); } + @Test + void shouldDeleteTasksAndItemsLogically() { + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class); + LambdaQueryChainWrapper taskQuery = mock(LambdaQueryChainWrapper.class); + SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO(); + task.setId("task-001"); + task.setState(1); + when(taskService.lambdaQuery()).thenReturn(taskQuery); + when(taskQuery.in(any(), any(List.class))).thenReturn(taskQuery); + when(taskQuery.eq(any(), any())).thenReturn(taskQuery); + when(taskQuery.list()).thenReturn(Collections.singletonList(task)); + when(taskService.update(any())).thenReturn(true); + when(itemService.update(any())).thenReturn(true); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService, + itemService, mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); + + boolean result = service.delete(Collections.singletonList("task-001")); + + Assertions.assertTrue(result); + verify(taskService).update(any()); + verify(itemService).update(any()); + } + @Test void shouldSaveChecksquareResultsInBatch() { SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); @@ -465,9 +494,8 @@ class SteadyChecksquareServiceImplTest { item.setExpectedPointCount(2); item.setActualPointCount(2); item.setMissingPointCount(0); - item.setMissingRate(BigDecimal.ZERO.setScale(6)); - item.setMissingRateText("0.00%"); - item.setMaxContinuousMissingMinutes(0); + item.setDataIntegrity(BigDecimal.ONE.setScale(6)); + item.setDataIntegrityText("100.00%"); item.setAbnormal(false); item.setAbnormalPointCount(0); item.setHarmonicParityAbnormal(false); @@ -479,9 +507,8 @@ class SteadyChecksquareServiceImplTest { summary.setExpectedPointCount(2); summary.setActualPointCount(2); summary.setMissingPointCount(0); - summary.setMissingRate(BigDecimal.ZERO.setScale(6)); - summary.setMissingRateText("0.00%"); - summary.setMaxContinuousMissingMinutes(0); + summary.setDataIntegrity(BigDecimal.ONE.setScale(6)); + summary.setDataIntegrityText("100.00%"); item.getStatSummaries().add(summary); result.getItems().add(item); @@ -492,12 +519,87 @@ class SteadyChecksquareServiceImplTest { verify(statSummaryService).saveBatch(any()); } + @Test + void shouldCountNoDataItemAsAbnormalWhenSavingTask() { + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService, + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); + SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); + param.setIndicatorCodes(Collections.singletonList("V_RMS")); + SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO(); + result.setLineId("line-001"); + result.setLineName("进线一"); + result.setTimeStart("2026-05-01 00:00:00"); + result.setTimeEnd("2026-05-01 00:01:00"); + result.setIntervalMinutes(1); + SteadyChecksquareItemVO item = new SteadyChecksquareItemVO(); + item.setItemKey("line-001|V_RMS"); + item.setIndicatorCode("V_RMS"); + item.setHasData(false); + item.setExpectedPointCount(2); + item.setActualPointCount(0); + item.setMissingPointCount(2); + item.setDataIntegrity(BigDecimal.ZERO.setScale(6)); + item.setDataIntegrityText("0.00%"); + item.setAbnormal(false); + item.setHarmonicParityAbnormal(false); + result.getItems().add(item); + + saveResult(service, param, result); + + ArgumentCaptor captor = ArgumentCaptor.forClass(SteadyChecksquareTaskPO.class); + verify(taskService).save(captor.capture()); + Assertions.assertEquals(Integer.valueOf(1), captor.getValue().getAbnormalItemCount()); + Assertions.assertEquals(BigDecimal.ZERO.setScale(6), captor.getValue().getMinDataIntegrity()); + } + + @Test + void shouldMarkAggregateHarmonicItemNoDataWhenDataIntegrityIsZero() { + SteadyChecksquareServiceImpl service = newService(); + SteadyTrendIndicatorDefinitionBO indicator = buildHarmonicIndicator(); + SteadyChecksquareItemVO orderItem = buildOrderItem(true, BigDecimal.ZERO.setScale(6)); + orderItem.getStatSummaries().add(buildSummaryVO(true, BigDecimal.ZERO.setScale(6))); + + SteadyChecksquareItemVO result = aggregateHarmonicItems(service, indicator, Collections.singletonList(orderItem)); + + Assertions.assertEquals(BigDecimal.ZERO.setScale(6), result.getDataIntegrity()); + Assertions.assertEquals(Boolean.FALSE, result.getHasData()); + Assertions.assertEquals(Boolean.FALSE, result.getStatSummaries().get(0).getHasData()); + } + + @Test + void shouldMarkAggregateHarmonicItemHasDataWhenDataIntegrityIsGreaterThanZero() { + SteadyChecksquareServiceImpl service = newService(); + SteadyTrendIndicatorDefinitionBO indicator = buildHarmonicIndicator(); + SteadyChecksquareItemVO orderItem = buildOrderItem(false, new BigDecimal("0.500000")); + orderItem.getStatSummaries().add(buildSummaryVO(false, new BigDecimal("0.500000"))); + + SteadyChecksquareItemVO result = aggregateHarmonicItems(service, indicator, Collections.singletonList(orderItem)); + + Assertions.assertEquals(new BigDecimal("0.500000"), result.getDataIntegrity()); + Assertions.assertEquals(Boolean.TRUE, result.getHasData()); + Assertions.assertEquals(Boolean.TRUE, result.getStatSummaries().get(0).getHasData()); + } + private void assertItemInterval(SteadyChecksquareItemVO item, String indicatorCode, int intervalMinutes, int expectedPointCount) { Assertions.assertEquals(indicatorCode, item.getIndicatorCode()); Assertions.assertEquals(Integer.valueOf(intervalMinutes), item.getIntervalMinutes()); Assertions.assertEquals(Integer.valueOf(expectedPointCount), item.getExpectedPointCount()); } + private SteadyChecksquareServiceImpl newService() { + return new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), mock(SteadyChecksquareTaskService.class), + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); + } + private SteadyChecksquareQueryVO calculate(SteadyChecksquareServiceImpl service, SteadyChecksquareQueryParam param) { try { Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("calculate", SteadyChecksquareQueryParam.class); @@ -519,6 +621,58 @@ class SteadyChecksquareServiceImplTest { } } + private SteadyChecksquareItemVO aggregateHarmonicItems(SteadyChecksquareServiceImpl service, + SteadyTrendIndicatorDefinitionBO indicator, + List orderItems) { + try { + Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("aggregateHarmonicItems", + String.class, SteadyTrendIndicatorDefinitionBO.class, List.class, int.class); + method.setAccessible(true); + return (SteadyChecksquareItemVO) method.invoke(service, "line-001", indicator, orderItems, 1); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + private SteadyTrendIndicatorDefinitionBO buildHarmonicIndicator() { + SteadyTrendIndicatorDefinitionBO indicator = new SteadyTrendIndicatorDefinitionBO(); + indicator.setIndicatorCode("V_HARMONIC"); + indicator.setName("V_HARMONIC"); + indicator.setHarmonic(true); + return indicator; + } + + private SteadyChecksquareItemVO buildOrderItem(boolean hasData, BigDecimal dataIntegrity) { + SteadyChecksquareItemVO item = new SteadyChecksquareItemVO(); + item.setItemKey("line-001|V_HARMONIC|2"); + item.setIndicatorCode("V_HARMONIC"); + item.setIndicatorName("V_HARMONIC"); + item.setHarmonicOrder(2); + item.setIntervalMinutes(1); + item.setHasData(hasData); + item.setExpectedPointCount(2); + item.setActualPointCount(dataIntegrity.compareTo(BigDecimal.ZERO) > 0 ? 1 : 0); + item.setMissingPointCount(dataIntegrity.compareTo(BigDecimal.ZERO) > 0 ? 1 : 2); + item.setDataIntegrity(dataIntegrity); + item.setAbnormal(false); + item.setAbnormalPointCount(0); + item.setHarmonicParityAbnormal(false); + item.setHarmonicParityAbnormalPointCount(0); + return item; + } + + private SteadyChecksquareStatSummaryVO buildSummaryVO(boolean hasData, BigDecimal dataIntegrity) { + SteadyChecksquareStatSummaryVO summary = new SteadyChecksquareStatSummaryVO(); + summary.setStatType("AVG"); + summary.setSupported(true); + summary.setHasData(hasData); + summary.setExpectedPointCount(2); + summary.setActualPointCount(dataIntegrity.compareTo(BigDecimal.ZERO) > 0 ? 1 : 0); + summary.setMissingPointCount(dataIntegrity.compareTo(BigDecimal.ZERO) > 0 ? 1 : 2); + summary.setDataIntegrity(dataIntegrity); + return summary; + } + private SteadyChecksquareItemPO buildItemPO(String itemId, String indicatorCode) { SteadyChecksquareItemPO item = new SteadyChecksquareItemPO(); item.setId(itemId); @@ -529,8 +683,7 @@ class SteadyChecksquareServiceImplTest { item.setExpectedPointCount(1); item.setActualPointCount(1); item.setMissingPointCount(0); - item.setMissingRate(BigDecimal.ZERO.setScale(6)); - item.setMaxContinuousMissingMinutes(0); + item.setDataIntegrity(BigDecimal.ONE.setScale(6)); item.setAbnormal(0); item.setAbnormalPointCount(0); item.setHarmonicParityAbnormal(0); @@ -547,8 +700,7 @@ class SteadyChecksquareServiceImplTest { summary.setExpectedPointCount(1); summary.setActualPointCount(1); summary.setMissingPointCount(0); - summary.setMissingRate(BigDecimal.ZERO.setScale(6)); - summary.setMaxContinuousMissingMinutes(0); + summary.setDataIntegrity(BigDecimal.ONE.setScale(6)); return summary; } diff --git a/steady/steady-DataView/steady-checksquare-api-debug_20260610.md b/steady/steady-DataView/steady-checksquare-api-debug_20260610.md new file mode 100644 index 0000000..20a0a31 --- /dev/null +++ b/steady/steady-DataView/steady-checksquare-api-debug_20260610.md @@ -0,0 +1,522 @@ +# 数据校验 API 调试文档 + +## 1. 基础信息 + +- 模块:`steady/steady-DataView` +- 控制器:`com.njcn.gather.steady.checksquare.controller.SteadyChecksquareController` +- 接口前缀:`/steady/data-view/checksquare` +- 本地默认地址:`http://localhost:18192` +- Content-Type:`application/json` +- 认证:除登录和 Swagger 资源外,请求需要携带登录后的 `Authorization` 请求头。 +- 数据库结果表:`steady_checksquare_task`、`steady_checksquare_item`、`steady_checksquare_stat_summary`、`steady_checksquare_detail` + +通用请求头: + +```http +Authorization: Bearer +Content-Type: application/json +``` + +通用返回结构: + +```json +{ + "code": 200, + "message": "success", + "data": {} +} +``` + +> 实际 `code`、`message` 字段以项目公共 `HttpResult` 封装为准,下面示例重点展示 `data` 内容。 + +## 2. 查询数据校验历史记录 + +- 方法:`POST` +- 路径:`/steady/data-view/checksquare/query` +- 返回:`HttpResult>` +- 说明:分页查询已落库的数据校验任务,按创建时间倒序返回。 + +请求示例: + +```json +{ + "pageNum": 1, + "pageSize": 10, + "lineId": "line-001", + "indicatorCode": "V_RMS", + "timeStart": "2026-05-01 00:00:00", + "timeEnd": "2026-05-01 23:59:59", + "hasAbnormal": true +} +``` + +请求字段: + +| 字段 | 必填 | 说明 | +| --- | --- | --- | +| `pageNum` | 否 | 当前页码,继承自 `BaseParam` | +| `pageSize` | 否 | 每页数量,继承自 `BaseParam` | +| `lineId` | 否 | 监测点 ID,精确匹配 | +| `indicatorCode` | 否 | 指标编码,按任务指标集合匹配 | +| `timeStart` | 否 | 检测开始时间下限,格式 `yyyy-MM-dd HH:mm:ss` | +| `timeEnd` | 否 | 检测结束时间上限,格式 `yyyy-MM-dd HH:mm:ss` | +| `hasAbnormal` | 否 | 是否只查询存在异常项的任务;`true` 表示 `abnormalItemCount > 0` | + +返回示例: + +```json +{ + "records": [ + { + "taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001", + "taskNo": "CS202606101630001", + "lineId": "line-001", + "lineName": "进线一", + "timeStart": "2026-05-01 00:00:00", + "timeEnd": "2026-05-01 23:59:59", + "intervalMinutes": 1, + "taskStatus": "SUCCESS", + "itemCount": 2, + "abnormalItemCount": 1, + "minDataIntegrity": 0.999306, + "createTime": "2026-06-10 16:30:00" + } + ], + "total": 1, + "size": 10, + "current": 1, + "pages": 1 +} +``` + +cURL 示例: + +```bash +curl -X POST "http://localhost:18192/steady/data-view/checksquare/query" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"pageNum":1,"pageSize":10,"lineId":"line-001","indicatorCode":"V_RMS","hasAbnormal":true}' +``` + +## 3. 新增数据校验记录 + +- 方法:`POST` +- 路径:`/steady/data-view/checksquare/create` +- 返回:`HttpResult` +- 说明:按监测点、指标和时间范围实时查询 InfluxDB,执行缺数校验、指标值大小关系校验、谐波奇偶关系校验,并将任务、检测项、统计摘要和明细写入 MySQL。 + +请求示例: + +```json +{ + "lineId": "line-001", + "indicatorCodes": ["V_RMS", "FREQ", "V_HARMONIC"], + "timeStart": "2026-05-01 00:00:00", + "timeEnd": "2026-05-01 23:59:59" +} +``` + +请求字段: + +| 字段 | 必填 | 说明 | +| --- | --- | --- | +| `lineId` | 是 | 监测点 ID,需要能在台账中找到且处于可用状态 | +| `indicatorCodes` | 是 | 指标编码列表,来自 `/steady/data-view/indicator-tree` | +| `timeStart` | 是 | 检测开始时间,格式 `yyyy-MM-dd HH:mm:ss` | +| `timeEnd` | 是 | 检测结束时间,格式 `yyyy-MM-dd HH:mm:ss` | + +返回示例: + +```json +{ + "taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001", + "taskNo": "CS202606101630001", + "lineId": "line-001", + "lineName": "进线一", + "timeStart": "2026-05-01 00:00:00", + "timeEnd": "2026-05-01 23:59:59", + "intervalMinutes": 1, + "itemCount": 3, + "abnormalItemCount": 1 +} +``` + +cURL 示例: + +```bash +curl -X POST "http://localhost:18192/steady/data-view/checksquare/create" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"lineId":"line-001","indicatorCodes":["V_RMS","FREQ","V_HARMONIC"],"timeStart":"2026-05-01 00:00:00","timeEnd":"2026-05-01 23:59:59"}' +``` + +调试建议: + +- 新增接口会实际写入 MySQL,重复调用会生成新的任务记录。 +- 时间范围越大、指标越多,InfluxDB 查询和明细落库耗时越高。 +- 谐波类指标固定按 2-50 次聚合检测,不需要在请求体传 `harmonicOrders`。 + +## 4. 查询数据校验任务详情 + +- 方法:`GET` +- 路径:`/steady/data-view/checksquare/detail` +- 返回:`HttpResult` +- 说明:按任务 ID 查询任务基础信息、检测项列表和各检测项统计摘要。检测项的明细列表通过 `/item-detail` 按需查询。 + +请求参数: + +| 参数 | 必填 | 说明 | +| --- | --- | --- | +| `taskId` | 是 | 数据校验任务 ID,即 `/query` 或 `/create` 返回的 `taskId` | + +请求示例: + +```http +GET /steady/data-view/checksquare/detail?taskId=8f7a4d6d1f3145a88b6f9d7a8e6c1001 +``` + +返回示例: + +```json +{ + "taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001", + "taskNo": "CS202606101630001", + "lineId": "line-001", + "lineName": "进线一", + "timeStart": "2026-05-01 00:00:00", + "timeEnd": "2026-05-01 23:59:59", + "intervalMinutes": 1, + "items": [ + { + "itemId": "0f4b6d6c3e9d400a902a2df101d10001", + "itemKey": "line-001|V_RMS", + "indicatorCode": "V_RMS", + "indicatorName": "相电压有效值", + "harmonicOrder": null, + "intervalMinutes": 1, + "hasData": true, + "expectedPointCount": 5760, + "actualPointCount": 5756, + "missingPointCount": 4, + "dataIntegrity": 0.999306, + "dataIntegrityText": "99.93%", + "abnormal": true, + "abnormalPointCount": 2, + "harmonicParityAbnormal": false, + "harmonicParityAbnormalPointCount": 0, + "statSummaries": [ + { + "statType": "AVG", + "supported": true, + "hasData": true, + "expectedPointCount": 1440, + "actualPointCount": 1439, + "missingPointCount": 1, + "dataIntegrity": 0.999306, + "dataIntegrityText": "99.93%", + } + ], + "statDetails": [] + } + ] +} +``` + +cURL 示例: + +```bash +curl -X GET "http://localhost:18192/steady/data-view/checksquare/detail?taskId=8f7a4d6d1f3145a88b6f9d7a8e6c1001" \ + -H "Authorization: Bearer " +``` + +## 5. 查询检测项明细 + +- 方法:`GET` +- 路径:`/steady/data-view/checksquare/item-detail` +- 返回:`HttpResult` +- 说明:按检测项 ID、明细类型查询缺数连续区间、指标值大小关系异常点或谐波奇偶关系异常点。`pageNum` 和 `pageSize` 未同时传入时保持全量返回;两者同时传入且均大于 0 时返回当前页明细,并在结果中带回分页元数据。 + +请求参数: + +| 参数 | 必填 | 说明 | +| --- | --- | --- | +| `itemId` | 是 | 检测项 ID,即任务详情中每个 `items[].itemId` | +| `detailType` | 是 | 明细类型:`SEGMENT`、`VALUE_ORDER`、`HARMONIC_PARITY` | +| `statType` | 否 | 统计类型:`AVG`、`MAX`、`MIN`、`CP95`;查询缺数区间时建议传入 | +| `pageNum` | 否 | 明细分页页码;与 `pageSize` 同时传入且大于 0 时启用分页 | +| `pageSize` | 否 | 明细分页条数;与 `pageNum` 同时传入且大于 0 时启用分页 | + +### 5.1 查询缺数连续区间 + +请求示例: + +```http +GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=SEGMENT&statType=AVG +``` + +返回示例: + +```json +{ + "itemId": "0f4b6d6c3e9d400a902a2df101d10001", + "detailType": "SEGMENT", + "statType": "AVG", + "pageNum": null, + "pageSize": null, + "total": null, + "segments": [ + { + "startTime": "2026-05-01 00:00:00", + "endTime": "2026-05-01 00:09:00", + "status": "NORMAL", + "harmonicOrder": null, + "missingPointCount": 0, + "durationMinutes": 10 + }, + { + "startTime": "2026-05-01 00:10:00", + "endTime": "2026-05-01 00:10:00", + "status": "MISSING", + "harmonicOrder": null, + "missingPointCount": 1, + "durationMinutes": 1 + } + ], + "valueOrderDetails": [], + "harmonicParityDetails": [] +} +``` + +### 5.2 查询指标值大小关系异常明细 + +请求示例: + +```http +GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER&pageNum=1&pageSize=20 +``` + +返回示例: + +```json +{ + "itemId": "0f4b6d6c3e9d400a902a2df101d10001", + "detailType": "VALUE_ORDER", + "statType": null, + "pageNum": 1, + "pageSize": 20, + "total": 1, + "segments": [], + "valueOrderDetails": [ + { + "time": "2026-05-01 00:10:00", + "phase": "A", + "harmonicOrder": null, + "maxValue": 219.8, + "minValue": 218.1, + "avgValue": 219.0, + "cp95Value": 219.8 + } + ], + "harmonicParityDetails": [] +} +``` + +### 5.3 查询谐波奇偶关系异常明细 + +请求示例: + +```http +GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10002&detailType=HARMONIC_PARITY&pageNum=1&pageSize=20 +``` + +返回示例: + +```json +{ + "itemId": "0f4b6d6c3e9d400a902a2df101d10002", + "detailType": "HARMONIC_PARITY", + "statType": null, + "pageNum": 1, + "pageSize": 20, + "total": 1, + "segments": [], + "valueOrderDetails": [], + "harmonicParityDetails": [ + { + "time": "2026-05-01 00:10:00", + "phase": "A", + "statType": "AVG", + "evenHarmonicOrder": 4, + "evenValue": 0.32, + "oddHarmonicOrders": [3, 5], + "oddValues": [0.08, 0.09], + "oddMedianValue": 0.085, + "thresholdMultiplier": 3.0 + } + ] +} +``` + +cURL 示例: + +```bash +curl -X GET "http://localhost:18192/steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER" \ + -H "Authorization: Bearer " +``` + +分页 cURL 示例: + +```bash +curl -X GET "http://localhost:18192/steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER&pageNum=1&pageSize=20" \ + -H "Authorization: Bearer " +``` + +## 6. 删除数据校验任务 + +- 方法:`POST` +- 路径:`/steady/data-view/checksquare/delete` +- 返回:`HttpResult` +- 说明:按任务 ID 批量逻辑删除数据校验任务,并同步将任务下检测项置为删除态。删除后历史查询不再返回该任务,详情和检测项明细会按不存在处理。 + +请求示例: + +```json +[ + "8f7a4d6d1f3145a88b6f9d7a8e6c1001" +] +``` + +请求字段: + +| 字段 | 必填 | 说明 | +| --- | --- | --- | +| 请求体 | 是 | 数据校验任务 ID 数组 | + +返回示例: + +```json +true +``` + +cURL 示例: + +```bash +curl -X POST "http://localhost:18192/steady/data-view/checksquare/delete" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '["8f7a4d6d1f3145a88b6f9d7a8e6c1001"]' +``` + +## 7. 校验规则说明 + +### 7.1 缺数校验 + +- 按检测项支持的统计类型分别查询实际存在的时间点。 +- 返回期望点数、实际点数、缺失点数、数据完整性和最大连续缺失时长。 +- `SEGMENT` 明细按连续区间返回,`status` 可取 `NORMAL`、`MISSING`。 +- `FLUC`、`PST` 固定按 10 分钟间隔校验,`PLT` 固定按 120 分钟间隔校验,其余指标按监测点统计间隔校验。 + +### 7.2 指标值大小关系校验 + +- 同一时间点、同一指标、同一相别需要满足 `MAX >= CP95 >= AVG >= MIN`。 +- 缺少 `MAX`、`CP95`、`AVG`、`MIN` 任一值的时间点不计入大小关系异常,由缺数校验体现。 +- 异常点写入 `VALUE_ORDER` 明细,前端可通过 `item-detail` 拉取后弹窗展示。 + +### 7.3 谐波奇偶关系校验 + +- 谐波指标固定聚合 2-50 次检测。 +- 偶次谐波值需要大于 `0.1` 才继续参与奇偶关系检测。 +- 异常点写入 `HARMONIC_PARITY` 明细,包含偶次谐波值、参与比较的奇次谐波值、中位数和阈值倍数。 + +## 8. 字段说明 + +### 8.1 任务字段 + +| 字段 | 说明 | +| --- | --- | +| `taskId` | 数据校验任务 ID | +| `taskNo` | 数据校验任务编号 | +| `lineId` | 监测点 ID | +| `lineName` | 监测点名称 | +| `timeStart` | 检测开始时间 | +| `timeEnd` | 检测结束时间 | +| `intervalMinutes` | 监测点统计间隔,单位分钟 | +| `taskStatus` | 任务状态,当前成功任务为 `SUCCESS` | +| `itemCount` | 检测项数量 | +| `abnormalItemCount` | 存在异常的检测项数量 | +| `minDataIntegrity` | 检测项中的最低数据完整性 | +| `createTime` | 任务创建时间 | + +### 8.2 检测项字段 + +| 字段 | 说明 | +| --- | --- | +| `itemId` | 检测项 ID | +| `itemKey` | 检测项唯一键 | +| `indicatorCode` | 指标编码 | +| `indicatorName` | 指标名称 | +| `harmonicOrder` | 谐波次数;聚合检测项为空 | +| `hasData` | 当前检测项是否存在任意数据 | +| `expectedPointCount` | 期望点数 | +| `actualPointCount` | 实际点数 | +| `missingPointCount` | 缺失点数 | +| `dataIntegrity` | 数据完整性数值 | +| `dataIntegrityText` | 数据完整性文本 | +| `abnormal` | 指标值大小关系是否异常 | +| `abnormalPointCount` | 指标值大小关系异常点数 | +| `harmonicParityAbnormal` | 谐波奇偶关系是否异常 | +| `harmonicParityAbnormalPointCount` | 谐波奇偶关系异常点数 | +| `statSummaries` | 各统计类型缺数摘要 | +| `statDetails` | 兼容字段;明细建议通过 `/item-detail` 按需查询 | + +### 8.3 检测项明细字段 + +| 字段 | 说明 | +| --- | --- | +| `itemId` | 检测项 ID | +| `detailType` | 明细类型:`SEGMENT`、`VALUE_ORDER`、`HARMONIC_PARITY` | +| `statType` | 统计类型;未传入或当前明细类型不按统计类型过滤时为空 | +| `pageNum` | 当前页码;未启用分页时为空 | +| `pageSize` | 每页条数;未启用分页时为空 | +| `total` | 当前查询条件下的总明细数;未启用分页时为空 | +| `segments` | 缺数连续区间,仅 `SEGMENT` 查询返回数据 | +| `valueOrderDetails` | 指标值大小关系异常点,仅 `VALUE_ORDER` 查询返回数据 | +| `harmonicParityDetails` | 谐波奇偶关系异常点,仅 `HARMONIC_PARITY` 查询返回数据 | + +## 9. 常见错误场景 + +| 场景 | 后端提示 | +| --- | --- | +| 新增时 `lineId` 为空 | `监测点 ID 不能为空` | +| 新增时 `indicatorCodes` 为空 | `指标不能为空` | +| 新增时开始时间为空 | `开始时间不能为空` | +| 新增时结束时间为空 | `结束时间不能为空` | +| 开始时间大于结束时间 | `开始时间不能大于结束时间` | +| 时间格式不正确 | `时间格式不正确,仅支持 yyyy-MM-dd HH:mm:ss` | +| 监测点不存在或不可用 | `监测点不存在或不可用` | +| 指标编码不支持 | `稳态指标不支持:xxx` | +| 任务不存在或已删除 | `数据校验任务不存在` | +| 检测项不存在或已删除 | `数据校验检测项不存在` | +| 删除时任务 ID 为空 | `数据校验任务 ID 不能为空` | +| 删除时任务不存在或已删除 | `数据校验任务不存在或已删除` | +| 明细类型为空 | `明细类型不能为空` | +| 明细类型不支持 | `明细类型不支持:xxx` | + +## 10. 调试流程建议 + +1. 调用 `/steady/data-view/ledger-tree` 获取可用监测点,取叶子节点 `lineId`。 +2. 调用 `/steady/data-view/indicator-tree` 获取可用指标编码。 +3. 调用 `/steady/data-view/checksquare/create` 新增校验任务。 +4. 使用返回的 `taskId` 调用 `/steady/data-view/checksquare/detail` 查看任务详情和检测项列表。 +5. 使用 `items[].itemId` 调用 `/steady/data-view/checksquare/item-detail` 查看缺数区间或异常点明细。 +6. 调用 `/steady/data-view/checksquare/query` 验证任务已落库,并按监测点、指标、时间和异常状态筛选历史记录。 +7. 调用 `/steady/data-view/checksquare/delete` 删除任务后,再调用 `/query`、`/detail` 验证任务已不可见。 + +## 11. 环境依赖 + +- MySQL:需要已初始化 4 张 `steady_checksquare_*` 结果表。 +- InfluxDB:新增校验任务时需要可访问 `steady.influxdb` 配置的时序库。 +- 台账:新增校验任务时需要 `lineId` 能在台账中解析到监测点路径和统计间隔。 +- 指标目录:`indicatorCodes` 必须来自后端趋势指标目录。 + + diff --git a/tools/README.md b/tools/README.md index f0ad21e..715aaeb 100644 --- a/tools/README.md +++ b/tools/README.md @@ -10,9 +10,10 @@ - `add-data` - `add-ledger` - `mms-mapping` +- `parse-pqdif` - `wave-tool` -因此,`tools` 现阶段仍然是聚合模块,但当前已实际承载激活工具、电能质量数据补录工具、数据台账工具空模块、ICD/MMS 映射工具和波形查看工具五个子模块。 +因此,`tools` 现阶段仍然是聚合模块,但当前已实际承载激活工具、电能质量数据补录工具、数据台账工具空模块、ICD/MMS 映射工具、PQDIF 解析预留工具和波形查看工具六个子模块。 ## 当前结构 @@ -22,6 +23,7 @@ tools/ ├── add-data/ ├── add-ledger/ ├── mms-mapping/ +├── parse-pqdif/ └── wave-tool/ ``` @@ -78,6 +80,16 @@ tools/ 从接口层看,当前主要围绕 `/api/mms-mapping` 路径提供能力。 +## parse-pqdif 的职责 + +`parse-pqdif` 当前仅作为 PQDIF 文件解析工具的预留骨架,参照 `mms-mapping` 的 Controller、Service、ServiceImpl 和 VO 分层组织。 + +- 预留 PQDIF 文件上传解析入口 +- 预留解析服务接口与实现 +- 当前不包含真实 PQDIF 解析、持久化或数据转换逻辑 + +从接口层看,当前预留路径为 `/api/parse-pqdif/parse`。 + ## mms-mapping 配置 `mms-mapping` 当前支持以下配置项: @@ -98,7 +110,7 @@ tools/ ## 依赖关系 -`tools/activate-tool`、`tools/add-data`、`tools/add-ledger`、`tools/mms-mapping` 与 `tools/wave-tool` 当前主要依赖: +`tools/activate-tool`、`tools/add-data`、`tools/add-ledger`、`tools/mms-mapping`、`tools/parse-pqdif` 与 `tools/wave-tool` 当前主要依赖: - `com.njcn:njcn-common` - `com.njcn:spingboot2.3.12` diff --git a/tools/mms-mapping/pom.xml b/tools/mms-mapping/pom.xml index f61b169..292a63d 100644 --- a/tools/mms-mapping/pom.xml +++ b/tools/mms-mapping/pom.xml @@ -20,6 +20,12 @@ 0.0.1 + + com.njcn + mybatis-plus + 0.0.1 + + com.njcn spingboot2.3.12 diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java new file mode 100644 index 0000000..b5f3fd9 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java @@ -0,0 +1,72 @@ +package com.njcn.gather.icd.mapping.controller; + +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.icd.mapping.pojo.param.IcdCheckResultSaveParam; +import com.njcn.gather.icd.mapping.pojo.vo.MmsDeviceTypeVO; +import com.njcn.gather.icd.mapping.pojo.vo.PqdifCheckPlaceholderVO; +import com.njcn.gather.icd.mapping.service.MmsDeviceTypeService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 设备类型校验入口。 + */ +@Slf4j +@Api(tags = "设备类型ICD校验") +@RestController +@RequestMapping("/api/mms-mapping/dev-types") +@RequiredArgsConstructor +public class MmsDeviceTypeController extends BaseController { + + private final MmsDeviceTypeService mmsDeviceTypeService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("查询设备类型校验列表") + @GetMapping + public HttpResult> listDeviceTypes() { + String methodDescribe = getMethodDescribe("listDeviceTypes"); + LogUtil.njcnDebug(log, "{},开始查询设备类型校验列表", methodDescribe); + List result = mmsDeviceTypeService.listDeviceTypes(); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("保存设备类型ICD唯一性校验结果") + @ApiImplicitParam(name = "id", value = "设备类型ID", required = true) + @PostMapping("/{id}/icd-check-result") + public HttpResult saveIcdCheckResult(@PathVariable("id") String id, + @RequestBody IcdCheckResultSaveParam param) { + String methodDescribe = getMethodDescribe("saveIcdCheckResult"); + LogUtil.njcnDebug(log, "{},开始保存设备类型ICD校验结果,devTypeId={}", methodDescribe, id); + boolean result = mmsDeviceTypeService.saveIcdCheckResult(id, param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("设备类型PQDIF校验") + @ApiImplicitParam(name = "id", value = "设备类型ID", required = true) + @PostMapping("/{id}/pqdif-check") + public HttpResult pqdifCheck(@PathVariable("id") String id) { + String methodDescribe = getMethodDescribe("pqdifCheck"); + LogUtil.njcnDebug(log, "{},设备类型PQDIF校验预留入口,devTypeId={}", methodDescribe, id); + PqdifCheckPlaceholderVO result = mmsDeviceTypeService.pqdifCheck(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java new file mode 100644 index 0000000..9102fa1 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java @@ -0,0 +1,15 @@ +package com.njcn.gather.icd.mapping.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.icd.mapping.pojo.po.CsDevTypePO; +import com.njcn.gather.icd.mapping.pojo.vo.MmsDeviceTypeVO; + +import java.util.List; + +/** + * 设备类型 Mapper。 + */ +public interface CsDevTypeMapper extends BaseMapper { + + List selectDeviceTypeCheckList(); +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsIcdPathMapper.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsIcdPathMapper.java new file mode 100644 index 0000000..501f058 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsIcdPathMapper.java @@ -0,0 +1,10 @@ +package com.njcn.gather.icd.mapping.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO; + +/** + * ICD 路径 Mapper。 + */ +public interface CsIcdPathMapper extends BaseMapper { +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml new file mode 100644 index 0000000..60d386a --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdCheckResultSaveParam.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdCheckResultSaveParam.java new file mode 100644 index 0000000..b2e3a9c --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdCheckResultSaveParam.java @@ -0,0 +1,25 @@ +package com.njcn.gather.icd.mapping.pojo.param; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * ICD 校验结果保存参数。 + */ +@Data +@ApiModel("ICD校验结果保存参数") +public class IcdCheckResultSaveParam { + + @ApiModelProperty("MMS映射JSON") + private String mappingJson; + + @ApiModelProperty("MMS映射XML") + private String xml; + + @ApiModelProperty("校验结论:0-否,1-是") + private Integer result; + + @ApiModelProperty("校验结论描述") + private String msg; +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java new file mode 100644 index 0000000..50be80f --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java @@ -0,0 +1,61 @@ +package com.njcn.gather.icd.mapping.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 设备类型表。 + */ +@Data +@TableName("cs_dev_type") +public class CsDevTypePO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId("id") + private String id; + + @TableField("name") + private String name; + + @TableField("icd") + private String icd; + + @TableField("power") + private String power; + + @TableField("Dev_Volt") + private Float devVolt; + + @TableField("Dev_Curr") + private Float devCurr; + + @TableField("Dev_Chns") + private Integer devChns; + + @TableField("Wave_Cmd") + private String waveCmd; + + @TableField("State") + private Integer state; + + @TableField("Create_By") + private String createBy; + + @TableField("Create_Time") + private LocalDateTime createTime; + + @TableField("Update_By") + private String updateBy; + + @TableField("Update_Time") + private LocalDateTime updateTime; + + @TableField("report_name") + private String reportName; +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsIcdPathPO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsIcdPathPO.java new file mode 100644 index 0000000..268a7e8 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsIcdPathPO.java @@ -0,0 +1,67 @@ +package com.njcn.gather.icd.mapping.pojo.po; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * ICD 存储记录。 + */ +@Data +@TableName("cs_icd_path") +public class CsIcdPathPO implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId("ID") + private String id; + + @TableField("Name") + private String name; + + @TableField("Path") + private String path; + + @TableField("Angle") + private Integer angle; + + @TableField("Use_Phase_Index") + private Integer usePhaseIndex; + + @TableField("State") + private Integer state; + + @TableField("Create_By") + private String createBy; + + @TableField("Create_Time") + private LocalDateTime createTime; + + @TableField("Update_By") + private String updateBy; + + @TableField("Update_Time") + private LocalDateTime updateTime; + + @TableField("Json_Str") + private String jsonStr; + + @TableField("Xml_Str") + private String xmlStr; + + @TableField("Result") + private Integer result; + + @TableField("Msg") + private String msg; + + @TableField("Type") + private Integer type; + + @TableField("Reference_Icd_Id") + private String referenceIcdId; +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java new file mode 100644 index 0000000..fe4ff16 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java @@ -0,0 +1,43 @@ +package com.njcn.gather.icd.mapping.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 设备类型校验列表项。 + */ +@Data +@ApiModel("设备类型校验列表项") +public class MmsDeviceTypeVO { + + @ApiModelProperty("设备类型ID") + private String id; + + @ApiModelProperty("设备类型名称") + private String name; + + @ApiModelProperty("关联ICD ID") + private String icdId; + + @ApiModelProperty("ICD名称") + private String icdName; + + @ApiModelProperty("ICD路径") + private String icdPath; + + @ApiModelProperty("ICD校验结论:0-否,1-是") + private Integer icdResult; + + @ApiModelProperty("ICD校验结论描述") + private String icdMsg; + + @ApiModelProperty("报告模板名称") + private String reportName; + + @ApiModelProperty("是否可执行ICD唯一性校验") + private Boolean canCheckIcd; + + @ApiModelProperty("是否可执行PQDIF校验") + private Boolean canCheckPqdif; +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java new file mode 100644 index 0000000..8f86b69 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java @@ -0,0 +1,19 @@ +package com.njcn.gather.icd.mapping.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * PQDIF 校验预留响应。 + */ +@Data +@ApiModel("PQDIF校验预留响应") +public class PqdifCheckPlaceholderVO { + + @ApiModelProperty("状态") + private String status; + + @ApiModelProperty("提示信息") + private String message; +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java new file mode 100644 index 0000000..67d9de4 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java @@ -0,0 +1,19 @@ +package com.njcn.gather.icd.mapping.service; + +import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam; +import com.njcn.gather.icd.mapping.pojo.vo.MmsDeviceTypeVO; +import com.njcn.gather.icd.mapping.pojo.vo.PqdifCheckPlaceholderVO; + +import java.util.List; + +/** + * 设备类型校验服务。 + */ +public interface MmsDeviceTypeService { + + List listDeviceTypes(); + + boolean saveIcdCheckResult(String devTypeId, IcdCheckResultSaveParam param); + + PqdifCheckPlaceholderVO pqdifCheck(String devTypeId); +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java new file mode 100644 index 0000000..94cc71b --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java @@ -0,0 +1,141 @@ +package com.njcn.gather.icd.mapping.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.njcn.gather.icd.mapping.mapper.CsDevTypeMapper; +import com.njcn.gather.icd.mapping.mapper.CsIcdPathMapper; +import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam; +import com.njcn.gather.icd.mapping.pojo.po.CsDevTypePO; +import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO; +import com.njcn.gather.icd.mapping.pojo.vo.MmsDeviceTypeVO; +import com.njcn.gather.icd.mapping.pojo.vo.PqdifCheckPlaceholderVO; +import com.njcn.gather.icd.mapping.service.MmsDeviceTypeService; +import com.njcn.web.utils.RequestUtil; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 设备类型校验服务实现。 + */ +@Service +@RequiredArgsConstructor +public class MmsDeviceTypeServiceImpl implements MmsDeviceTypeService { + + private static final int STATE_NORMAL = 1; + + private static final int ICD_TYPE_STANDARD = 1; + + private static final String PQDIF_NOT_SUPPORTED = "NOT_SUPPORTED"; + + private final CsDevTypeMapper csDevTypeMapper; + + private final CsIcdPathMapper csIcdPathMapper; + + @Override + public List listDeviceTypes() { + return csDevTypeMapper.selectDeviceTypeCheckList(); + } + + @Override + @Transactional + public boolean saveIcdCheckResult(String devTypeId, IcdCheckResultSaveParam param) { + if (param == null) { + throw new IllegalArgumentException("ICD校验结果不能为空"); + } + CsDevTypePO devType = requireDevType(devTypeId); + if (isBlank(devType.getIcd())) { + throw new IllegalArgumentException("设备类型未关联ICD,不能保存校验结果"); + } + + CsIcdPathPO referenceIcd = requireUniqueReferenceIcd(); + CsIcdPathPO icdPath = csIcdPathMapper.selectById(devType.getIcd()); + if (icdPath == null || !Integer.valueOf(STATE_NORMAL).equals(icdPath.getState())) { + throw new IllegalArgumentException("设备类型关联的ICD不存在或已删除"); + } + + icdPath.setJsonStr(trimToNull(param.getMappingJson())); + icdPath.setXmlStr(trimToNull(param.getXml())); + icdPath.setResult(normalizeResult(param.getResult())); + icdPath.setMsg(trimToNull(param.getMsg())); + icdPath.setReferenceIcdId(referenceIcd.getId()); + icdPath.setUpdateBy(currentUserId()); + icdPath.setUpdateTime(LocalDateTime.now()); + return csIcdPathMapper.updateById(icdPath) > 0; + } + + @Override + public PqdifCheckPlaceholderVO pqdifCheck(String devTypeId) { + requireDevType(devTypeId); + PqdifCheckPlaceholderVO result = new PqdifCheckPlaceholderVO(); + result.setStatus(PQDIF_NOT_SUPPORTED); + result.setMessage("PQDIF校验功能待实现"); + return result; + } + + private CsDevTypePO requireDevType(String devTypeId) { + String id = requireText(devTypeId, "设备类型ID不能为空"); + CsDevTypePO devType = csDevTypeMapper.selectById(id); + if (devType == null || !Integer.valueOf(STATE_NORMAL).equals(devType.getState())) { + throw new IllegalArgumentException("设备类型不存在或已删除"); + } + return devType; + } + + /** + * 全系统只允许一个正常状态的标准 ICD 作为唯一参照。 + */ + private CsIcdPathPO requireUniqueReferenceIcd() { + List referenceIcdList = csIcdPathMapper.selectList(new LambdaQueryWrapper() + .eq(CsIcdPathPO::getState, STATE_NORMAL) + .eq(CsIcdPathPO::getType, ICD_TYPE_STANDARD)); + if (referenceIcdList == null || referenceIcdList.isEmpty()) { + throw new IllegalArgumentException("未配置标准ICD,无法执行唯一性校验"); + } + if (referenceIcdList.size() > 1) { + throw new IllegalArgumentException("存在多个标准ICD,无法确定唯一参照"); + } + return referenceIcdList.get(0); + } + + private Integer normalizeResult(Integer result) { + if (result == null) { + throw new IllegalArgumentException("校验结论不能为空"); + } + if (result != 0 && result != 1) { + throw new IllegalArgumentException("校验结论只能是0或1"); + } + return result; + } + + private String requireText(String value, String message) { + String text = trimToNull(value); + if (text == null) { + throw new IllegalArgumentException(message); + } + return text; + } + + private String trimToNull(String value) { + if (value == null) { + return null; + } + String text = value.trim(); + return text.isEmpty() ? null : text; + } + + private boolean isBlank(String value) { + return trimToNull(value) == null; + } + + private String currentUserId() { + try { + String userId = RequestUtil.getUserId(); + return isBlank(userId) ? "未知用户" : userId; + } catch (Exception ex) { + return "未知用户"; + } + } +} diff --git a/tools/parse-pqdif/pom.xml b/tools/parse-pqdif/pom.xml new file mode 100644 index 0000000..9a6d660 --- /dev/null +++ b/tools/parse-pqdif/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + + com.njcn.gather + tools + 1.0.0 + + + parse-pqdif + jar + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-validation + + + + + parse-pqdif + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + + diff --git a/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/controller/ParsePqdifController.java b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/controller/ParsePqdifController.java new file mode 100644 index 0000000..58f7320 --- /dev/null +++ b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/controller/ParsePqdifController.java @@ -0,0 +1,46 @@ +package com.njcn.gather.tool.parsepqdif.controller; + +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.tool.parsepqdif.pojo.vo.PqdifParseResponse; +import com.njcn.gather.tool.parsepqdif.service.ParsePqdifService; +import com.njcn.web.controller.BaseController; +import com.njcn.web.utils.HttpResultUtil; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * PQDIF 解析接口入口。 + */ +@Slf4j +@Api(tags = "PQDIF解析") +@RestController +@RequestMapping("/api/parse-pqdif") +@RequiredArgsConstructor +public class ParsePqdifController extends BaseController { + + private final ParsePqdifService parsePqdifService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("上传 PQDIF 文件并解析") + @ApiImplicitParam(name = "pqdifFile", value = "PQDIF文件", required = true, dataType = "__file", paramType = "form") + @PostMapping(value = "/parse", consumes = {"multipart/form-data"}) + public HttpResult parse(@RequestPart("pqdifFile") MultipartFile pqdifFile) { + String methodDescribe = getMethodDescribe("parse"); + LogUtil.njcnDebug(log, "{},PQDIF解析预留入口,fileName={}", + methodDescribe, pqdifFile == null ? null : pqdifFile.getOriginalFilename()); + PqdifParseResponse result = parsePqdifService.parse(pqdifFile); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } +} diff --git a/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/pojo/vo/PqdifParseResponse.java b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/pojo/vo/PqdifParseResponse.java new file mode 100644 index 0000000..313f56a --- /dev/null +++ b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/pojo/vo/PqdifParseResponse.java @@ -0,0 +1,22 @@ +package com.njcn.gather.tool.parsepqdif.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * PQDIF 解析占位响应。 + */ +@Data +@ApiModel("PQDIF解析响应") +public class PqdifParseResponse { + + @ApiModelProperty("状态") + private String status; + + @ApiModelProperty("提示信息") + private String message; + + @ApiModelProperty("文件名") + private String fileName; +} diff --git a/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/ParsePqdifService.java b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/ParsePqdifService.java new file mode 100644 index 0000000..c74bc3a --- /dev/null +++ b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/ParsePqdifService.java @@ -0,0 +1,12 @@ +package com.njcn.gather.tool.parsepqdif.service; + +import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse; +import org.springframework.web.multipart.MultipartFile; + +/** + * PQDIF 解析服务。 + */ +public interface ParsePqdifService { + + PqdifParseResponse parse(MultipartFile pqdifFile); +} diff --git a/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/impl/ParsePqdifServiceImpl.java b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/impl/ParsePqdifServiceImpl.java new file mode 100644 index 0000000..78b9fe4 --- /dev/null +++ b/tools/parse-pqdif/src/main/java/com/njcn/gather/tool/parsepqdif/service/impl/ParsePqdifServiceImpl.java @@ -0,0 +1,24 @@ +package com.njcn.gather.tool.parsepqdif.service.impl; + +import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse; +import com.njcn.gather.tool.parsepqdif.service.ParsePqdifService; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +/** + * PQDIF 解析服务实现。 + */ +@Service +public class ParsePqdifServiceImpl implements ParsePqdifService { + + private static final String STATUS_NOT_SUPPORTED = "NOT_SUPPORTED"; + + @Override + public PqdifParseResponse parse(MultipartFile pqdifFile) { + PqdifParseResponse response = new PqdifParseResponse(); + response.setStatus(STATUS_NOT_SUPPORTED); + response.setMessage("PQDIF解析功能待实现"); + response.setFileName(pqdifFile == null ? null : pqdifFile.getOriginalFilename()); + return response; + } +} diff --git a/tools/parse-pqdif/src/main/resources/.gitkeep b/tools/parse-pqdif/src/main/resources/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tools/parse-pqdif/src/main/resources/.gitkeep @@ -0,0 +1 @@ + diff --git a/tools/pom.xml b/tools/pom.xml index c1d5dcf..369df55 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -23,6 +23,7 @@ wave-tool add-data add-ledger + parse-pqdif