diff --git a/.gitignore b/.gitignore index 399ac3e..61636bd 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ docs/ *.ear *.tar.gz *.rar +!tools/parse-pqdif/lib/pqdif-native-basic-bridge-1.0.0-jar-with-dependencies.jar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* diff --git a/README.md b/README.md index 3fedf8d..126d534 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓 - `activate-tool` - `add-data` - `add-ledger` +- `device-types` - `mms-mapping` - `parse-pqdif` - `wave-tool` @@ -44,7 +45,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`、`parse-pqdif`,是当前运行时主入口。 +`entrance` 模块聚合了 `system`、`disk-monitor`、`dbms`、`deploy`、`user`、`detection`、`activate-tool`、`add-data`、`add-ledger`、`wave-tool`、`device-types`、`mms-mapping`、`parse-pqdif`,是当前运行时主入口。 ## 技术基线 @@ -99,6 +100,8 @@ P0 已补齐基线文档,建议按以下顺序阅读: - 当前提供电能质量 13 张表批量补数、任务状态查询和模板规则查询能力 - `tools/add-ledger` - 当前为数据台账工具预留空模块 +- `tools/device-types` + - 负责设备类型维护、ICD 校验结果保存和 PQDIF 校验预留入口 - `tools/mms-mapping` - 负责 ICD 文件解析与 MMS 映射数据生成能力 - `tools/parse-pqdif` diff --git a/entrance/pom.xml b/entrance/pom.xml index c11af63..d19bd7b 100644 --- a/entrance/pom.xml +++ b/entrance/pom.xml @@ -53,6 +53,11 @@ wave-tool 1.0.0 + + com.njcn.gather + device-types + 1.0.0 + com.njcn.gather mms-mapping @@ -80,7 +85,12 @@ com.njcn.gather - steady-DataView + steady-dataView + 1.0.0 + + + com.njcn.gather + check-square 1.0.0 diff --git a/steady/check-square/pom.xml b/steady/check-square/pom.xml new file mode 100644 index 0000000..75567eb --- /dev/null +++ b/steady/check-square/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + com.njcn.gather + steady + 1.0.0 + + + check-square + + + + com.njcn.gather + steady-dataView + 1.0.0 + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + mybatis-plus + 0.0.1 + + + + com.njcn + spingboot2.3.12 + 2.3.12 + + + + org.springframework.boot + spring-boot-starter-test + test + + + diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculator.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponent.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponent.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponent.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponent.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java similarity index 85% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java index c3841d1..a53352d 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java +++ b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponent.java @@ -41,6 +41,7 @@ public class SteadyChecksquareInfluxQueryComponent { private static final DateTimeFormatter INFLUX_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final int QUERY_WINDOW_DAYS = 1; private static final ThreadLocal>> REQUEST_VALUE_CACHE = new ThreadLocal>>(); @@ -79,8 +80,7 @@ public class SteadyChecksquareInfluxQueryComponent { log.info("数据校验指标值 InfluxDB 查询开始,measurement={},field={},lineId={},phase={},statType={},query={}", field.getMeasurement(), field.getField(), field.getLineId(), field.getPhase(), field.getStatType(), query); try { - String body = executeQuery(query); - List points = parseValuePoints(body, intervalMinutes); + List points = queryValuePointsByWindow(field, startTime, endTime, intervalMinutes); if (cache != null) { cache.put(cacheKey, new ArrayList(points)); } @@ -124,7 +124,8 @@ public class SteadyChecksquareInfluxQueryComponent { log.info("数据校验指标值 InfluxDB 批量查询开始,measurement={},fieldCount={},lineId={},phase={},statType={},query={}", first.getMeasurement(), missingFields.size(), first.getLineId(), first.getPhase(), first.getStatType(), query); try { - Map> queried = parseBatchValuePoints(executeQuery(query), intervalMinutes); + Map> queried = + queryBatchValuePointsByWindow(missingFields, startTime, endTime, intervalMinutes); for (SteadyTrendResolvedFieldBO field : missingFields) { List points = queried.get(field.getField()); if (points == null) { @@ -155,6 +156,51 @@ public class SteadyChecksquareInfluxQueryComponent { return query + "|intervalMinutes=" + intervalMinutes; } + private List queryValuePointsByWindow(SteadyTrendResolvedFieldBO field, + LocalDateTime startTime, + LocalDateTime endTime, + int intervalMinutes) { + List result = new ArrayList(); + LocalDateTime windowStart = startTime; + while (!windowStart.isAfter(endTime)) { + LocalDateTime windowEnd = min(windowStart.plusDays(QUERY_WINDOW_DAYS).minusNanos(1), endTime); + result.addAll(parseValuePoints(executeQuery(buildValuePointQuery(field, windowStart, windowEnd)), intervalMinutes)); + windowStart = windowEnd.plusNanos(1); + } + return result; + } + + private Map> queryBatchValuePointsByWindow(List fields, + LocalDateTime startTime, + LocalDateTime endTime, + int intervalMinutes) { + Map> result = + new LinkedHashMap>(); + for (SteadyTrendResolvedFieldBO field : fields) { + result.put(field.getField(), new ArrayList()); + } + LocalDateTime windowStart = startTime; + while (!windowStart.isAfter(endTime)) { + LocalDateTime windowEnd = min(windowStart.plusDays(QUERY_WINDOW_DAYS).minusNanos(1), endTime); + Map> windowResult = + parseBatchValuePoints(executeQuery(buildBatchValuePointQuery(fields, windowStart, windowEnd)), intervalMinutes); + for (Map.Entry> entry : windowResult.entrySet()) { + List points = result.get(entry.getKey()); + if (points == null) { + points = new ArrayList(); + result.put(entry.getKey(), points); + } + points.addAll(entry.getValue()); + } + windowStart = windowEnd.plusNanos(1); + } + return result; + } + + private LocalDateTime min(LocalDateTime first, LocalDateTime second) { + return first.isAfter(second) ? second : first; + } + public String buildValuePointQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) { StringBuilder sql = new StringBuilder(); sql.append("SELECT \"").append(field.getField()).append("\" AS \"value\""); diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponent.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponent.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponent.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponent.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java similarity index 93% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java index d3f0c71..532180c 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java +++ b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareController.java @@ -9,7 +9,6 @@ import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.utils.LogUtil; import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam; import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam; -import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO; 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; @@ -36,7 +35,7 @@ import java.util.List; @Slf4j @Api(tags = "数据校验") @RestController -@RequestMapping("/steady/data-view/checksquare") +@RequestMapping("/steady/checksquare") @RequiredArgsConstructor public class SteadyChecksquareController extends BaseController { @@ -55,10 +54,10 @@ public class SteadyChecksquareController extends BaseController { @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD) @ApiOperation("新增数据校验记录") @PostMapping("/create") - public HttpResult create(@RequestBody @Validated SteadyChecksquareQueryParam param) { + public HttpResult create(@RequestBody @Validated SteadyChecksquareQueryParam param) { String methodDescribe = getMethodDescribe("create"); LogUtil.njcnDebug(log, "{},开始新增数据校验记录,param={}", methodDescribe, param); - SteadyChecksquareCreateVO result = checksquareService.create(param); + SteadyChecksquareTaskVO result = checksquareService.create(param); return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); } diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareDetailMapper.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareDetailMapper.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareDetailMapper.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareDetailMapper.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareItemMapper.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareItemMapper.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareItemMapper.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareItemMapper.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareStatSummaryMapper.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareStatSummaryMapper.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareStatSummaryMapper.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareStatSummaryMapper.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareTaskMapper.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareTaskMapper.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareTaskMapper.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/mapper/SteadyChecksquareTaskMapper.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/bo/SteadyChecksquareValuePointBO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/bo/SteadyChecksquareValuePointBO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/bo/SteadyChecksquareValuePointBO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/bo/SteadyChecksquareValuePointBO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java similarity index 82% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java index a588aa0..008cd2d 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java +++ b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/constant/SteadyChecksquareConst.java @@ -8,7 +8,9 @@ public final class SteadyChecksquareConst { public static final int STATE_DELETED = 0; public static final int STATE_ENABLED = 1; + public static final String TASK_STATUS_RUNNING = "RUNNING"; public static final String TASK_STATUS_SUCCESS = "SUCCESS"; + public static final String TASK_STATUS_FAIL = "FAIL"; public static final String DETAIL_TYPE_SEGMENT = "SEGMENT"; public static final String DETAIL_TYPE_VALUE_ORDER = "VALUE_ORDER"; public static final String DETAIL_TYPE_HARMONIC_PARITY = "HARMONIC_PARITY"; diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareHistoryQueryParam.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareHistoryQueryParam.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareHistoryQueryParam.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareHistoryQueryParam.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParam.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParam.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParam.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParam.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareDetailPO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareDetailPO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareDetailPO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareDetailPO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareItemPO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareStatSummaryPO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/po/SteadyChecksquareTaskPO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityDetailVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityDetailVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityDetailVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityDetailVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityRuleVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityRuleVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityRuleVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareHarmonicParityRuleVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemDetailVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemDetailVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemDetailVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemDetailVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareItemVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareQueryVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareQueryVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareQueryVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareQueryVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareSegmentVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareSegmentVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareSegmentVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareSegmentVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatDetailVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatDetailVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatDetailVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatDetailVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareStatSummaryVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareTaskVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderDetailVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderDetailVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderDetailVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderDetailVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderRuleVO.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderRuleVO.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderRuleVO.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareValueOrderRuleVO.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareDetailService.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareDetailService.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareDetailService.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareDetailService.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareItemService.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareItemService.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareItemService.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareItemService.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java similarity index 87% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java index 2e7ae00..2857a97 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java +++ b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareService.java @@ -3,7 +3,6 @@ package com.njcn.gather.steady.checksquare.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam; import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam; -import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO; 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; @@ -17,7 +16,7 @@ public interface SteadyChecksquareService { Page query(SteadyChecksquareHistoryQueryParam param); - SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param); + SteadyChecksquareTaskVO create(SteadyChecksquareQueryParam param); boolean delete(List taskIds); diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareStatSummaryService.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareStatSummaryService.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareStatSummaryService.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareStatSummaryService.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareTaskService.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareTaskService.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareTaskService.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/SteadyChecksquareTaskService.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareDetailServiceImpl.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareDetailServiceImpl.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareDetailServiceImpl.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareDetailServiceImpl.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareItemServiceImpl.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareItemServiceImpl.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareItemServiceImpl.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareItemServiceImpl.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java similarity index 88% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java index b694172..d7d437a 100644 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java +++ b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImpl.java @@ -17,7 +17,6 @@ import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO; import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO; import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO; import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO; -import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO; @@ -52,6 +51,7 @@ import org.springframework.transaction.support.TransactionTemplate; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.Duration; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -67,8 +67,7 @@ import java.util.Set; import java.util.stream.Collectors; /** - * 数据校验服务实现。 - */ + * 数据校验服务实现。 */ @Slf4j @Service @RequiredArgsConstructor @@ -80,6 +79,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { private static final int FLICKER_LONG_INTERVAL_MINUTES = 120; private static final int HARMONIC_AGGREGATE_ORDER_START = 2; private static final int HARMONIC_AGGREGATE_ORDER_END = 50; + private static final long MAX_CREATE_RANGE_DAYS = 7L; + private static final int DETAIL_SAVE_BATCH_SIZE = 1000; private final SteadyTrendIndicatorCatalog indicatorCatalog; private final SteadyChecksquareInfluxQueryComponent influxQueryComponent; @@ -116,18 +117,25 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { } @Override - public SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param) { + public SteadyChecksquareTaskVO create(SteadyChecksquareQueryParam param) { + validateCreateBaseParam(param); + String lineId = trimToNull(param.getLineId()); + LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); + LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空"); + SteadyChecksquareTaskPO existedTask = findExistingTask(lineId, startTime, endTime); + if (existedTask != null) { + return toTaskVO(existedTask); + } + prepareCreateContext(param); influxQueryComponent.enableRequestCache(); - SteadyChecksquareQueryVO result; try { - result = calculate(param); + SteadyChecksquareQueryVO result = calculate(param); + SteadyChecksquareTaskPO task = saveResultInTransaction(param, result); + return toTaskVO(task); } finally { influxQueryComponent.clearRequestCache(); } - SteadyChecksquareTaskPO task = saveResultInTransaction(param, result); - return toCreateVO(task); } - @Override public boolean delete(List taskIds) { List ids = normalizeTextList(taskIds); @@ -224,7 +232,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { .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) @@ -233,6 +240,51 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return taskResult; } + private CreateContext prepareCreateContext(SteadyChecksquareQueryParam param) { + validateParam(param); + String lineId = trimToNull(param.getLineId()); + LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); + LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空"); + if (startTime.isAfter(endTime)) { + throw fail("开始时间不能大于结束时间"); + } + AddLedgerLinePathVO linePath = requireLinePath(lineId); + int intervalMinutes = resolveIntervalMinutes(linePath); + List indicatorCodes = normalizeTextList(param.getIndicatorCodes()); + for (String indicatorCode : indicatorCodes) { + requireIndicator(indicatorCode); + } + validateCreateTimeRange(startTime, endTime); + return new CreateContext(lineId, linePath, startTime, endTime, intervalMinutes, indicatorCodes); + } + + private void validateCreateTimeRange(LocalDateTime startTime, LocalDateTime endTime) { + if (Duration.between(startTime, endTime).compareTo(Duration.ofDays(MAX_CREATE_RANGE_DAYS)) > 0) { + throw fail("数据校验时间范围不能超过7天"); + } + } + + private SteadyChecksquareTaskPO findExistingTask(String lineId, LocalDateTime startTime, LocalDateTime endTime) { + List tasks = taskService.lambdaQuery() + .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED) + .eq(SteadyChecksquareTaskPO::getLineId, lineId) + .eq(SteadyChecksquareTaskPO::getTimeStart, startTime) + .eq(SteadyChecksquareTaskPO::getTimeEnd, endTime) + .orderByDesc(SteadyChecksquareTaskPO::getCreateTime) + .list(); + return tasks == null || tasks.isEmpty() ? null : tasks.get(0); + } + + private void markTaskFail(String taskId, String message) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper() + .set(SteadyChecksquareTaskPO::getTaskStatus, SteadyChecksquareConst.TASK_STATUS_FAIL) + .set(SteadyChecksquareTaskPO::getResultMessage, limitMessage(message)) + .set(SteadyChecksquareTaskPO::getUpdateTime, LocalDateTime.now()) + .eq(SteadyChecksquareTaskPO::getId, taskId) + .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED); + taskService.update(wrapper); + } + private SteadyChecksquareQueryVO calculate(SteadyChecksquareQueryParam param) { validateParam(param); String lineId = trimToNull(param.getLineId()); @@ -275,10 +327,21 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return transactionTemplate.execute(status -> saveResult(param, result)); } + private SteadyChecksquareTaskPO saveResultInTransaction(String taskId, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) { + if (transactionTemplate == null) { + return saveResult(taskId, param, result); + } + return transactionTemplate.execute(status -> saveResult(taskId, param, result)); + } + private SteadyChecksquareTaskPO saveResult(SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) { + return saveResult(null, param, result); + } + + private SteadyChecksquareTaskPO saveResult(String taskId, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) { LocalDateTime now = LocalDateTime.now(); SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO(); - task.setId(SteadyChecksquareIdUtil.uuid()); + task.setId(trimToNull(taskId) == null ? SteadyChecksquareIdUtil.uuid() : taskId); task.setTaskNo(SteadyChecksquareIdUtil.taskNo()); task.setLineId(result.getLineId()); task.setLineName(result.getLineName()); @@ -296,7 +359,11 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { task.setState(SteadyChecksquareConst.STATE_ENABLED); task.setCreateTime(now); task.setUpdateTime(now); - taskService.save(task); + if (trimToNull(taskId) == null) { + taskService.save(task); + } else { + updateCompletedTask(task); + } List itemPOs = new ArrayList(); List summaryPOs = new ArrayList(); @@ -315,11 +382,33 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { statSummaryService.saveBatch(summaryPOs); } if (!detailPOs.isEmpty()) { - detailService.saveBatch(detailPOs); + saveDetailBatchInChunks(detailPOs); } return task; } + private void updateCompletedTask(SteadyChecksquareTaskPO task) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper() + .set(SteadyChecksquareTaskPO::getLineName, task.getLineName()) + .set(SteadyChecksquareTaskPO::getIntervalMinutes, task.getIntervalMinutes()) + .set(SteadyChecksquareTaskPO::getTaskStatus, task.getTaskStatus()) + .set(SteadyChecksquareTaskPO::getItemCount, task.getItemCount()) + .set(SteadyChecksquareTaskPO::getAbnormalItemCount, task.getAbnormalItemCount()) + .set(SteadyChecksquareTaskPO::getMinDataIntegrity, task.getMinDataIntegrity()) + .set(SteadyChecksquareTaskPO::getResultMessage, task.getResultMessage()) + .set(SteadyChecksquareTaskPO::getUpdateTime, task.getUpdateTime()) + .eq(SteadyChecksquareTaskPO::getId, task.getId()) + .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED); + taskService.update(wrapper); + } + + private void saveDetailBatchInChunks(List detailPOs) { + for (int start = 0; start < detailPOs.size(); start += DETAIL_SAVE_BATCH_SIZE) { + int end = Math.min(start + DETAIL_SAVE_BATCH_SIZE, detailPOs.size()); + detailService.saveBatch(detailPOs.subList(start, end)); + } + } + private SteadyChecksquareItemPO buildItemPO(String taskId, SteadyChecksquareItemVO item, LocalDateTime now) { SteadyChecksquareItemPO po = new SteadyChecksquareItemPO(); po.setId(SteadyChecksquareIdUtil.uuid()); @@ -444,7 +533,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { } } for (Map.Entry> entry : fieldMap.entrySet()) { - // 预取只依赖请求级缓存;后续缺数和规则校验复用同一批 Influx 结果。 influxQueryComponent.queryValuePointMap(entry.getValue(), startTime, endTime, intervalMap.get(entry.getKey())); } } @@ -727,13 +815,28 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { } private void validateParam(SteadyChecksquareQueryParam param) { + validateParam(param, false); + } + + private void validateCreateBaseParam(SteadyChecksquareQueryParam param) { if (param == null) { throw fail("数据校验参数不能为空"); } if (trimToNull(param.getLineId()) == null) { - throw fail("监测点 ID 不能为空"); + throw fail("监测点ID不能为空"); } - if (normalizeTextList(param.getIndicatorCodes()).isEmpty()) { + parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); + parseRequiredTime(param.getTimeEnd(), "结束时间不能为空"); + } + + private void validateParam(SteadyChecksquareQueryParam param, boolean allowEmptyIndicators) { + if (param == null) { + throw fail("数据校验参数不能为空"); + } + if (trimToNull(param.getLineId()) == null) { + throw fail("监测点ID不能为空"); + } + if (!allowEmptyIndicators && normalizeTextList(param.getIndicatorCodes()).isEmpty()) { throw fail("指标不能为空"); } parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); @@ -800,7 +903,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { int start = Math.max(HARMONIC_AGGREGATE_ORDER_START, indicator.getHarmonicOrderStart()); int end = Math.min(HARMONIC_AGGREGATE_ORDER_END, indicator.getHarmonicOrderEnd()); if (start > end) { - throw fail("谐波次数只能在 " + HARMONIC_AGGREGATE_ORDER_START + " 到 " + HARMONIC_AGGREGATE_ORDER_END + " 之间"); + throw fail("谐波次数只能在" + HARMONIC_AGGREGATE_ORDER_START + "到" + HARMONIC_AGGREGATE_ORDER_END + "之间"); } List result = new ArrayList(); for (int order = start; order <= end; order++) { @@ -914,20 +1017,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return vo; } - private SteadyChecksquareCreateVO toCreateVO(SteadyChecksquareTaskPO task) { - SteadyChecksquareCreateVO vo = new SteadyChecksquareCreateVO(); - vo.setTaskId(task.getId()); - vo.setTaskNo(task.getTaskNo()); - vo.setLineId(task.getLineId()); - vo.setLineName(task.getLineName()); - vo.setTimeStart(formatTime(task.getTimeStart())); - vo.setTimeEnd(formatTime(task.getTimeEnd())); - vo.setIntervalMinutes(task.getIntervalMinutes()); - vo.setItemCount(task.getItemCount()); - vo.setAbnormalItemCount(task.getAbnormalItemCount()); - return vo; - } - private SteadyChecksquareItemVO toItemVO(SteadyChecksquareItemPO item) { SteadyChecksquareItemVO vo = new SteadyChecksquareItemVO(); vo.setItemId(item.getId()); @@ -1167,6 +1256,14 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { return builder.toString(); } + private String limitMessage(String message) { + String text = trimToNull(message); + if (text == null) { + return "数据校验任务执行失败"; + } + return text.length() > 2000 ? text.substring(0, 2000) : text; + } + private List readIntegerList(String json) { if (trimToNull(json) == null) { return new ArrayList(); @@ -1179,6 +1276,18 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { } } + private List readStringList(String json) { + if (trimToNull(json) == null) { + return new ArrayList(); + } + try { + String[] values = objectMapper.readValue(json, String[].class); + return normalizeTextList(Arrays.asList(values)); + } catch (Exception exception) { + throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage()); + } + } + private List readBigDecimalList(String json) { if (trimToNull(json) == null) { return new ArrayList(); @@ -1194,4 +1303,26 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService { private BusinessException fail(String message) { return new BusinessException(CommonResponseEnum.FAIL, message); } + + private static class CreateContext { + private final String lineId; + private final AddLedgerLinePathVO linePath; + private final LocalDateTime startTime; + private final LocalDateTime endTime; + private final int intervalMinutes; + private final List indicatorCodes; + + private CreateContext(String lineId, AddLedgerLinePathVO linePath, LocalDateTime startTime, + LocalDateTime endTime, int intervalMinutes, List indicatorCodes) { + this.lineId = lineId; + this.linePath = linePath; + this.startTime = startTime; + this.endTime = endTime; + this.intervalMinutes = intervalMinutes; + this.indicatorCodes = indicatorCodes; + } + } } + + + diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareStatSummaryServiceImpl.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareStatSummaryServiceImpl.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareStatSummaryServiceImpl.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareStatSummaryServiceImpl.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareTaskServiceImpl.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareTaskServiceImpl.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareTaskServiceImpl.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareTaskServiceImpl.java diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/util/SteadyChecksquareIdUtil.java b/steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/util/SteadyChecksquareIdUtil.java similarity index 100% rename from steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/util/SteadyChecksquareIdUtil.java rename to steady/check-square/src/main/java/com/njcn/gather/steady/checksquare/util/SteadyChecksquareIdUtil.java diff --git a/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql b/steady/check-square/src/main/resources/sql/check-square/steady-checksquare-result-init.sql similarity index 99% rename from steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql rename to steady/check-square/src/main/resources/sql/check-square/steady-checksquare-result-init.sql index 0aa1a77..8e472e8 100644 --- a/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-init.sql +++ b/steady/check-square/src/main/resources/sql/check-square/steady-checksquare-result-init.sql @@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS `steady_checksquare_task` ( `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', + `task_status` VARCHAR(32) NOT NULL DEFAULT 'SUCCESS' COMMENT '任务状态:RUNNING/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 '最低数据完整性', diff --git a/steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql b/steady/check-square/src/main/resources/sql/check-square/steady-checksquare-result-upgrade_20260611.sql similarity index 100% rename from steady/steady-DataView/src/main/resources/sql/steady-DataView/steady-checksquare-result-upgrade_20260611.sql rename to steady/check-square/src/main/resources/sql/check-square/steady-checksquare-result-upgrade_20260611.sql diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculatorTest.java b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculatorTest.java similarity index 100% rename from steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculatorTest.java rename to steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareCalculatorTest.java diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponentTest.java b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponentTest.java similarity index 100% rename from steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponentTest.java rename to steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareHarmonicParityRuleComponentTest.java diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java similarity index 81% rename from steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java rename to steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java index 867adda..d2d43f0 100644 --- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java +++ b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareInfluxQueryComponentTest.java @@ -10,6 +10,7 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -141,6 +142,38 @@ class SteadyChecksquareInfluxQueryComponentTest { } } + @Test + void shouldSplitLongValuePointQueryByDay() throws Exception { + AtomicInteger requestCount = new AtomicInteger(); + HttpServer server = HttpServer.create(new InetSocketAddress(0), 0); + server.createContext("/query", exchange -> { + int index = requestCount.incrementAndGet(); + byte[] body = ("{\"results\":[{\"series\":[{\"values\":[" + + "[\"2026-05-0" + index + "T00:00:00Z\"," + index + "]" + + "]}]}]}").getBytes(StandardCharsets.UTF_8); + exchange.sendResponseHeaders(200, body.length); + exchange.getResponseBody().write(body); + exchange.close(); + }); + server.start(); + try { + SteadyInfluxDbProperties properties = new SteadyInfluxDbProperties(); + properties.setUrl("http://127.0.0.1:" + server.getAddress().getPort()); + properties.setDatabase("steady"); + SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(properties); + + List result = + component.queryValuePoints(buildField("h_2"), + LocalDateTime.of(2026, 5, 1, 0, 0, 0), + LocalDateTime.of(2026, 5, 3, 0, 0, 0), 1); + + Assertions.assertEquals(3, requestCount.get()); + Assertions.assertEquals(3, result.size()); + } finally { + server.stop(0); + } + } + private SteadyTrendResolvedFieldBO buildField(String fieldName) { SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO(); field.setMeasurement("data_harmonic"); diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponentTest.java b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponentTest.java similarity index 100% rename from steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponentTest.java rename to steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/component/SteadyChecksquareValueOrderRuleComponentTest.java diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java similarity index 72% rename from steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java rename to steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java index 37eda9d..52c0aac 100644 --- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java +++ b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/controller/SteadyChecksquareControllerTest.java @@ -6,8 +6,9 @@ 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; +import java.lang.reflect.ParameterizedType; +import java.util.List; /** * 数据校验接口契约测试。 @@ -17,7 +18,7 @@ class SteadyChecksquareControllerTest { @Test void shouldExposeChecksquareQueryEndpointInSeparateController() throws Exception { RequestMapping requestMapping = SteadyChecksquareController.class.getAnnotation(RequestMapping.class); - Assertions.assertArrayEquals(new String[]{"/steady/data-view/checksquare"}, requestMapping.value()); + Assertions.assertArrayEquals(new String[]{"/steady/checksquare"}, requestMapping.value()); Method queryMethod = SteadyChecksquareController.class.getDeclaredMethod("query", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam.class); PostMapping queryMapping = queryMethod.getAnnotation(PostMapping.class); @@ -40,4 +41,16 @@ class SteadyChecksquareControllerTest { PostMapping deleteMapping = deleteMethod.getAnnotation(PostMapping.class); Assertions.assertArrayEquals(new String[]{"/delete"}, deleteMapping.value()); } + + @Test + void shouldKeepCreateResponseAsTaskSummaryWithoutDetailItems() throws Exception { + Method createMethod = SteadyChecksquareController.class.getDeclaredMethod("create", + com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam.class); + ParameterizedType resultType = (ParameterizedType) createMethod.getGenericReturnType(); + + Assertions.assertEquals(com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO.class, + resultType.getActualTypeArguments()[0]); + Assertions.assertThrows(NoSuchFieldException.class, + () -> com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO.class.getDeclaredField("items")); + } } diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParamTest.java b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParamTest.java similarity index 100% rename from steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParamTest.java rename to steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/pojo/param/SteadyChecksquareQueryParamTest.java diff --git a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java similarity index 75% rename from steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java rename to steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java index 36a6a72..4ba231b 100644 --- a/steady/steady-DataView/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java +++ b/steady/check-square/src/test/java/com/njcn/gather/steady/checksquare/service/impl/SteadyChecksquareServiceImplTest.java @@ -18,6 +18,7 @@ import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatSummaryVO; +import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderDetailVO; import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderRuleVO; import com.njcn.gather.steady.checksquare.service.SteadyChecksquareDetailService; @@ -48,13 +49,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** - * 数据校验服务测试。 - */ + * 鏁版嵁鏍¢獙鏈嶅姟娴嬭瘯銆? */ class SteadyChecksquareServiceImplTest { @Test @@ -64,6 +65,177 @@ class SteadyChecksquareServiceImplTest { Assertions.assertNull(createMethod.getAnnotation(Transactional.class)); } + @Test + void shouldRejectCreateWhenTimeRangeExceedsSevenDays() { + AddLedgerService addLedgerService = mock(AddLedgerService.class); + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + LambdaQueryChainWrapper taskQuery = mock(LambdaQueryChainWrapper.class); + when(taskService.lambdaQuery()).thenReturn(taskQuery); + when(taskQuery.eq(any(), any())).thenReturn(taskQuery); + when(taskQuery.orderByDesc(any())).thenReturn(taskQuery); + when(taskQuery.list()).thenReturn(Collections.emptyList()); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), addLedgerService, taskService, + mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class), + mock(SteadyChecksquareDetailService.class), new ObjectMapper()); + AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); + linePath.setLineId("line-001"); + linePath.setLineName("line-001"); + linePath.setLineInterval(1); + when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) + .thenReturn(Collections.singletonMap("line-001", linePath)); + SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); + param.setLineId("line-001"); + param.setIndicatorCodes(Collections.singletonList("V_RMS")); + param.setTimeStart("2026-05-01 00:00:00"); + param.setTimeEnd("2026-05-08 00:01:00"); + + Assertions.assertThrows(RuntimeException.class, () -> service.create(param)); + + verify(taskService, never()).save(any()); + } + + @Test + void shouldNotRejectCreateByIndicatorCountWithinSevenDays() { + AddLedgerService addLedgerService = mock(AddLedgerService.class); + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class); + SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class); + SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class); + SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class); + SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class); + LambdaQueryChainWrapper taskQuery = mock(LambdaQueryChainWrapper.class); + when(taskService.lambdaQuery()).thenReturn(taskQuery); + when(taskQuery.eq(any(), any())).thenReturn(taskQuery); + when(taskQuery.orderByDesc(any())).thenReturn(taskQuery); + when(taskQuery.list()).thenReturn(Collections.emptyList()); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + influxQueryComponent, new SteadyChecksquareCalculator(), + valueOrderRuleComponent, harmonicParityRuleComponent, + new AddDataTimeSlotCalculator(), addLedgerService, taskService, + itemService, statSummaryService, detailService, new ObjectMapper()); + AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); + linePath.setLineId("line-001"); + linePath.setLineName("line-001"); + linePath.setLineInterval(1); + when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) + .thenReturn(Collections.singletonMap("line-001", linePath)); + when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(new HashSet()); + when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyRuleResult()); + when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyHarmonicParityRuleResult()); + ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(SteadyChecksquareTaskPO.class); + when(taskService.save(taskCaptor.capture())).thenReturn(true); + SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); + param.setLineId("line-001"); + param.setIndicatorCodes(Arrays.asList("V_RMS", "V_LINE_RMS", "FREQ", "I_RMS", "I_THD")); + param.setTimeStart("2026-05-01 00:00:00"); + param.setTimeEnd("2026-05-03 23:59:00"); + + SteadyChecksquareTaskVO result = service.create(param); + + Assertions.assertEquals(taskCaptor.getValue().getId(), result.getTaskId()); + Assertions.assertEquals(Integer.valueOf(5), result.getItemCount()); + verify(itemService).saveBatch(any()); + } + + @Test + void shouldReturnExistingTaskSummaryWhenCreateMatchesLineAndTime() { + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + LambdaQueryChainWrapper taskQuery = mock(LambdaQueryChainWrapper.class); + SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO(); + task.setId("task-001"); + task.setTaskNo("CS202605010001"); + task.setLineId("line-001"); + task.setLineName("line-001"); + task.setTimeStart(LocalDateTime.of(2026, 5, 1, 0, 0)); + task.setTimeEnd(LocalDateTime.of(2026, 5, 1, 0, 1)); + task.setIntervalMinutes(1); + task.setIndicatorCodesJson("[\"V_RMS\"]"); + task.setTaskStatus("SUCCESS"); + task.setItemCount(1); + task.setAbnormalItemCount(0); + task.setMinDataIntegrity(BigDecimal.ONE.setScale(6)); + task.setCreateTime(LocalDateTime.of(2026, 5, 1, 1, 0)); + task.setState(1); + when(taskService.lambdaQuery()).thenReturn(taskQuery); + when(taskQuery.eq(any(), any())).thenReturn(taskQuery); + when(taskQuery.orderByDesc(any())).thenReturn(taskQuery); + when(taskQuery.list()).thenReturn(Collections.singletonList(task)); + 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.setLineId("line-001"); + param.setIndicatorCodes(Collections.singletonList("I_RMS")); + param.setTimeStart("2026-05-01 00:00:00"); + param.setTimeEnd("2026-05-01 00:01:00"); + + SteadyChecksquareTaskVO result = service.create(param); + + Assertions.assertEquals("task-001", result.getTaskId()); + Assertions.assertEquals("CS202605010001", result.getTaskNo()); + Assertions.assertEquals("SUCCESS", result.getTaskStatus()); + verify(taskService, never()).save(any()); + } + + @Test + void shouldCreateTaskSynchronouslyAndReturnTaskSummary() { + AddLedgerService addLedgerService = mock(AddLedgerService.class); + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); + SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class); + SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class); + SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class); + SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class); + SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class); + LambdaQueryChainWrapper taskQuery = mock(LambdaQueryChainWrapper.class); + when(taskService.lambdaQuery()).thenReturn(taskQuery); + when(taskQuery.eq(any(), any())).thenReturn(taskQuery); + when(taskQuery.orderByDesc(any())).thenReturn(taskQuery); + when(taskQuery.list()).thenReturn(Collections.emptyList()); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + influxQueryComponent, new SteadyChecksquareCalculator(), + valueOrderRuleComponent, harmonicParityRuleComponent, + new AddDataTimeSlotCalculator(), addLedgerService, taskService, + itemService, statSummaryService, detailService, new ObjectMapper()); + AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); + linePath.setLineId("line-001"); + linePath.setLineName("line-001"); + linePath.setLineInterval(1); + when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) + .thenReturn(Collections.singletonMap("line-001", linePath)); + when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(new HashSet()); + when(valueOrderRuleComponent.check(any(), any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyRuleResult()); + when(harmonicParityRuleComponent.check(any(), any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt())) + .thenReturn(emptyHarmonicParityRuleResult()); + ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(SteadyChecksquareTaskPO.class); + when(taskService.save(taskCaptor.capture())).thenReturn(true); + SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); + param.setLineId("line-001"); + param.setIndicatorCodes(Collections.singletonList("V_RMS")); + param.setTimeStart("2026-05-01 00:00:00"); + param.setTimeEnd("2026-05-01 00:01:00"); + + SteadyChecksquareTaskVO result = service.create(param); + + Assertions.assertEquals(taskCaptor.getValue().getId(), result.getTaskId()); + Assertions.assertEquals("SUCCESS", result.getTaskStatus()); + Assertions.assertEquals(Integer.valueOf(1), result.getItemCount()); + verify(itemService).saveBatch(any()); + verify(statSummaryService).saveBatch(any()); + } + @Test void shouldUseFixedFlickerIntervalsPerIndicator() { SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); @@ -81,7 +253,7 @@ class SteadyChecksquareServiceImplTest { .thenReturn(emptyHarmonicParityRuleResult()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); - linePath.setLineName("进线一"); + linePath.setLineName("杩涚嚎涓€"); linePath.setLineInterval(1); when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) .thenReturn(Collections.singletonMap("line-001", linePath)); @@ -125,7 +297,7 @@ class SteadyChecksquareServiceImplTest { .thenReturn(emptyHarmonicParityRuleResult()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); - linePath.setLineName("进线一"); + linePath.setLineName("杩涚嚎涓€"); linePath.setLineInterval(1); when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) .thenReturn(Collections.singletonMap("line-001", linePath)); @@ -164,7 +336,7 @@ class SteadyChecksquareServiceImplTest { .thenReturn(emptyHarmonicParityRuleResult()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); - linePath.setLineName("进线一"); + linePath.setLineName("杩涚嚎涓€"); linePath.setLineInterval(1); when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) .thenReturn(Collections.singletonMap("line-001", linePath)); @@ -218,7 +390,7 @@ class SteadyChecksquareServiceImplTest { mock(SteadyChecksquareDetailService.class), new ObjectMapper()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); - linePath.setLineName("进线一"); + linePath.setLineName("杩涚嚎涓€"); linePath.setLineInterval(1); when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) .thenReturn(Collections.singletonMap("line-001", linePath)); @@ -267,7 +439,7 @@ class SteadyChecksquareServiceImplTest { .thenReturn(emptyHarmonicParityRuleResult()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); - linePath.setLineName("进线一"); + linePath.setLineName("杩涚嚎涓€"); linePath.setLineInterval(1); when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) .thenReturn(Collections.singletonMap("line-001", linePath)); @@ -314,7 +486,7 @@ class SteadyChecksquareServiceImplTest { .thenReturn(emptyRuleResult()); AddLedgerLinePathVO linePath = new AddLedgerLinePathVO(); linePath.setLineId("line-001"); - linePath.setLineName("进线一"); + linePath.setLineName("杩涚嚎涓€"); linePath.setLineInterval(1); when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001")))) .thenReturn(Collections.singletonMap("line-001", linePath)); @@ -376,7 +548,7 @@ class SteadyChecksquareServiceImplTest { task.setId("task-001"); task.setState(1); task.setLineId("line-001"); - task.setLineName("进线一"); + task.setLineName("杩涚嚎涓€"); task.setTimeStart(LocalDateTime.of(2026, 5, 1, 0, 0)); task.setTimeEnd(LocalDateTime.of(2026, 5, 1, 0, 1)); task.setIntervalMinutes(1); @@ -481,14 +653,14 @@ class SteadyChecksquareServiceImplTest { param.setIndicatorCodes(Collections.singletonList("V_RMS")); SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO(); result.setLineId("line-001"); - result.setLineName("进线一"); + 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.setIndicatorName("相电压有效值"); + item.setIndicatorName("鐩哥數鍘嬫湁鏁堝€?); item.setIntervalMinutes(1); item.setHasData(true); item.setExpectedPointCount(2); @@ -519,6 +691,41 @@ class SteadyChecksquareServiceImplTest { verify(statSummaryService).saveBatch(any()); } + @Test + void shouldSaveDetailResultsInChunks() { + SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); + SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class); + SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class); + SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class); + SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(), + mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(), + mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class), + new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService, + itemService, statSummaryService, detailService, new ObjectMapper()); + SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam(); + param.setIndicatorCodes(Collections.singletonList("V_RMS")); + SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO(); + result.setLineId("line-001"); + result.setLineName("line-001"); + result.setTimeStart("2026-05-01 00:00:00"); + result.setTimeEnd("2026-05-01 00:01:00"); + result.setIntervalMinutes(1); + SteadyChecksquareItemVO item = buildOrderItem(true, BigDecimal.ONE.setScale(6)); + item.setItemKey("line-001|V_RMS"); + item.setIndicatorCode("V_RMS"); + for (int i = 0; i < 1001; i++) { + SteadyChecksquareValueOrderDetailVO detail = new SteadyChecksquareValueOrderDetailVO(); + detail.setTime("2026-05-01 00:00:00"); + detail.setPhase("A"); + item.getAbnormalDetails().add(detail); + } + result.getItems().add(item); + + saveResult(service, param, result); + + verify(detailService, times(2)).saveBatch(any()); + } + @Test void shouldCountNoDataItemAsAbnormalWhenSavingTask() { SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); @@ -532,7 +739,7 @@ class SteadyChecksquareServiceImplTest { param.setIndicatorCodes(Collections.singletonList("V_RMS")); SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO(); result.setLineId("line-001"); - result.setLineName("进线一"); + result.setLineName("杩涚嚎涓€"); result.setTimeStart("2026-05-01 00:00:00"); result.setTimeEnd("2026-05-01 00:01:00"); result.setIntervalMinutes(1); @@ -712,3 +919,5 @@ class SteadyChecksquareServiceImplTest { return new SteadyChecksquareHarmonicParityRuleVO(); } } + + diff --git a/steady/pom.xml b/steady/pom.xml index 11d39b7..49a9d40 100644 --- a/steady/pom.xml +++ b/steady/pom.xml @@ -13,7 +13,8 @@ pom - steady-DataView + steady-dataView + check-square diff --git a/steady/steady-DataView/pom.xml b/steady/steady-DataView/pom.xml index 2d661e8..2e4bf0a 100644 --- a/steady/steady-DataView/pom.xml +++ b/steady/steady-DataView/pom.xml @@ -9,7 +9,7 @@ 1.0.0 - steady-DataView + steady-dataView diff --git a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareCreateVO.java b/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareCreateVO.java deleted file mode 100644 index a974484..0000000 --- a/steady/steady-DataView/src/main/java/com/njcn/gather/steady/checksquare/pojo/vo/SteadyChecksquareCreateVO.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.njcn.gather.steady.checksquare.pojo.vo; - -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.Data; - -import java.io.Serializable; - -/** - * 新增数据校验结果。 - */ -@Data -@ApiModel("新增数据校验结果") -public class SteadyChecksquareCreateVO implements Serializable { - - private static final long serialVersionUID = 1L; - - @ApiModelProperty("任务 ID") - private String taskId; - - @ApiModelProperty("任务编号") - private String taskNo; - - @ApiModelProperty("监测点 ID") - private String lineId; - - @ApiModelProperty("监测点名称") - private String lineName; - - @ApiModelProperty("开始时间") - private String timeStart; - - @ApiModelProperty("结束时间") - private String timeEnd; - - @ApiModelProperty("统计间隔,单位分钟") - private Integer intervalMinutes; - - @ApiModelProperty("检测项数量") - private Integer itemCount; - - @ApiModelProperty("异常检测项数量") - private Integer abnormalItemCount; -} diff --git a/steady/steady-DataView/steady-checksquare-api-debug_20260610.md b/steady/steady-DataView/steady-checksquare-api-debug_20260610.md deleted file mode 100644 index 20a0a31..0000000 --- a/steady/steady-DataView/steady-checksquare-api-debug_20260610.md +++ /dev/null @@ -1,522 +0,0 @@ -# 数据校验 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 715aaeb..4eac557 100644 --- a/tools/README.md +++ b/tools/README.md @@ -9,6 +9,7 @@ - `activate-tool` - `add-data` - `add-ledger` +- `device-types` - `mms-mapping` - `parse-pqdif` - `wave-tool` @@ -22,12 +23,13 @@ tools/ ├── activate-tool/ ├── add-data/ ├── add-ledger/ +├── device-types/ ├── mms-mapping/ ├── parse-pqdif/ └── wave-tool/ ``` -其中 `tools/mms-mapping` 当前 Maven `artifactId` 为 `mms-mapping`。 +其中 `tools/device-types` 当前 Maven `artifactId` 为 `device-types`,`tools/mms-mapping` 当前 Maven `artifactId` 为 `mms-mapping`。 ## add-data 的职责 @@ -80,6 +82,16 @@ tools/ 从接口层看,当前主要围绕 `/api/mms-mapping` 路径提供能力。 +## device-types 的职责 + +`device-types` 当前提供设备类型维护与校验入口: +- 查询设备类型列表 +- 新增、编辑、删除设备类型 +- 保存设备类型 ICD 唯一性校验结果 +- 预留设备类型 PQDIF 校验入口 + +从接口层看,当前主要围绕 `/api/device-types` 路径提供能力。 + ## parse-pqdif 的职责 `parse-pqdif` 当前仅作为 PQDIF 文件解析工具的预留骨架,参照 `mms-mapping` 的 Controller、Service、ServiceImpl 和 VO 分层组织。 @@ -110,7 +122,7 @@ tools/ ## 依赖关系 -`tools/activate-tool`、`tools/add-data`、`tools/add-ledger`、`tools/mms-mapping`、`tools/parse-pqdif` 与 `tools/wave-tool` 当前主要依赖: +`tools/activate-tool`、`tools/add-data`、`tools/add-ledger`、`tools/device-types`、`tools/mms-mapping`、`tools/parse-pqdif` 与 `tools/wave-tool` 当前主要依赖: - `com.njcn:njcn-common` - `com.njcn:spingboot2.3.12` diff --git a/tools/device-types/pom.xml b/tools/device-types/pom.xml new file mode 100644 index 0000000..d32cc34 --- /dev/null +++ b/tools/device-types/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + + com.njcn.gather + tools + 1.0.0 + + + device-types + jar + + + + com.njcn + njcn-common + 0.0.1 + + + + com.njcn + mybatis-plus + 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 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + device-types + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + + diff --git a/tools/device-types/src/main/java/com/njcn/gather/device/types/controller/DeviceTypeController.java b/tools/device-types/src/main/java/com/njcn/gather/device/types/controller/DeviceTypeController.java new file mode 100644 index 0000000..acb9936 --- /dev/null +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/controller/DeviceTypeController.java @@ -0,0 +1,93 @@ +package com.njcn.gather.device.types.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.device.types.pojo.param.DeviceTypeParam; +import com.njcn.gather.device.types.pojo.vo.DeviceTypeVO; +import com.njcn.gather.device.types.pojo.vo.PqdifCheckPlaceholderVO; +import com.njcn.gather.device.types.service.DeviceTypeService; +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.validation.annotation.Validated; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 设备类型维护入口。 + */ +@Slf4j +@Api(tags = "设备类型管理") +@RestController +@RequestMapping("/api/device-types") +@RequiredArgsConstructor +public class DeviceTypeController extends BaseController { + + private final DeviceTypeService deviceTypeService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("查询设备类型列表") + @ApiImplicitParam(name = "keyword", value = "keyword", required = false) + @GetMapping + public HttpResult> listDeviceTypes(@RequestParam(value = "keyword", required = false) String keyword) { + String methodDescribe = getMethodDescribe("listDeviceTypes"); + LogUtil.njcnDebug(log, "{},开始查询设备类型列表", methodDescribe); + List result = deviceTypeService.listDeviceTypes(keyword); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("新增设备类型") + @PostMapping("/add") + public HttpResult add(@RequestBody @Validated DeviceTypeParam param) { + String methodDescribe = getMethodDescribe("add"); + LogUtil.njcnDebug(log, "{},开始新增设备类型", methodDescribe); + boolean result = deviceTypeService.addDeviceType(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("编辑设备类型") + @PostMapping("/update") + public HttpResult update(@RequestBody @Validated DeviceTypeParam.UpdateParam param) { + String methodDescribe = getMethodDescribe("update"); + LogUtil.njcnDebug(log, "{},开始编辑设备类型,devTypeId={}", methodDescribe, param.getId()); + boolean result = deviceTypeService.updateDeviceType(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("删除设备类型") + @PostMapping("/delete") + public HttpResult delete(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},开始删除设备类型,ids={}", methodDescribe, ids); + boolean result = deviceTypeService.deleteDeviceType(ids); + 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 = deviceTypeService.pqdifCheck(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } +} diff --git a/tools/device-types/src/main/java/com/njcn/gather/device/types/mapper/CsDevTypeMapper.java b/tools/device-types/src/main/java/com/njcn/gather/device/types/mapper/CsDevTypeMapper.java new file mode 100644 index 0000000..0175a19 --- /dev/null +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/mapper/CsDevTypeMapper.java @@ -0,0 +1,16 @@ +package com.njcn.gather.device.types.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.njcn.gather.device.types.pojo.po.CsDevTypePO; +import com.njcn.gather.device.types.pojo.vo.DeviceTypeVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 设备类型 Mapper。 + */ +public interface CsDevTypeMapper extends BaseMapper { + + List selectDeviceTypeCheckList(@Param("keyword") String keyword); +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml b/tools/device-types/src/main/java/com/njcn/gather/device/types/mapper/mapping/CsDevTypeMapper.xml similarity index 55% rename from tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml rename to tools/device-types/src/main/java/com/njcn/gather/device/types/mapper/mapping/CsDevTypeMapper.xml index 60d386a..e2e78ae 100644 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsDevTypeMapper.xml +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/mapper/mapping/CsDevTypeMapper.xml @@ -1,10 +1,10 @@ - + diff --git a/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/param/DeviceTypeParam.java b/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/param/DeviceTypeParam.java new file mode 100644 index 0000000..eb90d76 --- /dev/null +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/param/DeviceTypeParam.java @@ -0,0 +1,54 @@ +package com.njcn.gather.device.types.pojo.param; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * 设备类型保存参数。 + */ +@Data +@ApiModel("设备类型保存参数") +public class DeviceTypeParam { + + @ApiModelProperty("设备类型名称") + @NotBlank(message = "设备类型名称不能为空") + private String name; + + @ApiModelProperty("关联ICD ID") + private String icd; + + @ApiModelProperty("功率字典code,不带单位,前端从字典下拉选择后提交code") + private String power; + + @ApiModelProperty("设备电压字典code,不带单位,前端从字典下拉选择后提交code") + private Float devVolt; + + @ApiModelProperty("设备电流字典code,不带单位,前端从字典下拉选择后提交code") + private Float devCurr; + + @ApiModelProperty("通道数字典code,不带单位,前端从字典下拉选择后提交code") + private Integer devChns; + + @ApiModelProperty("录波命令") + private String waveCmd; + + @ApiModelProperty("报告模板名称") + private String reportName; + + /** + * 设备类型编辑参数。 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @ApiModel("设备类型编辑参数") + public static class UpdateParam extends DeviceTypeParam { + + @ApiModelProperty("设备类型ID") + @NotBlank(message = "设备类型ID不能为空") + private String id; + } +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java b/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/po/CsDevTypePO.java similarity index 96% rename from tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java rename to tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/po/CsDevTypePO.java index 50be80f..679b8a7 100644 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/po/CsDevTypePO.java +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/po/CsDevTypePO.java @@ -1,4 +1,4 @@ -package com.njcn.gather.icd.mapping.pojo.po; +package com.njcn.gather.device.types.pojo.po; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java b/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/vo/DeviceTypeVO.java similarity index 57% rename from tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java rename to tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/vo/DeviceTypeVO.java index fe4ff16..448ee29 100644 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MmsDeviceTypeVO.java +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/vo/DeviceTypeVO.java @@ -1,4 +1,4 @@ -package com.njcn.gather.icd.mapping.pojo.vo; +package com.njcn.gather.device.types.pojo.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -9,7 +9,7 @@ import lombok.Data; */ @Data @ApiModel("设备类型校验列表项") -public class MmsDeviceTypeVO { +public class DeviceTypeVO { @ApiModelProperty("设备类型ID") private String id; @@ -26,12 +26,27 @@ public class MmsDeviceTypeVO { @ApiModelProperty("ICD路径") private String icdPath; - @ApiModelProperty("ICD校验结论:0-否,1-是") + @ApiModelProperty("ICD校验结论,0-否,1-是") private Integer icdResult; @ApiModelProperty("ICD校验结论描述") private String icdMsg; + @ApiModelProperty("功率字典code,不带单位,单位由前端展示") + private String power; + + @ApiModelProperty("设备电压字典code,不带单位,单位由前端展示") + private Float devVolt; + + @ApiModelProperty("设备电流字典code,不带单位,单位由前端展示") + private Float devCurr; + + @ApiModelProperty("通道数字典code,不带单位,单位由前端展示") + private Integer devChns; + + @ApiModelProperty("录波命令") + private String waveCmd; + @ApiModelProperty("报告模板名称") private String reportName; diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java b/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/vo/PqdifCheckPlaceholderVO.java similarity index 88% rename from tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java rename to tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/vo/PqdifCheckPlaceholderVO.java index 8f86b69..f5f4c2b 100644 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/PqdifCheckPlaceholderVO.java +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/pojo/vo/PqdifCheckPlaceholderVO.java @@ -1,4 +1,4 @@ -package com.njcn.gather.icd.mapping.pojo.vo; +package com.njcn.gather.device.types.pojo.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; diff --git a/tools/device-types/src/main/java/com/njcn/gather/device/types/service/DeviceTypeService.java b/tools/device-types/src/main/java/com/njcn/gather/device/types/service/DeviceTypeService.java new file mode 100644 index 0000000..602ef02 --- /dev/null +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/service/DeviceTypeService.java @@ -0,0 +1,23 @@ +package com.njcn.gather.device.types.service; + +import com.njcn.gather.device.types.pojo.param.DeviceTypeParam; +import com.njcn.gather.device.types.pojo.vo.DeviceTypeVO; +import com.njcn.gather.device.types.pojo.vo.PqdifCheckPlaceholderVO; + +import java.util.List; + +/** + * 设备类型服务。 + */ +public interface DeviceTypeService { + + List listDeviceTypes(String keyword); + + boolean addDeviceType(DeviceTypeParam param); + + boolean updateDeviceType(DeviceTypeParam.UpdateParam param); + + boolean deleteDeviceType(List ids); + + PqdifCheckPlaceholderVO pqdifCheck(String devTypeId); +} diff --git a/tools/device-types/src/main/java/com/njcn/gather/device/types/service/impl/DeviceTypeServiceImpl.java b/tools/device-types/src/main/java/com/njcn/gather/device/types/service/impl/DeviceTypeServiceImpl.java new file mode 100644 index 0000000..d5af722 --- /dev/null +++ b/tools/device-types/src/main/java/com/njcn/gather/device/types/service/impl/DeviceTypeServiceImpl.java @@ -0,0 +1,155 @@ +package com.njcn.gather.device.types.service.impl; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.njcn.gather.device.types.mapper.CsDevTypeMapper; +import com.njcn.gather.device.types.pojo.param.DeviceTypeParam; +import com.njcn.gather.device.types.pojo.po.CsDevTypePO; +import com.njcn.gather.device.types.pojo.vo.DeviceTypeVO; +import com.njcn.gather.device.types.pojo.vo.PqdifCheckPlaceholderVO; +import com.njcn.gather.device.types.service.DeviceTypeService; +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; +import java.util.UUID; + +/** + * 设备类型服务实现。 + */ +@Service +@RequiredArgsConstructor +public class DeviceTypeServiceImpl implements DeviceTypeService { + + private static final int STATE_NORMAL = 1; + + private static final int STATE_DELETED = 0; + + private static final String PQDIF_NOT_SUPPORTED = "NOT_SUPPORTED"; + + private final CsDevTypeMapper csDevTypeMapper; + + @Override + public List listDeviceTypes(String keyword) { + return csDevTypeMapper.selectDeviceTypeCheckList(trimToNull(keyword)); + } + + @Override + @Transactional + public boolean addDeviceType(DeviceTypeParam param) { + DeviceTypeParam checkedParam = requireParam(param); + LocalDateTime now = LocalDateTime.now(); + CsDevTypePO devType = buildDevType(checkedParam); + devType.setId(UUID.randomUUID().toString().replace("-", "")); + devType.setState(STATE_NORMAL); + devType.setCreateBy(currentUserId()); + devType.setCreateTime(now); + devType.setUpdateBy(currentUserId()); + devType.setUpdateTime(now); + return csDevTypeMapper.insert(devType) > 0; + } + + @Override + @Transactional + public boolean updateDeviceType(DeviceTypeParam.UpdateParam param) { + DeviceTypeParam.UpdateParam checkedParam = requireUpdateParam(param); + requireDevType(checkedParam.getId()); + CsDevTypePO devType = buildDevType(checkedParam); + devType.setId(checkedParam.getId()); + devType.setUpdateBy(currentUserId()); + devType.setUpdateTime(LocalDateTime.now()); + return csDevTypeMapper.updateById(devType) > 0; + } + + @Override + @Transactional + public boolean deleteDeviceType(List ids) { + if (ids == null || ids.isEmpty()) { + throw new IllegalArgumentException("设备类型ID不能为空"); + } + CsDevTypePO devType = new CsDevTypePO(); + devType.setState(STATE_DELETED); + devType.setUpdateBy(currentUserId()); + devType.setUpdateTime(LocalDateTime.now()); + return csDevTypeMapper.update(devType, new LambdaUpdateWrapper() + .in(CsDevTypePO::getId, ids) + .eq(CsDevTypePO::getState, STATE_NORMAL)) > 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 buildDevType(DeviceTypeParam param) { + CsDevTypePO devType = new CsDevTypePO(); + devType.setName(requireText(param.getName(), "设备类型名称不能为空")); + devType.setIcd(trimToNull(param.getIcd())); + devType.setPower(trimToNull(param.getPower())); + devType.setDevVolt(param.getDevVolt()); + devType.setDevCurr(param.getDevCurr()); + devType.setDevChns(param.getDevChns()); + devType.setWaveCmd(trimToNull(param.getWaveCmd())); + devType.setReportName(trimToNull(param.getReportName())); + return devType; + } + + private DeviceTypeParam requireParam(DeviceTypeParam param) { + if (param == null) { + throw new IllegalArgumentException("设备类型参数不能为空"); + } + return param; + } + + private DeviceTypeParam.UpdateParam requireUpdateParam(DeviceTypeParam.UpdateParam param) { + if (param == null) { + throw new IllegalArgumentException("设备类型参数不能为空"); + } + requireText(param.getId(), "设备类型ID不能为空"); + return param; + } + + 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; + } + + 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/device-types/src/test/java/com/njcn/gather/device/types/service/impl/DeviceTypeServiceImplTest.java b/tools/device-types/src/test/java/com/njcn/gather/device/types/service/impl/DeviceTypeServiceImplTest.java new file mode 100644 index 0000000..9ff98e9 --- /dev/null +++ b/tools/device-types/src/test/java/com/njcn/gather/device/types/service/impl/DeviceTypeServiceImplTest.java @@ -0,0 +1,89 @@ +package com.njcn.gather.device.types.service.impl; + +import com.njcn.gather.device.types.mapper.CsDevTypeMapper; +import com.njcn.gather.device.types.pojo.param.DeviceTypeParam; +import com.njcn.gather.device.types.pojo.po.CsDevTypePO; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class DeviceTypeServiceImplTest { + + private final CsDevTypeMapper csDevTypeMapper = mock(CsDevTypeMapper.class); + private final DeviceTypeServiceImpl service = new DeviceTypeServiceImpl(csDevTypeMapper); + + @Test + void listDeviceTypesShouldTrimKeywordBeforeQuery() { + service.listDeviceTypes(" PQ装置 "); + + verify(csDevTypeMapper).selectDeviceTypeCheckList(eq("PQ装置")); + } + + @Test + void addDeviceTypeShouldInsertEnabledRecord() { + DeviceTypeParam param = buildParam("设备类型A"); + when(csDevTypeMapper.insert(any(CsDevTypePO.class))).thenReturn(1); + + boolean result = service.addDeviceType(param); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CsDevTypePO.class); + verify(csDevTypeMapper).insert(captor.capture()); + Assertions.assertTrue(result); + Assertions.assertEquals("10", captor.getValue().getPower()); + Assertions.assertEquals("设备类型A", captor.getValue().getName()); + Assertions.assertEquals(1, captor.getValue().getState()); + Assertions.assertNotNull(captor.getValue().getId()); + Assertions.assertNotNull(captor.getValue().getCreateTime()); + } + + @Test + void updateDeviceTypeShouldRejectDeletedRecord() { + DeviceTypeParam.UpdateParam param = new DeviceTypeParam.UpdateParam(); + param.setId("dev-type-001"); + param.setName("设备类型A"); + + CsDevTypePO deleted = new CsDevTypePO(); + deleted.setId("dev-type-001"); + deleted.setState(0); + when(csDevTypeMapper.selectById(eq("dev-type-001"))).thenReturn(deleted); + + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, + () -> service.updateDeviceType(param)); + + Assertions.assertEquals("设备类型不存在或已删除", exception.getMessage()); + } + + @Test + void deleteDeviceTypeShouldMarkRecordDeleted() { + when(csDevTypeMapper.update(any(CsDevTypePO.class), any())).thenReturn(1); + + boolean result = service.deleteDeviceType(Collections.singletonList("dev-type-001")); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CsDevTypePO.class); + verify(csDevTypeMapper).update(captor.capture(), any()); + Assertions.assertTrue(result); + Assertions.assertEquals(0, captor.getValue().getState()); + Assertions.assertNotNull(captor.getValue().getUpdateTime()); + } + + private DeviceTypeParam buildParam(String name) { + DeviceTypeParam param = new DeviceTypeParam(); + param.setName(name); + param.setIcd("icd-001"); + param.setPower("10"); + param.setDevVolt(10.0F); + param.setDevCurr(5.0F); + param.setDevChns(4); + param.setWaveCmd("cmd"); + param.setReportName("report"); + return param; + } +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/component/IcdConsistencyCheckService.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/component/IcdConsistencyCheckService.java new file mode 100644 index 0000000..98a8c72 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/component/IcdConsistencyCheckService.java @@ -0,0 +1,546 @@ +package com.njcn.gather.icd.mapping.component; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.njcn.gather.icd.mapping.pojo.bo.mapping.DataSetGroupItem; +import com.njcn.gather.icd.mapping.pojo.bo.mapping.DoiItem; +import com.njcn.gather.icd.mapping.pojo.bo.mapping.InstItem; +import com.njcn.gather.icd.mapping.pojo.bo.mapping.MappingDocument; +import com.njcn.gather.icd.mapping.pojo.bo.mapping.ReportMapItem; +import com.njcn.gather.icd.mapping.pojo.bo.mapping.SdiItem; +import com.njcn.gather.icd.mapping.pojo.param.IcdConsistencyCheckRequest; +import com.njcn.gather.icd.mapping.pojo.vo.IcdConsistencyCheckResponse; +import com.njcn.gather.icd.mapping.pojo.vo.IcdConsistencyIssue; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * ICD 映射 JSON 一致性校验服务。 + */ +@Component +@RequiredArgsConstructor +public class IcdConsistencyCheckService { + + private static final String PASS_MESSAGE = "符合"; + private static final String NOT_PASS_MESSAGE = "不符合"; + private static final String ISSUE_FILE_NAME = "icd-consistency-issues.json"; + private static final List REQUIRED_REPORT_DESCS = Arrays.asList("统计数据", "波动闪变", "实时数据", "暂态事件"); + private static final List REQUIRED_LN_CLASSES = Arrays.asList("MMXU", "MSQI", "MHAI", "MFLK"); + + private final FileStorageService fileStorageService; + private final ObjectMapper objectMapper = buildMapper(); + + public IcdConsistencyCheckResponse check(IcdConsistencyCheckRequest request) { + if (request == null) { + throw new IllegalArgumentException("ICD 一致性校验请求不能为空"); + } + MappingDocument checked = parseMapping(request.getCheckedJson(), "待校验 JSON"); + MappingDocument standard = parseMapping(request.getStandardJson(), "标准 JSON"); + + List issues = new ArrayList(); + validateSelfFormat(checked, "待校验映射", issues); + boolean corrected = applySelfMappingRules(checked, issues); + validateConsistency(checked, standard, issues); + corrected = applyMappingCorrections(checked, standard, issues) || corrected; + + IcdConsistencyCheckResponse response = new IcdConsistencyCheckResponse(); + response.setResult(issues.isEmpty() ? 1 : 0); + response.setMessage(issues.isEmpty() ? PASS_MESSAGE : NOT_PASS_MESSAGE); + response.getIssues().addAll(issues); + if (!issues.isEmpty()) { + response.setIssuesJson(toJson(issues)); + if (request.isSaveToDisk()) { + fileStorageService.save(ISSUE_FILE_NAME, response.getIssuesJson(), request.getOutputDir()); + } + } + if (corrected) { + response.setCorrectedJson(toJson(checked)); + } + return response; + } + + private MappingDocument parseMapping(String json, String label) { + if (isBlank(json)) { + throw new IllegalArgumentException(label + "不能为空"); + } + try { + return objectMapper.readValue(json, MappingDocument.class); + } catch (Exception ex) { + throw new IllegalArgumentException(label + "解析失败:" + ex.getMessage(), ex); + } + } + + private void validateSelfFormat(MappingDocument document, String label, List issues) { + if (document == null) { + addIssue(issues, "自身格式校验", label, label + "不能为空", null, null, false); + return; + } + requireText(document.getIed(), "IED", issues); + requireText(document.getLd(), "LD", issues); + requireText(document.getDataType(), "DataType", issues); + requireText(document.getUnit(), "unit", issues); + + validateRequiredReportDescs(document, issues); + validateRequiredLnClasses(document, issues); + validateDoiDuplicateNames(document, issues); + validateNestedLists(document, issues); + } + + private void requireText(String value, String path, List issues) { + if (isBlank(value)) { + addIssue(issues, "自身格式校验", path, path + "不能为空", null, value, false); + } + } + + private void validateRequiredReportDescs(MappingDocument document, List issues) { + Set actualDescs = new HashSet(); + if (document.getReportMap() != null) { + for (ReportMapItem item : document.getReportMap()) { + actualDescs.add(trimToEmpty(item.getDesc())); + } + } + for (String desc : REQUIRED_REPORT_DESCS) { + if (!actualDescs.contains(desc)) { + addIssue(issues, "自身格式校验", "ReportMap.desc", "ReportMap 最少需要包含一条" + desc, desc, null, false); + } + } + } + + private void validateRequiredLnClasses(MappingDocument document, List issues) { + Set actualLnClasses = new HashSet(); + if (document.getDataSetList() != null) { + for (DataSetGroupItem item : document.getDataSetList()) { + actualLnClasses.add(trimToEmpty(item.getLnClass())); + } + } + for (String lnClass : REQUIRED_LN_CLASSES) { + if (!actualLnClasses.contains(lnClass)) { + addIssue(issues, "自身格式校验", "DataSetList.lnClass", "DataSetList 最少需要包含一组 lnClass=" + lnClass, lnClass, null, false); + } + } + } + + private void validateDoiDuplicateNames(MappingDocument document, List issues) { + if (document.getDataSetList() == null) { + return; + } + for (DataSetGroupItem group : document.getDataSetList()) { + if (group.getInstList() == null) { + continue; + } + for (InstItem inst : group.getInstList()) { + Set doiKeys = new HashSet(); + if (inst.getDoiList() == null) { + continue; + } + for (DoiItem doi : inst.getDoiList()) { + String key = buildKey(doi.getName(), doi.getDesc()); + if (!doiKeys.add(key)) { + String path = buildInstPath(group, inst) + ".doiList"; + addIssue(issues, "自身格式校验", path, "同一个 doiList 下不能存在 name+desc 都一样的指标", key, key, false); + } + } + } + } + } + + private void validateNestedLists(MappingDocument document, List issues) { + if (document.getDataSetList() == null) { + return; + } + for (DataSetGroupItem group : document.getDataSetList()) { + if (isEmpty(group.getInstList())) { + addIssue(issues, "自身格式校验", buildGroupPath(group), "instList 不能为空:" + trimToEmpty(group.getDesc()), null, null, false); + continue; + } + for (InstItem inst : group.getInstList()) { + String instPath = buildInstPath(group, inst); + if (isEmpty(inst.getDoiList())) { + addIssue(issues, "自身格式校验", instPath, "doiList 不能为空:" + joinDesc(group.getDesc(), inst.getDesc()), null, null, false); + continue; + } + for (DoiItem doi : inst.getDoiList()) { + if (isEmpty(doi.getSdiList())) { + addIssue(issues, "自身格式校验", instPath + ".doiList[" + buildKey(doi.getName(), doi.getDesc()) + "]", + "sdiList 不能为空:" + joinDesc(group.getDesc(), inst.getDesc(), doi.getDesc()), null, null, false); + continue; + } + for (SdiItem sdi : doi.getSdiList()) { + if (isEmpty(sdi.getTypeList())) { + addIssue(issues, "自身格式校验", instPath + ".doiList[" + buildKey(doi.getName(), doi.getDesc()) + "].sdiList[" + buildKey(sdi.getName(), sdi.getDesc()) + "]", + "typeList 不能为空:" + joinDesc(group.getDesc(), inst.getDesc(), doi.getDesc(), sdi.getDesc()), null, null, false); + } + } + } + } + } + } + + private void validateConsistency(MappingDocument checked, MappingDocument standard, List issues) { + compareValue(standard.getIed(), checked.getIed(), "基础字段", "IED", issues, false); + compareValue(standard.getLd(), checked.getLd(), "基础字段", "LD", issues, false); + compareValue(standard.getDataType(), checked.getDataType(), "基础字段", "DataType", issues, false); + compareValue(standard.getUnit(), checked.getUnit(), "基础字段", "unit", issues, true); + validateReportMapConsistency(checked, standard, issues); + validateDataSetConsistency(checked, standard, issues); + } + + private boolean applySelfMappingRules(MappingDocument checked, List issues) { + if (checked.getReportMap() == null) { + return false; + } + boolean hasRtFre = false; + boolean corrected = false; + for (ReportMapItem item : checked.getReportMap()) { + if ("实时数据".equals(trimToEmpty(item.getDesc())) && trimToEmpty(item.getRptId()).contains("RtFre")) { + hasRtFre = true; + if (!"1".equals(trimToEmpty(item.getFlickerFlag()))) { + addIssue(issues, "映射规则", "ReportMap[" + buildReportKey(item) + "].FlickerFlag", + "实时数据报告 rptID 包含 RtFre,FlickerFlag 已按规则调整为 1", "1", item.getFlickerFlag(), true); + item.setFlickerFlag("1"); + corrected = true; + } + if (item.getReportCount() != 0) { + addIssue(issues, "映射规则", "ReportMap[" + buildReportKey(item) + "].reportCount", + "实时数据报告 rptID 包含 RtFre,reportCount 已按规则调整为 0", "0", String.valueOf(item.getReportCount()), true); + item.setReportCount(0); + corrected = true; + } + } + } + if (!hasRtFre) { + return false; + } + for (ReportMapItem item : checked.getReportMap()) { + if ("统计数据".equals(trimToEmpty(item.getDesc()))) { + int adjustedCount = Math.max(0, item.getReportCount() - 1); + if (item.getReportCount() != adjustedCount) { + addIssue(issues, "映射规则", "ReportMap[" + buildReportKey(item) + "].reportCount", + "存在 RtFre 实时数据报告,统计数据 reportCount 已按规则减 1", + String.valueOf(adjustedCount), String.valueOf(item.getReportCount()), true); + item.setReportCount(adjustedCount); + corrected = true; + } + } + } + return corrected; + } + + private void validateReportMapConsistency(MappingDocument checked, MappingDocument standard, List issues) { + Map checkedMap = new HashMap(); + if (checked.getReportMap() != null) { + for (ReportMapItem item : checked.getReportMap()) { + checkedMap.put(buildReportKey(item), item); + } + } + if (standard.getReportMap() == null) { + return; + } + for (ReportMapItem standardItem : standard.getReportMap()) { + String key = buildReportKey(standardItem); + ReportMapItem checkedItem = checkedMap.get(key); + if (checkedItem == null) { + addIssue(issues, "一致性校验", "ReportMap[" + key + "]", "标准映射中的 ReportMap 对象在待校验映射中不存在", key, null, false); + continue; + } + compareValue(String.valueOf(standardItem.getReportCount()), String.valueOf(checkedItem.getReportCount()), "ReportMap", "ReportMap[" + key + "].reportCount", issues, true); + compareValue(standardItem.getBuffered(), checkedItem.getBuffered(), "ReportMap", "ReportMap[" + key + "].buffered", issues, true); + compareValue(standardItem.getInst(), checkedItem.getInst(), "ReportMap", "ReportMap[" + key + "].inst", issues, true); + compareValue(standardItem.getFlickerFlag(), checkedItem.getFlickerFlag(), "ReportMap", "ReportMap[" + key + "].FlickerFlag", issues, true); + compareValue(standardItem.getSelect(), checkedItem.getSelect(), "ReportMap", "ReportMap[" + key + "].Select", issues, true); + compareValue(standardItem.getTrgOps(), checkedItem.getTrgOps(), "ReportMap", "ReportMap[" + key + "].TrgOps", issues, true); + } + } + + private void validateDataSetConsistency(MappingDocument checked, MappingDocument standard, List issues) { + Map checkedGroups = indexGroups(checked); + if (standard.getDataSetList() == null) { + return; + } + for (DataSetGroupItem standardGroup : standard.getDataSetList()) { + String groupKey = buildKey(standardGroup.getLnClass(), standardGroup.getDesc()); + DataSetGroupItem checkedGroup = checkedGroups.get(groupKey); + if (checkedGroup == null) { + addIssue(issues, "一致性校验", "DataSetList[" + groupKey + "]", "标准映射中的 lnClass+desc 在待校验映射中不存在", groupKey, null, false); + continue; + } + validateInstConsistency(checkedGroup, standardGroup, groupKey, issues); + } + } + + private void validateInstConsistency(DataSetGroupItem checkedGroup, DataSetGroupItem standardGroup, String groupKey, List issues) { + Map checkedInstMap = indexInst(checkedGroup); + if (standardGroup.getInstList() == null) { + return; + } + for (InstItem standardInst : standardGroup.getInstList()) { + String instKey = buildKey(standardInst.getInst(), standardInst.getDesc()); + InstItem checkedInst = checkedInstMap.get(instKey); + if (checkedInst == null) { + addIssue(issues, "一致性校验", "DataSetList[" + groupKey + "].instList[" + instKey + "]", + "标准映射中的 inst+desc 在待校验映射中不存在", instKey, null, false); + continue; + } + validateDoiConsistency(checkedInst, standardInst, groupKey, instKey, issues); + } + } + + private void validateDoiConsistency(InstItem checkedInst, InstItem standardInst, String groupKey, String instKey, List issues) { + Map checkedDoiMap = indexDoi(checkedInst); + if (standardInst.getDoiList() == null) { + return; + } + for (DoiItem standardDoi : standardInst.getDoiList()) { + String doiKey = buildKey(standardDoi.getName(), standardDoi.getDesc()); + DoiItem checkedDoi = checkedDoiMap.get(doiKey); + String path = "DataSetList[" + groupKey + "].instList[" + instKey + "].doiList[" + doiKey + "]"; + if (checkedDoi == null) { + addIssue(issues, "一致性校验", path, "标准映射中的 name+desc 在待校验映射中不存在", doiKey, null, false); + continue; + } + compareDoiFields(checkedDoi, standardDoi, path, issues); + } + } + + private void compareDoiFields(DoiItem checked, DoiItem standard, String path, List issues) { + boolean canCorrectRange = canCorrectDoiRange(checked, standard) && standard.getEnd() <= checked.getIcdcout(); + compareValue(String.valueOf(standard.getStart()), String.valueOf(checked.getStart()), "doiList", path + ".start", issues, canCorrectRange); + compareValue(String.valueOf(standard.getEnd()), String.valueOf(checked.getEnd()), "doiList", path + ".end", issues, canCorrectRange); + compareValue(standard.getUnit(), checked.getUnit(), "doiList", path + ".unit", issues, false); + compareValue(String.valueOf(standard.getCoefficient()), String.valueOf(checked.getCoefficient()), "doiList", path + ".coefficient", issues, false); + compareValue(String.valueOf(standard.getBaseflag()), String.valueOf(checked.getBaseflag()), "doiList", path + ".baseflag", issues, false); + compareValue(String.valueOf(standard.getBasecount()), String.valueOf(checked.getBasecount()), "doiList", path + ".basecount", issues, false); + compareValue(String.valueOf(standard.getIcdcout()), String.valueOf(checked.getIcdcout()), "doiList", path + ".icdcout", issues, false); + } + + private boolean applyMappingCorrections(MappingDocument checked, MappingDocument standard, List issues) { + boolean corrected = false; + if (!equalsValue(checked.getUnit(), standard.getUnit())) { + checked.setUnit(standard.getUnit()); + corrected = true; + } + corrected = correctReportMap(checked, standard) || corrected; + corrected = correctDoiRanges(checked, standard, issues) || corrected; + return corrected; + } + + private boolean correctReportMap(MappingDocument checked, MappingDocument standard) { + boolean corrected = false; + Map checkedMap = new HashMap(); + if (checked.getReportMap() != null) { + for (ReportMapItem item : checked.getReportMap()) { + checkedMap.put(buildReportKey(item), item); + } + } + if (standard.getReportMap() == null) { + return false; + } + for (ReportMapItem standardItem : standard.getReportMap()) { + ReportMapItem checkedItem = checkedMap.get(buildReportKey(standardItem)); + if (checkedItem != null && !reportOtherFieldsEqual(checkedItem, standardItem)) { + checkedItem.setReportCount(standardItem.getReportCount()); + checkedItem.setBuffered(standardItem.getBuffered()); + checkedItem.setInst(standardItem.getInst()); + checkedItem.setFlickerFlag(standardItem.getFlickerFlag()); + checkedItem.setSelect(standardItem.getSelect()); + checkedItem.setTrgOps(standardItem.getTrgOps()); + corrected = true; + } + } + return corrected; + } + + private boolean correctDoiRanges(MappingDocument checked, MappingDocument standard, List issues) { + boolean corrected = false; + Map checkedGroups = indexGroups(checked); + if (standard.getDataSetList() == null) { + return false; + } + for (DataSetGroupItem standardGroup : standard.getDataSetList()) { + DataSetGroupItem checkedGroup = checkedGroups.get(buildKey(standardGroup.getLnClass(), standardGroup.getDesc())); + if (checkedGroup == null) { + continue; + } + corrected = correctGroupDoiRanges(checkedGroup, standardGroup, issues) || corrected; + } + return corrected; + } + + private boolean correctGroupDoiRanges(DataSetGroupItem checkedGroup, DataSetGroupItem standardGroup, List issues) { + boolean corrected = false; + Map checkedInstMap = indexInst(checkedGroup); + if (standardGroup.getInstList() == null) { + return false; + } + for (InstItem standardInst : standardGroup.getInstList()) { + InstItem checkedInst = checkedInstMap.get(buildKey(standardInst.getInst(), standardInst.getDesc())); + if (checkedInst == null) { + continue; + } + corrected = correctInstDoiRanges(checkedGroup, checkedInst, standardInst, issues) || corrected; + } + return corrected; + } + + private boolean correctInstDoiRanges(DataSetGroupItem checkedGroup, InstItem checkedInst, InstItem standardInst, List issues) { + boolean corrected = false; + Map checkedDoiMap = indexDoi(checkedInst); + if (standardInst.getDoiList() == null) { + return false; + } + for (DoiItem standardDoi : standardInst.getDoiList()) { + DoiItem checkedDoi = checkedDoiMap.get(buildKey(standardDoi.getName(), standardDoi.getDesc())); + if (checkedDoi == null || !canCorrectDoiRange(checkedDoi, standardDoi)) { + continue; + } + String path = buildInstPath(checkedGroup, checkedInst) + ".doiList[" + buildKey(checkedDoi.getName(), checkedDoi.getDesc()) + "]"; + if (standardDoi.getEnd() > checkedDoi.getIcdcout()) { + addIssue(issues, "映射修改", path, "标准 end 大于待校验 icdcout,不能自动修正 start/end", + String.valueOf(standardDoi.getEnd()), String.valueOf(checkedDoi.getIcdcout()), false); + continue; + } + if (checkedDoi.getStart() != standardDoi.getStart() || checkedDoi.getEnd() != standardDoi.getEnd()) { + checkedDoi.setStart(standardDoi.getStart()); + checkedDoi.setEnd(standardDoi.getEnd()); + corrected = true; + } + } + return corrected; + } + + private void compareValue(String standardValue, String checkedValue, String scope, String path, + List issues, boolean corrected) { + if (!equalsValue(standardValue, checkedValue)) { + addIssue(issues, scope, path, path + " 与标准映射不一致", standardValue, checkedValue, corrected); + } + } + + private void addIssue(List issues, String scope, String path, String message, + String standardValue, String checkedValue, boolean corrected) { + IcdConsistencyIssue issue = new IcdConsistencyIssue(); + issue.setScope(scope); + issue.setPath(path); + issue.setMessage(message); + issue.setStandardValue(standardValue); + issue.setCheckedValue(checkedValue); + issue.setCorrected(corrected); + issues.add(issue); + } + + private Map indexGroups(MappingDocument document) { + Map result = new HashMap(); + if (document.getDataSetList() == null) { + return result; + } + for (DataSetGroupItem item : document.getDataSetList()) { + result.put(buildKey(item.getLnClass(), item.getDesc()), item); + } + return result; + } + + private Map indexInst(DataSetGroupItem group) { + Map result = new HashMap(); + if (group.getInstList() == null) { + return result; + } + for (InstItem item : group.getInstList()) { + result.put(buildKey(item.getInst(), item.getDesc()), item); + } + return result; + } + + private Map indexDoi(InstItem inst) { + Map result = new HashMap(); + if (inst.getDoiList() == null) { + return result; + } + for (DoiItem item : inst.getDoiList()) { + result.put(buildKey(item.getName(), item.getDesc()), item); + } + return result; + } + + private String buildReportKey(ReportMapItem item) { + return buildKey(item.getDesc(), item.getRptId(), item.getName()); + } + + private String buildGroupPath(DataSetGroupItem group) { + return "DataSetList[" + buildKey(group.getLnClass(), group.getDesc()) + "]"; + } + + private String buildInstPath(DataSetGroupItem group, InstItem inst) { + return buildGroupPath(group) + ".instList[" + buildKey(inst.getInst(), inst.getDesc()) + "]"; + } + + private String buildKey(String... values) { + List parts = new ArrayList(); + for (String value : values) { + parts.add(trimToEmpty(value)); + } + return String.join("+", parts); + } + + private boolean reportOtherFieldsEqual(ReportMapItem checked, ReportMapItem standard) { + return checked.getReportCount() == standard.getReportCount() + && equalsValue(checked.getBuffered(), standard.getBuffered()) + && equalsValue(checked.getInst(), standard.getInst()) + && equalsValue(checked.getFlickerFlag(), standard.getFlickerFlag()) + && equalsValue(checked.getSelect(), standard.getSelect()) + && equalsValue(checked.getTrgOps(), standard.getTrgOps()); + } + + private boolean canCorrectDoiRange(DoiItem checked, DoiItem standard) { + return checked.getBaseflag() == standard.getBaseflag() + && (checked.getBaseflag() == 1 || checked.getBaseflag() == 2); + } + + private boolean equalsValue(String left, String right) { + return trimToEmpty(left).equals(trimToEmpty(right)); + } + + private boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + private String trimToEmpty(String value) { + return value == null ? "" : value.trim(); + } + + private boolean isEmpty(List list) { + return list == null || list.isEmpty(); + } + + private String joinDesc(String... descs) { + List values = new ArrayList(); + for (String desc : descs) { + if (!isBlank(desc)) { + values.add(desc.trim()); + } + } + return String.join(" / ", values); + } + + private String toJson(Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (Exception ex) { + throw new IllegalArgumentException("ICD 一致性校验结果序列化失败:" + ex.getMessage(), ex); + } + } + + private static ObjectMapper buildMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + return mapper; + } +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/CsIcdPathController.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/CsIcdPathController.java new file mode 100644 index 0000000..0625d35 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/CsIcdPathController.java @@ -0,0 +1,151 @@ +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.CsIcdPathParam; +import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam; +import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO; +import com.njcn.gather.icd.mapping.service.CsIcdPathService; +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.validation.annotation.Validated; +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.RequestPart; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +/** + * ICD 存储记录维护入口。 + */ +@Slf4j +@Api(tags = "ICD存储记录管理") +@RestController +@RequestMapping("/api/mms-mapping/icd-paths") +@RequiredArgsConstructor +public class CsIcdPathController extends BaseController { + + private final CsIcdPathService csIcdPathService; + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("查询ICD存储记录列表") + @PostMapping("/list") + public HttpResult> list(@RequestBody(required = false) CsIcdPathParam.ListParam param) { + String methodDescribe = getMethodDescribe("list"); + LogUtil.njcnDebug(log, "{},开始查询ICD存储记录列表", methodDescribe); + List result = csIcdPathService.listIcdPaths(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("新增ICD存储记录") + @PostMapping(value = "/add", consumes = {"application/json"}) + public HttpResult add(@RequestBody @Validated CsIcdPathParam param) { + String methodDescribe = getMethodDescribe("add"); + LogUtil.njcnDebug(log, "{},开始新增ICD存储记录", methodDescribe); + boolean result = csIcdPathService.addIcdPath(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("上传并新增ICD存储记录") + @PostMapping(value = "/add", consumes = {"multipart/form-data"}) + public HttpResult addWithFile(@RequestPart("icdFile") MultipartFile icdFile, + @RequestPart("request") @Validated CsIcdPathParam param) { + String methodDescribe = getMethodDescribe("addWithFile"); + LogUtil.njcnDebug(log, "{},开始上传并新增ICD存储记录,fileName={}", methodDescribe, resolveFileName(icdFile)); + fillIcdFile(param, icdFile); + boolean result = csIcdPathService.addIcdPath(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("编辑ICD存储记录") + @PostMapping(value = "/update", consumes = {"application/json"}) + public HttpResult update(@RequestBody @Validated CsIcdPathParam.UpdateParam param) { + String methodDescribe = getMethodDescribe("update"); + LogUtil.njcnDebug(log, "{},开始编辑ICD存储记录,icdId={}", methodDescribe, param.getId()); + boolean result = csIcdPathService.updateIcdPath(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("上传并编辑ICD存储记录") + @PostMapping(value = "/update", consumes = {"multipart/form-data"}) + public HttpResult updateWithFile(@RequestPart("icdFile") MultipartFile icdFile, + @RequestPart("request") @Validated CsIcdPathParam.UpdateParam param) { + String methodDescribe = getMethodDescribe("updateWithFile"); + LogUtil.njcnDebug(log, "{},开始上传并编辑ICD存储记录,icdId={},fileName={}", methodDescribe, param.getId(), resolveFileName(icdFile)); + fillIcdFile(param, icdFile); + boolean result = csIcdPathService.updateIcdPath(param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("激活ICD存储记录") + @ApiImplicitParam(name = "id", value = "ICD记录ID", required = true) + @PostMapping("/{id}/activate") + public HttpResult activate(@PathVariable("id") String id) { + String methodDescribe = getMethodDescribe("activate"); + LogUtil.njcnDebug(log, "{},开始激活ICD存储记录,icdId={}", methodDescribe, id); + boolean result = csIcdPathService.activateIcdPath(id); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("删除ICD存储记录") + @PostMapping("/delete") + public HttpResult delete(@RequestBody List ids) { + String methodDescribe = getMethodDescribe("delete"); + LogUtil.njcnDebug(log, "{},开始删除ICD存储记录,ids={}", methodDescribe, ids); + boolean result = csIcdPathService.deleteIcdPath(ids); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("保存ICD唯一性校验结果") + @ApiImplicitParam(name = "id", value = "ICD记录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校验结果,icdId={}", methodDescribe, id); + boolean result = csIcdPathService.saveIcdCheckResult(id, param); + return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); + } + + private void fillIcdFile(CsIcdPathParam param, MultipartFile icdFile) { + if (icdFile == null || icdFile.isEmpty()) { + throw new IllegalArgumentException("ICD文件不能为空"); + } + try { + param.setIcdContent(icdFile.getBytes()); + if (param.getPath() == null || param.getPath().trim().isEmpty()) { + param.setPath(resolveFileName(icdFile)); + } + } catch (IOException ex) { + throw new IllegalArgumentException("读取ICD文件失败:" + ex.getMessage(), ex); + } + } + + private String resolveFileName(MultipartFile icdFile) { + if (icdFile == null || icdFile.getOriginalFilename() == null) { + return null; + } + String fileName = icdFile.getOriginalFilename().trim(); + return fileName.isEmpty() ? null : fileName; + } +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MappingController.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MappingController.java index 2694fc1..76b052e 100644 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MappingController.java +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MappingController.java @@ -6,6 +6,7 @@ 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.component.IcdToXmlResponseConverter; +import com.njcn.gather.icd.mapping.component.IcdConsistencyCheckService; import com.njcn.gather.icd.mapping.component.IndexSelectionBuildService; import com.njcn.gather.icd.mapping.component.MappingRequestConverter; import com.njcn.gather.icd.mapping.component.MappingResponseConverter; @@ -14,9 +15,11 @@ import com.njcn.gather.icd.mapping.pojo.bo.IcdToXmlGenerateResult; import com.njcn.gather.icd.mapping.pojo.dto.GenerateFromIcdCommand; import com.njcn.gather.icd.mapping.pojo.param.BuildIndexSelectionRequest; import com.njcn.gather.icd.mapping.pojo.param.GenerateMappingFromIcdRequest; +import com.njcn.gather.icd.mapping.pojo.param.IcdConsistencyCheckRequest; import com.njcn.gather.icd.mapping.pojo.param.IndexCandidateRequest; import com.njcn.gather.icd.mapping.pojo.param.JsonToXmlRequest; import com.njcn.gather.icd.mapping.pojo.param.SubmitIndexSelectionRequest; +import com.njcn.gather.icd.mapping.pojo.vo.IcdConsistencyCheckResponse; import com.njcn.gather.icd.mapping.pojo.vo.IcdToXmlResponse; import com.njcn.gather.icd.mapping.pojo.vo.IndexConfirmGroupResponse; import com.njcn.gather.icd.mapping.pojo.vo.IndexSelectionGroupResponse; @@ -70,6 +73,9 @@ public class MappingController extends BaseController { /** ICD 结构确认弹窗结果组装服务。 */ private final IndexSelectionBuildService indexSelectionBuildService; + /** ICD 映射 JSON 一致性校验服务。 */ + private final IcdConsistencyCheckService icdConsistencyCheckService; + /** * 上传 ICD 文件,返回候选结果和可编辑的 ICD 解析结果。 */ @@ -181,4 +187,17 @@ public class MappingController extends BaseController { return response; } + /** + * 将待校验 MMS 映射 JSON 与标准 MMS 映射 JSON 做 ICD 一致性校验。 + */ + @OperateInfo(info = LogEnum.BUSINESS_COMMON) + @ApiOperation("ICD 一致性校验") + @ApiImplicitParam(name = "request", value = "ICD 一致性校验参数", required = true, dataType = "IcdConsistencyCheckRequest") + @PostMapping("/check-icd-json-consistency") + public IcdConsistencyCheckResponse checkIcdJsonConsistency(@Validated @RequestBody IcdConsistencyCheckRequest request) { + String methodDescribe = getMethodDescribe("checkIcdJsonConsistency"); + LogUtil.njcnDebug(log, "{},开始执行 ICD 一致性校验", methodDescribe); + return icdConsistencyCheckService.check(request); + } + } 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 deleted file mode 100644 index b5f3fd9..0000000 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MmsDeviceTypeController.java +++ /dev/null @@ -1,72 +0,0 @@ -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 deleted file mode 100644 index 9102fa1..0000000 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/CsDevTypeMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -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 index 501f058..297f440 100644 --- 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 @@ -2,9 +2,17 @@ package com.njcn.gather.icd.mapping.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO; +import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** * ICD 路径 Mapper。 */ public interface CsIcdPathMapper extends BaseMapper { + + List selectIcdPathList(@Param("keyword") String keyword, + @Param("type") Integer type, + @Param("result") Integer result); } diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsIcdPathMapper.xml b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsIcdPathMapper.xml new file mode 100644 index 0000000..2c87bda --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/mapper/mapping/CsIcdPathMapper.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/CsIcdPathParam.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/CsIcdPathParam.java new file mode 100644 index 0000000..62db083 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/CsIcdPathParam.java @@ -0,0 +1,65 @@ +package com.njcn.gather.icd.mapping.pojo.param; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import javax.validation.constraints.NotBlank; + +/** + * ICD 存储记录保存参数。 + */ +@Data +@ApiModel("ICD存储记录保存参数") +public class CsIcdPathParam { + + @ApiModelProperty("ICD名称") + @NotBlank(message = "ICD名称不能为空") + private String name; + + @ApiModelProperty("ICD存储路径") + private String path; + + @ApiModelProperty("ICD文件二进制内容") + private byte[] icdContent; + + @ApiModelProperty("角度") + private Integer angle; + + @ApiModelProperty("是否使用相位索引") + private Integer usePhaseIndex; + + @ApiModelProperty("ICD类型,1-标准ICD") + private Integer type; + + /** + * ICD 存储记录编辑参数。 + */ + @Data + @EqualsAndHashCode(callSuper = true) + @ApiModel("ICD存储记录编辑参数") + public static class UpdateParam extends CsIcdPathParam { + + @ApiModelProperty("ICD记录ID") + @NotBlank(message = "ICD记录ID不能为空") + private String id; + } + + /** + * ICD 存储记录列表查询参数。 + */ + @Data + @ApiModel("ICD存储记录列表查询参数") + public static class ListParam { + + @ApiModelProperty("关键字,匹配ICD名称或路径") + private String keyword; + + @ApiModelProperty("ICD类型") + private Integer type; + + @ApiModelProperty("ICD校验结果,0-否,1-是") + private Integer result; + } +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdConsistencyCheckRequest.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdConsistencyCheckRequest.java new file mode 100644 index 0000000..05f66d4 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/IcdConsistencyCheckRequest.java @@ -0,0 +1,29 @@ +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 IcdConsistencyCheckRequest { + + /** 待校验 MMS 映射 JSON。 */ + @ApiModelProperty(value = "待校验 MMS 映射 JSON", required = true) + private String checkedJson; + + /** 标准 MMS 映射 JSON。 */ + @ApiModelProperty(value = "标准 MMS 映射 JSON", required = true) + private String standardJson; + + /** 是否将不符合原因 JSON 保存到磁盘。 */ + @ApiModelProperty("是否将不符合原因 JSON 保存到磁盘") + private boolean saveToDisk; + + /** 输出目录,仅 saveToDisk=true 且存在不符合原因时生效。 */ + @ApiModelProperty("输出目录,仅 saveToDisk=true 且存在不符合原因时生效") + private String outputDir; +} 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 index 268a7e8..df70ac9 100644 --- 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 @@ -26,6 +26,9 @@ public class CsIcdPathPO implements Serializable { @TableField("Path") private String path; + @TableField("Icd_Content") + private byte[] icdContent; + @TableField("Angle") private Integer angle; diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/CsIcdPathVO.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/CsIcdPathVO.java new file mode 100644 index 0000000..b27cebc --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/CsIcdPathVO.java @@ -0,0 +1,63 @@ +package com.njcn.gather.icd.mapping.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * ICD 存储记录列表项。 + */ +@Data +@ApiModel("ICD存储记录列表项") +public class CsIcdPathVO { + + @ApiModelProperty("ICD记录ID") + private String id; + + @ApiModelProperty("ICD名称") + private String name; + + @ApiModelProperty("ICD存储路径") + private String path; + + @ApiModelProperty("角度") + private Integer angle; + + @ApiModelProperty("是否使用相位索引") + private Integer usePhaseIndex; + + @ApiModelProperty("状态,1-正常,0-删除") + private Integer state; + + @ApiModelProperty("MMS映射JSON") + private String jsonStr; + + @ApiModelProperty("MMS映射XML") + private String xmlStr; + + @ApiModelProperty("校验结论,0-否,1-是") + private Integer result; + + @ApiModelProperty("校验结论描述") + private String msg; + + @ApiModelProperty("ICD类型,1-标准ICD") + private Integer type; + + @ApiModelProperty("标准ICD引用ID") + private String referenceIcdId; + + @ApiModelProperty("创建人") + private String createBy; + + @ApiModelProperty("创建时间") + private LocalDateTime createTime; + + @ApiModelProperty("更新人") + private String updateBy; + + @ApiModelProperty("更新时间") + private LocalDateTime updateTime; +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/IcdConsistencyCheckResponse.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/IcdConsistencyCheckResponse.java new file mode 100644 index 0000000..4898cc0 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/IcdConsistencyCheckResponse.java @@ -0,0 +1,38 @@ +package com.njcn.gather.icd.mapping.pojo.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * ICD 一致性校验响应。 + */ +@Data +@JsonInclude(JsonInclude.Include.NON_EMPTY) +@ApiModel("ICD 一致性校验响应") +public class IcdConsistencyCheckResponse { + + /** 校验结论:1-符合,0-不符合。 */ + @ApiModelProperty("校验结论:1-符合,0-不符合") + private Integer result; + + /** 校验结论描述。 */ + @ApiModelProperty("校验结论描述") + private String message; + + /** 不符合原因结构化列表。 */ + @ApiModelProperty("不符合原因结构化列表") + private List issues = new ArrayList(); + + /** 不符合原因 JSON 字符串。 */ + @ApiModelProperty("不符合原因 JSON 字符串") + private String issuesJson; + + /** 按映射修改规则修正后的 JSON,存在可修正内容时返回。 */ + @ApiModelProperty("按映射修改规则修正后的 JSON,存在可修正内容时返回") + private String correctedJson; +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/IcdConsistencyIssue.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/IcdConsistencyIssue.java new file mode 100644 index 0000000..ee711a5 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/IcdConsistencyIssue.java @@ -0,0 +1,37 @@ +package com.njcn.gather.icd.mapping.pojo.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * ICD 一致性校验问题项。 + */ +@Data +@ApiModel("ICD 一致性校验问题项") +public class IcdConsistencyIssue { + + /** 问题所属规则或区域。 */ + @ApiModelProperty("问题所属规则或区域") + private String scope; + + /** JSON 路径或业务定位信息。 */ + @ApiModelProperty("JSON 路径或业务定位信息") + private String path; + + /** 问题描述。 */ + @ApiModelProperty("问题描述") + private String message; + + /** 标准映射中的值。 */ + @ApiModelProperty("标准映射中的值") + private String standardValue; + + /** 待校验映射中的值。 */ + @ApiModelProperty("待校验映射中的值") + private String checkedValue; + + /** 是否已按映射修改规则自动修正。 */ + @ApiModelProperty("是否已按映射修改规则自动修正") + private boolean corrected; +} diff --git a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/CsIcdPathService.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/CsIcdPathService.java new file mode 100644 index 0000000..ffeae63 --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/CsIcdPathService.java @@ -0,0 +1,25 @@ +package com.njcn.gather.icd.mapping.service; + +import com.njcn.gather.icd.mapping.pojo.param.CsIcdPathParam; +import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam; +import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO; + +import java.util.List; + +/** + * ICD 存储记录服务。 + */ +public interface CsIcdPathService { + + List listIcdPaths(CsIcdPathParam.ListParam param); + + boolean addIcdPath(CsIcdPathParam param); + + boolean updateIcdPath(CsIcdPathParam.UpdateParam param); + + boolean activateIcdPath(String icdId); + + boolean deleteIcdPath(List ids); + + boolean saveIcdCheckResult(String icdId, IcdCheckResultSaveParam param); +} 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 deleted file mode 100644 index 67d9de4..0000000 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/MmsDeviceTypeService.java +++ /dev/null @@ -1,19 +0,0 @@ -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/CsIcdPathServiceImpl.java b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/CsIcdPathServiceImpl.java new file mode 100644 index 0000000..44306df --- /dev/null +++ b/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/CsIcdPathServiceImpl.java @@ -0,0 +1,215 @@ +package com.njcn.gather.icd.mapping.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.njcn.gather.icd.mapping.mapper.CsIcdPathMapper; +import com.njcn.gather.icd.mapping.pojo.param.CsIcdPathParam; +import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam; +import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO; +import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO; +import com.njcn.gather.icd.mapping.service.CsIcdPathService; +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; +import java.util.UUID; + +/** + * ICD 存储记录服务实现。 + */ +@Service +@RequiredArgsConstructor +public class CsIcdPathServiceImpl implements CsIcdPathService { + + private static final int STATE_NORMAL = 1; + + private static final int STATE_DELETED = 0; + + private static final int ICD_TYPE_STANDARD = 1; + + private final CsIcdPathMapper csIcdPathMapper; + + @Override + public List listIcdPaths(CsIcdPathParam.ListParam param) { + CsIcdPathParam.ListParam checkedParam = param == null ? new CsIcdPathParam.ListParam() : param; + return csIcdPathMapper.selectIcdPathList( + trimToNull(checkedParam.getKeyword()), + checkedParam.getType(), + checkedParam.getResult()); + } + + @Override + @Transactional + public boolean addIcdPath(CsIcdPathParam param) { + CsIcdPathParam checkedParam = requireParam(param); + LocalDateTime now = LocalDateTime.now(); + CsIcdPathPO icdPath = buildIcdPath(checkedParam); + icdPath.setId(UUID.randomUUID().toString().replace("-", "")); + icdPath.setState(STATE_NORMAL); + icdPath.setCreateBy(currentUserId()); + icdPath.setCreateTime(now); + icdPath.setUpdateBy(currentUserId()); + icdPath.setUpdateTime(now); + return csIcdPathMapper.insert(icdPath) > 0; + } + + @Override + @Transactional + public boolean updateIcdPath(CsIcdPathParam.UpdateParam param) { + CsIcdPathParam.UpdateParam checkedParam = requireUpdateParam(param); + requireIcdPath(checkedParam.getId()); + CsIcdPathPO icdPath = buildIcdPath(checkedParam); + icdPath.setId(checkedParam.getId()); + icdPath.setUpdateBy(currentUserId()); + icdPath.setUpdateTime(LocalDateTime.now()); + return csIcdPathMapper.updateById(icdPath) > 0; + } + + @Override + @Transactional + public boolean activateIcdPath(String icdId) { + CsIcdPathPO targetIcdPath = requireIcdPath(icdId); + LocalDateTime now = LocalDateTime.now(); + String currentUserId = currentUserId(); + + csIcdPathMapper.update(null, new LambdaUpdateWrapper() + .set(CsIcdPathPO::getType, null) + .set(CsIcdPathPO::getUpdateBy, currentUserId) + .set(CsIcdPathPO::getUpdateTime, now) + .eq(CsIcdPathPO::getState, STATE_NORMAL)); + + CsIcdPathPO activeIcdPath = new CsIcdPathPO(); + activeIcdPath.setType(ICD_TYPE_STANDARD); + activeIcdPath.setUpdateBy(currentUserId); + activeIcdPath.setUpdateTime(now); + return csIcdPathMapper.update(activeIcdPath, new LambdaUpdateWrapper() + .eq(CsIcdPathPO::getId, targetIcdPath.getId()) + .eq(CsIcdPathPO::getState, STATE_NORMAL)) > 0; + } + + @Override + @Transactional + public boolean deleteIcdPath(List ids) { + if (ids == null || ids.isEmpty()) { + throw new IllegalArgumentException("ICD记录ID不能为空"); + } + CsIcdPathPO icdPath = new CsIcdPathPO(); + icdPath.setState(STATE_DELETED); + icdPath.setUpdateBy(currentUserId()); + icdPath.setUpdateTime(LocalDateTime.now()); + return csIcdPathMapper.update(icdPath, new LambdaUpdateWrapper() + .in(CsIcdPathPO::getId, ids) + .eq(CsIcdPathPO::getState, STATE_NORMAL)) > 0; + } + + @Override + @Transactional + public boolean saveIcdCheckResult(String icdId, IcdCheckResultSaveParam param) { + if (param == null) { + throw new IllegalArgumentException("ICD校验结果不能为空"); + } + CsIcdPathPO icdPath = requireIcdPath(icdId); + CsIcdPathPO referenceIcd = requireUniqueReferenceIcd(); + 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; + } + + private CsIcdPathPO buildIcdPath(CsIcdPathParam param) { + CsIcdPathPO icdPath = new CsIcdPathPO(); + icdPath.setName(requireText(param.getName(), "ICD名称不能为空")); + icdPath.setPath(requireText(param.getPath(), "ICD存储路径不能为空")); + icdPath.setIcdContent(param.getIcdContent()); + icdPath.setAngle(param.getAngle()); + icdPath.setUsePhaseIndex(param.getUsePhaseIndex()); + icdPath.setType(param.getType()); + return icdPath; + } + + private CsIcdPathParam requireParam(CsIcdPathParam param) { + if (param == null) { + throw new IllegalArgumentException("ICD记录参数不能为空"); + } + return param; + } + + private CsIcdPathParam.UpdateParam requireUpdateParam(CsIcdPathParam.UpdateParam param) { + if (param == null) { + throw new IllegalArgumentException("ICD记录参数不能为空"); + } + requireText(param.getId(), "ICD记录ID不能为空"); + return param; + } + + private CsIcdPathPO requireIcdPath(String icdId) { + String id = requireText(icdId, "ICD记录ID不能为空"); + CsIcdPathPO icdPath = csIcdPathMapper.selectById(id); + if (icdPath == null || !Integer.valueOf(STATE_NORMAL).equals(icdPath.getState())) { + throw new IllegalArgumentException("ICD记录不存在或已删除"); + } + return icdPath; + } + + /** + * 全系统只允许一个正常状态的标准 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/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 deleted file mode 100644 index 94cc71b..0000000 --- a/tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MmsDeviceTypeServiceImpl.java +++ /dev/null @@ -1,141 +0,0 @@ -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/mms-mapping/src/test/java/com/njcn/gather/icd/mapping/component/IcdConsistencyCheckServiceTest.java b/tools/mms-mapping/src/test/java/com/njcn/gather/icd/mapping/component/IcdConsistencyCheckServiceTest.java new file mode 100644 index 0000000..3efcd4a --- /dev/null +++ b/tools/mms-mapping/src/test/java/com/njcn/gather/icd/mapping/component/IcdConsistencyCheckServiceTest.java @@ -0,0 +1,104 @@ +package com.njcn.gather.icd.mapping.component; + +import com.njcn.gather.icd.mapping.pojo.param.IcdConsistencyCheckRequest; +import com.njcn.gather.icd.mapping.pojo.vo.IcdConsistencyCheckResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; + +class IcdConsistencyCheckServiceTest { + + private final FileStorageService fileStorageService = new FileStorageService(); + private final IcdConsistencyCheckService service = new IcdConsistencyCheckService(fileStorageService); + + @TempDir + Path tempDir; + + @Test + void checkShouldReturnMismatchIssuesAndCorrectedJsonWithoutSavedPath() throws Exception { + IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest(); + request.setStandardJson(buildStandardJson()); + request.setCheckedJson(buildCheckedJson()); + request.setSaveToDisk(true); + request.setOutputDir(tempDir.toString()); + + IcdConsistencyCheckResponse response = service.check(request); + + Assertions.assertEquals(0, response.getResult()); + Assertions.assertEquals("不符合", response.getMessage()); + Assertions.assertFalse(response.getIssues().isEmpty()); + Assertions.assertTrue(response.getIssuesJson().contains("ReportMap")); + Assertions.assertTrue(response.getCorrectedJson().contains("\"unit\" : \"s\"")); + Assertions.assertTrue(response.getCorrectedJson().contains("\"reportCount\" : 2")); + Assertions.assertTrue(response.getCorrectedJson().contains("\"start\" : 1")); + Assertions.assertTrue(response.getCorrectedJson().contains("\"end\" : 4")); + Assertions.assertTrue(Files.list(tempDir).findAny().isPresent()); + } + + @Test + void checkShouldReturnPassWhenCheckedJsonMatchesStandardJson() { + IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest(); + request.setStandardJson(buildStandardJson()); + request.setCheckedJson(buildStandardJson()); + + IcdConsistencyCheckResponse response = service.check(request); + + Assertions.assertEquals(1, response.getResult()); + Assertions.assertEquals("符合", response.getMessage()); + Assertions.assertTrue(response.getIssues().isEmpty()); + Assertions.assertNull(response.getCorrectedJson()); + } + + private String buildStandardJson() { + return "{\n" + + " \"IED\":\"IED1\",\n" + + " \"LD\":\"LD0\",\n" + + " \"DataType\":\"1\",\n" + + " \"unit\":\"s\",\n" + + " \"ReportMap\":[\n" + + " {\"desc\":\"统计数据\",\"reportCount\":2,\"rptID\":\"rpt-stat\",\"name\":\"brcbStat\",\"buffered\":\"BR\",\"inst\":\"01\",\"FlickerFlag\":\"0\",\"Select\":\"all\",\"TrgOps\":\"dchg\"},\n" + + " {\"desc\":\"波动闪变\",\"reportCount\":1,\"rptID\":\"rpt-flk\",\"name\":\"brcbFlk\",\"buffered\":\"BR\",\"inst\":\"02\",\"FlickerFlag\":\"0\",\"Select\":\"all\",\"TrgOps\":\"dchg\"},\n" + + " {\"desc\":\"实时数据\",\"reportCount\":1,\"rptID\":\"rpt-rt\",\"name\":\"brcbRt\",\"buffered\":\"RP\",\"inst\":\"03\",\"FlickerFlag\":\"0\",\"Select\":\"all\",\"TrgOps\":\"dchg\"},\n" + + " {\"desc\":\"暂态事件\",\"reportCount\":1,\"rptID\":\"rpt-tran\",\"name\":\"brcbTran\",\"buffered\":\"BR\",\"inst\":\"04\",\"FlickerFlag\":\"0\",\"Select\":\"all\",\"TrgOps\":\"dchg\"}\n" + + " ],\n" + + " \"DataSetList\":[\n" + + buildDataSet("MMXU", "统计数据", "1", "A相", "Hz", "频率", 1, 4, "Hz") + ",\n" + + buildDataSet("MSQI", "实时数据", "1", "A相", "A", "电流", 1, 2, "A") + ",\n" + + buildDataSet("MHAI", "谐波数据", "1", "A相", "Har", "谐波", 1, 2, "%") + ",\n" + + buildDataSet("MFLK", "波动闪变", "1", "A相", "Flk", "闪变", 1, 2, "pu") + "\n" + + " ]\n" + + "}"; + } + + private String buildCheckedJson() { + return "{\n" + + " \"IED\":\"IED1\",\n" + + " \"LD\":\"LD0\",\n" + + " \"DataType\":\"1\",\n" + + " \"unit\":\"ms\",\n" + + " \"ReportMap\":[\n" + + " {\"desc\":\"统计数据\",\"reportCount\":1,\"rptID\":\"rpt-stat\",\"name\":\"brcbStat\",\"buffered\":\"BR\",\"inst\":\"01\",\"FlickerFlag\":\"0\",\"Select\":\"part\",\"TrgOps\":\"dchg\"},\n" + + " {\"desc\":\"波动闪变\",\"reportCount\":1,\"rptID\":\"rpt-flk\",\"name\":\"brcbFlk\",\"buffered\":\"BR\",\"inst\":\"02\",\"FlickerFlag\":\"0\",\"Select\":\"all\",\"TrgOps\":\"dchg\"},\n" + + " {\"desc\":\"实时数据\",\"reportCount\":1,\"rptID\":\"rpt-rt\",\"name\":\"brcbRt\",\"buffered\":\"RP\",\"inst\":\"03\",\"FlickerFlag\":\"0\",\"Select\":\"all\",\"TrgOps\":\"dchg\"},\n" + + " {\"desc\":\"暂态事件\",\"reportCount\":1,\"rptID\":\"rpt-tran\",\"name\":\"brcbTran\",\"buffered\":\"BR\",\"inst\":\"04\",\"FlickerFlag\":\"0\",\"Select\":\"all\",\"TrgOps\":\"dchg\"}\n" + + " ],\n" + + " \"DataSetList\":[\n" + + buildDataSet("MMXU", "统计数据", "1", "A相", "Hz", "频率", 3, 6, "Hz") + ",\n" + + buildDataSet("MSQI", "实时数据", "1", "A相", "A", "电流", 1, 2, "A") + ",\n" + + buildDataSet("MHAI", "谐波数据", "1", "A相", "Har", "谐波", 1, 2, "%") + ",\n" + + buildDataSet("MFLK", "波动闪变", "1", "A相", "Flk", "闪变", 1, 2, "pu") + "\n" + + " ]\n" + + "}"; + } + + private String buildDataSet(String lnClass, String groupDesc, String inst, String instDesc, + String doiName, String doiDesc, int start, int end, String unit) { + return " {\"desc\":\"" + groupDesc + "\",\"lnClass\":\"" + lnClass + "\",\"instList\":[{\"inst\":\"" + inst + + "\",\"desc\":\"" + instDesc + "\",\"doiList\":[{\"name\":\"" + doiName + "\",\"desc\":\"" + doiDesc + + "\",\"start\":" + start + ",\"end\":" + end + ",\"unit\":\"" + unit + + "\",\"coefficient\":1.0,\"baseflag\":1,\"basecount\":1,\"icdcout\":10,\"sdiList\":[{\"name\":\"mag\",\"desc\":\"幅值\",\"typeList\":[{\"name\":\"f\",\"desc\":\"浮点\"}]}]}]}]}"; + } +} diff --git a/tools/mms-mapping/src/test/java/com/njcn/gather/icd/mapping/service/impl/CsIcdPathServiceImplTest.java b/tools/mms-mapping/src/test/java/com/njcn/gather/icd/mapping/service/impl/CsIcdPathServiceImplTest.java new file mode 100644 index 0000000..903a07c --- /dev/null +++ b/tools/mms-mapping/src/test/java/com/njcn/gather/icd/mapping/service/impl/CsIcdPathServiceImplTest.java @@ -0,0 +1,175 @@ +package com.njcn.gather.icd.mapping.service.impl; + +import com.njcn.gather.icd.mapping.mapper.CsIcdPathMapper; +import com.njcn.gather.icd.mapping.pojo.param.CsIcdPathParam; +import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam; +import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.util.Collections; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class CsIcdPathServiceImplTest { + + private final CsIcdPathMapper csIcdPathMapper = mock(CsIcdPathMapper.class); + private final CsIcdPathServiceImpl service = new CsIcdPathServiceImpl(csIcdPathMapper); + + @Test + void listIcdPathsShouldTrimKeywordBeforeQuery() { + CsIcdPathParam.ListParam param = new CsIcdPathParam.ListParam(); + param.setKeyword(" standard "); + + service.listIcdPaths(param); + + verify(csIcdPathMapper).selectIcdPathList(eq("standard"), eq(null), eq(null)); + } + + @Test + void addIcdPathShouldInsertEnabledRecord() { + CsIcdPathParam param = buildParam("标准ICD"); + when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1); + + boolean result = service.addIcdPath(param); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CsIcdPathPO.class); + verify(csIcdPathMapper).insert(captor.capture()); + Assertions.assertTrue(result); + Assertions.assertEquals("标准ICD", captor.getValue().getName()); + Assertions.assertEquals("D:/icd/standard.icd", captor.getValue().getPath()); + Assertions.assertEquals(1, captor.getValue().getState()); + Assertions.assertNotNull(captor.getValue().getId()); + Assertions.assertNotNull(captor.getValue().getCreateTime()); + } + + @Test + void addIcdPathShouldSaveIcdBinaryContent() { + CsIcdPathParam param = buildParam("标准ICD"); + byte[] fileContent = "".getBytes(); + param.setIcdContent(fileContent); + when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1); + + boolean result = service.addIcdPath(param); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CsIcdPathPO.class); + verify(csIcdPathMapper).insert(captor.capture()); + Assertions.assertTrue(result); + Assertions.assertArrayEquals(fileContent, captor.getValue().getIcdContent()); + } + + @Test + void updateIcdPathShouldRejectDeletedRecord() { + CsIcdPathParam.UpdateParam param = new CsIcdPathParam.UpdateParam(); + param.setId("icd-001"); + param.setName("标准ICD"); + param.setPath("D:/icd/standard.icd"); + + CsIcdPathPO deleted = new CsIcdPathPO(); + deleted.setId("icd-001"); + deleted.setState(0); + when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(deleted); + + IllegalArgumentException exception = Assertions.assertThrows(IllegalArgumentException.class, + () -> service.updateIcdPath(param)); + + Assertions.assertEquals("ICD记录不存在或已删除", exception.getMessage()); + } + + @Test + void updateIcdPathShouldSaveIcdBinaryContentWhenProvided() { + CsIcdPathParam.UpdateParam param = new CsIcdPathParam.UpdateParam(); + byte[] fileContent = "".getBytes(); + param.setId("icd-001"); + param.setName("标准ICD"); + param.setPath("standard.icd"); + param.setIcdContent(fileContent); + + CsIcdPathPO existed = new CsIcdPathPO(); + existed.setId("icd-001"); + existed.setState(1); + when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(existed); + when(csIcdPathMapper.updateById(any(CsIcdPathPO.class))).thenReturn(1); + + boolean result = service.updateIcdPath(param); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CsIcdPathPO.class); + verify(csIcdPathMapper).updateById(captor.capture()); + Assertions.assertTrue(result); + Assertions.assertArrayEquals(fileContent, captor.getValue().getIcdContent()); + } + + @Test + void deleteIcdPathShouldMarkRecordDeleted() { + when(csIcdPathMapper.update(any(CsIcdPathPO.class), any())).thenReturn(1); + + boolean result = service.deleteIcdPath(Collections.singletonList("icd-001")); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CsIcdPathPO.class); + verify(csIcdPathMapper).update(captor.capture(), any()); + Assertions.assertTrue(result); + Assertions.assertEquals(0, captor.getValue().getState()); + Assertions.assertNotNull(captor.getValue().getUpdateTime()); + } + + @Test + void activateIcdPathShouldOnlyKeepTargetAsStandardIcd() { + CsIcdPathPO icdPath = new CsIcdPathPO(); + icdPath.setId("icd-001"); + icdPath.setState(1); + when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath); + when(csIcdPathMapper.update(any(CsIcdPathPO.class), any())).thenReturn(1); + + boolean result = service.activateIcdPath("icd-001"); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CsIcdPathPO.class); + verify(csIcdPathMapper, times(2)).update(captor.capture(), any()); + Assertions.assertTrue(result); + Assertions.assertNull(captor.getAllValues().get(0)); + Assertions.assertEquals(1, captor.getAllValues().get(1).getType()); + Assertions.assertNotNull(captor.getAllValues().get(1).getUpdateTime()); + } + + @Test + void saveIcdCheckResultShouldUpdateIcdRecord() { + CsIcdPathPO referenceIcd = new CsIcdPathPO(); + referenceIcd.setId("reference-icd"); + when(csIcdPathMapper.selectList(any())).thenReturn(Collections.singletonList(referenceIcd)); + + CsIcdPathPO icdPath = new CsIcdPathPO(); + icdPath.setId("icd-001"); + icdPath.setState(1); + when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath); + when(csIcdPathMapper.updateById(any(CsIcdPathPO.class))).thenReturn(1); + + IcdCheckResultSaveParam param = new IcdCheckResultSaveParam(); + param.setMappingJson("{}"); + param.setXml(""); + param.setResult(1); + param.setMsg("通过"); + + boolean result = service.saveIcdCheckResult("icd-001", param); + + ArgumentCaptor captor = ArgumentCaptor.forClass(CsIcdPathPO.class); + verify(csIcdPathMapper).updateById(captor.capture()); + Assertions.assertTrue(result); + Assertions.assertEquals("reference-icd", captor.getValue().getReferenceIcdId()); + Assertions.assertEquals(1, captor.getValue().getResult()); + } + + private CsIcdPathParam buildParam(String name) { + CsIcdPathParam param = new CsIcdPathParam(); + param.setName(name); + param.setPath("D:/icd/standard.icd"); + param.setAngle(0); + param.setUsePhaseIndex(1); + param.setType(1); + return param; + } +} diff --git a/tools/parse-pqdif/lib/.gitkeep b/tools/parse-pqdif/lib/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tools/parse-pqdif/lib/.gitkeep @@ -0,0 +1 @@ + diff --git a/tools/parse-pqdif/lib/pqdif-native-basic-bridge-1.0.0-jar-with-dependencies.jar b/tools/parse-pqdif/lib/pqdif-native-basic-bridge-1.0.0-jar-with-dependencies.jar new file mode 100644 index 0000000..dcddfa5 Binary files /dev/null and b/tools/parse-pqdif/lib/pqdif-native-basic-bridge-1.0.0-jar-with-dependencies.jar differ diff --git a/tools/parse-pqdif/pom.xml b/tools/parse-pqdif/pom.xml index 22e177e..f06d4f3 100644 --- a/tools/parse-pqdif/pom.xml +++ b/tools/parse-pqdif/pom.xml @@ -36,6 +36,12 @@ spring-boot-starter-validation + + net.java.dev.jna + jna + 5.13.0 + + com.njcn pqdif-native-basic-bridge diff --git a/tools/pom.xml b/tools/pom.xml index 369df55..095ea2e 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -19,6 +19,7 @@ activate-tool + device-types mms-mapping wave-tool add-data