feat(icd): 完善ICD映射管理功能

- 在AuthGlobalFilter中添加稳态检验相关接口的免认证路径
- 修改CsDevTypeMapper.xml移除icdPath字段返回避免数据冗余
- 在CsIcdPathController中新增查询参照ICD列表和ICD校验详情接口
- 更新CsIcdPathMapper添加selectReferenceIcdPathList等方法实现
- 移除CsIcdPath相关实体和参数中的path字段简化数据结构
- 扩展ICD类型定义支持手动录入和上游解析的标准/非标准分类
- 重构激活标准ICD逻辑支持不同类型间的正确转换
- 新增ICD一致性校验排除规则跳过特定描述的DOI项检查
- 优化报告映射规则应用逻辑提升校验准确性
- 添加去除重复DOI项功能确保数据唯一性
This commit is contained in:
2026-06-18 16:33:40 +08:00
parent 7fb4c8e78a
commit 97b1334714
48 changed files with 2373 additions and 264 deletions

View File

@@ -148,6 +148,65 @@ public class SteadyChecksquareInfluxQueryComponent {
return result; return result;
} }
public Map<String, List<SteadyChecksquareValuePointBO>> queryStatValuePointMap(List<SteadyTrendResolvedFieldBO> fields,
LocalDateTime startTime,
LocalDateTime endTime,
int intervalMinutes) {
Map<String, List<SteadyChecksquareValuePointBO>> result =
new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>();
if (fields == null || fields.isEmpty()) {
return result;
}
if (!hasValueTypeTag(fields.get(0).getMeasurement())) {
for (SteadyTrendResolvedFieldBO field : fields) {
result.put(resolveValueType(field.getStatType()), queryValuePoints(field, startTime, endTime, intervalMinutes));
}
return result;
}
validateConfig();
Map<String, List<SteadyChecksquareValuePointBO>> cache = REQUEST_VALUE_CACHE.get();
List<SteadyTrendResolvedFieldBO> missingFields = new ArrayList<SteadyTrendResolvedFieldBO>();
for (SteadyTrendResolvedFieldBO field : fields) {
String statType = resolveValueType(field.getStatType());
String cacheKey = buildCacheKey(buildValuePointQuery(field, startTime, endTime), intervalMinutes);
if (cache != null && cache.containsKey(cacheKey)) {
result.put(statType, new ArrayList<SteadyChecksquareValuePointBO>(cache.get(cacheKey)));
} else {
missingFields.add(field);
}
}
if (!missingFields.isEmpty()) {
String query = buildStatValuePointQuery(missingFields, startTime, endTime);
long startMillis = System.currentTimeMillis();
SteadyTrendResolvedFieldBO first = missingFields.get(0);
log.info("数据校验指标值 InfluxDB 统计类型批量查询开始measurement={}field={}statCount={}lineId={}phase={}query={}",
first.getMeasurement(), first.getField(), missingFields.size(), first.getLineId(), first.getPhase(), query);
try {
Map<String, List<SteadyChecksquareValuePointBO>> queried =
queryStatValuePointsByWindow(missingFields, startTime, endTime, intervalMinutes);
for (SteadyTrendResolvedFieldBO field : missingFields) {
String statType = resolveValueType(field.getStatType());
List<SteadyChecksquareValuePointBO> points = queried.get(statType);
if (points == null) {
points = new ArrayList<SteadyChecksquareValuePointBO>();
}
result.put(statType, points);
if (cache != null) {
String cacheKey = buildCacheKey(buildValuePointQuery(field, startTime, endTime), intervalMinutes);
cache.put(cacheKey, new ArrayList<SteadyChecksquareValuePointBO>(points));
}
}
log.info("数据校验指标值 InfluxDB 统计类型批量查询结束statCount={}costMs={}",
missingFields.size(), System.currentTimeMillis() - startMillis);
} catch (RuntimeException ex) {
log.warn("数据校验指标值 InfluxDB 统计类型批量查询异常statCount={}costMs={}error={}",
missingFields.size(), System.currentTimeMillis() - startMillis, ex.getMessage());
throw ex;
}
}
return result;
}
public String buildChecksquareQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) { public String buildChecksquareQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) {
return buildValuePointQuery(field, startTime, endTime); return buildValuePointQuery(field, startTime, endTime);
} }
@@ -197,6 +256,33 @@ public class SteadyChecksquareInfluxQueryComponent {
return result; return result;
} }
private Map<String, List<SteadyChecksquareValuePointBO>> queryStatValuePointsByWindow(List<SteadyTrendResolvedFieldBO> fields,
LocalDateTime startTime,
LocalDateTime endTime,
int intervalMinutes) {
Map<String, List<SteadyChecksquareValuePointBO>> result =
new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>();
for (SteadyTrendResolvedFieldBO field : fields) {
result.put(resolveValueType(field.getStatType()), new ArrayList<SteadyChecksquareValuePointBO>());
}
LocalDateTime windowStart = startTime;
while (!windowStart.isAfter(endTime)) {
LocalDateTime windowEnd = min(windowStart.plusDays(QUERY_WINDOW_DAYS).minusNanos(1), endTime);
Map<String, List<SteadyChecksquareValuePointBO>> windowResult =
parseStatValuePoints(executeQuery(buildStatValuePointQuery(fields, windowStart, windowEnd)), intervalMinutes);
for (Map.Entry<String, List<SteadyChecksquareValuePointBO>> entry : windowResult.entrySet()) {
List<SteadyChecksquareValuePointBO> points = result.get(entry.getKey());
if (points == null) {
points = new ArrayList<SteadyChecksquareValuePointBO>();
result.put(entry.getKey(), points);
}
points.addAll(entry.getValue());
}
windowStart = windowEnd.plusNanos(1);
}
return result;
}
private LocalDateTime min(LocalDateTime first, LocalDateTime second) { private LocalDateTime min(LocalDateTime first, LocalDateTime second) {
return first.isAfter(second) ? second : first; return first.isAfter(second) ? second : first;
} }
@@ -238,6 +324,28 @@ public class SteadyChecksquareInfluxQueryComponent {
return sql.toString(); return sql.toString();
} }
public String buildStatValuePointQuery(List<SteadyTrendResolvedFieldBO> fields, LocalDateTime startTime, LocalDateTime endTime) {
SteadyTrendResolvedFieldBO first = fields.get(0);
StringBuilder sql = new StringBuilder();
sql.append("SELECT \"").append(first.getField()).append("\" AS \"value\"");
sql.append(" FROM \"").append(first.getMeasurement()).append("\"");
sql.append(" WHERE time >= '").append(INFLUX_TIME_FORMATTER.format(startTime)).append("'");
sql.append(" AND time <= '").append(INFLUX_TIME_FORMATTER.format(endTime)).append("'");
sql.append(" AND \"line_id\" = '").append(escapeTagValue(first.getLineId())).append("'");
sql.append(" AND \"phasic_type\" = '").append(escapeTagValue(first.getPhase())).append("'");
sql.append(" AND \"value_type\" =~ /^(");
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
sql.append("|");
}
sql.append(escapeRegexValue(resolveValueType(fields.get(i).getStatType())));
}
sql.append(")$/");
sql.append(" GROUP BY \"value_type\"");
sql.append(" ORDER BY time ASC");
return sql.toString();
}
private List<SteadyChecksquareValuePointBO> parseValuePoints(String body, int intervalMinutes) { private List<SteadyChecksquareValuePointBO> parseValuePoints(String body, int intervalMinutes) {
try { try {
JsonNode root = OBJECT_MAPPER.readTree(body); JsonNode root = OBJECT_MAPPER.readTree(body);
@@ -312,6 +420,52 @@ public class SteadyChecksquareInfluxQueryComponent {
} }
} }
private Map<String, List<SteadyChecksquareValuePointBO>> parseStatValuePoints(String body, int intervalMinutes) {
try {
JsonNode root = OBJECT_MAPPER.readTree(body);
JsonNode seriesArray = root.path("results").path(0).path("series");
Map<String, List<SteadyChecksquareValuePointBO>> result =
new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>();
if (!seriesArray.isArray()) {
return result;
}
for (JsonNode series : seriesArray) {
String statType = series.path("tags").path("value_type").asText(null);
if (statType == null || statType.trim().isEmpty()) {
continue;
}
statType = resolveValueType(statType);
List<SteadyChecksquareValuePointBO> points = result.get(statType);
if (points == null) {
points = new ArrayList<SteadyChecksquareValuePointBO>();
result.put(statType, points);
}
JsonNode values = series.path("values");
if (!values.isArray()) {
continue;
}
for (JsonNode value : values) {
if (value.size() < 2 || value.get(1).isNull()) {
continue;
}
LocalDateTime time = parseInfluxTime(value.get(0).asText());
if (time == null) {
continue;
}
SteadyChecksquareValuePointBO point = new SteadyChecksquareValuePointBO();
point.setTime(alignToPreviousSlot(time, intervalMinutes));
point.setValue(new BigDecimal(value.get(1).asText()));
points.add(point);
}
}
return result;
} catch (IOException ex) {
throw fail("InfluxDB 返回结果解析失败:" + ex.getMessage());
} catch (NumberFormatException ex) {
throw fail("InfluxDB 返回指标值格式不正确:" + ex.getMessage());
}
}
private LocalDateTime alignToPreviousSlot(LocalDateTime time, int intervalMinutes) { private LocalDateTime alignToPreviousSlot(LocalDateTime time, int intervalMinutes) {
LocalDateTime minuteFloor = time.withSecond(0).withNano(0); LocalDateTime minuteFloor = time.withSecond(0).withNano(0);
int minuteOfDay = minuteFloor.getHour() * 60 + minuteFloor.getMinute(); int minuteOfDay = minuteFloor.getHour() * 60 + minuteFloor.getMinute();
@@ -390,6 +544,16 @@ public class SteadyChecksquareInfluxQueryComponent {
return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'"); return value == null ? "" : value.replace("\\", "\\\\").replace("'", "\\'");
} }
private String escapeRegexValue(String value) {
return value == null ? "" : value.replace("\\", "\\\\")
.replace("|", "\\|")
.replace("(", "\\(")
.replace(")", "\\)")
.replace("^", "\\^")
.replace("$", "\\$")
.replace(".", "\\.");
}
private String resolveValueType(String statType) { private String resolveValueType(String statType) {
if (statType == null || statType.trim().isEmpty()) { if (statType == null || statType.trim().isEmpty()) {
return "AVG"; return "AVG";

View File

@@ -12,6 +12,7 @@ import org.springframework.stereotype.Component;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -58,9 +59,18 @@ public class SteadyChecksquareValueOrderRuleComponent {
LocalDateTime startTime, LocalDateTime endTime, LocalDateTime startTime, LocalDateTime endTime,
int intervalMinutes) { int intervalMinutes) {
Map<String, Map<LocalDateTime, BigDecimal>> result = new LinkedHashMap<String, Map<LocalDateTime, BigDecimal>>(); Map<String, Map<LocalDateTime, BigDecimal>> result = new LinkedHashMap<String, Map<LocalDateTime, BigDecimal>>();
List<SteadyTrendResolvedFieldBO> fields = new ArrayList<SteadyTrendResolvedFieldBO>();
for (String statType : REQUIRED_STATS) { for (String statType : REQUIRED_STATS) {
SteadyTrendResolvedFieldBO field = buildResolvedField(lineId, indicator, harmonicOrder, phase, statType); SteadyTrendResolvedFieldBO field = buildResolvedField(lineId, indicator, harmonicOrder, phase, statType);
result.put(statType, toValueMap(influxQueryComponent.queryValuePoints(field, startTime, endTime, intervalMinutes))); fields.add(field);
}
Map<String, List<SteadyChecksquareValuePointBO>> fieldValueMap =
influxQueryComponent.queryStatValuePointMap(fields, startTime, endTime, intervalMinutes);
if (fieldValueMap == null) {
fieldValueMap = Collections.emptyMap();
}
for (String statType : REQUIRED_STATS) {
result.put(statType, toValueMap(fieldValueMap.get(statType)));
} }
return result; return result;
} }

View File

@@ -0,0 +1,41 @@
package com.njcn.gather.steady.checksquare.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 数据校验后台任务线程池配置。
*/
@Slf4j
@Configuration
public class SteadyChecksquareExecutorConfig {
@Bean(name = "steadyChecksquareExecutorService", destroyMethod = "shutdown")
public ExecutorService steadyChecksquareExecutorService() {
AtomicInteger threadIndex = new AtomicInteger(1);
return new ThreadPoolExecutor(
1,
1,
30,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(8),
runnable -> {
Thread thread = new Thread(runnable);
thread.setName("steady-checksquare-task-" + threadIndex.getAndIncrement());
return thread;
},
(runnable, executor) -> {
log.warn("数据校验任务线程池已满,拒绝新的校验任务");
throw new RejectedExecutionException("数据校验任务线程池已满");
}
);
}
}

View File

@@ -61,6 +61,16 @@ public class SteadyChecksquareController extends BaseController {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
} }
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@ApiOperation("Restart failed checksquare task")
@PostMapping("/restart")
public HttpResult<SteadyChecksquareTaskVO> restart(@RequestParam("taskId") String taskId) {
String methodDescribe = getMethodDescribe("restart");
LogUtil.njcnDebug(log, "{} restart checksquare task, taskId={}", methodDescribe, taskId);
SteadyChecksquareTaskVO result = checksquareService.restart(taskId);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE) @OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
@ApiOperation("删除数据校验任务") @ApiOperation("删除数据校验任务")
@PostMapping("/delete") @PostMapping("/delete")

View File

@@ -19,6 +19,9 @@ public class SteadyChecksquareQueryParam implements Serializable {
@ApiModelProperty("监测点 ID") @ApiModelProperty("监测点 ID")
private String lineId; private String lineId;
@ApiModelProperty("监测点 ID 列表")
private List<String> lineIds;
@ApiModelProperty("指标编码") @ApiModelProperty("指标编码")
private List<String> indicatorCodes; private List<String> indicatorCodes;

View File

@@ -24,6 +24,10 @@ public class SteadyChecksquareItemPO implements Serializable {
private String taskId; private String taskId;
@TableField("item_key") @TableField("item_key")
private String itemKey; private String itemKey;
@TableField("line_id")
private String lineId;
@TableField("line_name")
private String lineName;
@TableField("indicator_code") @TableField("indicator_code")
private String indicatorCode; private String indicatorCode;
@TableField("indicator_name") @TableField("indicator_name")

View File

@@ -26,6 +26,10 @@ public class SteadyChecksquareTaskPO implements Serializable {
private String lineId; private String lineId;
@TableField("line_name") @TableField("line_name")
private String lineName; private String lineName;
@TableField("line_ids_json")
private String lineIdsJson;
@TableField("line_ids_text")
private String lineIdsText;
@TableField("time_start") @TableField("time_start")
private LocalDateTime timeStart; private LocalDateTime timeStart;
@TableField("time_end") @TableField("time_end")

View File

@@ -20,6 +20,12 @@ public class SteadyChecksquareItemDetailVO implements Serializable {
@ApiModelProperty("检测项 ID") @ApiModelProperty("检测项 ID")
private String itemId; private String itemId;
@ApiModelProperty("监测点 ID")
private String lineId;
@ApiModelProperty("监测点名称")
private String lineName;
@ApiModelProperty("明细类型") @ApiModelProperty("明细类型")
private String detailType; private String detailType;

View File

@@ -24,6 +24,12 @@ public class SteadyChecksquareItemVO implements Serializable {
@ApiModelProperty("校验项唯一键") @ApiModelProperty("校验项唯一键")
private String itemKey; private String itemKey;
@ApiModelProperty("监测点 ID")
private String lineId;
@ApiModelProperty("监测点名称")
private String lineName;
@ApiModelProperty("指标编码") @ApiModelProperty("指标编码")
private String indicatorCode; private String indicatorCode;

View File

@@ -26,6 +26,9 @@ public class SteadyChecksquareQueryVO implements Serializable {
@ApiModelProperty("监测点 ID") @ApiModelProperty("监测点 ID")
private String lineId; private String lineId;
@ApiModelProperty("监测点 ID 列表")
private List<String> lineIds = new ArrayList<String>();
@ApiModelProperty("监测点名称") @ApiModelProperty("监测点名称")
private String lineName; private String lineName;

View File

@@ -6,6 +6,7 @@ import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.List;
/** /**
* 数据校验历史任务。 * 数据校验历史任务。
@@ -25,6 +26,9 @@ public class SteadyChecksquareTaskVO implements Serializable {
@ApiModelProperty("监测点 ID") @ApiModelProperty("监测点 ID")
private String lineId; private String lineId;
@ApiModelProperty("监测点 ID 列表")
private List<String> lineIds;
@ApiModelProperty("监测点名称") @ApiModelProperty("监测点名称")
private String lineName; private String lineName;

View File

@@ -18,6 +18,8 @@ public interface SteadyChecksquareService {
SteadyChecksquareTaskVO create(SteadyChecksquareQueryParam param); SteadyChecksquareTaskVO create(SteadyChecksquareQueryParam param);
SteadyChecksquareTaskVO restart(String taskId);
boolean delete(List<String> taskIds); boolean delete(List<String> taskIds);
SteadyChecksquareQueryVO detail(String taskId); SteadyChecksquareQueryVO detail(String taskId);

View File

@@ -46,6 +46,7 @@ import com.njcn.web.factory.PageFactory;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
@@ -64,6 +65,8 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -98,12 +101,16 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
@Autowired(required = false) @Autowired(required = false)
private TransactionTemplate transactionTemplate; private TransactionTemplate transactionTemplate;
@Autowired(required = false)
@Qualifier("steadyChecksquareExecutorService")
private ExecutorService steadyChecksquareExecutorService;
@Override @Override
public Page<SteadyChecksquareTaskVO> query(SteadyChecksquareHistoryQueryParam param) { public Page<SteadyChecksquareTaskVO> query(SteadyChecksquareHistoryQueryParam param) {
SteadyChecksquareHistoryQueryParam query = param == null ? new SteadyChecksquareHistoryQueryParam() : param; SteadyChecksquareHistoryQueryParam query = param == null ? new SteadyChecksquareHistoryQueryParam() : param;
LambdaQueryWrapper<SteadyChecksquareTaskPO> wrapper = new LambdaQueryWrapper<SteadyChecksquareTaskPO>() LambdaQueryWrapper<SteadyChecksquareTaskPO> wrapper = new LambdaQueryWrapper<SteadyChecksquareTaskPO>()
.eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED) .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED)
.eq(trimToNull(query.getLineId()) != null, SteadyChecksquareTaskPO::getLineId, trimToNull(query.getLineId())) .like(trimToNull(query.getLineId()) != null, SteadyChecksquareTaskPO::getLineIdsText, "|" + trimToNull(query.getLineId()) + "|")
.like(trimToNull(query.getIndicatorCode()) != null, SteadyChecksquareTaskPO::getIndicatorCodesText, "|" + trimToNull(query.getIndicatorCode()) + "|") .like(trimToNull(query.getIndicatorCode()) != null, SteadyChecksquareTaskPO::getIndicatorCodesText, "|" + trimToNull(query.getIndicatorCode()) + "|")
.ge(trimToNull(query.getTimeStart()) != null, SteadyChecksquareTaskPO::getTimeStart, parseOptionalTime(query.getTimeStart())) .ge(trimToNull(query.getTimeStart()) != null, SteadyChecksquareTaskPO::getTimeStart, parseOptionalTime(query.getTimeStart()))
.le(trimToNull(query.getTimeEnd()) != null, SteadyChecksquareTaskPO::getTimeEnd, parseOptionalTime(query.getTimeEnd())) .le(trimToNull(query.getTimeEnd()) != null, SteadyChecksquareTaskPO::getTimeEnd, parseOptionalTime(query.getTimeEnd()))
@@ -118,24 +125,47 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
@Override @Override
public SteadyChecksquareTaskVO create(SteadyChecksquareQueryParam param) { public SteadyChecksquareTaskVO create(SteadyChecksquareQueryParam param) {
long createStartMillis = System.currentTimeMillis();
validateCreateBaseParam(param); validateCreateBaseParam(param);
String lineId = trimToNull(param.getLineId()); List<String> lineIds = resolveLineIds(param);
LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空"); LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
SteadyChecksquareTaskPO existedTask = findExistingTask(lineId, startTime, endTime); SteadyChecksquareTaskPO existedTask = findExistingTask(lineIds, startTime, endTime);
if (existedTask != null) { if (existedTask != null) {
log.info("数据校验创建命中已有任务taskId={}lineId={}costMs={}",
existedTask.getId(), lineIds, System.currentTimeMillis() - createStartMillis);
return toTaskVO(existedTask); return toTaskVO(existedTask);
} }
prepareCreateContext(param); CreateContext context = prepareCreateContext(param);
influxQueryComponent.enableRequestCache(); SteadyChecksquareQueryParam taskParam = copyCreateParam(param, context.indicatorCodes);
SteadyChecksquareTaskPO task = saveRunningTaskInTransaction(taskParam, context);
try { try {
SteadyChecksquareQueryVO result = calculate(param); submitCreateTask(task.getId(), taskParam);
SteadyChecksquareTaskPO task = saveResultInTransaction(param, result); } catch (RejectedExecutionException ex) {
return toTaskVO(task); markTaskFail(task.getId(), ex.getMessage());
} finally { throw fail("数据校验任务线程池已满,请稍后重试");
influxQueryComponent.clearRequestCache();
} }
log.info("数据校验创建任务已提交后台执行taskId={}lineId={}indicatorCount={}costMs={}",
task.getId(), context.lineIds, context.indicatorCodes.size(), System.currentTimeMillis() - createStartMillis);
return toTaskVO(task);
} }
@Override
public SteadyChecksquareTaskVO restart(String taskId) {
SteadyChecksquareTaskPO task = requireTask(taskId);
if (!SteadyChecksquareConst.TASK_STATUS_FAIL.equals(task.getTaskStatus())) {
throw fail("只有执行失败的数据校验任务允许重新启动");
}
SteadyChecksquareQueryParam taskParam = buildRestartParam(task);
SteadyChecksquareTaskPO runningTask = resetFailTaskInTransaction(task);
try {
submitCreateTask(runningTask.getId(), taskParam);
} catch (RejectedExecutionException ex) {
markTaskFail(runningTask.getId(), ex.getMessage());
throw fail("数据校验任务线程池已满,请稍后重试");
}
return toTaskVO(runningTask);
}
@Override @Override
public boolean delete(List<String> taskIds) { public boolean delete(List<String> taskIds) {
List<String> ids = normalizeTextList(taskIds); List<String> ids = normalizeTextList(taskIds);
@@ -166,6 +196,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
result.setTaskId(task.getId()); result.setTaskId(task.getId());
result.setTaskNo(task.getTaskNo()); result.setTaskNo(task.getTaskNo());
result.setLineId(task.getLineId()); result.setLineId(task.getLineId());
result.setLineIds(readTaskLineIds(task));
result.setLineName(task.getLineName()); result.setLineName(task.getLineName());
result.setTimeStart(formatTime(task.getTimeStart())); result.setTimeStart(formatTime(task.getTimeStart()));
result.setTimeEnd(formatTime(task.getTimeEnd())); result.setTimeEnd(formatTime(task.getTimeEnd()));
@@ -198,6 +229,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
} }
SteadyChecksquareItemDetailVO result = new SteadyChecksquareItemDetailVO(); SteadyChecksquareItemDetailVO result = new SteadyChecksquareItemDetailVO();
result.setItemId(item.getId()); result.setItemId(item.getId());
result.setLineId(item.getLineId());
result.setLineName(item.getLineName());
result.setDetailType(type); result.setDetailType(type);
result.setStatType(statType); result.setStatType(statType);
LambdaQueryWrapper<SteadyChecksquareDetailPO> wrapper = new LambdaQueryWrapper<SteadyChecksquareDetailPO>() LambdaQueryWrapper<SteadyChecksquareDetailPO> wrapper = new LambdaQueryWrapper<SteadyChecksquareDetailPO>()
@@ -240,22 +273,85 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return taskResult; return taskResult;
} }
private SteadyChecksquareTaskPO resetFailTaskInTransaction(SteadyChecksquareTaskPO task) {
if (transactionTemplate == null) {
return resetFailTask(task);
}
return transactionTemplate.execute(status -> resetFailTask(task));
}
private SteadyChecksquareTaskPO resetFailTask(SteadyChecksquareTaskPO task) {
LocalDateTime now = LocalDateTime.now();
LambdaUpdateWrapper<SteadyChecksquareTaskPO> wrapper = new LambdaUpdateWrapper<SteadyChecksquareTaskPO>()
.set(SteadyChecksquareTaskPO::getTaskStatus, SteadyChecksquareConst.TASK_STATUS_RUNNING)
.set(SteadyChecksquareTaskPO::getItemCount, 0)
.set(SteadyChecksquareTaskPO::getAbnormalItemCount, 0)
.set(SteadyChecksquareTaskPO::getMinDataIntegrity, BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP))
.set(SteadyChecksquareTaskPO::getResultMessage, "数据校验任务重新执行中")
.set(SteadyChecksquareTaskPO::getUpdateTime, now)
.eq(SteadyChecksquareTaskPO::getId, task.getId())
.eq(SteadyChecksquareTaskPO::getTaskStatus, SteadyChecksquareConst.TASK_STATUS_FAIL)
.eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED);
if (!taskService.update(wrapper)) {
throw fail("数据校验任务状态已变化,请刷新后重试");
}
clearTaskResults(task.getId());
task.setTaskStatus(SteadyChecksquareConst.TASK_STATUS_RUNNING);
task.setItemCount(0);
task.setAbnormalItemCount(0);
task.setMinDataIntegrity(BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP));
task.setResultMessage("数据校验任务重新执行中");
task.setUpdateTime(now);
return task;
}
private void clearTaskResults(String taskId) {
List<SteadyChecksquareItemPO> items = itemService.lambdaQuery()
.eq(SteadyChecksquareItemPO::getTaskId, taskId)
.list();
if (items == null || items.isEmpty()) {
return;
}
List<String> itemIds = items.stream().map(SteadyChecksquareItemPO::getId).collect(Collectors.toList());
detailService.remove(new LambdaQueryWrapper<SteadyChecksquareDetailPO>()
.in(SteadyChecksquareDetailPO::getItemId, itemIds));
statSummaryService.remove(new LambdaQueryWrapper<SteadyChecksquareStatSummaryPO>()
.in(SteadyChecksquareStatSummaryPO::getItemId, itemIds));
itemService.remove(new LambdaQueryWrapper<SteadyChecksquareItemPO>()
.eq(SteadyChecksquareItemPO::getTaskId, taskId));
}
private SteadyChecksquareQueryParam buildRestartParam(SteadyChecksquareTaskPO task) {
List<String> lineIds = readTaskLineIds(task);
List<String> indicatorCodes = readStringList(task.getIndicatorCodesJson());
if (indicatorCodes.isEmpty()) {
indicatorCodes = parseTextListSearchValue(task.getIndicatorCodesText());
}
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
param.setLineId(lineIds.isEmpty() ? task.getLineId() : lineIds.get(0));
param.setLineIds(lineIds);
param.setIndicatorCodes(indicatorCodes);
param.setTimeStart(formatTime(task.getTimeStart()));
param.setTimeEnd(formatTime(task.getTimeEnd()));
return param;
}
private CreateContext prepareCreateContext(SteadyChecksquareQueryParam param) { private CreateContext prepareCreateContext(SteadyChecksquareQueryParam param) {
validateParam(param); validateParam(param);
String lineId = trimToNull(param.getLineId()); List<String> lineIds = resolveLineIds(param);
LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空"); LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
if (startTime.isAfter(endTime)) { if (startTime.isAfter(endTime)) {
throw fail("开始时间不能大于结束时间"); throw fail("开始时间不能大于结束时间");
} }
AddLedgerLinePathVO linePath = requireLinePath(lineId); Map<String, AddLedgerLinePathVO> linePathMap = requireLinePaths(lineIds);
int intervalMinutes = resolveIntervalMinutes(linePath); int intervalMinutes = resolveIntervalMinutes(linePathMap.get(lineIds.get(0)));
List<String> indicatorCodes = normalizeTextList(param.getIndicatorCodes()); List<String> indicatorCodes = resolveIndicatorCodes(param);
for (String indicatorCode : indicatorCodes) { for (String indicatorCode : indicatorCodes) {
requireIndicator(indicatorCode); requireIndicator(indicatorCode);
} }
validateCreateTimeRange(startTime, endTime); validateCreateTimeRange(startTime, endTime);
return new CreateContext(lineId, linePath, startTime, endTime, intervalMinutes, indicatorCodes); return new CreateContext(lineIds, linePathMap, startTime, endTime, intervalMinutes, indicatorCodes);
} }
private void validateCreateTimeRange(LocalDateTime startTime, LocalDateTime endTime) { private void validateCreateTimeRange(LocalDateTime startTime, LocalDateTime endTime) {
@@ -264,10 +360,10 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
} }
} }
private SteadyChecksquareTaskPO findExistingTask(String lineId, LocalDateTime startTime, LocalDateTime endTime) { private SteadyChecksquareTaskPO findExistingTask(List<String> lineIds, LocalDateTime startTime, LocalDateTime endTime) {
List<SteadyChecksquareTaskPO> tasks = taskService.lambdaQuery() List<SteadyChecksquareTaskPO> tasks = taskService.lambdaQuery()
.eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED) .eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED)
.eq(SteadyChecksquareTaskPO::getLineId, lineId) .eq(SteadyChecksquareTaskPO::getLineIdsText, buildTextListSearchValue(lineIds))
.eq(SteadyChecksquareTaskPO::getTimeStart, startTime) .eq(SteadyChecksquareTaskPO::getTimeStart, startTime)
.eq(SteadyChecksquareTaskPO::getTimeEnd, endTime) .eq(SteadyChecksquareTaskPO::getTimeEnd, endTime)
.orderByDesc(SteadyChecksquareTaskPO::getCreateTime) .orderByDesc(SteadyChecksquareTaskPO::getCreateTime)
@@ -287,36 +383,43 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
private SteadyChecksquareQueryVO calculate(SteadyChecksquareQueryParam param) { private SteadyChecksquareQueryVO calculate(SteadyChecksquareQueryParam param) {
validateParam(param); validateParam(param);
String lineId = trimToNull(param.getLineId()); List<String> lineIds = resolveLineIds(param);
LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空"); LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
if (startTime.isAfter(endTime)) { if (startTime.isAfter(endTime)) {
throw fail("开始时间不能大于结束时间"); throw fail("开始时间不能大于结束时间");
} }
AddLedgerLinePathVO linePath = requireLinePath(lineId); Map<String, AddLedgerLinePathVO> linePathMap = requireLinePaths(lineIds);
int intervalMinutes = resolveIntervalMinutes(linePath); int intervalMinutes = resolveIntervalMinutes(linePathMap.get(lineIds.get(0)));
SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO(); SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO();
result.setLineId(lineId); result.setLineId(lineIds.get(0));
result.setLineName(trimToNull(linePath.getLineName()) == null ? EMPTY_TEXT : linePath.getLineName()); result.setLineIds(new ArrayList<String>(lineIds));
result.setLineName(buildLineNames(lineIds, linePathMap));
result.setTimeStart(param.getTimeStart()); result.setTimeStart(param.getTimeStart());
result.setTimeEnd(param.getTimeEnd()); result.setTimeEnd(param.getTimeEnd());
result.setIntervalMinutes(intervalMinutes); result.setIntervalMinutes(intervalMinutes);
long startMillis = System.currentTimeMillis(); long startMillis = System.currentTimeMillis();
List<String> indicatorCodes = normalizeTextList(param.getIndicatorCodes()); List<String> indicatorCodes = resolveIndicatorCodes(param);
List<SteadyTrendIndicatorDefinitionBO> indicators = new ArrayList<SteadyTrendIndicatorDefinitionBO>(); List<SteadyTrendIndicatorDefinitionBO> indicators = new ArrayList<SteadyTrendIndicatorDefinitionBO>();
for (String indicatorCode : indicatorCodes) { for (String indicatorCode : indicatorCodes) {
indicators.add(requireIndicator(indicatorCode)); indicators.add(requireIndicator(indicatorCode));
} }
log.info("数据校验新增检测开始lineId={}indicatorCount={}timeStart={}timeEnd={}intervalMinutes={}", log.info("数据校验新增检测开始lineId={}indicatorCount={}timeStart={}timeEnd={}intervalMinutes={}",
lineId, indicatorCodes.size(), startTime, endTime, intervalMinutes); lineIds, indicatorCodes.size(), startTime, endTime, intervalMinutes);
prefetchNormalIndicatorPoints(lineId, indicators, startTime, endTime, intervalMinutes); for (String lineId : lineIds) {
for (SteadyTrendIndicatorDefinitionBO indicator : indicators) { AddLedgerLinePathVO linePath = linePathMap.get(lineId);
int itemIntervalMinutes = resolveIndicatorIntervalMinutes(indicator, intervalMinutes); int lineIntervalMinutes = resolveIntervalMinutes(linePath);
List<LocalDateTime> itemSlots = timeSlotCalculator.buildTimeSlots(startTime, endTime, itemIntervalMinutes); String lineName = linePath == null ? null : trimToNull(linePath.getLineName());
result.getItems().addAll(buildIndicatorItems(lineId, indicator, startTime, endTime, itemSlots, itemIntervalMinutes)); prefetchNormalIndicatorPoints(lineId, indicators, startTime, endTime, lineIntervalMinutes);
for (SteadyTrendIndicatorDefinitionBO indicator : indicators) {
int itemIntervalMinutes = resolveIndicatorIntervalMinutes(indicator, lineIntervalMinutes);
List<LocalDateTime> itemSlots = timeSlotCalculator.buildTimeSlots(startTime, endTime, itemIntervalMinutes);
result.getItems().addAll(buildIndicatorItems(lineId, lineName, indicator, startTime, endTime,
itemSlots, itemIntervalMinutes));
}
} }
log.info("数据校验新增检测结束lineId={}itemCount={}costMs={}", lineId, result.getItems().size(), System.currentTimeMillis() - startMillis); log.info("数据校验新增检测结束lineIds={}itemCount={}costMs={}", lineIds, result.getItems().size(), System.currentTimeMillis() - startMillis);
return result; return result;
} }
@@ -327,6 +430,13 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return transactionTemplate.execute(status -> saveResult(param, result)); return transactionTemplate.execute(status -> saveResult(param, result));
} }
private SteadyChecksquareTaskPO saveRunningTaskInTransaction(SteadyChecksquareQueryParam param, CreateContext context) {
if (transactionTemplate == null) {
return saveRunningTask(param, context);
}
return transactionTemplate.execute(status -> saveRunningTask(param, context));
}
private SteadyChecksquareTaskPO saveResultInTransaction(String taskId, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) { private SteadyChecksquareTaskPO saveResultInTransaction(String taskId, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) {
if (transactionTemplate == null) { if (transactionTemplate == null) {
return saveResult(taskId, param, result); return saveResult(taskId, param, result);
@@ -338,17 +448,73 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return saveResult(null, param, result); return saveResult(null, param, result);
} }
private SteadyChecksquareTaskPO saveRunningTask(SteadyChecksquareQueryParam param, CreateContext context) {
LocalDateTime now = LocalDateTime.now();
SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO();
task.setId(SteadyChecksquareIdUtil.uuid());
task.setTaskNo(SteadyChecksquareIdUtil.taskNo());
task.setLineId(context.lineIds.get(0));
task.setLineName(buildLineNames(context.lineIds, context.linePathMap));
task.setLineIdsJson(writeJson(context.lineIds));
task.setLineIdsText(buildTextListSearchValue(context.lineIds));
task.setTimeStart(context.startTime);
task.setTimeEnd(context.endTime);
task.setIntervalMinutes(context.intervalMinutes);
task.setIndicatorCodesJson(writeJson(context.indicatorCodes));
task.setIndicatorCodesText(buildIndicatorCodesText(context.indicatorCodes));
task.setTaskStatus(SteadyChecksquareConst.TASK_STATUS_RUNNING);
task.setItemCount(0);
task.setAbnormalItemCount(0);
task.setMinDataIntegrity(BigDecimal.ZERO.setScale(6, RoundingMode.HALF_UP));
task.setResultMessage("数据校验任务执行中");
task.setState(SteadyChecksquareConst.STATE_ENABLED);
task.setCreateTime(now);
task.setUpdateTime(now);
taskService.save(task);
return task;
}
private void submitCreateTask(String taskId, SteadyChecksquareQueryParam param) {
if (steadyChecksquareExecutorService == null) {
executeCreateTask(taskId, param);
return;
}
steadyChecksquareExecutorService.submit(() -> executeCreateTask(taskId, param));
}
private void executeCreateTask(String taskId, SteadyChecksquareQueryParam param) {
long startMillis = System.currentTimeMillis();
influxQueryComponent.enableRequestCache();
try {
SteadyChecksquareQueryVO result = calculate(param);
SteadyChecksquareTaskPO task = saveResultInTransaction(taskId, param, result);
log.info("数据校验后台任务执行成功taskId={}itemCount={}costMs={}",
taskId, task.getItemCount(), System.currentTimeMillis() - startMillis);
} catch (Exception ex) {
log.error("数据校验后台任务执行失败taskId={}costMs={}", taskId, System.currentTimeMillis() - startMillis, ex);
markTaskFail(taskId, ex.getMessage());
} finally {
influxQueryComponent.clearRequestCache();
}
}
private SteadyChecksquareTaskPO saveResult(String taskId, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) { private SteadyChecksquareTaskPO saveResult(String taskId, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) {
long saveStartMillis = System.currentTimeMillis();
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO(); SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO();
task.setId(trimToNull(taskId) == null ? SteadyChecksquareIdUtil.uuid() : taskId); task.setId(trimToNull(taskId) == null ? SteadyChecksquareIdUtil.uuid() : taskId);
task.setTaskNo(SteadyChecksquareIdUtil.taskNo()); if (trimToNull(taskId) == null) {
task.setTaskNo(SteadyChecksquareIdUtil.taskNo());
}
task.setLineId(result.getLineId()); task.setLineId(result.getLineId());
task.setLineName(result.getLineName()); task.setLineName(result.getLineName());
List<String> lineIds = resolveResultLineIds(result);
task.setLineIdsJson(writeJson(lineIds));
task.setLineIdsText(buildTextListSearchValue(lineIds));
task.setTimeStart(parseRequiredTime(result.getTimeStart(), "开始时间不能为空")); task.setTimeStart(parseRequiredTime(result.getTimeStart(), "开始时间不能为空"));
task.setTimeEnd(parseRequiredTime(result.getTimeEnd(), "结束时间不能为空")); task.setTimeEnd(parseRequiredTime(result.getTimeEnd(), "结束时间不能为空"));
task.setIntervalMinutes(result.getIntervalMinutes()); task.setIntervalMinutes(result.getIntervalMinutes());
List<String> indicatorCodes = normalizeTextList(param.getIndicatorCodes()); List<String> indicatorCodes = resolveIndicatorCodes(param);
task.setIndicatorCodesJson(writeJson(indicatorCodes)); task.setIndicatorCodesJson(writeJson(indicatorCodes));
task.setIndicatorCodesText(buildIndicatorCodesText(indicatorCodes)); task.setIndicatorCodesText(buildIndicatorCodesText(indicatorCodes));
task.setTaskStatus(SteadyChecksquareConst.TASK_STATUS_SUCCESS); task.setTaskStatus(SteadyChecksquareConst.TASK_STATUS_SUCCESS);
@@ -384,12 +550,18 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
if (!detailPOs.isEmpty()) { if (!detailPOs.isEmpty()) {
saveDetailBatchInChunks(detailPOs); saveDetailBatchInChunks(detailPOs);
} }
log.info("数据校验结果保存完成taskId={}itemCount={}summaryCount={}detailCount={}costMs={}",
task.getId(), itemPOs.size(), summaryPOs.size(), detailPOs.size(), System.currentTimeMillis() - saveStartMillis);
return task; return task;
} }
private void updateCompletedTask(SteadyChecksquareTaskPO task) { private void updateCompletedTask(SteadyChecksquareTaskPO task) {
LambdaUpdateWrapper<SteadyChecksquareTaskPO> wrapper = new LambdaUpdateWrapper<SteadyChecksquareTaskPO>() LambdaUpdateWrapper<SteadyChecksquareTaskPO> wrapper = new LambdaUpdateWrapper<SteadyChecksquareTaskPO>()
.set(SteadyChecksquareTaskPO::getLineName, task.getLineName()) .set(SteadyChecksquareTaskPO::getLineName, task.getLineName())
.set(SteadyChecksquareTaskPO::getLineIdsJson, task.getLineIdsJson())
.set(SteadyChecksquareTaskPO::getLineIdsText, task.getLineIdsText())
.set(SteadyChecksquareTaskPO::getIndicatorCodesJson, task.getIndicatorCodesJson())
.set(SteadyChecksquareTaskPO::getIndicatorCodesText, task.getIndicatorCodesText())
.set(SteadyChecksquareTaskPO::getIntervalMinutes, task.getIntervalMinutes()) .set(SteadyChecksquareTaskPO::getIntervalMinutes, task.getIntervalMinutes())
.set(SteadyChecksquareTaskPO::getTaskStatus, task.getTaskStatus()) .set(SteadyChecksquareTaskPO::getTaskStatus, task.getTaskStatus())
.set(SteadyChecksquareTaskPO::getItemCount, task.getItemCount()) .set(SteadyChecksquareTaskPO::getItemCount, task.getItemCount())
@@ -414,6 +586,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
po.setId(SteadyChecksquareIdUtil.uuid()); po.setId(SteadyChecksquareIdUtil.uuid());
po.setTaskId(taskId); po.setTaskId(taskId);
po.setItemKey(item.getItemKey()); po.setItemKey(item.getItemKey());
po.setLineId(item.getLineId());
po.setLineName(item.getLineName());
po.setIndicatorCode(item.getIndicatorCode()); po.setIndicatorCode(item.getIndicatorCode());
po.setIndicatorName(item.getIndicatorName()); po.setIndicatorName(item.getIndicatorName());
po.setHarmonicOrder(item.getHarmonicOrder()); po.setHarmonicOrder(item.getHarmonicOrder());
@@ -542,7 +716,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
+ field.getStatType() + "|" + intervalMinutes; + field.getStatType() + "|" + intervalMinutes;
} }
private List<SteadyChecksquareItemVO> buildIndicatorItems(String lineId, SteadyTrendIndicatorDefinitionBO indicator, private List<SteadyChecksquareItemVO> buildIndicatorItems(String lineId, String lineName,
SteadyTrendIndicatorDefinitionBO indicator,
LocalDateTime startTime, LocalDateTime endTime, LocalDateTime startTime, LocalDateTime endTime,
List<LocalDateTime> slots, int intervalMinutes) { List<LocalDateTime> slots, int intervalMinutes) {
List<SteadyChecksquareItemVO> result = new ArrayList<SteadyChecksquareItemVO>(); List<SteadyChecksquareItemVO> result = new ArrayList<SteadyChecksquareItemVO>();
@@ -550,12 +725,12 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
List<Integer> harmonicOrders = buildAggregateHarmonicOrders(indicator); List<Integer> harmonicOrders = buildAggregateHarmonicOrders(indicator);
prefetchHarmonicIndicatorPoints(lineId, indicator, harmonicOrders, startTime, endTime, intervalMinutes); prefetchHarmonicIndicatorPoints(lineId, indicator, harmonicOrders, startTime, endTime, intervalMinutes);
for (Integer order : harmonicOrders) { for (Integer order : harmonicOrders) {
result.add(buildItem(lineId, indicator, order, startTime, endTime, slots, intervalMinutes)); result.add(buildItem(lineId, lineName, indicator, order, startTime, endTime, slots, intervalMinutes));
} }
fillHarmonicParityRuleResult(result, lineId, indicator, startTime, endTime, intervalMinutes); fillHarmonicParityRuleResult(result, lineId, indicator, startTime, endTime, intervalMinutes);
return Collections.singletonList(aggregateHarmonicItems(lineId, indicator, result, intervalMinutes)); return Collections.singletonList(aggregateHarmonicItems(lineId, lineName, indicator, result, intervalMinutes));
} }
result.add(buildItem(lineId, indicator, null, startTime, endTime, slots, intervalMinutes)); result.add(buildItem(lineId, lineName, indicator, null, startTime, endTime, slots, intervalMinutes));
return result; return result;
} }
@@ -576,10 +751,13 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
} }
} }
private SteadyChecksquareItemVO aggregateHarmonicItems(String lineId, SteadyTrendIndicatorDefinitionBO indicator, private SteadyChecksquareItemVO aggregateHarmonicItems(String lineId, String lineName,
SteadyTrendIndicatorDefinitionBO indicator,
List<SteadyChecksquareItemVO> orderItems, int intervalMinutes) { List<SteadyChecksquareItemVO> orderItems, int intervalMinutes) {
SteadyChecksquareItemVO result = new SteadyChecksquareItemVO(); SteadyChecksquareItemVO result = new SteadyChecksquareItemVO();
result.setItemKey(buildItemKey(lineId, indicator, null)); result.setItemKey(buildItemKey(lineId, indicator, null));
result.setLineId(lineId);
result.setLineName(lineName);
result.setIndicatorCode(indicator.getIndicatorCode()); result.setIndicatorCode(indicator.getIndicatorCode());
result.setIndicatorName(indicator.getName()); result.setIndicatorName(indicator.getName());
result.setHarmonicOrder(null); result.setHarmonicOrder(null);
@@ -654,11 +832,14 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return new ArrayList<SteadyChecksquareStatDetailVO>(detailMap.values()); return new ArrayList<SteadyChecksquareStatDetailVO>(detailMap.values());
} }
private SteadyChecksquareItemVO buildItem(String lineId, SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder, private SteadyChecksquareItemVO buildItem(String lineId, String lineName,
SteadyTrendIndicatorDefinitionBO indicator, Integer harmonicOrder,
LocalDateTime startTime, LocalDateTime endTime, LocalDateTime startTime, LocalDateTime endTime,
List<LocalDateTime> slots, int intervalMinutes) { List<LocalDateTime> slots, int intervalMinutes) {
SteadyChecksquareItemVO item = new SteadyChecksquareItemVO(); SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
item.setItemKey(buildItemKey(lineId, indicator, harmonicOrder)); item.setItemKey(buildItemKey(lineId, indicator, harmonicOrder));
item.setLineId(lineId);
item.setLineName(lineName);
item.setIndicatorCode(indicator.getIndicatorCode()); item.setIndicatorCode(indicator.getIndicatorCode());
item.setIndicatorName(indicator.getName()); item.setIndicatorName(indicator.getName());
item.setHarmonicOrder(harmonicOrder); item.setHarmonicOrder(harmonicOrder);
@@ -822,7 +1003,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
if (param == null) { if (param == null) {
throw fail("数据校验参数不能为空"); throw fail("数据校验参数不能为空");
} }
if (trimToNull(param.getLineId()) == null) { if (resolveLineIds(param).isEmpty()) {
throw fail("监测点ID不能为空"); throw fail("监测点ID不能为空");
} }
parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
@@ -833,12 +1014,9 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
if (param == null) { if (param == null) {
throw fail("数据校验参数不能为空"); throw fail("数据校验参数不能为空");
} }
if (trimToNull(param.getLineId()) == null) { if (resolveLineIds(param).isEmpty()) {
throw fail("监测点ID不能为空"); throw fail("监测点ID不能为空");
} }
if (!allowEmptyIndicators && normalizeTextList(param.getIndicatorCodes()).isEmpty()) {
throw fail("指标不能为空");
}
parseRequiredTime(param.getTimeStart(), "开始时间不能为空"); parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
parseRequiredTime(param.getTimeEnd(), "结束时间不能为空"); parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
} }
@@ -872,6 +1050,26 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return linePath; return linePath;
} }
private Map<String, AddLedgerLinePathVO> requireLinePaths(List<String> lineIds) {
Map<String, AddLedgerLinePathVO> linePathMap = addLedgerService.listLinePathByLineIds(lineIds);
for (String lineId : lineIds) {
if (linePathMap == null || linePathMap.get(lineId) == null) {
throw fail("鐩戞祴鐐逛笉瀛樺湪鎴栦笉鍙敤");
}
}
return linePathMap;
}
private String buildLineNames(List<String> lineIds, Map<String, AddLedgerLinePathVO> linePathMap) {
List<String> names = new ArrayList<String>();
for (String lineId : lineIds) {
AddLedgerLinePathVO linePath = linePathMap.get(lineId);
String lineName = linePath == null ? null : trimToNull(linePath.getLineName());
names.add(lineName == null ? EMPTY_TEXT : lineName);
}
return String.join("", names);
}
private int resolveIntervalMinutes(AddLedgerLinePathVO linePath) { private int resolveIntervalMinutes(AddLedgerLinePathVO linePath) {
Integer interval = linePath.getLineInterval(); Integer interval = linePath.getLineInterval();
if (interval == null || interval <= 0) { if (interval == null || interval <= 0) {
@@ -1005,6 +1203,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
vo.setTaskId(task.getId()); vo.setTaskId(task.getId());
vo.setTaskNo(task.getTaskNo()); vo.setTaskNo(task.getTaskNo());
vo.setLineId(task.getLineId()); vo.setLineId(task.getLineId());
vo.setLineIds(readTaskLineIds(task));
vo.setLineName(task.getLineName()); vo.setLineName(task.getLineName());
vo.setTimeStart(formatTime(task.getTimeStart())); vo.setTimeStart(formatTime(task.getTimeStart()));
vo.setTimeEnd(formatTime(task.getTimeEnd())); vo.setTimeEnd(formatTime(task.getTimeEnd()));
@@ -1021,6 +1220,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
SteadyChecksquareItemVO vo = new SteadyChecksquareItemVO(); SteadyChecksquareItemVO vo = new SteadyChecksquareItemVO();
vo.setItemId(item.getId()); vo.setItemId(item.getId());
vo.setItemKey(item.getItemKey()); vo.setItemKey(item.getItemKey());
vo.setLineId(item.getLineId());
vo.setLineName(item.getLineName());
vo.setIndicatorCode(item.getIndicatorCode()); vo.setIndicatorCode(item.getIndicatorCode());
vo.setIndicatorName(item.getIndicatorName()); vo.setIndicatorName(item.getIndicatorName());
vo.setHarmonicOrder(item.getHarmonicOrder()); vo.setHarmonicOrder(item.getHarmonicOrder());
@@ -1229,6 +1430,53 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return new ArrayList<String>(result); return new ArrayList<String>(result);
} }
private List<String> resolveLineIds(SteadyChecksquareQueryParam param) {
List<String> lineIds = normalizeTextList(param == null ? null : param.getLineIds());
if (lineIds.isEmpty() && param != null && trimToNull(param.getLineId()) != null) {
lineIds.add(trimToNull(param.getLineId()));
}
return lineIds;
}
private List<String> resolveIndicatorCodes(SteadyChecksquareQueryParam param) {
List<String> indicatorCodes = normalizeTextList(param == null ? null : param.getIndicatorCodes());
if (!indicatorCodes.isEmpty()) {
return indicatorCodes;
}
List<String> allIndicatorCodes = new ArrayList<String>();
for (SteadyTrendIndicatorDefinitionBO indicator : indicatorCatalog.listIndicators()) {
allIndicatorCodes.add(indicator.getIndicatorCode());
}
return allIndicatorCodes;
}
private List<String> resolveResultLineIds(SteadyChecksquareQueryVO result) {
List<String> lineIds = normalizeTextList(result == null ? null : result.getLineIds());
if (lineIds.isEmpty() && result != null && trimToNull(result.getLineId()) != null) {
lineIds.add(trimToNull(result.getLineId()));
}
return lineIds;
}
private List<String> readTaskLineIds(SteadyChecksquareTaskPO task) {
List<String> lineIds = readStringList(task.getLineIdsJson());
if (lineIds.isEmpty() && trimToNull(task.getLineId()) != null) {
lineIds.add(trimToNull(task.getLineId()));
}
return lineIds;
}
private SteadyChecksquareQueryParam copyCreateParam(SteadyChecksquareQueryParam param, List<String> indicatorCodes) {
SteadyChecksquareQueryParam result = new SteadyChecksquareQueryParam();
List<String> lineIds = resolveLineIds(param);
result.setLineId(lineIds.isEmpty() ? null : lineIds.get(0));
result.setLineIds(new ArrayList<String>(lineIds));
result.setIndicatorCodes(new ArrayList<String>(indicatorCodes));
result.setTimeStart(param.getTimeStart());
result.setTimeEnd(param.getTimeEnd());
return result;
}
private String trimToNull(String value) { private String trimToNull(String value) {
if (value == null) { if (value == null) {
return null; return null;
@@ -1246,12 +1494,16 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
} }
private String buildIndicatorCodesText(List<String> indicatorCodes) { private String buildIndicatorCodesText(List<String> indicatorCodes) {
if (indicatorCodes == null || indicatorCodes.isEmpty()) { return buildTextListSearchValue(indicatorCodes);
}
private String buildTextListSearchValue(List<String> values) {
if (values == null || values.isEmpty()) {
return null; return null;
} }
StringBuilder builder = new StringBuilder("|"); StringBuilder builder = new StringBuilder("|");
for (String indicatorCode : indicatorCodes) { for (String value : values) {
builder.append(indicatorCode).append("|"); builder.append(value).append("|");
} }
return builder.toString(); return builder.toString();
} }
@@ -1264,6 +1516,14 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
return text.length() > 2000 ? text.substring(0, 2000) : text; return text.length() > 2000 ? text.substring(0, 2000) : text;
} }
private List<String> parseTextListSearchValue(String value) {
String text = trimToNull(value);
if (text == null) {
return new ArrayList<String>();
}
return normalizeTextList(Arrays.asList(text.split("\\|")));
}
private List<Integer> readIntegerList(String json) { private List<Integer> readIntegerList(String json) {
if (trimToNull(json) == null) { if (trimToNull(json) == null) {
return new ArrayList<Integer>(); return new ArrayList<Integer>();
@@ -1305,17 +1565,17 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
} }
private static class CreateContext { private static class CreateContext {
private final String lineId; private final List<String> lineIds;
private final AddLedgerLinePathVO linePath; private final Map<String, AddLedgerLinePathVO> linePathMap;
private final LocalDateTime startTime; private final LocalDateTime startTime;
private final LocalDateTime endTime; private final LocalDateTime endTime;
private final int intervalMinutes; private final int intervalMinutes;
private final List<String> indicatorCodes; private final List<String> indicatorCodes;
private CreateContext(String lineId, AddLedgerLinePathVO linePath, LocalDateTime startTime, private CreateContext(List<String> lineIds, Map<String, AddLedgerLinePathVO> linePathMap, LocalDateTime startTime,
LocalDateTime endTime, int intervalMinutes, List<String> indicatorCodes) { LocalDateTime endTime, int intervalMinutes, List<String> indicatorCodes) {
this.lineId = lineId; this.lineIds = lineIds;
this.linePath = linePath; this.linePathMap = linePathMap;
this.startTime = startTime; this.startTime = startTime;
this.endTime = endTime; this.endTime = endTime;
this.intervalMinutes = intervalMinutes; this.intervalMinutes = intervalMinutes;

View File

@@ -3,6 +3,8 @@ CREATE TABLE IF NOT EXISTS `steady_checksquare_task` (
`task_no` VARCHAR(64) NOT NULL COMMENT '检测任务编号', `task_no` VARCHAR(64) NOT NULL COMMENT '检测任务编号',
`line_id` VARCHAR(64) NOT NULL COMMENT '监测点ID', `line_id` VARCHAR(64) NOT NULL COMMENT '监测点ID',
`line_name` VARCHAR(255) NULL COMMENT '监测点名称', `line_name` VARCHAR(255) NULL COMMENT '监测点名称',
`line_ids_json` JSON NULL COMMENT '请求监测点ID列表',
`line_ids_text` VARCHAR(2000) NULL COMMENT '请求监测点ID检索文本格式 |line1|line2|',
`time_start` DATETIME NOT NULL COMMENT '检测开始时间', `time_start` DATETIME NOT NULL COMMENT '检测开始时间',
`time_end` DATETIME NOT NULL COMMENT '检测结束时间', `time_end` DATETIME NOT NULL COMMENT '检测结束时间',
`interval_minutes` INT NULL COMMENT '默认统计间隔,单位分钟', `interval_minutes` INT NULL COMMENT '默认统计间隔,单位分钟',
@@ -22,6 +24,7 @@ CREATE TABLE IF NOT EXISTS `steady_checksquare_task` (
UNIQUE KEY `uk_steady_checksquare_task_no` (`task_no`), UNIQUE KEY `uk_steady_checksquare_task_no` (`task_no`),
KEY `idx_steady_checksquare_task_line_time` (`line_id`, `time_start`, `time_end`), KEY `idx_steady_checksquare_task_line_time` (`line_id`, `time_start`, `time_end`),
KEY `idx_steady_checksquare_task_status` (`task_status`), KEY `idx_steady_checksquare_task_status` (`task_status`),
KEY `idx_steady_checksquare_task_line_ids_text` (`line_ids_text`(255)),
KEY `idx_steady_checksquare_task_indicator_text` (`indicator_codes_text`(255)), KEY `idx_steady_checksquare_task_indicator_text` (`indicator_codes_text`(255)),
KEY `idx_steady_checksquare_task_create_time` (`create_time`) KEY `idx_steady_checksquare_task_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验任务表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稳态数据校验任务表';
@@ -30,6 +33,8 @@ CREATE TABLE IF NOT EXISTS `steady_checksquare_item` (
`id` VARCHAR(64) NOT NULL COMMENT '主键', `id` VARCHAR(64) NOT NULL COMMENT '主键',
`task_id` VARCHAR(64) NOT NULL COMMENT '检测任务ID', `task_id` VARCHAR(64) NOT NULL COMMENT '检测任务ID',
`item_key` VARCHAR(255) NOT NULL COMMENT '检测项唯一键', `item_key` VARCHAR(255) NOT NULL COMMENT '检测项唯一键',
`line_id` VARCHAR(64) NULL COMMENT '监测点ID',
`line_name` VARCHAR(255) NULL COMMENT '监测点名称',
`indicator_code` VARCHAR(64) NOT NULL COMMENT '指标编码', `indicator_code` VARCHAR(64) NOT NULL COMMENT '指标编码',
`indicator_name` VARCHAR(255) NULL COMMENT '指标名称', `indicator_name` VARCHAR(255) NULL COMMENT '指标名称',
`harmonic_order` INT NULL COMMENT '谐波次数;聚合项为空', `harmonic_order` INT NULL COMMENT '谐波次数;聚合项为空',

View File

@@ -142,6 +142,71 @@ class SteadyChecksquareInfluxQueryComponentTest {
} }
} }
@Test
void shouldQueryMultipleStatTypesOnce() throws Exception {
AtomicInteger requestCount = new AtomicInteger();
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
server.createContext("/query", exchange -> {
requestCount.incrementAndGet();
byte[] body = ("{\"results\":[{\"series\":["
+ "{\"tags\":{\"value_type\":\"MAX\"},\"values\":[[\"2026-05-01T00:00:00Z\",10]]},"
+ "{\"tags\":{\"value_type\":\"AVG\"},\"values\":[[\"2026-05-01T00:00:00Z\",8]]}"
+ "]}]}").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);
SteadyTrendResolvedFieldBO max = buildField("rms");
max.setMeasurement("data_v");
max.setStatType("MAX");
SteadyTrendResolvedFieldBO avg = buildField("rms");
avg.setMeasurement("data_v");
avg.setStatType("AVG");
component.enableRequestCache();
Map<String, java.util.List<com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO>> result =
component.queryStatValuePointMap(Arrays.asList(max, avg),
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
LocalDateTime.of(2026, 5, 1, 0, 1, 0), 1);
component.clearRequestCache();
Assertions.assertEquals(1, requestCount.get());
Assertions.assertEquals(1, result.get("MAX").size());
Assertions.assertEquals(1, result.get("AVG").size());
Assertions.assertEquals(new java.math.BigDecimal("10"), result.get("MAX").get(0).getValue());
Assertions.assertEquals(new java.math.BigDecimal("8"), result.get("AVG").get(0).getValue());
} finally {
server.stop(0);
}
}
@Test
void shouldBuildStatValuePointQueryWithValueTypeRegex() {
SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(new SteadyInfluxDbProperties());
SteadyTrendResolvedFieldBO max = buildField("rms");
max.setMeasurement("data_v");
max.setStatType("MAX");
SteadyTrendResolvedFieldBO avg = buildField("rms");
avg.setMeasurement("data_v");
avg.setStatType("AVG");
String query = component.buildStatValuePointQuery(Arrays.asList(max, avg),
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
LocalDateTime.of(2026, 5, 1, 0, 1, 0));
Assertions.assertTrue(query.contains("SELECT \"rms\" AS \"value\""));
Assertions.assertTrue(query.contains("\"value_type\" =~ /^(MAX|AVG)$/"));
Assertions.assertTrue(query.contains("GROUP BY \"value_type\""));
Assertions.assertTrue(query.endsWith("ORDER BY time ASC"));
}
@Test @Test
void shouldSplitLongValuePointQueryByDay() throws Exception { void shouldSplitLongValuePointQueryByDay() throws Exception {
AtomicInteger requestCount = new AtomicInteger(); AtomicInteger requestCount = new AtomicInteger();

View File

@@ -11,6 +11,9 @@ import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@@ -28,23 +31,12 @@ class SteadyChecksquareValueOrderRuleComponentTest {
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent); SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
LocalDateTime firstTime = LocalDateTime.of(2026, 5, 1, 0, 0); LocalDateTime firstTime = LocalDateTime.of(2026, 5, 1, 0, 0);
LocalDateTime secondTime = LocalDateTime.of(2026, 5, 1, 0, 1); LocalDateTime secondTime = LocalDateTime.of(2026, 5, 1, 0, 1);
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) when(influxQueryComponent.queryStatValuePointMap(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
.thenAnswer(invocation -> { .thenReturn(statPointMap(
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType(); Arrays.asList(point(firstTime, "8"), point(secondTime, "9")),
if ("MAX".equals(statType)) { Arrays.asList(point(firstTime, "9"), point(secondTime, "10")),
return Arrays.asList(point(firstTime, "8"), point(secondTime, "9")); Arrays.asList(point(firstTime, "7"), point(secondTime, "8")),
} Arrays.asList(point(firstTime, "1"), point(secondTime, "9"))));
if ("CP95".equals(statType)) {
return Arrays.asList(point(firstTime, "9"), point(secondTime, "10"));
}
if ("AVG".equals(statType)) {
return Arrays.asList(point(firstTime, "7"), point(secondTime, "8"));
}
if ("MIN".equals(statType)) {
return Arrays.asList(point(firstTime, "1"), point(secondTime, "9"));
}
return Collections.emptyList();
});
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null, SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null,
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 2), 1); LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 2), 1);
@@ -65,23 +57,12 @@ class SteadyChecksquareValueOrderRuleComponentTest {
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent); SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) when(influxQueryComponent.queryStatValuePointMap(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
.thenAnswer(invocation -> { .thenReturn(statPointMap(
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType(); Collections.singletonList(point(time, "10")),
if ("MAX".equals(statType)) { Collections.singletonList(point(time, "10")),
return Collections.singletonList(point(time, "10")); Collections.singletonList(point(time, "8")),
} Collections.singletonList(point(time, "8"))));
if ("CP95".equals(statType)) {
return Collections.singletonList(point(time, "10"));
}
if ("AVG".equals(statType)) {
return Collections.singletonList(point(time, "8"));
}
if ("MIN".equals(statType)) {
return Collections.singletonList(point(time, "8"));
}
return Collections.emptyList();
});
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null, SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null,
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1); LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1);
@@ -96,23 +77,12 @@ class SteadyChecksquareValueOrderRuleComponentTest {
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent); SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) when(influxQueryComponent.queryStatValuePointMap(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
.thenAnswer(invocation -> { .thenReturn(statPointMap(
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType(); Collections.singletonList(point(time, "8")),
if ("MAX".equals(statType)) { Collections.singletonList(point(time, "10")),
return Collections.singletonList(point(time, "8")); Collections.singletonList(point(time, "8")),
} Collections.singletonList(point(time, "1"))));
if ("CP95".equals(statType)) {
return Collections.singletonList(point(time, "10"));
}
if ("AVG".equals(statType)) {
return Collections.singletonList(point(time, "8"));
}
if ("MIN".equals(statType)) {
return Collections.singletonList(point(time, "1"));
}
return Collections.emptyList();
});
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null, SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null,
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1); LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1);
@@ -127,23 +97,12 @@ class SteadyChecksquareValueOrderRuleComponentTest {
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent); SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) when(influxQueryComponent.queryStatValuePointMap(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
.thenAnswer(invocation -> { .thenReturn(statPointMap(
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType(); Collections.singletonList(point(time, "8")),
if ("MAX".equals(statType)) { Collections.singletonList(point(time, "10")),
return Collections.singletonList(point(time, "8")); Collections.singletonList(point(time, "8")),
} Collections.singletonList(point(time, "1"))));
if ("CP95".equals(statType)) {
return Collections.singletonList(point(time, "10"));
}
if ("AVG".equals(statType)) {
return Collections.singletonList(point(time, "8"));
}
if ("MIN".equals(statType)) {
return Collections.singletonList(point(time, "1"));
}
return Collections.emptyList();
});
SteadyTrendIndicatorDefinitionBO indicator = indicator(); SteadyTrendIndicatorDefinitionBO indicator = indicator();
indicator.setHarmonic(true); indicator.setHarmonic(true);
indicator.setHarmonicFieldPrefix("v"); indicator.setHarmonicFieldPrefix("v");
@@ -160,20 +119,12 @@ class SteadyChecksquareValueOrderRuleComponentTest {
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent); SteadyChecksquareValueOrderRuleComponent component = new SteadyChecksquareValueOrderRuleComponent(influxQueryComponent);
LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0); LocalDateTime time = LocalDateTime.of(2026, 5, 1, 0, 0);
when(influxQueryComponent.queryValuePoints(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1))) when(influxQueryComponent.queryStatValuePointMap(any(), any(LocalDateTime.class), any(LocalDateTime.class), eq(1)))
.thenAnswer(invocation -> { .thenReturn(statPointMap(
String statType = invocation.getArgument(0, com.njcn.gather.steady.datavie.pojo.bo.SteadyTrendResolvedFieldBO.class).getStatType(); Collections.singletonList(point(time, "10")),
if ("MAX".equals(statType)) { Collections.singletonList(point(time, "11")),
return Collections.singletonList(point(time, "10")); Collections.emptyList(),
} Collections.singletonList(point(time, "1"))));
if ("CP95".equals(statType)) {
return Collections.singletonList(point(time, "11"));
}
if ("MIN".equals(statType)) {
return Collections.singletonList(point(time, "1"));
}
return Collections.emptyList();
});
SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null, SteadyChecksquareValueOrderRuleVO result = component.check("line-001", indicator(), null,
LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1); LocalDateTime.of(2026, 5, 1, 0, 0), LocalDateTime.of(2026, 5, 1, 0, 1), 1);
@@ -216,4 +167,17 @@ class SteadyChecksquareValueOrderRuleComponentTest {
point.setValue(new BigDecimal(value)); point.setValue(new BigDecimal(value));
return point; return point;
} }
private Map<String, List<SteadyChecksquareValuePointBO>> statPointMap(List<SteadyChecksquareValuePointBO> maxPoints,
List<SteadyChecksquareValuePointBO> cp95Points,
List<SteadyChecksquareValuePointBO> avgPoints,
List<SteadyChecksquareValuePointBO> minPoints) {
Map<String, List<SteadyChecksquareValuePointBO>> result =
new LinkedHashMap<String, List<SteadyChecksquareValuePointBO>>();
result.put("MAX", maxPoints);
result.put("CP95", cp95Points);
result.put("AVG", avgPoints);
result.put("MIN", minPoints);
return result;
}
} }

View File

@@ -28,6 +28,10 @@ class SteadyChecksquareControllerTest {
PostMapping createMapping = createMethod.getAnnotation(PostMapping.class); PostMapping createMapping = createMethod.getAnnotation(PostMapping.class);
Assertions.assertArrayEquals(new String[]{"/create"}, createMapping.value()); Assertions.assertArrayEquals(new String[]{"/create"}, createMapping.value());
Method restartMethod = SteadyChecksquareController.class.getDeclaredMethod("restart", String.class);
PostMapping restartMapping = restartMethod.getAnnotation(PostMapping.class);
Assertions.assertArrayEquals(new String[]{"/restart"}, restartMapping.value());
Method detailMethod = SteadyChecksquareController.class.getDeclaredMethod("detail", String.class); Method detailMethod = SteadyChecksquareController.class.getDeclaredMethod("detail", String.class);
GetMapping detailMapping = detailMethod.getAnnotation(GetMapping.class); GetMapping detailMapping = detailMethod.getAnnotation(GetMapping.class);
Assertions.assertArrayEquals(new String[]{"/detail"}, detailMapping.value()); Assertions.assertArrayEquals(new String[]{"/detail"}, detailMapping.value());

View File

@@ -48,6 +48,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@@ -68,6 +69,7 @@ class SteadyChecksquareServiceImplTest {
MapperBuilderAssistant assistant = new MapperBuilderAssistant(new Configuration(), ""); MapperBuilderAssistant assistant = new MapperBuilderAssistant(new Configuration(), "");
TableInfoHelper.initTableInfo(assistant, SteadyChecksquareTaskPO.class); TableInfoHelper.initTableInfo(assistant, SteadyChecksquareTaskPO.class);
TableInfoHelper.initTableInfo(assistant, SteadyChecksquareItemPO.class); TableInfoHelper.initTableInfo(assistant, SteadyChecksquareItemPO.class);
TableInfoHelper.initTableInfo(assistant, SteadyChecksquareStatSummaryPO.class);
TableInfoHelper.initTableInfo(assistant, SteadyChecksquareDetailPO.class); TableInfoHelper.initTableInfo(assistant, SteadyChecksquareDetailPO.class);
} }
@@ -153,7 +155,8 @@ class SteadyChecksquareServiceImplTest {
SteadyChecksquareTaskVO result = service.create(param); SteadyChecksquareTaskVO result = service.create(param);
Assertions.assertEquals(taskCaptor.getValue().getId(), result.getTaskId()); Assertions.assertEquals(taskCaptor.getValue().getId(), result.getTaskId());
Assertions.assertEquals(Integer.valueOf(5), result.getItemCount()); Assertions.assertEquals("RUNNING", result.getTaskStatus());
Assertions.assertEquals(Integer.valueOf(0), result.getItemCount());
verify(itemService).saveBatch(any()); verify(itemService).saveBatch(any());
} }
@@ -201,7 +204,7 @@ class SteadyChecksquareServiceImplTest {
} }
@Test @Test
void shouldCreateTaskSynchronouslyAndReturnTaskSummary() { void shouldCreateRunningTaskAndReturnTaskSummaryBeforeCalculationCompletes() {
AddLedgerService addLedgerService = mock(AddLedgerService.class); AddLedgerService addLedgerService = mock(AddLedgerService.class);
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
@@ -243,12 +246,69 @@ class SteadyChecksquareServiceImplTest {
SteadyChecksquareTaskVO result = service.create(param); SteadyChecksquareTaskVO result = service.create(param);
Assertions.assertEquals(taskCaptor.getValue().getId(), result.getTaskId()); Assertions.assertEquals(taskCaptor.getValue().getId(), result.getTaskId());
Assertions.assertEquals("SUCCESS", result.getTaskStatus()); Assertions.assertEquals("RUNNING", result.getTaskStatus());
Assertions.assertEquals(Integer.valueOf(1), result.getItemCount()); Assertions.assertEquals(Integer.valueOf(0), result.getItemCount());
verify(itemService).saveBatch(any()); verify(itemService).saveBatch(any());
verify(statSummaryService).saveBatch(any()); verify(statSummaryService).saveBatch(any());
} }
@Test
void shouldCreateSingleRunningTaskForMultipleLines() {
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<SteadyChecksquareTaskPO> taskQuery = mock(LambdaQueryChainWrapper.class);
when(taskService.lambdaQuery()).thenReturn(taskQuery);
when(taskQuery.eq(any(), any())).thenReturn(taskQuery);
when(taskQuery.and(any())).thenReturn(taskQuery);
when(taskQuery.orderByDesc(any(SFunction.class))).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 linePath1 = new AddLedgerLinePathVO();
linePath1.setLineId("line-001");
linePath1.setLineName("line-001");
linePath1.setLineInterval(1);
AddLedgerLinePathVO linePath2 = new AddLedgerLinePathVO();
linePath2.setLineId("line-002");
linePath2.setLineName("line-002");
linePath2.setLineInterval(1);
LinkedHashMap<String, AddLedgerLinePathVO> linePathMap = new LinkedHashMap<String, AddLedgerLinePathVO>();
linePathMap.put("line-001", linePath1);
linePathMap.put("line-002", linePath2);
when(addLedgerService.listLinePathByLineIds(eq(Arrays.asList("line-001", "line-002"))))
.thenReturn(linePathMap);
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
.thenReturn(new HashSet<LocalDateTime>());
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<SteadyChecksquareTaskPO> taskCaptor = ArgumentCaptor.forClass(SteadyChecksquareTaskPO.class);
when(taskService.save(taskCaptor.capture())).thenReturn(true);
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
param.setLineIds(Arrays.asList("line-001", "line-002"));
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(Arrays.asList("line-001", "line-002"), result.getLineIds());
Assertions.assertEquals("|line-001|line-002|", taskCaptor.getValue().getLineIdsText());
Assertions.assertEquals("line-001、line-002", taskCaptor.getValue().getLineName());
verify(taskService, times(1)).save(any());
}
@Test @Test
void shouldUseFixedFlickerIntervalsPerIndicator() { void shouldUseFixedFlickerIntervalsPerIndicator() {
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
@@ -293,6 +353,50 @@ class SteadyChecksquareServiceImplTest {
assertItemInterval(result.getItems().get(2), "PLT", 120, 2); assertItemInterval(result.getItems().get(2), "PLT", 120, 2);
} }
@Test
void shouldUseAllIndicatorsWhenCreateIndicatorCodesIsEmpty() {
SteadyTrendIndicatorCatalog indicatorCatalog = new SteadyTrendIndicatorCatalog();
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
AddLedgerService addLedgerService = mock(AddLedgerService.class);
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(indicatorCatalog,
influxQueryComponent, new SteadyChecksquareCalculator(), valueOrderRuleComponent, harmonicParityRuleComponent,
new AddDataTimeSlotCalculator(), addLedgerService, mock(SteadyChecksquareTaskService.class),
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
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());
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<LocalDateTime>());
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
param.setLineId("line-001");
param.setIndicatorCodes(Collections.<String>emptyList());
param.setTimeStart("2026-05-01 00:00:00");
param.setTimeEnd("2026-05-01 00:01:00");
SteadyChecksquareQueryVO result = calculate(service, param);
List<String> expectedIndicatorCodes = new ArrayList<String>();
for (SteadyTrendIndicatorDefinitionBO indicator : indicatorCatalog.listIndicators()) {
expectedIndicatorCodes.add(indicator.getIndicatorCode());
}
List<String> actualIndicatorCodes = new ArrayList<String>();
for (SteadyChecksquareItemVO item : result.getItems()) {
actualIndicatorCodes.add(item.getIndicatorCode());
}
Assertions.assertEquals(expectedIndicatorCodes, actualIndicatorCodes);
}
@Test @Test
void shouldAggregateAllHarmonicOrdersIntoIndicatorItem() { void shouldAggregateAllHarmonicOrdersIntoIndicatorItem() {
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class); SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
@@ -566,7 +670,11 @@ class SteadyChecksquareServiceImplTest {
task.setTimeEnd(LocalDateTime.of(2026, 5, 1, 0, 1)); task.setTimeEnd(LocalDateTime.of(2026, 5, 1, 0, 1));
task.setIntervalMinutes(1); task.setIntervalMinutes(1);
SteadyChecksquareItemPO item1 = buildItemPO("item-001", "V_RMS"); SteadyChecksquareItemPO item1 = buildItemPO("item-001", "V_RMS");
item1.setLineId("line-001");
item1.setLineName("line-001-name");
SteadyChecksquareItemPO item2 = buildItemPO("item-002", "FREQ"); SteadyChecksquareItemPO item2 = buildItemPO("item-002", "FREQ");
item2.setLineId("line-002");
item2.setLineName("line-002-name");
SteadyChecksquareStatSummaryPO summary1 = buildSummaryPO("item-001", "AVG"); SteadyChecksquareStatSummaryPO summary1 = buildSummaryPO("item-001", "AVG");
SteadyChecksquareStatSummaryPO summary2 = buildSummaryPO("item-002", "AVG"); SteadyChecksquareStatSummaryPO summary2 = buildSummaryPO("item-002", "AVG");
when(taskService.getById("task-001")).thenReturn(task); when(taskService.getById("task-001")).thenReturn(task);
@@ -585,6 +693,10 @@ class SteadyChecksquareServiceImplTest {
SteadyChecksquareQueryVO result = service.detail("task-001"); SteadyChecksquareQueryVO result = service.detail("task-001");
Assertions.assertEquals(2, result.getItems().size()); Assertions.assertEquals(2, result.getItems().size());
Assertions.assertEquals("line-001", result.getItems().get(0).getLineId());
Assertions.assertEquals("line-001-name", result.getItems().get(0).getLineName());
Assertions.assertEquals("line-002", result.getItems().get(1).getLineId());
Assertions.assertEquals("line-002-name", result.getItems().get(1).getLineName());
Assertions.assertEquals(1, result.getItems().get(0).getStatSummaries().size()); Assertions.assertEquals(1, result.getItems().get(0).getStatSummaries().size());
Assertions.assertEquals(1, result.getItems().get(1).getStatSummaries().size()); Assertions.assertEquals(1, result.getItems().get(1).getStatSummaries().size());
verify(statSummaryService, times(1)).lambdaQuery(); verify(statSummaryService, times(1)).lambdaQuery();
@@ -596,6 +708,8 @@ class SteadyChecksquareServiceImplTest {
SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class); SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class);
SteadyChecksquareItemPO item = new SteadyChecksquareItemPO(); SteadyChecksquareItemPO item = new SteadyChecksquareItemPO();
item.setId("item-001"); item.setId("item-001");
item.setLineId("line-001");
item.setLineName("line-001-name");
item.setState(1); item.setState(1);
SteadyChecksquareDetailPO detail = new SteadyChecksquareDetailPO(); SteadyChecksquareDetailPO detail = new SteadyChecksquareDetailPO();
detail.setItemId("item-001"); detail.setItemId("item-001");
@@ -619,6 +733,8 @@ class SteadyChecksquareServiceImplTest {
Assertions.assertEquals(Integer.valueOf(2), result.getPageNum()); Assertions.assertEquals(Integer.valueOf(2), result.getPageNum());
Assertions.assertEquals(Integer.valueOf(1), result.getPageSize()); Assertions.assertEquals(Integer.valueOf(1), result.getPageSize());
Assertions.assertEquals(Long.valueOf(1L), result.getTotal()); Assertions.assertEquals(Long.valueOf(1L), result.getTotal());
Assertions.assertEquals("line-001", result.getLineId());
Assertions.assertEquals("line-001-name", result.getLineName());
Assertions.assertEquals(1, result.getValueOrderDetails().size()); Assertions.assertEquals(1, result.getValueOrderDetails().size());
verify(detailService).page(any(Page.class), any()); verify(detailService).page(any(Page.class), any());
} }
@@ -651,6 +767,75 @@ class SteadyChecksquareServiceImplTest {
verify(itemService).update(any()); verify(itemService).update(any());
} }
@Test
void shouldRejectRestartWhenTaskIsNotFail() {
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
SteadyChecksquareTaskPO task = buildTask("task-001", "SUCCESS");
when(taskService.getById("task-001")).thenReturn(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());
Assertions.assertThrows(RuntimeException.class, () -> service.restart("task-001"));
verify(taskService, never()).update(any());
}
@Test
void shouldRestartFailTaskWithSameTaskIdAndOriginalParam() {
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<SteadyChecksquareItemPO> itemQuery = mock(LambdaQueryChainWrapper.class);
SteadyChecksquareTaskPO task = buildTask("task-001", "FAIL");
SteadyChecksquareItemPO oldItem = new SteadyChecksquareItemPO();
oldItem.setId("item-001");
oldItem.setTaskId("task-001");
when(taskService.getById("task-001")).thenReturn(task);
when(taskService.update(any())).thenReturn(true);
when(itemService.lambdaQuery()).thenReturn(itemQuery);
when(itemQuery.eq(any(), any())).thenReturn(itemQuery);
when(itemQuery.list()).thenReturn(Collections.singletonList(oldItem));
when(itemService.remove(any())).thenReturn(true);
when(statSummaryService.remove(any())).thenReturn(true);
when(detailService.remove(any())).thenReturn(true);
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<LocalDateTime>());
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());
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
influxQueryComponent, new SteadyChecksquareCalculator(),
valueOrderRuleComponent, harmonicParityRuleComponent,
new AddDataTimeSlotCalculator(), addLedgerService, taskService,
itemService, statSummaryService, detailService, new ObjectMapper());
SteadyChecksquareTaskVO result = service.restart("task-001");
Assertions.assertEquals("task-001", result.getTaskId());
Assertions.assertEquals("RUNNING", result.getTaskStatus());
verify(detailService).remove(any());
verify(statSummaryService).remove(any());
verify(itemService).remove(any());
verify(taskService, times(2)).update(any());
verify(itemService).saveBatch(any());
}
@Test @Test
void shouldSaveChecksquareResultsInBatch() { void shouldSaveChecksquareResultsInBatch() {
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class); SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
@@ -672,6 +857,8 @@ class SteadyChecksquareServiceImplTest {
result.setIntervalMinutes(1); result.setIntervalMinutes(1);
SteadyChecksquareItemVO item = new SteadyChecksquareItemVO(); SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
item.setItemKey("line-001|V_RMS"); item.setItemKey("line-001|V_RMS");
item.setLineId("line-001");
item.setLineName("line-001-name");
item.setIndicatorCode("V_RMS"); item.setIndicatorCode("V_RMS");
item.setIndicatorName("鐩哥數鍘嬫湁鏁堝€?"); item.setIndicatorName("鐩哥數鍘嬫湁鏁堝€?");
item.setIntervalMinutes(1); item.setIntervalMinutes(1);
@@ -700,7 +887,11 @@ class SteadyChecksquareServiceImplTest {
saveResult(service, param, result); saveResult(service, param, result);
verify(taskService).save(any()); verify(taskService).save(any());
verify(itemService).saveBatch(any()); ArgumentCaptor<List> itemCaptor = ArgumentCaptor.forClass(List.class);
verify(itemService).saveBatch(itemCaptor.capture());
SteadyChecksquareItemPO savedItem = (SteadyChecksquareItemPO) itemCaptor.getValue().get(0);
Assertions.assertEquals("line-001", savedItem.getLineId());
Assertions.assertEquals("line-001-name", savedItem.getLineName());
verify(statSummaryService).saveBatch(any()); verify(statSummaryService).saveBatch(any());
} }
@@ -846,9 +1037,9 @@ class SteadyChecksquareServiceImplTest {
List<SteadyChecksquareItemVO> orderItems) { List<SteadyChecksquareItemVO> orderItems) {
try { try {
Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("aggregateHarmonicItems", Method method = SteadyChecksquareServiceImpl.class.getDeclaredMethod("aggregateHarmonicItems",
String.class, SteadyTrendIndicatorDefinitionBO.class, List.class, int.class); String.class, String.class, SteadyTrendIndicatorDefinitionBO.class, List.class, int.class);
method.setAccessible(true); method.setAccessible(true);
return (SteadyChecksquareItemVO) method.invoke(service, "line-001", indicator, orderItems, 1); return (SteadyChecksquareItemVO) method.invoke(service, "line-001", "line-001-name", indicator, orderItems, 1);
} catch (Exception exception) { } catch (Exception exception) {
throw new RuntimeException(exception); throw new RuntimeException(exception);
} }
@@ -862,6 +1053,29 @@ class SteadyChecksquareServiceImplTest {
return indicator; return indicator;
} }
private SteadyChecksquareTaskPO buildTask(String taskId, String taskStatus) {
SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO();
task.setId(taskId);
task.setTaskNo("CS202605010001");
task.setLineId("line-001");
task.setLineName("line-001");
task.setLineIdsJson("[\"line-001\"]");
task.setLineIdsText("|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.setIndicatorCodesText("|V_RMS|");
task.setTaskStatus(taskStatus);
task.setItemCount(1);
task.setAbnormalItemCount(1);
task.setMinDataIntegrity(BigDecimal.ZERO.setScale(6));
task.setResultMessage("failed");
task.setState(1);
task.setCreateTime(LocalDateTime.of(2026, 5, 1, 1, 0));
return task;
}
private SteadyChecksquareItemVO buildOrderItem(boolean hasData, BigDecimal dataIntegrity) { private SteadyChecksquareItemVO buildOrderItem(boolean hasData, BigDecimal dataIntegrity) {
SteadyChecksquareItemVO item = new SteadyChecksquareItemVO(); SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
item.setItemKey("line-001|V_HARMONIC|2"); item.setItemKey("line-001|V_HARMONIC|2");

View File

@@ -30,7 +30,7 @@
d.name AS name, d.name AS name,
d.icd AS icdId, d.icd AS icdId,
p.Name AS icdName, p.Name AS icdName,
p.Path AS icdPath, NULL AS icdPath,
p.Result AS icdResult, p.Result AS icdResult,
p.Msg AS icdMsg, p.Msg AS icdMsg,
d.power AS power, d.power AS power,

View File

@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -37,6 +38,9 @@ public class IcdConsistencyCheckService {
private static final String ISSUE_FILE_NAME = "icd-consistency-issues.json"; private static final String ISSUE_FILE_NAME = "icd-consistency-issues.json";
private static final List<String> REQUIRED_REPORT_DESCS = Arrays.asList("统计数据", "波动闪变", "实时数据", "暂态事件"); private static final List<String> REQUIRED_REPORT_DESCS = Arrays.asList("统计数据", "波动闪变", "实时数据", "暂态事件");
private static final List<String> REQUIRED_LN_CLASSES = Arrays.asList("MMXU", "MSQI", "MHAI", "MFLK"); private static final List<String> REQUIRED_LN_CLASSES = Arrays.asList("MMXU", "MSQI", "MHAI", "MFLK");
private static final Set<String> EXCLUDED_DOI_DESCS = new HashSet<String>(Arrays.asList(
"电压扰动事件启动", "电压暂降事件启动", "电压暂升事件启动", "电压中断事件启动",
"电压暂降启动定值", "电压暂升启动定值", "电压中断启动定值"));
private final FileStorageService fileStorageService; private final FileStorageService fileStorageService;
private final ObjectMapper objectMapper = buildMapper(); private final ObjectMapper objectMapper = buildMapper();
@@ -45,14 +49,13 @@ public class IcdConsistencyCheckService {
if (request == null) { if (request == null) {
throw new IllegalArgumentException("ICD 一致性校验请求不能为空"); throw new IllegalArgumentException("ICD 一致性校验请求不能为空");
} }
log.info("ICD一致性校验标准ICD JSON={}", request.getStandardJson());
log.info("ICD一致性校验待校验ICD JSON={}", request.getCheckedJson());
MappingDocument checked = parseMapping(request.getCheckedJson(), "待校验 JSON"); MappingDocument checked = parseMapping(request.getCheckedJson(), "待校验 JSON");
MappingDocument standard = parseMapping(request.getStandardJson(), "标准 JSON"); MappingDocument standard = parseMapping(request.getStandardJson(), "标准 JSON");
List<IcdConsistencyIssue> issues = new ArrayList<IcdConsistencyIssue>(); List<IcdConsistencyIssue> issues = new ArrayList<IcdConsistencyIssue>();
validateSelfFormat(checked, "待校验映射", issues); validateSelfFormat(checked, "待校验映射", issues);
boolean corrected = applySelfMappingRules(checked, issues); boolean corrected = applySelfMappingRules(checked, standard);
corrected = removeDuplicateDescDoiItems(checked, standard, issues) || corrected;
validateConsistency(checked, standard, issues); validateConsistency(checked, standard, issues);
IcdConsistencyCheckResponse response = new IcdConsistencyCheckResponse(); IcdConsistencyCheckResponse response = new IcdConsistencyCheckResponse();
@@ -172,6 +175,9 @@ public class IcdConsistencyCheckService {
continue; continue;
} }
for (DoiItem doi : inst.getDoiList()) { for (DoiItem doi : inst.getDoiList()) {
if (isExcludedDoiDesc(doi.getDesc())) {
continue;
}
if (isEmpty(doi.getSdiList())) { if (isEmpty(doi.getSdiList())) {
addIssue(issues, "自身格式校验", instPath + ".doiList[" + buildKey(doi.getName(), doi.getDesc()) + "]", addIssue(issues, "自身格式校验", instPath + ".doiList[" + buildKey(doi.getName(), doi.getDesc()) + "]",
"typeList 不能为空:" + joinDesc(group.getDesc(), inst.getDesc(), doi.getDesc()), null, null, false); "typeList 不能为空:" + joinDesc(group.getDesc(), inst.getDesc(), doi.getDesc()), null, null, false);
@@ -197,24 +203,28 @@ public class IcdConsistencyCheckService {
validateDataSetConsistency(checked, standard, issues); validateDataSetConsistency(checked, standard, issues);
} }
private boolean applySelfMappingRules(MappingDocument checked, List<IcdConsistencyIssue> issues) { private boolean applySelfMappingRules(MappingDocument checked, MappingDocument standard) {
if (checked.getReportMap() == null) { if (checked.getReportMap() == null) {
return false; return false;
} }
Map<String, ReportMapItem> standardMap = indexReportMap(standard);
boolean hasRtFre = false; boolean hasRtFre = false;
boolean corrected = false; boolean corrected = false;
for (ReportMapItem item : checked.getReportMap()) { for (ReportMapItem item : checked.getReportMap()) {
if ("实时数据".equals(trimToEmpty(item.getDesc())) && trimToEmpty(item.getRptId()).contains("RtFre")) { if ("实时数据".equals(trimToEmpty(item.getDesc())) && containsRtFre(item.getRptId())) {
hasRtFre = true; hasRtFre = true;
ReportMapItem standardItem = standardMap.get(buildReportKey(item));
boolean needRuleCorrection = standardItem == null
|| !equalsValue(String.valueOf(standardItem.getReportCount()), String.valueOf(item.getReportCount()))
|| !equalsValue(standardItem.getFlickerFlag(), item.getFlickerFlag());
if (!needRuleCorrection) {
continue;
}
if (!"1".equals(trimToEmpty(item.getFlickerFlag()))) { if (!"1".equals(trimToEmpty(item.getFlickerFlag()))) {
addIssue(issues, "映射规则", "ReportMap[" + buildReportKey(item) + "].FlickerFlag",
"实时数据报告 rptID 包含 RtFreFlickerFlag 已按规则调整为 1", "1", item.getFlickerFlag(), true);
item.setFlickerFlag("1"); item.setFlickerFlag("1");
corrected = true; corrected = true;
} }
if (item.getReportCount() != 0) { if (item.getReportCount() != 0) {
addIssue(issues, "映射规则", "ReportMap[" + buildReportKey(item) + "].reportCount",
"实时数据报告 rptID 包含 RtFrereportCount 已按规则调整为 0", "0", String.valueOf(item.getReportCount()), true);
item.setReportCount(0); item.setReportCount(0);
corrected = true; corrected = true;
} }
@@ -225,11 +235,12 @@ public class IcdConsistencyCheckService {
} }
for (ReportMapItem item : checked.getReportMap()) { for (ReportMapItem item : checked.getReportMap()) {
if ("统计数据".equals(trimToEmpty(item.getDesc()))) { if ("统计数据".equals(trimToEmpty(item.getDesc()))) {
ReportMapItem standardItem = standardMap.get(buildReportKey(item));
if (standardItem != null && equalsValue(String.valueOf(standardItem.getReportCount()), String.valueOf(item.getReportCount()))) {
continue;
}
int adjustedCount = item.getReportCount() - 1; int adjustedCount = item.getReportCount() - 1;
if (item.getReportCount() != adjustedCount) { if (item.getReportCount() != adjustedCount) {
addIssue(issues, "映射规则", "ReportMap[" + buildReportKey(item) + "].reportCount",
"存在 RtFre 实时数据报告,统计数据 reportCount 已按规则减 1",
String.valueOf(adjustedCount), String.valueOf(item.getReportCount()), true);
item.setReportCount(adjustedCount); item.setReportCount(adjustedCount);
corrected = true; corrected = true;
} }
@@ -238,6 +249,47 @@ public class IcdConsistencyCheckService {
return corrected; return corrected;
} }
private boolean removeDuplicateDescDoiItems(MappingDocument checked, MappingDocument standard, List<IcdConsistencyIssue> issues) {
if (checked.getDataSetList() == null) {
return false;
}
Map<String, Set<String>> standardDoiKeys = indexStandardDoiKeysByInst(standard);
boolean corrected = false;
for (DataSetGroupItem group : checked.getDataSetList()) {
if (group.getInstList() == null) {
continue;
}
for (InstItem inst : group.getInstList()) {
if (inst.getDoiList() == null) {
continue;
}
Map<String, List<DoiItem>> doiItemsByDesc = new HashMap<String, List<DoiItem>>();
for (DoiItem doi : inst.getDoiList()) {
String desc = trimToEmpty(doi.getDesc());
if (!doiItemsByDesc.containsKey(desc)) {
doiItemsByDesc.put(desc, new ArrayList<DoiItem>());
}
doiItemsByDesc.get(desc).add(doi);
}
String instPath = buildInstPath(group, inst);
Set<String> currentStandardKeys = standardDoiKeys.get(buildInstKey(group, inst));
for (Map.Entry<String, List<DoiItem>> entry : doiItemsByDesc.entrySet()) {
if (entry.getValue().size() <= 1) {
continue;
}
DoiItem retained = chooseRetainedDoi(entry.getValue(), currentStandardKeys);
String message = "同一个 doiList 下存在 desc 相同的指标,已仅保留 " + buildKey(retained.getDesc(), retained.getName())
+ ";重复组合:" + describeDoiStandardMatches(entry.getValue(), currentStandardKeys);
addIssue(issues, "映射规则", instPath + ".doiList[desc=" + entry.getKey() + "]", message,
null, describeDoiKeys(entry.getValue()), true);
removeDuplicatedDoiItems(inst.getDoiList(), entry.getValue(), retained);
corrected = true;
}
}
}
return corrected;
}
private void validateReportMapConsistency(MappingDocument checked, MappingDocument standard, List<IcdConsistencyIssue> issues) { private void validateReportMapConsistency(MappingDocument checked, MappingDocument standard, List<IcdConsistencyIssue> issues) {
Map<String, ReportMapItem> checkedMap = new HashMap<String, ReportMapItem>(); Map<String, ReportMapItem> checkedMap = new HashMap<String, ReportMapItem>();
if (checked.getReportMap() != null) { if (checked.getReportMap() != null) {
@@ -303,6 +355,9 @@ public class IcdConsistencyCheckService {
return; return;
} }
for (DoiItem standardDoi : standardInst.getDoiList()) { for (DoiItem standardDoi : standardInst.getDoiList()) {
if (isExcludedDoiDesc(standardDoi.getDesc())) {
continue;
}
String doiKey = buildKey(standardDoi.getName(), standardDoi.getDesc()); String doiKey = buildKey(standardDoi.getName(), standardDoi.getDesc());
DoiItem checkedDoi = checkedDoiMap.get(doiKey); DoiItem checkedDoi = checkedDoiMap.get(doiKey);
String path = "DataSetList[" + groupKey + "].instList[" + instKey + "].doiList[" + doiKey + "]"; String path = "DataSetList[" + groupKey + "].instList[" + instKey + "].doiList[" + doiKey + "]";
@@ -343,6 +398,17 @@ public class IcdConsistencyCheckService {
issues.add(issue); issues.add(issue);
} }
private Map<String, ReportMapItem> indexReportMap(MappingDocument document) {
Map<String, ReportMapItem> result = new HashMap<String, ReportMapItem>();
if (document.getReportMap() == null) {
return result;
}
for (ReportMapItem item : document.getReportMap()) {
result.put(buildReportKey(item), item);
}
return result;
}
private Map<String, DataSetGroupItem> indexGroups(MappingDocument document) { private Map<String, DataSetGroupItem> indexGroups(MappingDocument document) {
Map<String, DataSetGroupItem> result = new HashMap<String, DataSetGroupItem>(); Map<String, DataSetGroupItem> result = new HashMap<String, DataSetGroupItem>();
if (document.getDataSetList() == null) { if (document.getDataSetList() == null) {
@@ -376,10 +442,36 @@ public class IcdConsistencyCheckService {
return result; return result;
} }
private Map<String, Set<String>> indexStandardDoiKeysByInst(MappingDocument standard) {
Map<String, Set<String>> result = new HashMap<String, Set<String>>();
if (standard.getDataSetList() == null) {
return result;
}
for (DataSetGroupItem group : standard.getDataSetList()) {
if (group.getInstList() == null) {
continue;
}
for (InstItem inst : group.getInstList()) {
Set<String> doiKeys = new HashSet<String>();
if (inst.getDoiList() != null) {
for (DoiItem doi : inst.getDoiList()) {
doiKeys.add(buildKey(doi.getDesc(), doi.getName()));
}
}
result.put(buildInstKey(group, inst), doiKeys);
}
}
return result;
}
private String buildReportKey(ReportMapItem item) { private String buildReportKey(ReportMapItem item) {
return buildKey(item.getDesc(), item.getRptId(), item.getName()); return buildKey(item.getDesc(), item.getRptId(), item.getName());
} }
private String buildInstKey(DataSetGroupItem group, InstItem inst) {
return buildKey(group.getLnClass(), group.getDesc(), inst.getInst(), inst.getDesc());
}
private String buildGroupPath(DataSetGroupItem group) { private String buildGroupPath(DataSetGroupItem group) {
return "DataSetList[" + buildKey(group.getLnClass(), group.getDesc()) + "]"; return "DataSetList[" + buildKey(group.getLnClass(), group.getDesc()) + "]";
} }
@@ -396,10 +488,57 @@ public class IcdConsistencyCheckService {
return String.join("+", parts); return String.join("+", parts);
} }
private DoiItem chooseRetainedDoi(List<DoiItem> doiItems, Set<String> standardDoiKeys) {
if (standardDoiKeys != null) {
for (DoiItem item : doiItems) {
if (standardDoiKeys.contains(buildKey(item.getDesc(), item.getName()))) {
return item;
}
}
}
return doiItems.get(0);
}
private void removeDuplicatedDoiItems(List<DoiItem> allDoiItems, List<DoiItem> duplicatedItems, DoiItem retained) {
Iterator<DoiItem> iterator = allDoiItems.iterator();
while (iterator.hasNext()) {
DoiItem item = iterator.next();
if (duplicatedItems.contains(item) && item != retained) {
iterator.remove();
}
}
}
private String describeDoiStandardMatches(List<DoiItem> doiItems, Set<String> standardDoiKeys) {
List<String> values = new ArrayList<String>();
for (DoiItem item : doiItems) {
String key = buildKey(item.getDesc(), item.getName());
boolean existsInStandard = standardDoiKeys != null && standardDoiKeys.contains(key);
values.add(key + (existsInStandard ? " 在标准映射中存在" : " 不在标准映射中"));
}
return String.join("", values);
}
private String describeDoiKeys(List<DoiItem> doiItems) {
List<String> values = new ArrayList<String>();
for (DoiItem item : doiItems) {
values.add(buildKey(item.getDesc(), item.getName()));
}
return String.join("", values);
}
private boolean equalsValue(String left, String right) { private boolean equalsValue(String left, String right) {
return trimToEmpty(left).equals(trimToEmpty(right)); return trimToEmpty(left).equals(trimToEmpty(right));
} }
private boolean containsRtFre(String value) {
return trimToEmpty(value).toLowerCase().contains("rtfre");
}
private boolean isExcludedDoiDesc(String desc) {
return EXCLUDED_DOI_DESCS.contains(trimToEmpty(desc));
}
private boolean isBlank(String value) { private boolean isBlank(String value) {
return value == null || value.trim().isEmpty(); return value == null || value.trim().isEmpty();
} }

View File

@@ -5,8 +5,10 @@ import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.LogUtil; import com.njcn.common.utils.LogUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.njcn.gather.icd.mapping.pojo.param.CsIcdPathParam; 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.param.IcdCheckResultSaveParam;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathDetailVO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO; import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO;
import com.njcn.gather.icd.mapping.service.CsIcdPathService; import com.njcn.gather.icd.mapping.service.CsIcdPathService;
import com.njcn.web.controller.BaseController; import com.njcn.web.controller.BaseController;
@@ -50,6 +52,16 @@ public class CsIcdPathController extends BaseController {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
} }
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询参照ICD列表")
@PostMapping("/reference-list")
public HttpResult<List<CsIcdPathVO>> referenceList() {
String methodDescribe = getMethodDescribe("referenceList");
LogUtil.njcnDebug(log, "{}开始查询参照ICD列表", methodDescribe);
List<CsIcdPathVO> result = csIcdPathService.listReferenceIcdPaths();
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON) @OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("新增ICD存储记录") @ApiOperation("新增ICD存储记录")
@PostMapping(value = "/add", consumes = {"application/json"}) @PostMapping(value = "/add", consumes = {"application/json"})
@@ -115,6 +127,28 @@ public class CsIcdPathController extends BaseController {
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); 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-msg")
public HttpResult<JsonNode> getIcdCheckMsg(@PathVariable("id") String id) {
String methodDescribe = getMethodDescribe("getIcdCheckMsg");
LogUtil.njcnDebug(log, "{}开始查询ICD校验结果详情icdId={}", methodDescribe, id);
JsonNode result = csIcdPathService.getIcdCheckMsg(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询ICD映射文件详情")
@ApiImplicitParam(name = "id", value = "ICD记录ID", required = true)
@PostMapping("/{id}/mapping-detail")
public HttpResult<CsIcdPathDetailVO> getMappingDetail(@PathVariable("id") String id) {
String methodDescribe = getMethodDescribe("getMappingDetail");
LogUtil.njcnDebug(log, "{}开始查询ICD映射文件详情icdId={}", methodDescribe, id);
CsIcdPathDetailVO result = csIcdPathService.getMappingDetail(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON) @OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("保存ICD唯一性校验结果") @ApiOperation("保存ICD唯一性校验结果")
@ApiImplicitParam(name = "id", value = "ICD记录ID", required = true) @ApiImplicitParam(name = "id", value = "ICD记录ID", required = true)
@@ -147,9 +181,6 @@ public class CsIcdPathController extends BaseController {
} }
try { try {
param.setIcdContent(icdFile.getBytes()); param.setIcdContent(icdFile.getBytes());
if (param.getPath() == null || param.getPath().trim().isEmpty()) {
param.setPath(resolveFileName(icdFile));
}
} catch (IOException ex) { } catch (IOException ex) {
throw new IllegalArgumentException("读取ICD文件失败" + ex.getMessage(), ex); throw new IllegalArgumentException("读取ICD文件失败" + ex.getMessage(), ex);
} }

View File

@@ -2,6 +2,7 @@ package com.njcn.gather.icd.mapping.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO; import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathDetailVO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO; import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
@@ -15,4 +16,10 @@ public interface CsIcdPathMapper extends BaseMapper<CsIcdPathPO> {
List<CsIcdPathVO> selectIcdPathList(@Param("keyword") String keyword, List<CsIcdPathVO> selectIcdPathList(@Param("keyword") String keyword,
@Param("type") Integer type, @Param("type") Integer type,
@Param("result") Integer result); @Param("result") Integer result);
List<CsIcdPathVO> selectReferenceIcdPathList();
CsIcdPathVO selectIcdCheckMsgById(@Param("id") String id);
CsIcdPathDetailVO selectIcdPathDetailById(@Param("id") String id);
} }

View File

@@ -7,12 +7,9 @@
type="com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO"> type="com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO">
<id column="id" property="id"/> <id column="id" property="id"/>
<result column="name" property="name"/> <result column="name" property="name"/>
<result column="path" property="path"/>
<result column="angle" property="angle"/> <result column="angle" property="angle"/>
<result column="usePhaseIndex" property="usePhaseIndex"/> <result column="usePhaseIndex" property="usePhaseIndex"/>
<result column="state" property="state"/> <result column="state" property="state"/>
<result column="jsonStr" property="jsonStr"/>
<result column="xmlStr" property="xmlStr"/>
<result column="result" property="result"/> <result column="result" property="result"/>
<result column="msg" property="msg" <result column="msg" property="msg"
typeHandler="com.njcn.gather.icd.mapping.typehandler.JsonNodeTypeHandler"/> typeHandler="com.njcn.gather.icd.mapping.typehandler.JsonNodeTypeHandler"/>
@@ -29,12 +26,9 @@
SELECT SELECT
ID AS id, ID AS id,
Name AS name, Name AS name,
Path AS path,
Angle AS angle, Angle AS angle,
Use_Phase_Index AS usePhaseIndex, Use_Phase_Index AS usePhaseIndex,
State AS state, State AS state,
Json_Str AS jsonStr,
Xml_Str AS xmlStr,
Result AS result, Result AS result,
Msg AS msg, Msg AS msg,
Type AS type, Type AS type,
@@ -46,8 +40,7 @@
FROM cs_icd_path FROM cs_icd_path
WHERE State = 1 WHERE State = 1
<if test="keyword != null and keyword != ''"> <if test="keyword != null and keyword != ''">
AND (Name LIKE CONCAT('%', #{keyword}, '%') AND Name LIKE CONCAT('%', #{keyword}, '%')
OR Path LIKE CONCAT('%', #{keyword}, '%'))
</if> </if>
<if test="type != null"> <if test="type != null">
AND Type = #{type} AND Type = #{type}
@@ -58,4 +51,47 @@
ORDER BY Update_Time DESC, Create_Time DESC ORDER BY Update_Time DESC, Create_Time DESC
</select> </select>
<select id="selectReferenceIcdPathList"
resultMap="CsIcdPathVOResultMap">
SELECT
ID AS id,
Name AS name,
Angle AS angle,
Use_Phase_Index AS usePhaseIndex,
State AS state,
Result AS result,
Msg AS msg,
Type AS type,
Reference_Icd_Id AS referenceIcdId,
Create_By AS createBy,
Create_Time AS createTime,
Update_By AS updateBy,
Update_Time AS updateTime
FROM cs_icd_path
WHERE State = 1
AND Type IN (1, 3)
ORDER BY Update_Time DESC, Create_Time DESC
</select>
<select id="selectIcdCheckMsgById"
resultMap="CsIcdPathVOResultMap">
SELECT Msg AS msg
FROM cs_icd_path
WHERE ID = #{id}
AND State = 1
</select>
<select id="selectIcdPathDetailById"
resultType="com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathDetailVO">
SELECT
ID AS id,
Name AS name,
Json_Str AS jsonStr,
Xml_Str AS xmlStr,
Icd AS icdContent
FROM cs_icd_path
WHERE ID = #{id}
AND State = 1
</select>
</mapper> </mapper>

View File

@@ -18,9 +18,6 @@ public class CsIcdPathParam {
@NotBlank(message = "ICD名称不能为空") @NotBlank(message = "ICD名称不能为空")
private String name; private String name;
@ApiModelProperty("ICD存储路径")
private String path;
@ApiModelProperty("ICD文件二进制内容") @ApiModelProperty("ICD文件二进制内容")
private byte[] icdContent; private byte[] icdContent;
@@ -30,7 +27,7 @@ public class CsIcdPathParam {
@ApiModelProperty("是否使用相位索引") @ApiModelProperty("是否使用相位索引")
private Integer usePhaseIndex; private Integer usePhaseIndex;
@ApiModelProperty("ICD类型1-标准ICD") @ApiModelProperty("ICD类型1-手动录入的标准ICD2-手动录入的非标准ICD3-上游解析传递的标准ICD4-上游解析传递的非标准ICD")
private Integer type; private Integer type;
/** /**
@@ -53,10 +50,10 @@ public class CsIcdPathParam {
@ApiModel("ICD存储记录列表查询参数") @ApiModel("ICD存储记录列表查询参数")
public static class ListParam { public static class ListParam {
@ApiModelProperty("关键字匹配ICD名称或路径") @ApiModelProperty("关键字匹配ICD名称")
private String keyword; private String keyword;
@ApiModelProperty("ICD类型") @ApiModelProperty("ICD类型1-手动录入的标准ICD2-手动录入的非标准ICD3-上游解析传递的标准ICD4-上游解析传递的非标准ICD")
private Integer type; private Integer type;
@ApiModelProperty("ICD校验结果0-否1-是") @ApiModelProperty("ICD校验结果0-否1-是")

View File

@@ -25,9 +25,6 @@ public class CsIcdPathPO implements Serializable {
@TableField("Name") @TableField("Name")
private String name; private String name;
@TableField("Path")
private String path;
@TableField("Icd") @TableField("Icd")
private byte[] icdContent; private byte[] icdContent;

View File

@@ -0,0 +1,32 @@
package com.njcn.gather.icd.mapping.pojo.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* ICD 映射文件详情。
*/
@Data
@ApiModel("ICD映射文件详情")
public class CsIcdPathDetailVO {
@ApiModelProperty("ICD记录ID")
private String id;
@ApiModelProperty("ICD名称")
private String name;
@ApiModelProperty("MMS映射JSON")
private String jsonStr;
@ApiModelProperty("MMS映射XML")
private String xmlStr;
@ApiModelProperty("ICD源文件文本")
private String icdText;
@JsonIgnore
private byte[] icdContent;
}

View File

@@ -20,9 +20,6 @@ public class CsIcdPathVO {
@ApiModelProperty("ICD名称") @ApiModelProperty("ICD名称")
private String name; private String name;
@ApiModelProperty("ICD存储路径")
private String path;
@ApiModelProperty("角度") @ApiModelProperty("角度")
private Integer angle; private Integer angle;
@@ -32,19 +29,13 @@ public class CsIcdPathVO {
@ApiModelProperty("状态1-正常0-删除") @ApiModelProperty("状态1-正常0-删除")
private Integer state; private Integer state;
@ApiModelProperty("MMS映射JSON")
private String jsonStr;
@ApiModelProperty("MMS映射XML")
private String xmlStr;
@ApiModelProperty("校验结论0-否1-是") @ApiModelProperty("校验结论0-否1-是")
private Integer result; private Integer result;
@ApiModelProperty("校验结论详情JSON") @ApiModelProperty("校验结论详情JSON")
private JsonNode msg; private JsonNode msg;
@ApiModelProperty("ICD类型1-标准ICD") @ApiModelProperty("ICD类型1-手动录入的标准ICD2-手动录入的非标准ICD3-上游解析传递的标准ICD4-上游解析传递的非标准ICD")
private Integer type; private Integer type;
@ApiModelProperty("标准ICD引用ID") @ApiModelProperty("标准ICD引用ID")

View File

@@ -1,7 +1,9 @@
package com.njcn.gather.icd.mapping.service; package com.njcn.gather.icd.mapping.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.njcn.gather.icd.mapping.pojo.param.CsIcdPathParam; 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.param.IcdCheckResultSaveParam;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathDetailVO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO; import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO;
import java.util.List; import java.util.List;
@@ -13,6 +15,12 @@ public interface CsIcdPathService {
List<CsIcdPathVO> listIcdPaths(CsIcdPathParam.ListParam param); List<CsIcdPathVO> listIcdPaths(CsIcdPathParam.ListParam param);
List<CsIcdPathVO> listReferenceIcdPaths();
JsonNode getIcdCheckMsg(String icdId);
CsIcdPathDetailVO getMappingDetail(String icdId);
boolean addIcdPath(CsIcdPathParam param); boolean addIcdPath(CsIcdPathParam param);
boolean updateIcdPath(CsIcdPathParam.UpdateParam param); boolean updateIcdPath(CsIcdPathParam.UpdateParam param);

View File

@@ -1,13 +1,14 @@
package com.njcn.gather.icd.mapping.service.impl; 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.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.gather.icd.mapping.mapper.CsIcdPathMapper; 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.CsIcdPathParam;
import com.njcn.gather.icd.mapping.pojo.param.IcdCheckResultSaveParam; 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.po.CsIcdPathPO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathDetailVO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO; import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO;
import com.njcn.gather.icd.mapping.service.CsIcdPathService; import com.njcn.gather.icd.mapping.service.CsIcdPathService;
import com.njcn.web.utils.RequestUtil; import com.njcn.web.utils.RequestUtil;
@@ -15,6 +16,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -30,7 +32,13 @@ public class CsIcdPathServiceImpl implements CsIcdPathService {
private static final int STATE_DELETED = 0; private static final int STATE_DELETED = 0;
private static final int ICD_TYPE_STANDARD = 1; private static final int ICD_TYPE_MANUAL_STANDARD = 1;
private static final int ICD_TYPE_MANUAL_NON_STANDARD = 2;
private static final int ICD_TYPE_UPSTREAM_STANDARD = 3;
private static final int ICD_TYPE_UPSTREAM_NON_STANDARD = 4;
private final CsIcdPathMapper csIcdPathMapper; private final CsIcdPathMapper csIcdPathMapper;
@@ -45,12 +53,38 @@ public class CsIcdPathServiceImpl implements CsIcdPathService {
checkedParam.getResult()); checkedParam.getResult());
} }
@Override
public List<CsIcdPathVO> listReferenceIcdPaths() {
return csIcdPathMapper.selectReferenceIcdPathList();
}
@Override
public JsonNode getIcdCheckMsg(String icdId) {
String id = requireText(icdId, "ICD璁板綍ID涓嶈兘涓虹┖");
CsIcdPathVO icdPath = csIcdPathMapper.selectIcdCheckMsgById(id);
return icdPath == null ? null : icdPath.getMsg();
}
@Override
public CsIcdPathDetailVO getMappingDetail(String icdId) {
String id = requireText(icdId, "ICD记录ID不能为空");
CsIcdPathDetailVO detail = csIcdPathMapper.selectIcdPathDetailById(id);
if (detail == null) {
return null;
}
byte[] icdContent = detail.getIcdContent();
if (icdContent != null && icdContent.length > 0) {
detail.setIcdText(new String(icdContent, StandardCharsets.UTF_8));
}
return detail;
}
@Override @Override
@Transactional @Transactional
public boolean addIcdPath(CsIcdPathParam param) { public boolean addIcdPath(CsIcdPathParam param) {
CsIcdPathParam checkedParam = requireParam(param); CsIcdPathParam checkedParam = requireParam(param);
LocalDateTime now = LocalDateTime.now(); LocalDateTime now = LocalDateTime.now();
CsIcdPathPO icdPath = buildIcdPath(checkedParam); CsIcdPathPO icdPath = buildIcdPath(checkedParam, true);
icdPath.setId(UUID.randomUUID().toString().replace("-", "")); icdPath.setId(UUID.randomUUID().toString().replace("-", ""));
icdPath.setState(STATE_NORMAL); icdPath.setState(STATE_NORMAL);
icdPath.setCreateBy(currentUserId()); icdPath.setCreateBy(currentUserId());
@@ -65,7 +99,7 @@ public class CsIcdPathServiceImpl implements CsIcdPathService {
public boolean updateIcdPath(CsIcdPathParam.UpdateParam param) { public boolean updateIcdPath(CsIcdPathParam.UpdateParam param) {
CsIcdPathParam.UpdateParam checkedParam = requireUpdateParam(param); CsIcdPathParam.UpdateParam checkedParam = requireUpdateParam(param);
requireIcdPath(checkedParam.getId()); requireIcdPath(checkedParam.getId());
CsIcdPathPO icdPath = buildIcdPath(checkedParam); CsIcdPathPO icdPath = buildIcdPath(checkedParam, false);
icdPath.setId(checkedParam.getId()); icdPath.setId(checkedParam.getId());
icdPath.setUpdateBy(currentUserId()); icdPath.setUpdateBy(currentUserId());
icdPath.setUpdateTime(LocalDateTime.now()); icdPath.setUpdateTime(LocalDateTime.now());
@@ -80,13 +114,21 @@ public class CsIcdPathServiceImpl implements CsIcdPathService {
String currentUserId = currentUserId(); String currentUserId = currentUserId();
csIcdPathMapper.update(null, new LambdaUpdateWrapper<CsIcdPathPO>() csIcdPathMapper.update(null, new LambdaUpdateWrapper<CsIcdPathPO>()
.set(CsIcdPathPO::getType, null) .set(CsIcdPathPO::getType, ICD_TYPE_MANUAL_NON_STANDARD)
.set(CsIcdPathPO::getUpdateBy, currentUserId) .set(CsIcdPathPO::getUpdateBy, currentUserId)
.set(CsIcdPathPO::getUpdateTime, now) .set(CsIcdPathPO::getUpdateTime, now)
.eq(CsIcdPathPO::getState, STATE_NORMAL)); .eq(CsIcdPathPO::getState, STATE_NORMAL)
.eq(CsIcdPathPO::getType, ICD_TYPE_MANUAL_STANDARD));
csIcdPathMapper.update(null, new LambdaUpdateWrapper<CsIcdPathPO>()
.set(CsIcdPathPO::getType, ICD_TYPE_UPSTREAM_NON_STANDARD)
.set(CsIcdPathPO::getUpdateBy, currentUserId)
.set(CsIcdPathPO::getUpdateTime, now)
.eq(CsIcdPathPO::getState, STATE_NORMAL)
.eq(CsIcdPathPO::getType, ICD_TYPE_UPSTREAM_STANDARD));
CsIcdPathPO activeIcdPath = new CsIcdPathPO(); CsIcdPathPO activeIcdPath = new CsIcdPathPO();
activeIcdPath.setType(ICD_TYPE_STANDARD); activeIcdPath.setType(resolveStandardType(targetIcdPath.getType()));
activeIcdPath.setUpdateBy(currentUserId); activeIcdPath.setUpdateBy(currentUserId);
activeIcdPath.setUpdateTime(now); activeIcdPath.setUpdateTime(now);
return csIcdPathMapper.update(activeIcdPath, new LambdaUpdateWrapper<CsIcdPathPO>() return csIcdPathMapper.update(activeIcdPath, new LambdaUpdateWrapper<CsIcdPathPO>()
@@ -116,7 +158,7 @@ public class CsIcdPathServiceImpl implements CsIcdPathService {
throw new IllegalArgumentException("ICD校验结果不能为空"); throw new IllegalArgumentException("ICD校验结果不能为空");
} }
CsIcdPathPO icdPath = requireIcdPath(icdId); CsIcdPathPO icdPath = requireIcdPath(icdId);
CsIcdPathPO referenceIcd = requireUniqueReferenceIcd(); CsIcdPathPO referenceIcd = requireReferenceIcd(icdPath.getReferenceIcdId());
icdPath.setJsonStr(trimToNull(param.getMappingJson())); icdPath.setJsonStr(trimToNull(param.getMappingJson()));
icdPath.setXmlStr(trimToNull(param.getXml())); icdPath.setXmlStr(trimToNull(param.getXml()));
icdPath.setResult(normalizeResult(param.getResult())); icdPath.setResult(normalizeResult(param.getResult()));
@@ -140,17 +182,34 @@ public class CsIcdPathServiceImpl implements CsIcdPathService {
} }
} }
private CsIcdPathPO buildIcdPath(CsIcdPathParam param) { private CsIcdPathPO buildIcdPath(CsIcdPathParam param, boolean useDefaultType) {
CsIcdPathPO icdPath = new CsIcdPathPO(); CsIcdPathPO icdPath = new CsIcdPathPO();
icdPath.setName(requireText(param.getName(), "ICD名称不能为空")); icdPath.setName(requireText(param.getName(), "ICD名称不能为空"));
icdPath.setPath(requireText(param.getPath(), "ICD存储路径不能为空"));
icdPath.setIcdContent(param.getIcdContent()); icdPath.setIcdContent(param.getIcdContent());
icdPath.setAngle(param.getAngle()); icdPath.setAngle(param.getAngle());
icdPath.setUsePhaseIndex(param.getUsePhaseIndex()); icdPath.setUsePhaseIndex(param.getUsePhaseIndex());
icdPath.setType(param.getType()); icdPath.setType(useDefaultType ? resolveIcdType(param.getType()) : param.getType());
return icdPath; return icdPath;
} }
/**
* 新增 ICD 记录未显式传类型时,默认归类为手动录入的非标准 ICD。
*/
private Integer resolveIcdType(Integer type) {
return type == null ? ICD_TYPE_MANUAL_NON_STANDARD : type;
}
/**
* 激活标准 ICD 时保留记录来源:手动录入升为 1上游解析传递升为 3。
*/
private Integer resolveStandardType(Integer type) {
if (Integer.valueOf(ICD_TYPE_UPSTREAM_STANDARD).equals(type)
|| Integer.valueOf(ICD_TYPE_UPSTREAM_NON_STANDARD).equals(type)) {
return ICD_TYPE_UPSTREAM_STANDARD;
}
return ICD_TYPE_MANUAL_STANDARD;
}
private CsIcdPathParam requireParam(CsIcdPathParam param) { private CsIcdPathParam requireParam(CsIcdPathParam param) {
if (param == null) { if (param == null) {
throw new IllegalArgumentException("ICD记录参数不能为空"); throw new IllegalArgumentException("ICD记录参数不能为空");
@@ -176,19 +235,15 @@ public class CsIcdPathServiceImpl implements CsIcdPathService {
} }
/** /**
* 全系统只允许一个正常状态的标准 ICD 作为唯一参照 * ICD 校验保存时以当前记录绑定的 Reference_Icd_Id 作为参照来源
*/ */
private CsIcdPathPO requireUniqueReferenceIcd() { private CsIcdPathPO requireReferenceIcd(String referenceIcdId) {
List<CsIcdPathPO> referenceIcdList = csIcdPathMapper.selectList(new LambdaQueryWrapper<CsIcdPathPO>() String id = requireText(referenceIcdId, "未配置参照ICD无法保存校验结果");
.eq(CsIcdPathPO::getState, STATE_NORMAL) CsIcdPathPO referenceIcd = csIcdPathMapper.selectById(id);
.eq(CsIcdPathPO::getType, ICD_TYPE_STANDARD)); if (referenceIcd == null || !Integer.valueOf(STATE_NORMAL).equals(referenceIcd.getState())) {
if (referenceIcdList == null || referenceIcdList.isEmpty()) { throw new IllegalArgumentException("参照ICD不存在或已删除无法保存校验结果");
throw new IllegalArgumentException("未配置标准ICD无法执行唯一性校验");
} }
if (referenceIcdList.size() > 1) { return referenceIcd;
throw new IllegalArgumentException("存在多个标准ICD无法确定唯一参照");
}
return referenceIcdList.get(0);
} }
private Integer normalizeResult(Integer result) { private Integer normalizeResult(Integer result) {

View File

@@ -36,20 +36,62 @@ class IcdConsistencyCheckServiceTest {
} }
@Test @Test
void checkShouldOnlyReturnCorrectedJsonForRtFreSelfMappingRule() { void checkShouldReturnPassAndCorrectedJsonForRtFreSelfMappingRule() {
IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest(); IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest();
request.setStandardJson(buildRtFreStandardJson()); request.setStandardJson(buildRtFreStandardJson());
request.setCheckedJson(buildRtFreCheckedJson()); request.setCheckedJson(buildRtFreCheckedJson());
IcdConsistencyCheckResponse response = service.check(request); IcdConsistencyCheckResponse response = service.check(request);
Assertions.assertEquals(0, response.getResult()); Assertions.assertEquals(1, response.getResult());
Assertions.assertTrue(response.getIssues().isEmpty());
Assertions.assertTrue(response.getCorrectedJson().contains("\"rptID\" : \"demoRtFre\"")); Assertions.assertTrue(response.getCorrectedJson().contains("\"rptID\" : \"demoRtFre\""));
Assertions.assertTrue(response.getCorrectedJson().contains("\"FlickerFlag\" : \"1\"")); Assertions.assertTrue(response.getCorrectedJson().contains("\"FlickerFlag\" : \"1\""));
Assertions.assertTrue(response.getCorrectedJson().contains("\"reportCount\" : 0")); Assertions.assertTrue(response.getCorrectedJson().contains("\"reportCount\" : 0"));
Assertions.assertTrue(response.getCorrectedJson().contains("\"reportCount\" : 1")); Assertions.assertTrue(response.getCorrectedJson().contains("\"reportCount\" : 1"));
} }
@Test
void checkShouldNotCorrectRtFreWhenReportMapAlreadyMatchesStandard() {
IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest();
request.setStandardJson(buildRtFreStandardJson());
request.setCheckedJson(buildRtFreStandardJson());
IcdConsistencyCheckResponse response = service.check(request);
Assertions.assertEquals(1, response.getResult());
Assertions.assertNull(response.getCorrectedJson());
}
@Test
void checkShouldKeepStandardDoiWhenSameDoiListContainsDuplicateDesc() {
IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest();
request.setStandardJson(buildStandardJson());
request.setCheckedJson(buildDuplicateDoiDescJson());
IcdConsistencyCheckResponse response = service.check(request);
Assertions.assertEquals(0, response.getResult());
Assertions.assertTrue(response.getIssuesJson().contains("同一个 doiList 下存在 desc 相同的指标"));
Assertions.assertTrue(response.getIssuesJson().contains("频率+Hz 在标准映射中存在"));
Assertions.assertTrue(response.getIssuesJson().contains("频率+Hz2 不在标准映射中"));
Assertions.assertNotNull(response.getCorrectedJson());
Assertions.assertTrue(response.getCorrectedJson().contains("\"name\" : \"Hz\""));
Assertions.assertFalse(response.getCorrectedJson().contains("\"name\" : \"Hz2\""));
}
@Test
void checkShouldIgnoreVoltageStartMetricsDuringDoiConsistency() {
IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest();
request.setStandardJson(buildVoltageStartMetricsStandardJson());
request.setCheckedJson(buildStandardJson());
IcdConsistencyCheckResponse response = service.check(request);
Assertions.assertEquals(1, response.getResult());
Assertions.assertTrue(response.getIssues().isEmpty());
}
@Test @Test
void checkShouldReportEmptySdiListAsTypeListProblem() { void checkShouldReportEmptySdiListAsTypeListProblem() {
IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest(); IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest();
@@ -63,6 +105,18 @@ class IcdConsistencyCheckServiceTest {
Assertions.assertFalse(response.getIssuesJson().contains("sdiList 不能为空")); Assertions.assertFalse(response.getIssuesJson().contains("sdiList 不能为空"));
} }
@Test
void checkShouldIgnoreVoltageStartMetricsDuringSelfFormatTypeListValidation() {
IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest();
request.setStandardJson(buildVoltageStartMetricsWithoutSdiJson());
request.setCheckedJson(buildVoltageStartMetricsWithoutSdiJson());
IcdConsistencyCheckResponse response = service.check(request);
Assertions.assertEquals(1, response.getResult());
Assertions.assertTrue(response.getIssues().isEmpty());
}
@Test @Test
void checkShouldReturnPassWhenCheckedJsonMatchesStandardJson() { void checkShouldReturnPassWhenCheckedJsonMatchesStandardJson() {
IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest(); IcdConsistencyCheckRequest request = new IcdConsistencyCheckRequest();
@@ -161,6 +215,69 @@ class IcdConsistencyCheckServiceTest {
"}"; "}";
} }
private String buildDuplicateDoiDescJson() {
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" +
buildDataSetWithDuplicateDoiDesc("MMXU", "统计数据", "1", "A相") + ",\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 buildVoltageStartMetricsStandardJson() {
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" +
buildDataSetWithVoltageStartMetrics("MMXU", "统计数据", "1", "A相") + ",\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 buildVoltageStartMetricsWithoutSdiJson() {
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" +
buildDataSetWithVoltageStartMetricsWithoutSdi("MMXU", "统计数据", "1", "A相") + ",\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 buildEmptySdiListJson() { private String buildEmptySdiListJson() {
return "{\n" + return "{\n" +
" \"IED\":\"IED1\",\n" + " \"IED\":\"IED1\",\n" +
@@ -196,4 +313,51 @@ class IcdConsistencyCheckServiceTest {
"\",\"desc\":\"" + instDesc + "\",\"doiList\":[{\"name\":\"" + doiName + "\",\"desc\":\"" + doiDesc + "\",\"desc\":\"" + instDesc + "\",\"doiList\":[{\"name\":\"" + doiName + "\",\"desc\":\"" + doiDesc +
"\",\"start\":1,\"end\":4,\"unit\":\"Hz\",\"coefficient\":1.0,\"baseflag\":1,\"basecount\":1,\"icdcout\":10,\"sdiList\":[]}]}]}"; "\",\"start\":1,\"end\":4,\"unit\":\"Hz\",\"coefficient\":1.0,\"baseflag\":1,\"basecount\":1,\"icdcout\":10,\"sdiList\":[]}]}]}";
} }
private String buildDataSetWithDuplicateDoiDesc(String lnClass, String groupDesc, String inst, String instDesc) {
return " {\"desc\":\"" + groupDesc + "\",\"lnClass\":\"" + lnClass + "\",\"instList\":[{\"inst\":\"" + inst +
"\",\"desc\":\"" + instDesc + "\",\"doiList\":[" +
buildDoi("Hz2", "频率", 5, 8, "Hz") + "," +
buildDoi("Hz", "频率", 1, 4, "Hz") +
"]}]}";
}
private String buildDataSetWithVoltageStartMetrics(String lnClass, String groupDesc, String inst, String instDesc) {
return " {\"desc\":\"" + groupDesc + "\",\"lnClass\":\"" + lnClass + "\",\"instList\":[{\"inst\":\"" + inst +
"\",\"desc\":\"" + instDesc + "\",\"doiList\":[" +
buildDoi("Hz", "频率", 1, 4, "Hz") + "," +
buildDoi("VolDistStr", "电压扰动事件启动", 5, 6, "") + "," +
buildDoi("VolDipStr", "电压暂降事件启动", 7, 8, "") + "," +
buildDoi("VolSwellStr", "电压暂升事件启动", 9, 10, "") + "," +
buildDoi("VolInterStr", "电压中断事件启动", 11, 12, "") + "," +
buildDoi("VolDipSet", "电压暂降启动定值", 13, 14, "V") + "," +
buildDoi("VolSwellSet", "电压暂升启动定值", 15, 16, "V") + "," +
buildDoi("VolInterSet", "电压中断启动定值", 17, 18, "V") +
"]}]}";
}
private String buildDataSetWithVoltageStartMetricsWithoutSdi(String lnClass, String groupDesc, String inst, String instDesc) {
return " {\"desc\":\"" + groupDesc + "\",\"lnClass\":\"" + lnClass + "\",\"instList\":[{\"inst\":\"" + inst +
"\",\"desc\":\"" + instDesc + "\",\"doiList\":[" +
buildDoiWithoutSdi("VolDistStr", "电压扰动事件启动", 5, 6, "") + "," +
buildDoiWithoutSdi("VolDipStr", "电压暂降事件启动", 7, 8, "") + "," +
buildDoiWithoutSdi("VolSwellStr", "电压暂升事件启动", 9, 10, "") + "," +
buildDoiWithoutSdi("VolInterStr", "电压中断事件启动", 11, 12, "") + "," +
buildDoiWithoutSdi("VolDipSet", "电压暂降启动定值", 13, 14, "V") + "," +
buildDoiWithoutSdi("VolSwellSet", "电压暂升启动定值", 15, 16, "V") + "," +
buildDoiWithoutSdi("VolInterSet", "电压中断启动定值", 17, 18, "V") +
"]}]}";
}
private String buildDoi(String doiName, String doiDesc, int start, int end, String unit) {
return "{\"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\":\"浮点\"}]}]}";
}
private String buildDoiWithoutSdi(String doiName, String doiDesc, int start, int end, String unit) {
return "{\"name\":\"" + doiName + "\",\"desc\":\"" + doiDesc +
"\",\"start\":" + start + ",\"end\":" + end + ",\"unit\":\"" + unit +
"\",\"coefficient\":1.0,\"baseflag\":1,\"basecount\":1,\"icdcout\":10,\"sdiList\":[]}";
}
} }

View File

@@ -5,6 +5,8 @@ import com.njcn.gather.icd.mapping.pojo.bo.icd.IcdDocument;
import com.njcn.gather.icd.mapping.pojo.param.CsIcdPathParam; 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.param.IcdCheckResultSaveParam;
import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO; import com.njcn.gather.icd.mapping.pojo.po.CsIcdPathPO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathDetailVO;
import com.njcn.gather.icd.mapping.pojo.vo.CsIcdPathVO;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
@@ -47,6 +49,26 @@ class CsIcdPathServiceImplTest {
verify(csIcdPathMapper).selectIcdPathList(eq("standard"), eq(null), eq(null)); verify(csIcdPathMapper).selectIcdPathList(eq("standard"), eq(null), eq(null));
} }
@Test
void listReferenceIcdPathsShouldQueryStandardIcdTypes() {
service.listReferenceIcdPaths();
verify(csIcdPathMapper).selectReferenceIcdPathList();
}
@Test
void getIcdCheckMsgShouldQueryMsgByTrimmedId() {
JsonNode msg = objectMapper.createObjectNode().put("summary", "通过");
CsIcdPathVO vo = new CsIcdPathVO();
vo.setMsg(msg);
when(csIcdPathMapper.selectIcdCheckMsgById(eq("icd-001"))).thenReturn(vo);
JsonNode result = service.getIcdCheckMsg(" icd-001 ");
Assertions.assertSame(msg, result);
verify(csIcdPathMapper).selectIcdCheckMsgById(eq("icd-001"));
}
@Test @Test
void addIcdPathShouldInsertEnabledRecord() { void addIcdPathShouldInsertEnabledRecord() {
CsIcdPathParam param = buildParam("标准ICD"); CsIcdPathParam param = buildParam("标准ICD");
@@ -58,7 +80,6 @@ class CsIcdPathServiceImplTest {
verify(csIcdPathMapper).insert(captor.capture()); verify(csIcdPathMapper).insert(captor.capture());
Assertions.assertTrue(result); Assertions.assertTrue(result);
Assertions.assertEquals("标准ICD", captor.getValue().getName()); Assertions.assertEquals("标准ICD", captor.getValue().getName());
Assertions.assertEquals("D:/icd/standard.icd", captor.getValue().getPath());
Assertions.assertEquals(1, captor.getValue().getState()); Assertions.assertEquals(1, captor.getValue().getState());
Assertions.assertNotNull(captor.getValue().getId()); Assertions.assertNotNull(captor.getValue().getId());
Assertions.assertNotNull(captor.getValue().getCreateTime()); Assertions.assertNotNull(captor.getValue().getCreateTime());
@@ -79,12 +100,25 @@ class CsIcdPathServiceImplTest {
Assertions.assertArrayEquals(fileContent, captor.getValue().getIcdContent()); Assertions.assertArrayEquals(fileContent, captor.getValue().getIcdContent());
} }
@Test
void addIcdPathShouldDefaultTypeToManualNonStandard() {
CsIcdPathParam param = buildParam("手动录入非标准ICD");
param.setType(null);
when(csIcdPathMapper.insert(any(CsIcdPathPO.class))).thenReturn(1);
boolean result = service.addIcdPath(param);
ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
verify(csIcdPathMapper).insert(captor.capture());
Assertions.assertTrue(result);
Assertions.assertEquals(2, captor.getValue().getType());
}
@Test @Test
void updateIcdPathShouldRejectDeletedRecord() { void updateIcdPathShouldRejectDeletedRecord() {
CsIcdPathParam.UpdateParam param = new CsIcdPathParam.UpdateParam(); CsIcdPathParam.UpdateParam param = new CsIcdPathParam.UpdateParam();
param.setId("icd-001"); param.setId("icd-001");
param.setName("标准ICD"); param.setName("标准ICD");
param.setPath("D:/icd/standard.icd");
CsIcdPathPO deleted = new CsIcdPathPO(); CsIcdPathPO deleted = new CsIcdPathPO();
deleted.setId("icd-001"); deleted.setId("icd-001");
@@ -103,7 +137,6 @@ class CsIcdPathServiceImplTest {
byte[] fileContent = "<SCL version=\"1\"></SCL>".getBytes(); byte[] fileContent = "<SCL version=\"1\"></SCL>".getBytes();
param.setId("icd-001"); param.setId("icd-001");
param.setName("标准ICD"); param.setName("标准ICD");
param.setPath("standard.icd");
param.setIcdContent(fileContent); param.setIcdContent(fileContent);
CsIcdPathPO existed = new CsIcdPathPO(); CsIcdPathPO existed = new CsIcdPathPO();
@@ -134,21 +167,40 @@ class CsIcdPathServiceImplTest {
} }
@Test @Test
void activateIcdPathShouldOnlyKeepTargetAsStandardIcd() { void activateIcdPathShouldOnlyKeepTargetAsManualStandardIcd() {
CsIcdPathPO icdPath = new CsIcdPathPO(); CsIcdPathPO icdPath = new CsIcdPathPO();
icdPath.setId("icd-001"); icdPath.setId("icd-001");
icdPath.setState(1); icdPath.setState(1);
icdPath.setType(2);
when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath); when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath);
when(csIcdPathMapper.update(any(CsIcdPathPO.class), any())).thenReturn(1); when(csIcdPathMapper.update(any(CsIcdPathPO.class), any())).thenReturn(1);
boolean result = service.activateIcdPath("icd-001"); boolean result = service.activateIcdPath("icd-001");
ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class); ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
verify(csIcdPathMapper, times(2)).update(captor.capture(), any()); verify(csIcdPathMapper, times(3)).update(captor.capture(), any());
Assertions.assertTrue(result); Assertions.assertTrue(result);
Assertions.assertNull(captor.getAllValues().get(0)); Assertions.assertNull(captor.getAllValues().get(0));
Assertions.assertEquals(1, captor.getAllValues().get(1).getType()); Assertions.assertNull(captor.getAllValues().get(1));
Assertions.assertNotNull(captor.getAllValues().get(1).getUpdateTime()); Assertions.assertEquals(1, captor.getAllValues().get(2).getType());
Assertions.assertNotNull(captor.getAllValues().get(2).getUpdateTime());
}
@Test
void activateIcdPathShouldKeepUpstreamSourceWhenTargetIsUpstreamIcd() {
CsIcdPathPO icdPath = new CsIcdPathPO();
icdPath.setId("icd-001");
icdPath.setState(1);
icdPath.setType(4);
when(csIcdPathMapper.selectById(eq("icd-001"))).thenReturn(icdPath);
when(csIcdPathMapper.update(any(CsIcdPathPO.class), any())).thenReturn(1);
boolean result = service.activateIcdPath("icd-001");
ArgumentCaptor<CsIcdPathPO> captor = ArgumentCaptor.forClass(CsIcdPathPO.class);
verify(csIcdPathMapper, times(3)).update(captor.capture(), any());
Assertions.assertTrue(result);
Assertions.assertEquals(3, captor.getAllValues().get(2).getType());
} }
@Test @Test
@@ -235,10 +287,36 @@ class CsIcdPathServiceImplTest {
Assertions.assertArrayEquals(objectMapper.writeValueAsBytes(icdDocument), captor.getValue().getIcdContent()); Assertions.assertArrayEquals(objectMapper.writeValueAsBytes(icdDocument), captor.getValue().getIcdContent());
} }
@Test
void getMappingDetailShouldReturnJsonXmlAndUtf8IcdText() {
CsIcdPathDetailVO detail = new CsIcdPathDetailVO();
detail.setId("icd-001");
detail.setName("标准ICD");
detail.setJsonStr("{\"ied\":\"IED1\"}");
detail.setXmlStr("<Root></Root>");
detail.setIcdContent("<SCL name=\"demo\"></SCL>".getBytes(java.nio.charset.StandardCharsets.UTF_8));
when(csIcdPathMapper.selectIcdPathDetailById(eq("icd-001"))).thenReturn(detail);
CsIcdPathDetailVO result = service.getMappingDetail(" icd-001 ");
Assertions.assertEquals("{\"ied\":\"IED1\"}", result.getJsonStr());
Assertions.assertEquals("<Root></Root>", result.getXmlStr());
Assertions.assertEquals("<SCL name=\"demo\"></SCL>", result.getIcdText());
verify(csIcdPathMapper).selectIcdPathDetailById(eq("icd-001"));
}
@Test
void getMappingDetailShouldReturnNullWhenRecordMissing() {
when(csIcdPathMapper.selectIcdPathDetailById(eq("missing"))).thenReturn(null);
CsIcdPathDetailVO result = service.getMappingDetail("missing");
Assertions.assertNull(result);
}
private CsIcdPathParam buildParam(String name) { private CsIcdPathParam buildParam(String name) {
CsIcdPathParam param = new CsIcdPathParam(); CsIcdPathParam param = new CsIcdPathParam();
param.setName(name); param.setName(name);
param.setPath("D:/icd/standard.icd");
param.setAngle(0); param.setAngle(0);
param.setUsePhaseIndex(1); param.setUsePhaseIndex(1);
param.setType(1); param.setType(1);

View File

@@ -20,6 +20,12 @@
<version>0.0.1</version> <version>0.0.1</version>
</dependency> </dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>mybatis-plus</artifactId>
<version>0.0.1</version>
</dependency>
<dependency> <dependency>
<groupId>com.njcn</groupId> <groupId>com.njcn</groupId>
<artifactId>spingboot2.3.12</artifactId> <artifactId>spingboot2.3.12</artifactId>
@@ -74,6 +80,14 @@
<exclude>pqdif-samples/**</exclude> <exclude>pqdif-samples/**</exclude>
</excludes> </excludes>
</resource> </resource>
<!-- Mapper XML 按当前项目约定放在 Java 包路径下,需要显式复制到 classpath。 -->
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources> </resources>
<plugins> <plugins>

View File

@@ -0,0 +1,162 @@
package com.njcn.gather.tool.parsepqdif.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.LogUtil;
import com.njcn.gather.tool.parsepqdif.pojo.param.CsPqdifPathParam;
import com.njcn.gather.tool.parsepqdif.pojo.param.PqdifParseResultSaveParam;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathDetailVO;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathVO;
import com.njcn.gather.tool.parsepqdif.service.CsPqdifPathService;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
/**
* PQDIF 存储记录维护入口。
*/
@Slf4j
@Api(tags = "PQDIF存储记录管理")
@RestController
@RequestMapping("/api/parse-pqdif/pqdif-paths")
@RequiredArgsConstructor
public class CsPqdifPathController extends BaseController {
private final CsPqdifPathService csPqdifPathService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询PQDIF存储记录列表")
@PostMapping("/list")
public HttpResult<List<CsPqdifPathVO>> list(@RequestBody(required = false) CsPqdifPathParam.ListParam param) {
String methodDescribe = getMethodDescribe("list");
LogUtil.njcnDebug(log, "{}开始查询PQDIF存储记录列表", methodDescribe);
List<CsPqdifPathVO> result = csPqdifPathService.listPqdifPaths(param);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("新增PQDIF存储记录")
@PostMapping(value = "/add", consumes = {"application/json"})
public HttpResult<Boolean> add(@RequestBody @Validated CsPqdifPathParam param) {
String methodDescribe = getMethodDescribe("add");
LogUtil.njcnDebug(log, "{}开始新增PQDIF存储记录", methodDescribe);
boolean result = csPqdifPathService.addPqdifPath(param);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("上传并新增PQDIF存储记录")
@PostMapping(value = "/add", consumes = {"multipart/form-data"})
public HttpResult<Boolean> addWithFile(@RequestPart("pqdifFile") MultipartFile pqdifFile,
@RequestPart("request") @Validated CsPqdifPathParam param) {
String methodDescribe = getMethodDescribe("addWithFile");
LogUtil.njcnDebug(log, "{}开始上传并新增PQDIF存储记录fileName={}", methodDescribe, resolveFileName(pqdifFile));
fillPqdifFile(param, pqdifFile);
boolean result = csPqdifPathService.addPqdifPath(param);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("编辑PQDIF存储记录")
@PostMapping(value = "/update", consumes = {"application/json"})
public HttpResult<Boolean> update(@RequestBody @Validated CsPqdifPathParam.UpdateParam param) {
String methodDescribe = getMethodDescribe("update");
LogUtil.njcnDebug(log, "{}开始编辑PQDIF存储记录pqdifId={}", methodDescribe, param.getId());
boolean result = csPqdifPathService.updatePqdifPath(param);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("上传并编辑PQDIF存储记录")
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
public HttpResult<Boolean> updateWithFile(@RequestPart("pqdifFile") MultipartFile pqdifFile,
@RequestPart("request") @Validated CsPqdifPathParam.UpdateParam param) {
String methodDescribe = getMethodDescribe("updateWithFile");
LogUtil.njcnDebug(log, "{}开始上传并编辑PQDIF存储记录pqdifId={}fileName={}",
methodDescribe, param.getId(), resolveFileName(pqdifFile));
fillPqdifFile(param, pqdifFile);
boolean result = csPqdifPathService.updatePqdifPath(param);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("删除PQDIF存储记录")
@PostMapping("/delete")
public HttpResult<Boolean> delete(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("delete");
LogUtil.njcnDebug(log, "{}开始删除PQDIF存储记录ids={}", methodDescribe, ids);
boolean result = csPqdifPathService.deletePqdifPath(ids);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询PQDIF解析结果详情")
@ApiImplicitParam(name = "id", value = "PQDIF记录ID", required = true)
@PostMapping("/{id}/parse-msg")
public HttpResult<JsonNode> getPqdifParseMsg(@PathVariable("id") String id) {
String methodDescribe = getMethodDescribe("getPqdifParseMsg");
LogUtil.njcnDebug(log, "{}开始查询PQDIF解析结果详情pqdifId={}", methodDescribe, id);
JsonNode result = csPqdifPathService.getPqdifParseMsg(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询PQDIF文件和解析结果详情")
@ApiImplicitParam(name = "id", value = "PQDIF记录ID", required = true)
@PostMapping("/{id}/parse-detail")
public HttpResult<CsPqdifPathDetailVO> getPqdifParseDetail(@PathVariable("id") String id) {
String methodDescribe = getMethodDescribe("getPqdifParseDetail");
LogUtil.njcnDebug(log, "{}开始查询PQDIF文件和解析结果详情pqdifId={}", methodDescribe, id);
CsPqdifPathDetailVO result = csPqdifPathService.getPqdifParseDetail(id);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("保存PQDIF解析结果")
@ApiImplicitParam(name = "id", value = "PQDIF记录ID", required = true)
@PostMapping(value = "/{id}/parse-result", consumes = {"application/json"})
public HttpResult<Boolean> savePqdifParseResult(@PathVariable("id") String id,
@RequestBody PqdifParseResultSaveParam param) {
String methodDescribe = getMethodDescribe("savePqdifParseResult");
LogUtil.njcnDebug(log, "{}开始保存PQDIF解析结果pqdifId={}", methodDescribe, id);
boolean result = csPqdifPathService.savePqdifParseResult(id, param);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
}
private void fillPqdifFile(CsPqdifPathParam param, MultipartFile pqdifFile) {
if (pqdifFile == null || pqdifFile.isEmpty()) {
throw new IllegalArgumentException("PQDIF文件不能为空");
}
try {
param.setPqdifContent(pqdifFile.getBytes());
} catch (IOException ex) {
throw new IllegalArgumentException("读取PQDIF文件失败" + ex.getMessage(), ex);
}
}
private String resolveFileName(MultipartFile pqdifFile) {
if (pqdifFile == null || pqdifFile.getOriginalFilename() == null) {
return null;
}
String fileName = pqdifFile.getOriginalFilename().trim();
return fileName.isEmpty() ? null : fileName;
}
}

View File

@@ -28,7 +28,7 @@ import org.springframework.web.multipart.MultipartFile;
@RestController @RestController
@RequestMapping("/api/parse-pqdif") @RequestMapping("/api/parse-pqdif")
@RequiredArgsConstructor @RequiredArgsConstructor
public class ParsePqdifController extends BaseController { public class ParsePqdifController extends BaseController {
private final ParsePqdifService parsePqdifService; private final ParsePqdifService parsePqdifService;
@@ -38,7 +38,7 @@ public class ParsePqdifController extends BaseController {
@PostMapping(value = "/parse", consumes = {"multipart/form-data"}) @PostMapping(value = "/parse", consumes = {"multipart/form-data"})
public HttpResult<PqdifParseResponse> parse(@RequestPart("pqdifFile") MultipartFile pqdifFile) { public HttpResult<PqdifParseResponse> parse(@RequestPart("pqdifFile") MultipartFile pqdifFile) {
String methodDescribe = getMethodDescribe("parse"); String methodDescribe = getMethodDescribe("parse");
LogUtil.njcnDebug(log, "{}PQDIF解析预留入口fileName={}", LogUtil.njcnDebug(log, "{}PQDIF解析入口fileName={}",
methodDescribe, pqdifFile == null ? null : pqdifFile.getOriginalFilename()); methodDescribe, pqdifFile == null ? null : pqdifFile.getOriginalFilename());
PqdifParseResponse result = parsePqdifService.parse(pqdifFile); PqdifParseResponse result = parsePqdifService.parse(pqdifFile);
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe); return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);

View File

@@ -0,0 +1,19 @@
package com.njcn.gather.tool.parsepqdif.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.njcn.gather.tool.parsepqdif.pojo.po.CsPqdifPathPO;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathDetailVO;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface CsPqdifPathMapper extends BaseMapper<CsPqdifPathPO> {
List<CsPqdifPathVO> selectPqdifPathList(@Param("keyword") String keyword,
@Param("result") Integer result);
CsPqdifPathVO selectPqdifParseMsgById(@Param("id") String id);
CsPqdifPathDetailVO selectPqdifPathDetailById(@Param("id") String id);
}

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.njcn.gather.tool.parsepqdif.mapper.CsPqdifPathMapper">
<resultMap id="CsPqdifPathVOResultMap"
type="com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathVO">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="nativeVersion" property="nativeVersion"/>
<result column="recordCount" property="recordCount"/>
<result column="observationCount" property="observationCount"/>
<result column="sampleValueCount" property="sampleValueCount"/>
<result column="state" property="state"/>
<result column="result" property="result"/>
<result column="msg" property="msg"
typeHandler="com.njcn.gather.tool.parsepqdif.typehandler.JsonNodeTypeHandler"/>
<result column="createBy" property="createBy"/>
<result column="createTime" property="createTime"/>
<result column="updateBy" property="updateBy"/>
<result column="updateTime" property="updateTime"/>
</resultMap>
<select id="selectPqdifPathList"
resultMap="CsPqdifPathVOResultMap">
SELECT
ID AS id,
Name AS name,
Native_Version AS nativeVersion,
Record_Count AS recordCount,
Observation_Count AS observationCount,
Sample_Value_Count AS sampleValueCount,
State AS state,
Result AS result,
Msg AS msg,
Create_By AS createBy,
Create_Time AS createTime,
Update_By AS updateBy,
Update_Time AS updateTime
FROM cs_pqdif_path
WHERE State = 1
<if test="keyword != null and keyword != ''">
AND Name LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="result != null">
AND Result = #{result}
</if>
ORDER BY Update_Time DESC, Create_Time DESC
</select>
<select id="selectPqdifParseMsgById"
resultMap="CsPqdifPathVOResultMap">
SELECT Msg AS msg
FROM cs_pqdif_path
WHERE ID = #{id}
AND State = 1
</select>
<select id="selectPqdifPathDetailById"
resultType="com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathDetailVO">
SELECT
ID AS id,
Name AS name,
Json_Str AS jsonStr,
Pqdif AS pqdifContent
FROM cs_pqdif_path
WHERE ID = #{id}
AND State = 1
</select>
</mapper>

View File

@@ -1,12 +1,17 @@
package com.njcn.gather.tool.parsepqdif.nativebridge; package com.njcn.gather.tool.parsepqdif.nativebridge;
import com.sun.jna.NativeLibrary; import com.sun.jna.NativeLibrary;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.*; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Locale; import java.util.Locale;
@Slf4j
public final class PqdifNativeLibraryLoader { public final class PqdifNativeLibraryLoader {
private static final String RESOURCE_DLL = "/pqdif-native/win-x64/pqdifbasic.dll"; private static final String RESOURCE_DLL = "/pqdif-native/win-x64/pqdifbasic.dll";
@@ -41,8 +46,8 @@ public final class PqdifNativeLibraryLoader {
NativeLibrary.addSearchPath("pqdifbasic", nativeDir.toAbsolutePath().toString()); NativeLibrary.addSearchPath("pqdifbasic", nativeDir.toAbsolutePath().toString());
NativeLibrary.addSearchPath("pqdifbasic.dll", nativeDir.toAbsolutePath().toString()); NativeLibrary.addSearchPath("pqdifbasic.dll", nativeDir.toAbsolutePath().toString());
System.out.println("PQDIF native dir = " + nativeDir.toAbsolutePath()); log.info("PQDIF native dir = {}", nativeDir.toAbsolutePath());
System.out.println("PQDIF native dll = " + dllPath.toAbsolutePath()); log.info("PQDIF native dll = {}", dllPath.toAbsolutePath());
preparedNativeDir = nativeDir; preparedNativeDir = nativeDir;
prepared = true; prepared = true;

View File

@@ -0,0 +1,50 @@
package com.njcn.gather.tool.parsepqdif.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
/**
* PQDIF 存储记录保存参数。
*/
@Data
@ApiModel("PQDIF存储记录保存参数")
public class CsPqdifPathParam {
@ApiModelProperty("PQDIF名称")
@NotBlank(message = "PQDIF名称不能为空")
private String name;
@ApiModelProperty("PQDIF文件二进制内容")
private byte[] pqdifContent;
/**
* PQDIF 存储记录编辑参数。
*/
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("PQDIF存储记录编辑参数")
public static class UpdateParam extends CsPqdifPathParam {
@ApiModelProperty("PQDIF记录ID")
@NotBlank(message = "PQDIF记录ID不能为空")
private String id;
}
/**
* PQDIF 存储记录列表查询参数。
*/
@Data
@ApiModel("PQDIF存储记录列表查询参数")
public static class ListParam {
@ApiModelProperty("关键字匹配PQDIF名称")
private String keyword;
@ApiModelProperty("解析结果1-成功0-失败")
private Integer result;
}
}

View File

@@ -0,0 +1,35 @@
package com.njcn.gather.tool.parsepqdif.pojo.param;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* PQDIF 解析结果保存参数。
*/
@Data
@ApiModel("PQDIF解析结果保存参数")
public class PqdifParseResultSaveParam {
@ApiModelProperty("native解析库版本")
private String nativeVersion;
@ApiModelProperty("Record总数")
private Long recordCount;
@ApiModelProperty("Observation Record总数")
private Long observationCount;
@ApiModelProperty("每个Series返回的样例采样值数量")
private Integer sampleValueCount;
@ApiModelProperty("解析结果1-成功0-失败")
private Integer result;
@ApiModelProperty("解析提示、失败原因或解析结论JSON")
private JsonNode msg;
@ApiModelProperty("完整PQDIF解析结果JSON")
private String jsonStr;
}

View File

@@ -0,0 +1,66 @@
package com.njcn.gather.tool.parsepqdif.pojo.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.JsonNode;
import com.njcn.gather.tool.parsepqdif.typehandler.JsonNodeTypeHandler;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* PQDIF 文件存储和解析结果记录。
*/
@Data
@TableName(value = "cs_pqdif_path", autoResultMap = true)
public class CsPqdifPathPO implements Serializable {
private static final long serialVersionUID = 1L;
@TableId("ID")
private String id;
@TableField("Name")
private String name;
@TableField("Pqdif")
private byte[] pqdifContent;
@TableField("Native_Version")
private String nativeVersion;
@TableField("Record_Count")
private Long recordCount;
@TableField("Observation_Count")
private Long observationCount;
@TableField("Sample_Value_Count")
private Integer sampleValueCount;
@TableField("Result")
private Integer result;
@TableField(value = "Msg", typeHandler = JsonNodeTypeHandler.class)
private JsonNode msg;
@TableField("Json_Str")
private String jsonStr;
@TableField("State")
private Integer state;
@TableField("Create_By")
private String createBy;
@TableField("Create_Time")
private LocalDateTime createTime;
@TableField("Update_By")
private String updateBy;
@TableField("Update_Time")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,26 @@
package com.njcn.gather.tool.parsepqdif.pojo.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* PQDIF 文件和解析结果详情。
*/
@Data
@ApiModel("PQDIF文件和解析结果详情")
public class CsPqdifPathDetailVO {
@ApiModelProperty("PQDIF记录ID")
private String id;
@ApiModelProperty("PQDIF名称")
private String name;
@ApiModelProperty("完整PQDIF解析结果JSON")
private String jsonStr;
@JsonIgnore
private byte[] pqdifContent;
}

View File

@@ -0,0 +1,55 @@
package com.njcn.gather.tool.parsepqdif.pojo.vo;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
/**
* PQDIF 存储记录列表项。
*/
@Data
@ApiModel("PQDIF存储记录列表项")
public class CsPqdifPathVO {
@ApiModelProperty("PQDIF记录ID")
private String id;
@ApiModelProperty("PQDIF名称")
private String name;
@ApiModelProperty("native解析库版本")
private String nativeVersion;
@ApiModelProperty("Record总数")
private Long recordCount;
@ApiModelProperty("Observation Record总数")
private Long observationCount;
@ApiModelProperty("每个Series返回的样例采样值数量")
private Integer sampleValueCount;
@ApiModelProperty("状态1-正常0-删除")
private Integer state;
@ApiModelProperty("解析结果1-成功0-失败")
private Integer result;
@ApiModelProperty("解析提示、失败原因或解析结论JSON")
private JsonNode msg;
@ApiModelProperty("创建人")
private String createBy;
@ApiModelProperty("创建时间")
private LocalDateTime createTime;
@ApiModelProperty("更新人")
private String updateBy;
@ApiModelProperty("更新时间")
private LocalDateTime updateTime;
}

View File

@@ -4,15 +4,20 @@ import com.njcn.gather.tool.parsepqdif.nativebridge.PqdifNativeLibraryLoader;
import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse; import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse;
import com.njcn.pqdif.nativebridge.PqdifBasicNative; import com.njcn.pqdif.nativebridge.PqdifBasicNative;
import com.njcn.pqdif.nativebridge.PqdifNativeSession; import com.njcn.pqdif.nativebridge.PqdifNativeSession;
import org.springframework.stereotype.Component;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@Component
public class PqdifNativeReader { public class PqdifNativeReader {
private static final String STATUS_SUCCESS = "SUCCESS"; private static final String STATUS_SUCCESS = "SUCCESS";
private static final String DATA_SUCCESS = "DATA_SUCCESS";
private static final String DATA_FAILED = "DATA_FAILED";
private static final int DEFAULT_SAMPLE_VALUE_COUNT = 5; private static final int DEFAULT_SAMPLE_VALUE_COUNT = 5;
public PqdifParseResponse read(Path pqdifPath, String fileName) { public PqdifParseResponse read(Path pqdifPath, String fileName) {
@@ -40,10 +45,11 @@ public class PqdifNativeReader {
recordVO.setRecordIndex(recordIndex); recordVO.setRecordIndex(recordIndex);
recordVO.setTypeGuid(recordInfo.typeGuid); recordVO.setTypeGuid(recordInfo.typeGuid);
recordVO.setTypeName(recordInfo.typeName); recordVO.setTypeName(recordInfo.typeName);
recordVO.setObservation(isObservation(recordInfo)); boolean observationRecord = isObservation(recordInfo);
recordVO.setObservation(observationRecord);
response.getRecords().add(recordVO); response.getRecords().add(recordVO);
if (!isObservation(recordInfo)) { if (!observationRecord) {
continue; continue;
} }
@@ -120,7 +126,7 @@ public class PqdifNativeReader {
vo.setScale(seriesInfo.scale); vo.setScale(seriesInfo.scale);
vo.setOffset(seriesInfo.offset); vo.setOffset(seriesInfo.offset);
} catch (Throwable e) { } catch (Throwable e) {
vo.setDataStatus("DATA_FAILED"); vo.setDataStatus(DATA_FAILED);
vo.setDataMessage("getSeriesInfo failed, channel=" + channelIndex vo.setDataMessage("getSeriesInfo failed, channel=" + channelIndex
+ ", series=" + seriesIndex + ", series=" + seriesIndex
+ ", error=" + e.getMessage()); + ", error=" + e.getMessage());
@@ -132,12 +138,12 @@ public class PqdifNativeReader {
try { try {
double[] values = observation.getSeriesData(channelIndex, seriesIndex); double[] values = observation.getSeriesData(channelIndex, seriesIndex);
vo.setDataStatus("DATA_SUCCESS"); vo.setDataStatus(DATA_SUCCESS);
vo.setDataMessage(null); vo.setDataMessage(null);
vo.setValueCount(values == null ? 0 : values.length); vo.setValueCount(values == null ? 0 : values.length);
vo.setFirstValues(firstValues(values, DEFAULT_SAMPLE_VALUE_COUNT)); vo.setFirstValues(firstValues(values, DEFAULT_SAMPLE_VALUE_COUNT));
} catch (Throwable e) { } catch (Throwable e) {
vo.setDataStatus("DATA_FAILED"); vo.setDataStatus(DATA_FAILED);
vo.setDataMessage("getSeriesData failed, channel=" + channelIndex vo.setDataMessage("getSeriesData failed, channel=" + channelIndex
+ ", series=" + seriesIndex + ", series=" + seriesIndex
+ ", seriesBaseType=" + vo.getSeriesBaseType() + ", seriesBaseType=" + vo.getSeriesBaseType()

View File

@@ -0,0 +1,29 @@
package com.njcn.gather.tool.parsepqdif.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.njcn.gather.tool.parsepqdif.pojo.param.CsPqdifPathParam;
import com.njcn.gather.tool.parsepqdif.pojo.param.PqdifParseResultSaveParam;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathDetailVO;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathVO;
import java.util.List;
/**
* PQDIF 存储记录服务。
*/
public interface CsPqdifPathService {
List<CsPqdifPathVO> listPqdifPaths(CsPqdifPathParam.ListParam param);
JsonNode getPqdifParseMsg(String pqdifId);
CsPqdifPathDetailVO getPqdifParseDetail(String pqdifId);
boolean addPqdifPath(CsPqdifPathParam param);
boolean updatePqdifPath(CsPqdifPathParam.UpdateParam param);
boolean deletePqdifPath(List<String> ids);
boolean savePqdifParseResult(String pqdifId, PqdifParseResultSaveParam param);
}

View File

@@ -0,0 +1,185 @@
package com.njcn.gather.tool.parsepqdif.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.njcn.gather.tool.parsepqdif.mapper.CsPqdifPathMapper;
import com.njcn.gather.tool.parsepqdif.pojo.param.CsPqdifPathParam;
import com.njcn.gather.tool.parsepqdif.pojo.param.PqdifParseResultSaveParam;
import com.njcn.gather.tool.parsepqdif.pojo.po.CsPqdifPathPO;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathDetailVO;
import com.njcn.gather.tool.parsepqdif.pojo.vo.CsPqdifPathVO;
import com.njcn.gather.tool.parsepqdif.service.CsPqdifPathService;
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;
/**
* PQDIF 存储记录服务实现。
*/
@Service
@RequiredArgsConstructor
public class CsPqdifPathServiceImpl implements CsPqdifPathService {
private static final int STATE_NORMAL = 1;
private static final int STATE_DELETED = 0;
private final CsPqdifPathMapper csPqdifPathMapper;
@Override
public List<CsPqdifPathVO> listPqdifPaths(CsPqdifPathParam.ListParam param) {
CsPqdifPathParam.ListParam checkedParam = param == null ? new CsPqdifPathParam.ListParam() : param;
return csPqdifPathMapper.selectPqdifPathList(
trimToNull(checkedParam.getKeyword()),
checkedParam.getResult());
}
@Override
public JsonNode getPqdifParseMsg(String pqdifId) {
String id = requireText(pqdifId, "PQDIF记录ID不能为空");
CsPqdifPathVO pqdifPath = csPqdifPathMapper.selectPqdifParseMsgById(id);
return pqdifPath == null ? null : pqdifPath.getMsg();
}
@Override
public CsPqdifPathDetailVO getPqdifParseDetail(String pqdifId) {
String id = requireText(pqdifId, "PQDIF记录ID不能为空");
return csPqdifPathMapper.selectPqdifPathDetailById(id);
}
@Override
@Transactional
public boolean addPqdifPath(CsPqdifPathParam param) {
CsPqdifPathParam checkedParam = requireParam(param);
LocalDateTime now = LocalDateTime.now();
CsPqdifPathPO pqdifPath = buildPqdifPath(checkedParam);
pqdifPath.setId(UUID.randomUUID().toString().replace("-", ""));
pqdifPath.setState(STATE_NORMAL);
pqdifPath.setCreateBy(currentUserId());
pqdifPath.setCreateTime(now);
pqdifPath.setUpdateBy(currentUserId());
pqdifPath.setUpdateTime(now);
return csPqdifPathMapper.insert(pqdifPath) > 0;
}
@Override
@Transactional
public boolean updatePqdifPath(CsPqdifPathParam.UpdateParam param) {
CsPqdifPathParam.UpdateParam checkedParam = requireUpdateParam(param);
requirePqdifPath(checkedParam.getId());
CsPqdifPathPO pqdifPath = buildPqdifPath(checkedParam);
pqdifPath.setId(checkedParam.getId());
pqdifPath.setUpdateBy(currentUserId());
pqdifPath.setUpdateTime(LocalDateTime.now());
return csPqdifPathMapper.updateById(pqdifPath) > 0;
}
@Override
@Transactional
public boolean deletePqdifPath(List<String> ids) {
if (ids == null || ids.isEmpty()) {
throw new IllegalArgumentException("PQDIF记录ID不能为空");
}
CsPqdifPathPO pqdifPath = new CsPqdifPathPO();
pqdifPath.setState(STATE_DELETED);
pqdifPath.setUpdateBy(currentUserId());
pqdifPath.setUpdateTime(LocalDateTime.now());
return csPqdifPathMapper.update(pqdifPath, new LambdaUpdateWrapper<CsPqdifPathPO>()
.in(CsPqdifPathPO::getId, ids)
.eq(CsPqdifPathPO::getState, STATE_NORMAL)) > 0;
}
@Override
@Transactional
public boolean savePqdifParseResult(String pqdifId, PqdifParseResultSaveParam param) {
if (param == null) {
throw new IllegalArgumentException("PQDIF解析结果不能为空");
}
CsPqdifPathPO pqdifPath = requirePqdifPath(pqdifId);
pqdifPath.setNativeVersion(trimToNull(param.getNativeVersion()));
pqdifPath.setRecordCount(param.getRecordCount());
pqdifPath.setObservationCount(param.getObservationCount());
pqdifPath.setSampleValueCount(param.getSampleValueCount());
pqdifPath.setResult(normalizeResult(param.getResult()));
pqdifPath.setMsg(param.getMsg());
pqdifPath.setJsonStr(trimToNull(param.getJsonStr()));
pqdifPath.setUpdateBy(currentUserId());
pqdifPath.setUpdateTime(LocalDateTime.now());
return csPqdifPathMapper.updateById(pqdifPath) > 0;
}
private CsPqdifPathPO buildPqdifPath(CsPqdifPathParam param) {
CsPqdifPathPO pqdifPath = new CsPqdifPathPO();
pqdifPath.setName(requireText(param.getName(), "PQDIF名称不能为空"));
pqdifPath.setPqdifContent(param.getPqdifContent());
return pqdifPath;
}
private CsPqdifPathParam requireParam(CsPqdifPathParam param) {
if (param == null) {
throw new IllegalArgumentException("PQDIF记录参数不能为空");
}
return param;
}
private CsPqdifPathParam.UpdateParam requireUpdateParam(CsPqdifPathParam.UpdateParam param) {
if (param == null) {
throw new IllegalArgumentException("PQDIF记录参数不能为空");
}
requireText(param.getId(), "PQDIF记录ID不能为空");
return param;
}
private CsPqdifPathPO requirePqdifPath(String pqdifId) {
String id = requireText(pqdifId, "PQDIF记录ID不能为空");
CsPqdifPathPO pqdifPath = csPqdifPathMapper.selectById(id);
if (pqdifPath == null || !Integer.valueOf(STATE_NORMAL).equals(pqdifPath.getState())) {
throw new IllegalArgumentException("PQDIF记录不存在或已删除");
}
return pqdifPath;
}
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 "未知用户";
}
}
}

View File

@@ -3,87 +3,115 @@ package com.njcn.gather.tool.parsepqdif.service.impl;
import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse; import com.njcn.gather.tool.parsepqdif.pojo.vo.PqdifParseResponse;
import com.njcn.gather.tool.parsepqdif.reader.PqdifNativeReader; import com.njcn.gather.tool.parsepqdif.reader.PqdifNativeReader;
import com.njcn.gather.tool.parsepqdif.service.ParsePqdifService; import com.njcn.gather.tool.parsepqdif.service.ParsePqdifService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.*;
import java.util.ArrayList;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Locale;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor
public class ParsePqdifServiceImpl implements ParsePqdifService { public class ParsePqdifServiceImpl implements ParsePqdifService {
private static final String STATUS_FAILED = "FAILED"; private static final String STATUS_FAILED = "FAILED";
private static final String DEFAULT_SUFFIX = ".pqd";
private static final String PQDIF_SUFFIX = ".pqdif";
private static final String TEMP_DIR_NAME = "cn-tool-pqdif-upload";
private static final String EMPTY_FILE_MESSAGE = "PQDIF文件不能为空";
private static final String UNSUPPORTED_FILE_MESSAGE = "仅支持 .pqd 或 .pqdif 格式文件";
private static final String DEFAULT_FAILED_MESSAGE = "PQDIF解析失败";
private static final String UNKNOWN_FAILED_REASON = "请检查文件内容或原生解析库状态";
private final PqdifNativeReader pqdifNativeReader = new PqdifNativeReader(); private final PqdifNativeReader pqdifNativeReader;
@Override @Override
public PqdifParseResponse parse(MultipartFile pqdifFile) { public PqdifParseResponse parse(MultipartFile pqdifFile) {
if (pqdifFile == null || pqdifFile.isEmpty()) { if (pqdifFile == null || pqdifFile.isEmpty()) {
return failed(null, "PQDIF文件不能为空"); return failed(null, EMPTY_FILE_MESSAGE);
}
String originalFilename = pqdifFile.getOriginalFilename();
String suffix = getSupportedSuffix(originalFilename);
if (suffix == null) {
return failed(originalFilename, UNSUPPORTED_FILE_MESSAGE);
} }
Path tempFile = null; Path tempFile = null;
try { try {
tempFile = createTempPqdifFile(pqdifFile); tempFile = createTempPqdifFile(pqdifFile, suffix);
return pqdifNativeReader.read(tempFile, pqdifFile.getOriginalFilename()); return pqdifNativeReader.read(tempFile, originalFilename);
} catch (Exception e) { } catch (Exception e) {
log.error("PQDIF解析失败fileName={}", pqdifFile.getOriginalFilename(), e); log.error("PQDIF解析失败fileName={}", originalFilename, e);
return failed(pqdifFile.getOriginalFilename(), e.getMessage()); return failed(originalFilename, buildFailedMessage(e));
} finally { } finally {
if (tempFile != null) { deleteTempFile(tempFile);
try {
Files.deleteIfExists(tempFile);
} catch (Exception e) {
log.warn("删除PQDIF临时文件失败path={}", tempFile, e);
}
}
} }
} }
private Path createTempPqdifFile(MultipartFile pqdifFile) throws Exception { private Path createTempPqdifFile(MultipartFile pqdifFile, String suffix) throws Exception {
String originalFilename = pqdifFile.getOriginalFilename(); // 原生解析库只接收文件路径,因此上传内容需先落到系统临时目录。
String suffix = getSuffix(originalFilename); Path uploadDir = Paths.get(System.getProperty("java.io.tmpdir"), TEMP_DIR_NAME);
Path uploadDir = Paths.get("D:", "CN_Tool_Runtime", "pqdif-upload");
Files.createDirectories(uploadDir); Files.createDirectories(uploadDir);
String safeFileName = "parse-pqdif-" + System.currentTimeMillis() + "-" + Path tempFile = Files.createTempFile(uploadDir, "parse-pqdif-", suffix);
java.util.UUID.randomUUID().toString().replace("-", "") + suffix;
Path tempFile = uploadDir.resolve(safeFileName);
try (InputStream inputStream = pqdifFile.getInputStream()) { try (InputStream inputStream = pqdifFile.getInputStream()) {
Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING); Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING);
} }
return tempFile; return tempFile;
} }
private String getSuffix(String originalFilename) { private void deleteTempFile(Path tempFile) {
if (originalFilename == null) { if (tempFile == null) {
return ".pqd"; return;
}
try {
Files.deleteIfExists(tempFile);
} catch (Exception e) {
log.warn("删除PQDIF临时文件失败path={}", tempFile, e);
}
}
private String getSupportedSuffix(String originalFilename) {
if (originalFilename == null || originalFilename.trim().isEmpty()) {
return null;
} }
int index = originalFilename.lastIndexOf('.'); int index = originalFilename.lastIndexOf('.');
if (index < 0 || index == originalFilename.length() - 1) { if (index < 0 || index == originalFilename.length() - 1) {
return ".pqd"; return null;
} }
return originalFilename.substring(index); String suffix = originalFilename.substring(index).toLowerCase(Locale.ROOT);
if (DEFAULT_SUFFIX.equals(suffix) || PQDIF_SUFFIX.equals(suffix)) {
return suffix;
}
return null;
}
private String resolveErrorMessage(Exception e) {
String message = e.getMessage();
if (message == null || message.trim().isEmpty()) {
return UNKNOWN_FAILED_REASON;
}
return message;
}
private String buildFailedMessage(Exception e) {
return DEFAULT_FAILED_MESSAGE + "" + resolveErrorMessage(e);
} }
private PqdifParseResponse failed(String fileName, String message) { private PqdifParseResponse failed(String fileName, String message) {
PqdifParseResponse response = new PqdifParseResponse(); PqdifParseResponse response = new PqdifParseResponse();
response.setStatus(STATUS_FAILED); response.setStatus(STATUS_FAILED);
response.setMessage(message == null ? "PQDIF解析失败" : message); response.setMessage(message == null ? DEFAULT_FAILED_MESSAGE : message);
response.setFileName(fileName); response.setFileName(fileName);
response.setRecordCount(0L); response.setRecordCount(0L);
response.setObservationCount(0L); response.setObservationCount(0L);

View File

@@ -0,0 +1,51 @@
package com.njcn.gather.tool.parsepqdif.typehandler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 将数据库 JSON 文本映射为 Jackson JsonNode便于接口直接返回结构化解析结果。
*/
public class JsonNodeTypeHandler extends BaseTypeHandler<JsonNode> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, JsonNode parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter.toString());
}
@Override
public JsonNode getNullableResult(ResultSet rs, String columnName) throws SQLException {
return parse(rs.getString(columnName));
}
@Override
public JsonNode getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return parse(rs.getString(columnIndex));
}
@Override
public JsonNode getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return parse(cs.getString(columnIndex));
}
private JsonNode parse(String value) throws SQLException {
if (value == null || value.trim().isEmpty()) {
return null;
}
try {
return OBJECT_MAPPER.readTree(value);
} catch (Exception ex) {
throw new SQLException("解析JSON字段失败", ex);
}
}
}

View File

@@ -36,7 +36,10 @@ public class AuthGlobalFilter implements Filter, Ordered {
"/admin/login", "/admin/login",
"/admin/getPublicKey", "/admin/getPublicKey",
"/event/list/transient/page", "/event/list/transient/page",
"/event/list/transient/page/debug" "/event/list/transient/page/debug",
"/steady/checksquare/create",
"/steady/checksquare/detail",
"/steady/checksquare/item-detail"
); );
@Resource @Resource