feat(mms-mapping): 添加ICD一致性校验功能并重构设备类型管理
- 在MappingController中新增ICD一致性校验接口checkIcdJsonConsistency - 添加IcdConsistencyCheckService服务实现ICD映射JSON一致性校验逻辑 - 添加IcdConsistencyCheckRequest和IcdConsistencyCheckResponse相关数据传输对象 - 在CsIcdPathPO中新增icdContent字段存储ICD内容字节数组 - 在CsIcdPathMapper中新增selectIcdPathList方法支持关键词搜索 - 移除设备类型相关的控制器、服务接口及实现类(MmsDeviceTypeController等) - 更新.gitignore文件排除特定jar包路径 - 在pom.xml中添加device-types模块依赖和JNA库依赖 - 更新README.md文档添加device-types模块说明 - 重命名steady-DataView为steady-dataView模块名统一格式
This commit is contained in:
45
steady/check-square/pom.xml
Normal file
45
steady/check-square/pom.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.njcn.gather</groupId>
|
||||
<artifactId>steady</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>check-square</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.njcn.gather</groupId>
|
||||
<artifactId>steady-dataView</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>njcn-common</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>mybatis-plus</artifactId>
|
||||
<version>0.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.njcn</groupId>
|
||||
<artifactId>spingboot2.3.12</artifactId>
|
||||
<version>2.3.12</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -41,6 +41,7 @@ public class SteadyChecksquareInfluxQueryComponent {
|
||||
|
||||
private static final DateTimeFormatter INFLUX_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'");
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private static final int QUERY_WINDOW_DAYS = 1;
|
||||
private static final ThreadLocal<Map<String, List<SteadyChecksquareValuePointBO>>> REQUEST_VALUE_CACHE =
|
||||
new ThreadLocal<Map<String, List<SteadyChecksquareValuePointBO>>>();
|
||||
|
||||
@@ -79,8 +80,7 @@ public class SteadyChecksquareInfluxQueryComponent {
|
||||
log.info("数据校验指标值 InfluxDB 查询开始,measurement={},field={},lineId={},phase={},statType={},query={}",
|
||||
field.getMeasurement(), field.getField(), field.getLineId(), field.getPhase(), field.getStatType(), query);
|
||||
try {
|
||||
String body = executeQuery(query);
|
||||
List<SteadyChecksquareValuePointBO> points = parseValuePoints(body, intervalMinutes);
|
||||
List<SteadyChecksquareValuePointBO> points = queryValuePointsByWindow(field, startTime, endTime, intervalMinutes);
|
||||
if (cache != null) {
|
||||
cache.put(cacheKey, new ArrayList<SteadyChecksquareValuePointBO>(points));
|
||||
}
|
||||
@@ -124,7 +124,8 @@ public class SteadyChecksquareInfluxQueryComponent {
|
||||
log.info("数据校验指标值 InfluxDB 批量查询开始,measurement={},fieldCount={},lineId={},phase={},statType={},query={}",
|
||||
first.getMeasurement(), missingFields.size(), first.getLineId(), first.getPhase(), first.getStatType(), query);
|
||||
try {
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> queried = parseBatchValuePoints(executeQuery(query), intervalMinutes);
|
||||
Map<String, List<SteadyChecksquareValuePointBO>> queried =
|
||||
queryBatchValuePointsByWindow(missingFields, startTime, endTime, intervalMinutes);
|
||||
for (SteadyTrendResolvedFieldBO field : missingFields) {
|
||||
List<SteadyChecksquareValuePointBO> points = queried.get(field.getField());
|
||||
if (points == null) {
|
||||
@@ -155,6 +156,51 @@ public class SteadyChecksquareInfluxQueryComponent {
|
||||
return query + "|intervalMinutes=" + intervalMinutes;
|
||||
}
|
||||
|
||||
private List<SteadyChecksquareValuePointBO> queryValuePointsByWindow(SteadyTrendResolvedFieldBO field,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime,
|
||||
int intervalMinutes) {
|
||||
List<SteadyChecksquareValuePointBO> result = new ArrayList<SteadyChecksquareValuePointBO>();
|
||||
LocalDateTime windowStart = startTime;
|
||||
while (!windowStart.isAfter(endTime)) {
|
||||
LocalDateTime windowEnd = min(windowStart.plusDays(QUERY_WINDOW_DAYS).minusNanos(1), endTime);
|
||||
result.addAll(parseValuePoints(executeQuery(buildValuePointQuery(field, windowStart, windowEnd)), intervalMinutes));
|
||||
windowStart = windowEnd.plusNanos(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, List<SteadyChecksquareValuePointBO>> queryBatchValuePointsByWindow(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(field.getField(), 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 =
|
||||
parseBatchValuePoints(executeQuery(buildBatchValuePointQuery(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) {
|
||||
return first.isAfter(second) ? second : first;
|
||||
}
|
||||
|
||||
public String buildValuePointQuery(SteadyTrendResolvedFieldBO field, LocalDateTime startTime, LocalDateTime endTime) {
|
||||
StringBuilder sql = new StringBuilder();
|
||||
sql.append("SELECT \"").append(field.getField()).append("\" AS \"value\"");
|
||||
@@ -9,7 +9,6 @@ import com.njcn.common.pojo.response.HttpResult;
|
||||
import com.njcn.common.utils.LogUtil;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO;
|
||||
@@ -36,7 +35,7 @@ import java.util.List;
|
||||
@Slf4j
|
||||
@Api(tags = "数据校验")
|
||||
@RestController
|
||||
@RequestMapping("/steady/data-view/checksquare")
|
||||
@RequestMapping("/steady/checksquare")
|
||||
@RequiredArgsConstructor
|
||||
public class SteadyChecksquareController extends BaseController {
|
||||
|
||||
@@ -55,10 +54,10 @@ public class SteadyChecksquareController extends BaseController {
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||
@ApiOperation("新增数据校验记录")
|
||||
@PostMapping("/create")
|
||||
public HttpResult<SteadyChecksquareCreateVO> create(@RequestBody @Validated SteadyChecksquareQueryParam param) {
|
||||
public HttpResult<SteadyChecksquareTaskVO> create(@RequestBody @Validated SteadyChecksquareQueryParam param) {
|
||||
String methodDescribe = getMethodDescribe("create");
|
||||
LogUtil.njcnDebug(log, "{},开始新增数据校验记录,param={}", methodDescribe, param);
|
||||
SteadyChecksquareCreateVO result = checksquareService.create(param);
|
||||
SteadyChecksquareTaskVO result = checksquareService.create(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ public final class SteadyChecksquareConst {
|
||||
public static final int STATE_DELETED = 0;
|
||||
public static final int STATE_ENABLED = 1;
|
||||
|
||||
public static final String TASK_STATUS_RUNNING = "RUNNING";
|
||||
public static final String TASK_STATUS_SUCCESS = "SUCCESS";
|
||||
public static final String TASK_STATUS_FAIL = "FAIL";
|
||||
public static final String DETAIL_TYPE_SEGMENT = "SEGMENT";
|
||||
public static final String DETAIL_TYPE_VALUE_ORDER = "VALUE_ORDER";
|
||||
public static final String DETAIL_TYPE_HARMONIC_PARITY = "HARMONIC_PARITY";
|
||||
@@ -3,7 +3,6 @@ package com.njcn.gather.steady.checksquare.service;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO;
|
||||
@@ -17,7 +16,7 @@ public interface SteadyChecksquareService {
|
||||
|
||||
Page<SteadyChecksquareTaskVO> query(SteadyChecksquareHistoryQueryParam param);
|
||||
|
||||
SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param);
|
||||
SteadyChecksquareTaskVO create(SteadyChecksquareQueryParam param);
|
||||
|
||||
boolean delete(List<String> taskIds);
|
||||
|
||||
@@ -17,7 +17,6 @@ import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareDetailPO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareItemPO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareStatSummaryPO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.po.SteadyChecksquareTaskPO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareCreateVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareHarmonicParityRuleVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO;
|
||||
@@ -52,6 +51,7 @@ import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
@@ -67,8 +67,7 @@ import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 数据校验服务实现。
|
||||
*/
|
||||
* 数据校验服务实现。 */
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@@ -80,6 +79,8 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
private static final int FLICKER_LONG_INTERVAL_MINUTES = 120;
|
||||
private static final int HARMONIC_AGGREGATE_ORDER_START = 2;
|
||||
private static final int HARMONIC_AGGREGATE_ORDER_END = 50;
|
||||
private static final long MAX_CREATE_RANGE_DAYS = 7L;
|
||||
private static final int DETAIL_SAVE_BATCH_SIZE = 1000;
|
||||
|
||||
private final SteadyTrendIndicatorCatalog indicatorCatalog;
|
||||
private final SteadyChecksquareInfluxQueryComponent influxQueryComponent;
|
||||
@@ -116,18 +117,25 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SteadyChecksquareCreateVO create(SteadyChecksquareQueryParam param) {
|
||||
public SteadyChecksquareTaskVO create(SteadyChecksquareQueryParam param) {
|
||||
validateCreateBaseParam(param);
|
||||
String lineId = trimToNull(param.getLineId());
|
||||
LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
|
||||
LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
|
||||
SteadyChecksquareTaskPO existedTask = findExistingTask(lineId, startTime, endTime);
|
||||
if (existedTask != null) {
|
||||
return toTaskVO(existedTask);
|
||||
}
|
||||
prepareCreateContext(param);
|
||||
influxQueryComponent.enableRequestCache();
|
||||
SteadyChecksquareQueryVO result;
|
||||
try {
|
||||
result = calculate(param);
|
||||
SteadyChecksquareQueryVO result = calculate(param);
|
||||
SteadyChecksquareTaskPO task = saveResultInTransaction(param, result);
|
||||
return toTaskVO(task);
|
||||
} finally {
|
||||
influxQueryComponent.clearRequestCache();
|
||||
}
|
||||
SteadyChecksquareTaskPO task = saveResultInTransaction(param, result);
|
||||
return toCreateVO(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(List<String> taskIds) {
|
||||
List<String> ids = normalizeTextList(taskIds);
|
||||
@@ -224,7 +232,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
.in(SteadyChecksquareTaskPO::getId, taskIds)
|
||||
.eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED);
|
||||
boolean taskResult = taskService.update(taskWrapper);
|
||||
// 检测项同步置为删除态,避免已删任务下的 item-detail 被继续访问。
|
||||
LambdaUpdateWrapper<SteadyChecksquareItemPO> itemWrapper = new LambdaUpdateWrapper<SteadyChecksquareItemPO>()
|
||||
.set(SteadyChecksquareItemPO::getState, SteadyChecksquareConst.STATE_DELETED)
|
||||
.in(SteadyChecksquareItemPO::getTaskId, taskIds)
|
||||
@@ -233,6 +240,51 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
return taskResult;
|
||||
}
|
||||
|
||||
private CreateContext prepareCreateContext(SteadyChecksquareQueryParam param) {
|
||||
validateParam(param);
|
||||
String lineId = trimToNull(param.getLineId());
|
||||
LocalDateTime startTime = parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
|
||||
LocalDateTime endTime = parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
|
||||
if (startTime.isAfter(endTime)) {
|
||||
throw fail("开始时间不能大于结束时间");
|
||||
}
|
||||
AddLedgerLinePathVO linePath = requireLinePath(lineId);
|
||||
int intervalMinutes = resolveIntervalMinutes(linePath);
|
||||
List<String> indicatorCodes = normalizeTextList(param.getIndicatorCodes());
|
||||
for (String indicatorCode : indicatorCodes) {
|
||||
requireIndicator(indicatorCode);
|
||||
}
|
||||
validateCreateTimeRange(startTime, endTime);
|
||||
return new CreateContext(lineId, linePath, startTime, endTime, intervalMinutes, indicatorCodes);
|
||||
}
|
||||
|
||||
private void validateCreateTimeRange(LocalDateTime startTime, LocalDateTime endTime) {
|
||||
if (Duration.between(startTime, endTime).compareTo(Duration.ofDays(MAX_CREATE_RANGE_DAYS)) > 0) {
|
||||
throw fail("数据校验时间范围不能超过7天");
|
||||
}
|
||||
}
|
||||
|
||||
private SteadyChecksquareTaskPO findExistingTask(String lineId, LocalDateTime startTime, LocalDateTime endTime) {
|
||||
List<SteadyChecksquareTaskPO> tasks = taskService.lambdaQuery()
|
||||
.eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED)
|
||||
.eq(SteadyChecksquareTaskPO::getLineId, lineId)
|
||||
.eq(SteadyChecksquareTaskPO::getTimeStart, startTime)
|
||||
.eq(SteadyChecksquareTaskPO::getTimeEnd, endTime)
|
||||
.orderByDesc(SteadyChecksquareTaskPO::getCreateTime)
|
||||
.list();
|
||||
return tasks == null || tasks.isEmpty() ? null : tasks.get(0);
|
||||
}
|
||||
|
||||
private void markTaskFail(String taskId, String message) {
|
||||
LambdaUpdateWrapper<SteadyChecksquareTaskPO> wrapper = new LambdaUpdateWrapper<SteadyChecksquareTaskPO>()
|
||||
.set(SteadyChecksquareTaskPO::getTaskStatus, SteadyChecksquareConst.TASK_STATUS_FAIL)
|
||||
.set(SteadyChecksquareTaskPO::getResultMessage, limitMessage(message))
|
||||
.set(SteadyChecksquareTaskPO::getUpdateTime, LocalDateTime.now())
|
||||
.eq(SteadyChecksquareTaskPO::getId, taskId)
|
||||
.eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED);
|
||||
taskService.update(wrapper);
|
||||
}
|
||||
|
||||
private SteadyChecksquareQueryVO calculate(SteadyChecksquareQueryParam param) {
|
||||
validateParam(param);
|
||||
String lineId = trimToNull(param.getLineId());
|
||||
@@ -275,10 +327,21 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
return transactionTemplate.execute(status -> saveResult(param, result));
|
||||
}
|
||||
|
||||
private SteadyChecksquareTaskPO saveResultInTransaction(String taskId, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) {
|
||||
if (transactionTemplate == null) {
|
||||
return saveResult(taskId, param, result);
|
||||
}
|
||||
return transactionTemplate.execute(status -> saveResult(taskId, param, result));
|
||||
}
|
||||
|
||||
private SteadyChecksquareTaskPO saveResult(SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) {
|
||||
return saveResult(null, param, result);
|
||||
}
|
||||
|
||||
private SteadyChecksquareTaskPO saveResult(String taskId, SteadyChecksquareQueryParam param, SteadyChecksquareQueryVO result) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO();
|
||||
task.setId(SteadyChecksquareIdUtil.uuid());
|
||||
task.setId(trimToNull(taskId) == null ? SteadyChecksquareIdUtil.uuid() : taskId);
|
||||
task.setTaskNo(SteadyChecksquareIdUtil.taskNo());
|
||||
task.setLineId(result.getLineId());
|
||||
task.setLineName(result.getLineName());
|
||||
@@ -296,7 +359,11 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
task.setState(SteadyChecksquareConst.STATE_ENABLED);
|
||||
task.setCreateTime(now);
|
||||
task.setUpdateTime(now);
|
||||
taskService.save(task);
|
||||
if (trimToNull(taskId) == null) {
|
||||
taskService.save(task);
|
||||
} else {
|
||||
updateCompletedTask(task);
|
||||
}
|
||||
|
||||
List<SteadyChecksquareItemPO> itemPOs = new ArrayList<SteadyChecksquareItemPO>();
|
||||
List<SteadyChecksquareStatSummaryPO> summaryPOs = new ArrayList<SteadyChecksquareStatSummaryPO>();
|
||||
@@ -315,11 +382,33 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
statSummaryService.saveBatch(summaryPOs);
|
||||
}
|
||||
if (!detailPOs.isEmpty()) {
|
||||
detailService.saveBatch(detailPOs);
|
||||
saveDetailBatchInChunks(detailPOs);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
private void updateCompletedTask(SteadyChecksquareTaskPO task) {
|
||||
LambdaUpdateWrapper<SteadyChecksquareTaskPO> wrapper = new LambdaUpdateWrapper<SteadyChecksquareTaskPO>()
|
||||
.set(SteadyChecksquareTaskPO::getLineName, task.getLineName())
|
||||
.set(SteadyChecksquareTaskPO::getIntervalMinutes, task.getIntervalMinutes())
|
||||
.set(SteadyChecksquareTaskPO::getTaskStatus, task.getTaskStatus())
|
||||
.set(SteadyChecksquareTaskPO::getItemCount, task.getItemCount())
|
||||
.set(SteadyChecksquareTaskPO::getAbnormalItemCount, task.getAbnormalItemCount())
|
||||
.set(SteadyChecksquareTaskPO::getMinDataIntegrity, task.getMinDataIntegrity())
|
||||
.set(SteadyChecksquareTaskPO::getResultMessage, task.getResultMessage())
|
||||
.set(SteadyChecksquareTaskPO::getUpdateTime, task.getUpdateTime())
|
||||
.eq(SteadyChecksquareTaskPO::getId, task.getId())
|
||||
.eq(SteadyChecksquareTaskPO::getState, SteadyChecksquareConst.STATE_ENABLED);
|
||||
taskService.update(wrapper);
|
||||
}
|
||||
|
||||
private void saveDetailBatchInChunks(List<SteadyChecksquareDetailPO> detailPOs) {
|
||||
for (int start = 0; start < detailPOs.size(); start += DETAIL_SAVE_BATCH_SIZE) {
|
||||
int end = Math.min(start + DETAIL_SAVE_BATCH_SIZE, detailPOs.size());
|
||||
detailService.saveBatch(detailPOs.subList(start, end));
|
||||
}
|
||||
}
|
||||
|
||||
private SteadyChecksquareItemPO buildItemPO(String taskId, SteadyChecksquareItemVO item, LocalDateTime now) {
|
||||
SteadyChecksquareItemPO po = new SteadyChecksquareItemPO();
|
||||
po.setId(SteadyChecksquareIdUtil.uuid());
|
||||
@@ -444,7 +533,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
}
|
||||
}
|
||||
for (Map.Entry<String, List<SteadyTrendResolvedFieldBO>> entry : fieldMap.entrySet()) {
|
||||
// 预取只依赖请求级缓存;后续缺数和规则校验复用同一批 Influx 结果。
|
||||
influxQueryComponent.queryValuePointMap(entry.getValue(), startTime, endTime, intervalMap.get(entry.getKey()));
|
||||
}
|
||||
}
|
||||
@@ -727,13 +815,28 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
}
|
||||
|
||||
private void validateParam(SteadyChecksquareQueryParam param) {
|
||||
validateParam(param, false);
|
||||
}
|
||||
|
||||
private void validateCreateBaseParam(SteadyChecksquareQueryParam param) {
|
||||
if (param == null) {
|
||||
throw fail("数据校验参数不能为空");
|
||||
}
|
||||
if (trimToNull(param.getLineId()) == null) {
|
||||
throw fail("监测点 ID 不能为空");
|
||||
throw fail("监测点ID不能为空");
|
||||
}
|
||||
if (normalizeTextList(param.getIndicatorCodes()).isEmpty()) {
|
||||
parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
|
||||
parseRequiredTime(param.getTimeEnd(), "结束时间不能为空");
|
||||
}
|
||||
|
||||
private void validateParam(SteadyChecksquareQueryParam param, boolean allowEmptyIndicators) {
|
||||
if (param == null) {
|
||||
throw fail("数据校验参数不能为空");
|
||||
}
|
||||
if (trimToNull(param.getLineId()) == null) {
|
||||
throw fail("监测点ID不能为空");
|
||||
}
|
||||
if (!allowEmptyIndicators && normalizeTextList(param.getIndicatorCodes()).isEmpty()) {
|
||||
throw fail("指标不能为空");
|
||||
}
|
||||
parseRequiredTime(param.getTimeStart(), "开始时间不能为空");
|
||||
@@ -800,7 +903,7 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
int start = Math.max(HARMONIC_AGGREGATE_ORDER_START, indicator.getHarmonicOrderStart());
|
||||
int end = Math.min(HARMONIC_AGGREGATE_ORDER_END, indicator.getHarmonicOrderEnd());
|
||||
if (start > end) {
|
||||
throw fail("谐波次数只能在 " + HARMONIC_AGGREGATE_ORDER_START + " 到 " + HARMONIC_AGGREGATE_ORDER_END + " 之间");
|
||||
throw fail("谐波次数只能在" + HARMONIC_AGGREGATE_ORDER_START + "到" + HARMONIC_AGGREGATE_ORDER_END + "之间");
|
||||
}
|
||||
List<Integer> result = new ArrayList<Integer>();
|
||||
for (int order = start; order <= end; order++) {
|
||||
@@ -914,20 +1017,6 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
return vo;
|
||||
}
|
||||
|
||||
private SteadyChecksquareCreateVO toCreateVO(SteadyChecksquareTaskPO task) {
|
||||
SteadyChecksquareCreateVO vo = new SteadyChecksquareCreateVO();
|
||||
vo.setTaskId(task.getId());
|
||||
vo.setTaskNo(task.getTaskNo());
|
||||
vo.setLineId(task.getLineId());
|
||||
vo.setLineName(task.getLineName());
|
||||
vo.setTimeStart(formatTime(task.getTimeStart()));
|
||||
vo.setTimeEnd(formatTime(task.getTimeEnd()));
|
||||
vo.setIntervalMinutes(task.getIntervalMinutes());
|
||||
vo.setItemCount(task.getItemCount());
|
||||
vo.setAbnormalItemCount(task.getAbnormalItemCount());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private SteadyChecksquareItemVO toItemVO(SteadyChecksquareItemPO item) {
|
||||
SteadyChecksquareItemVO vo = new SteadyChecksquareItemVO();
|
||||
vo.setItemId(item.getId());
|
||||
@@ -1167,6 +1256,14 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private String limitMessage(String message) {
|
||||
String text = trimToNull(message);
|
||||
if (text == null) {
|
||||
return "数据校验任务执行失败";
|
||||
}
|
||||
return text.length() > 2000 ? text.substring(0, 2000) : text;
|
||||
}
|
||||
|
||||
private List<Integer> readIntegerList(String json) {
|
||||
if (trimToNull(json) == null) {
|
||||
return new ArrayList<Integer>();
|
||||
@@ -1179,6 +1276,18 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> readStringList(String json) {
|
||||
if (trimToNull(json) == null) {
|
||||
return new ArrayList<String>();
|
||||
}
|
||||
try {
|
||||
String[] values = objectMapper.readValue(json, String[].class);
|
||||
return normalizeTextList(Arrays.asList(values));
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private List<BigDecimal> readBigDecimalList(String json) {
|
||||
if (trimToNull(json) == null) {
|
||||
return new ArrayList<BigDecimal>();
|
||||
@@ -1194,4 +1303,26 @@ public class SteadyChecksquareServiceImpl implements SteadyChecksquareService {
|
||||
private BusinessException fail(String message) {
|
||||
return new BusinessException(CommonResponseEnum.FAIL, message);
|
||||
}
|
||||
|
||||
private static class CreateContext {
|
||||
private final String lineId;
|
||||
private final AddLedgerLinePathVO linePath;
|
||||
private final LocalDateTime startTime;
|
||||
private final LocalDateTime endTime;
|
||||
private final int intervalMinutes;
|
||||
private final List<String> indicatorCodes;
|
||||
|
||||
private CreateContext(String lineId, AddLedgerLinePathVO linePath, LocalDateTime startTime,
|
||||
LocalDateTime endTime, int intervalMinutes, List<String> indicatorCodes) {
|
||||
this.lineId = lineId;
|
||||
this.linePath = linePath;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.intervalMinutes = intervalMinutes;
|
||||
this.indicatorCodes = indicatorCodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS `steady_checksquare_task` (
|
||||
`interval_minutes` INT NULL COMMENT '默认统计间隔,单位分钟',
|
||||
`indicator_codes_json` JSON NULL COMMENT '请求指标编码列表',
|
||||
`indicator_codes_text` VARCHAR(2000) NULL COMMENT '请求指标编码检索文本,格式 |code1|code2|',
|
||||
`task_status` VARCHAR(32) NOT NULL DEFAULT 'SUCCESS' COMMENT '任务状态:SUCCESS/FAIL',
|
||||
`task_status` VARCHAR(32) NOT NULL DEFAULT 'SUCCESS' COMMENT '任务状态:RUNNING/SUCCESS/FAIL',
|
||||
`item_count` INT NOT NULL DEFAULT 0 COMMENT '检测项数量',
|
||||
`abnormal_item_count` INT NOT NULL DEFAULT 0 COMMENT '异常检测项数量',
|
||||
`min_data_integrity` DECIMAL(12,6) NOT NULL DEFAULT 0.000000 COMMENT '最低数据完整性',
|
||||
@@ -10,6 +10,7 @@ import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@@ -141,6 +142,38 @@ class SteadyChecksquareInfluxQueryComponentTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSplitLongValuePointQueryByDay() throws Exception {
|
||||
AtomicInteger requestCount = new AtomicInteger();
|
||||
HttpServer server = HttpServer.create(new InetSocketAddress(0), 0);
|
||||
server.createContext("/query", exchange -> {
|
||||
int index = requestCount.incrementAndGet();
|
||||
byte[] body = ("{\"results\":[{\"series\":[{\"values\":["
|
||||
+ "[\"2026-05-0" + index + "T00:00:00Z\"," + index + "]"
|
||||
+ "]}]}]}").getBytes(StandardCharsets.UTF_8);
|
||||
exchange.sendResponseHeaders(200, body.length);
|
||||
exchange.getResponseBody().write(body);
|
||||
exchange.close();
|
||||
});
|
||||
server.start();
|
||||
try {
|
||||
SteadyInfluxDbProperties properties = new SteadyInfluxDbProperties();
|
||||
properties.setUrl("http://127.0.0.1:" + server.getAddress().getPort());
|
||||
properties.setDatabase("steady");
|
||||
SteadyChecksquareInfluxQueryComponent component = new SteadyChecksquareInfluxQueryComponent(properties);
|
||||
|
||||
List<com.njcn.gather.steady.checksquare.pojo.bo.SteadyChecksquareValuePointBO> result =
|
||||
component.queryValuePoints(buildField("h_2"),
|
||||
LocalDateTime.of(2026, 5, 1, 0, 0, 0),
|
||||
LocalDateTime.of(2026, 5, 3, 0, 0, 0), 1);
|
||||
|
||||
Assertions.assertEquals(3, requestCount.get());
|
||||
Assertions.assertEquals(3, result.size());
|
||||
} finally {
|
||||
server.stop(0);
|
||||
}
|
||||
}
|
||||
|
||||
private SteadyTrendResolvedFieldBO buildField(String fieldName) {
|
||||
SteadyTrendResolvedFieldBO field = new SteadyTrendResolvedFieldBO();
|
||||
field.setMeasurement("data_harmonic");
|
||||
@@ -6,8 +6,9 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.util.List;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据校验接口契约测试。
|
||||
@@ -17,7 +18,7 @@ class SteadyChecksquareControllerTest {
|
||||
@Test
|
||||
void shouldExposeChecksquareQueryEndpointInSeparateController() throws Exception {
|
||||
RequestMapping requestMapping = SteadyChecksquareController.class.getAnnotation(RequestMapping.class);
|
||||
Assertions.assertArrayEquals(new String[]{"/steady/data-view/checksquare"}, requestMapping.value());
|
||||
Assertions.assertArrayEquals(new String[]{"/steady/checksquare"}, requestMapping.value());
|
||||
|
||||
Method queryMethod = SteadyChecksquareController.class.getDeclaredMethod("query", com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareHistoryQueryParam.class);
|
||||
PostMapping queryMapping = queryMethod.getAnnotation(PostMapping.class);
|
||||
@@ -40,4 +41,16 @@ class SteadyChecksquareControllerTest {
|
||||
PostMapping deleteMapping = deleteMethod.getAnnotation(PostMapping.class);
|
||||
Assertions.assertArrayEquals(new String[]{"/delete"}, deleteMapping.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepCreateResponseAsTaskSummaryWithoutDetailItems() throws Exception {
|
||||
Method createMethod = SteadyChecksquareController.class.getDeclaredMethod("create",
|
||||
com.njcn.gather.steady.checksquare.pojo.param.SteadyChecksquareQueryParam.class);
|
||||
ParameterizedType resultType = (ParameterizedType) createMethod.getGenericReturnType();
|
||||
|
||||
Assertions.assertEquals(com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO.class,
|
||||
resultType.getActualTypeArguments()[0]);
|
||||
Assertions.assertThrows(NoSuchFieldException.class,
|
||||
() -> com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO.class.getDeclaredField("items"));
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareItemVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareQueryVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareStatSummaryVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareTaskVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderDetailVO;
|
||||
import com.njcn.gather.steady.checksquare.pojo.vo.SteadyChecksquareValueOrderRuleVO;
|
||||
import com.njcn.gather.steady.checksquare.service.SteadyChecksquareDetailService;
|
||||
@@ -48,13 +49,13 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 数据校验服务测试。
|
||||
*/
|
||||
* 鏁版嵁鏍¢獙鏈嶅姟娴嬭瘯銆? */
|
||||
class SteadyChecksquareServiceImplTest {
|
||||
|
||||
@Test
|
||||
@@ -64,6 +65,177 @@ class SteadyChecksquareServiceImplTest {
|
||||
Assertions.assertNull(createMethod.getAnnotation(Transactional.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectCreateWhenTimeRangeExceedsSevenDays() {
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
|
||||
LambdaQueryChainWrapper<SteadyChecksquareTaskPO> taskQuery = mock(LambdaQueryChainWrapper.class);
|
||||
when(taskService.lambdaQuery()).thenReturn(taskQuery);
|
||||
when(taskQuery.eq(any(), any())).thenReturn(taskQuery);
|
||||
when(taskQuery.orderByDesc(any())).thenReturn(taskQuery);
|
||||
when(taskQuery.list()).thenReturn(Collections.emptyList());
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
|
||||
mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, taskService,
|
||||
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("line-001");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setLineId("line-001");
|
||||
param.setIndicatorCodes(Collections.singletonList("V_RMS"));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-08 00:01:00");
|
||||
|
||||
Assertions.assertThrows(RuntimeException.class, () -> service.create(param));
|
||||
|
||||
verify(taskService, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRejectCreateByIndicatorCountWithinSevenDays() {
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
|
||||
SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class);
|
||||
SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class);
|
||||
SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class);
|
||||
LambdaQueryChainWrapper<SteadyChecksquareTaskPO> taskQuery = mock(LambdaQueryChainWrapper.class);
|
||||
when(taskService.lambdaQuery()).thenReturn(taskQuery);
|
||||
when(taskQuery.eq(any(), any())).thenReturn(taskQuery);
|
||||
when(taskQuery.orderByDesc(any())).thenReturn(taskQuery);
|
||||
when(taskQuery.list()).thenReturn(Collections.emptyList());
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(),
|
||||
valueOrderRuleComponent, harmonicParityRuleComponent,
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, taskService,
|
||||
itemService, statSummaryService, detailService, new ObjectMapper());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("line-001");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(new HashSet<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.setLineId("line-001");
|
||||
param.setIndicatorCodes(Arrays.asList("V_RMS", "V_LINE_RMS", "FREQ", "I_RMS", "I_THD"));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-03 23:59:00");
|
||||
|
||||
SteadyChecksquareTaskVO result = service.create(param);
|
||||
|
||||
Assertions.assertEquals(taskCaptor.getValue().getId(), result.getTaskId());
|
||||
Assertions.assertEquals(Integer.valueOf(5), result.getItemCount());
|
||||
verify(itemService).saveBatch(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnExistingTaskSummaryWhenCreateMatchesLineAndTime() {
|
||||
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
|
||||
LambdaQueryChainWrapper<SteadyChecksquareTaskPO> taskQuery = mock(LambdaQueryChainWrapper.class);
|
||||
SteadyChecksquareTaskPO task = new SteadyChecksquareTaskPO();
|
||||
task.setId("task-001");
|
||||
task.setTaskNo("CS202605010001");
|
||||
task.setLineId("line-001");
|
||||
task.setLineName("line-001");
|
||||
task.setTimeStart(LocalDateTime.of(2026, 5, 1, 0, 0));
|
||||
task.setTimeEnd(LocalDateTime.of(2026, 5, 1, 0, 1));
|
||||
task.setIntervalMinutes(1);
|
||||
task.setIndicatorCodesJson("[\"V_RMS\"]");
|
||||
task.setTaskStatus("SUCCESS");
|
||||
task.setItemCount(1);
|
||||
task.setAbnormalItemCount(0);
|
||||
task.setMinDataIntegrity(BigDecimal.ONE.setScale(6));
|
||||
task.setCreateTime(LocalDateTime.of(2026, 5, 1, 1, 0));
|
||||
task.setState(1);
|
||||
when(taskService.lambdaQuery()).thenReturn(taskQuery);
|
||||
when(taskQuery.eq(any(), any())).thenReturn(taskQuery);
|
||||
when(taskQuery.orderByDesc(any())).thenReturn(taskQuery);
|
||||
when(taskQuery.list()).thenReturn(Collections.singletonList(task));
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
|
||||
mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
|
||||
new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService,
|
||||
mock(SteadyChecksquareItemService.class), mock(SteadyChecksquareStatSummaryService.class),
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setLineId("line-001");
|
||||
param.setIndicatorCodes(Collections.singletonList("I_RMS"));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-01 00:01:00");
|
||||
|
||||
SteadyChecksquareTaskVO result = service.create(param);
|
||||
|
||||
Assertions.assertEquals("task-001", result.getTaskId());
|
||||
Assertions.assertEquals("CS202605010001", result.getTaskNo());
|
||||
Assertions.assertEquals("SUCCESS", result.getTaskStatus());
|
||||
verify(taskService, never()).save(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateTaskSynchronouslyAndReturnTaskSummary() {
|
||||
AddLedgerService addLedgerService = mock(AddLedgerService.class);
|
||||
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
SteadyChecksquareValueOrderRuleComponent valueOrderRuleComponent = mock(SteadyChecksquareValueOrderRuleComponent.class);
|
||||
SteadyChecksquareHarmonicParityRuleComponent harmonicParityRuleComponent = mock(SteadyChecksquareHarmonicParityRuleComponent.class);
|
||||
SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class);
|
||||
SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class);
|
||||
SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class);
|
||||
LambdaQueryChainWrapper<SteadyChecksquareTaskPO> taskQuery = mock(LambdaQueryChainWrapper.class);
|
||||
when(taskService.lambdaQuery()).thenReturn(taskQuery);
|
||||
when(taskQuery.eq(any(), any())).thenReturn(taskQuery);
|
||||
when(taskQuery.orderByDesc(any())).thenReturn(taskQuery);
|
||||
when(taskQuery.list()).thenReturn(Collections.emptyList());
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
influxQueryComponent, new SteadyChecksquareCalculator(),
|
||||
valueOrderRuleComponent, harmonicParityRuleComponent,
|
||||
new AddDataTimeSlotCalculator(), addLedgerService, taskService,
|
||||
itemService, statSummaryService, detailService, new ObjectMapper());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("line-001");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
when(influxQueryComponent.queryExistingSlots(any(), any(LocalDateTime.class), any(LocalDateTime.class), anyInt()))
|
||||
.thenReturn(new HashSet<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.setLineId("line-001");
|
||||
param.setIndicatorCodes(Collections.singletonList("V_RMS"));
|
||||
param.setTimeStart("2026-05-01 00:00:00");
|
||||
param.setTimeEnd("2026-05-01 00:01:00");
|
||||
|
||||
SteadyChecksquareTaskVO result = service.create(param);
|
||||
|
||||
Assertions.assertEquals(taskCaptor.getValue().getId(), result.getTaskId());
|
||||
Assertions.assertEquals("SUCCESS", result.getTaskStatus());
|
||||
Assertions.assertEquals(Integer.valueOf(1), result.getItemCount());
|
||||
verify(itemService).saveBatch(any());
|
||||
verify(statSummaryService).saveBatch(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseFixedFlickerIntervalsPerIndicator() {
|
||||
SteadyChecksquareInfluxQueryComponent influxQueryComponent = mock(SteadyChecksquareInfluxQueryComponent.class);
|
||||
@@ -81,7 +253,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
.thenReturn(emptyHarmonicParityRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineName("杩涚嚎涓€");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
@@ -125,7 +297,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
.thenReturn(emptyHarmonicParityRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineName("杩涚嚎涓€");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
@@ -164,7 +336,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
.thenReturn(emptyHarmonicParityRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineName("杩涚嚎涓€");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
@@ -218,7 +390,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
mock(SteadyChecksquareDetailService.class), new ObjectMapper());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineName("杩涚嚎涓€");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
@@ -267,7 +439,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
.thenReturn(emptyHarmonicParityRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineName("杩涚嚎涓€");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
@@ -314,7 +486,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
.thenReturn(emptyRuleResult());
|
||||
AddLedgerLinePathVO linePath = new AddLedgerLinePathVO();
|
||||
linePath.setLineId("line-001");
|
||||
linePath.setLineName("进线一");
|
||||
linePath.setLineName("杩涚嚎涓€");
|
||||
linePath.setLineInterval(1);
|
||||
when(addLedgerService.listLinePathByLineIds(eq(Collections.singletonList("line-001"))))
|
||||
.thenReturn(Collections.singletonMap("line-001", linePath));
|
||||
@@ -376,7 +548,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
task.setId("task-001");
|
||||
task.setState(1);
|
||||
task.setLineId("line-001");
|
||||
task.setLineName("进线一");
|
||||
task.setLineName("杩涚嚎涓€");
|
||||
task.setTimeStart(LocalDateTime.of(2026, 5, 1, 0, 0));
|
||||
task.setTimeEnd(LocalDateTime.of(2026, 5, 1, 0, 1));
|
||||
task.setIntervalMinutes(1);
|
||||
@@ -481,14 +653,14 @@ class SteadyChecksquareServiceImplTest {
|
||||
param.setIndicatorCodes(Collections.singletonList("V_RMS"));
|
||||
SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO();
|
||||
result.setLineId("line-001");
|
||||
result.setLineName("进线一");
|
||||
result.setLineName("杩涚嚎涓€");
|
||||
result.setTimeStart("2026-05-01 00:00:00");
|
||||
result.setTimeEnd("2026-05-01 00:01:00");
|
||||
result.setIntervalMinutes(1);
|
||||
SteadyChecksquareItemVO item = new SteadyChecksquareItemVO();
|
||||
item.setItemKey("line-001|V_RMS");
|
||||
item.setIndicatorCode("V_RMS");
|
||||
item.setIndicatorName("相电压有效值");
|
||||
item.setIndicatorName("鐩哥數鍘嬫湁鏁堝€?);
|
||||
item.setIntervalMinutes(1);
|
||||
item.setHasData(true);
|
||||
item.setExpectedPointCount(2);
|
||||
@@ -519,6 +691,41 @@ class SteadyChecksquareServiceImplTest {
|
||||
verify(statSummaryService).saveBatch(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSaveDetailResultsInChunks() {
|
||||
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
|
||||
SteadyChecksquareItemService itemService = mock(SteadyChecksquareItemService.class);
|
||||
SteadyChecksquareStatSummaryService statSummaryService = mock(SteadyChecksquareStatSummaryService.class);
|
||||
SteadyChecksquareDetailService detailService = mock(SteadyChecksquareDetailService.class);
|
||||
SteadyChecksquareServiceImpl service = new SteadyChecksquareServiceImpl(new SteadyTrendIndicatorCatalog(),
|
||||
mock(SteadyChecksquareInfluxQueryComponent.class), new SteadyChecksquareCalculator(),
|
||||
mock(SteadyChecksquareValueOrderRuleComponent.class), mock(SteadyChecksquareHarmonicParityRuleComponent.class),
|
||||
new AddDataTimeSlotCalculator(), mock(AddLedgerService.class), taskService,
|
||||
itemService, statSummaryService, detailService, new ObjectMapper());
|
||||
SteadyChecksquareQueryParam param = new SteadyChecksquareQueryParam();
|
||||
param.setIndicatorCodes(Collections.singletonList("V_RMS"));
|
||||
SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO();
|
||||
result.setLineId("line-001");
|
||||
result.setLineName("line-001");
|
||||
result.setTimeStart("2026-05-01 00:00:00");
|
||||
result.setTimeEnd("2026-05-01 00:01:00");
|
||||
result.setIntervalMinutes(1);
|
||||
SteadyChecksquareItemVO item = buildOrderItem(true, BigDecimal.ONE.setScale(6));
|
||||
item.setItemKey("line-001|V_RMS");
|
||||
item.setIndicatorCode("V_RMS");
|
||||
for (int i = 0; i < 1001; i++) {
|
||||
SteadyChecksquareValueOrderDetailVO detail = new SteadyChecksquareValueOrderDetailVO();
|
||||
detail.setTime("2026-05-01 00:00:00");
|
||||
detail.setPhase("A");
|
||||
item.getAbnormalDetails().add(detail);
|
||||
}
|
||||
result.getItems().add(item);
|
||||
|
||||
saveResult(service, param, result);
|
||||
|
||||
verify(detailService, times(2)).saveBatch(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCountNoDataItemAsAbnormalWhenSavingTask() {
|
||||
SteadyChecksquareTaskService taskService = mock(SteadyChecksquareTaskService.class);
|
||||
@@ -532,7 +739,7 @@ class SteadyChecksquareServiceImplTest {
|
||||
param.setIndicatorCodes(Collections.singletonList("V_RMS"));
|
||||
SteadyChecksquareQueryVO result = new SteadyChecksquareQueryVO();
|
||||
result.setLineId("line-001");
|
||||
result.setLineName("进线一");
|
||||
result.setLineName("杩涚嚎涓€");
|
||||
result.setTimeStart("2026-05-01 00:00:00");
|
||||
result.setTimeEnd("2026-05-01 00:01:00");
|
||||
result.setIntervalMinutes(1);
|
||||
@@ -712,3 +919,5 @@ class SteadyChecksquareServiceImplTest {
|
||||
return new SteadyChecksquareHarmonicParityRuleVO();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>steady-DataView</module>
|
||||
<module>steady-dataView</module>
|
||||
<module>check-square</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>steady-DataView</artifactId>
|
||||
<artifactId>steady-dataView</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package com.njcn.gather.steady.checksquare.pojo.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 新增数据校验结果。
|
||||
*/
|
||||
@Data
|
||||
@ApiModel("新增数据校验结果")
|
||||
public class SteadyChecksquareCreateVO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ApiModelProperty("任务 ID")
|
||||
private String taskId;
|
||||
|
||||
@ApiModelProperty("任务编号")
|
||||
private String taskNo;
|
||||
|
||||
@ApiModelProperty("监测点 ID")
|
||||
private String lineId;
|
||||
|
||||
@ApiModelProperty("监测点名称")
|
||||
private String lineName;
|
||||
|
||||
@ApiModelProperty("开始时间")
|
||||
private String timeStart;
|
||||
|
||||
@ApiModelProperty("结束时间")
|
||||
private String timeEnd;
|
||||
|
||||
@ApiModelProperty("统计间隔,单位分钟")
|
||||
private Integer intervalMinutes;
|
||||
|
||||
@ApiModelProperty("检测项数量")
|
||||
private Integer itemCount;
|
||||
|
||||
@ApiModelProperty("异常检测项数量")
|
||||
private Integer abnormalItemCount;
|
||||
}
|
||||
@@ -1,522 +0,0 @@
|
||||
# 数据校验 API 调试文档
|
||||
|
||||
## 1. 基础信息
|
||||
|
||||
- 模块:`steady/steady-DataView`
|
||||
- 控制器:`com.njcn.gather.steady.checksquare.controller.SteadyChecksquareController`
|
||||
- 接口前缀:`/steady/data-view/checksquare`
|
||||
- 本地默认地址:`http://localhost:18192`
|
||||
- Content-Type:`application/json`
|
||||
- 认证:除登录和 Swagger 资源外,请求需要携带登录后的 `Authorization` 请求头。
|
||||
- 数据库结果表:`steady_checksquare_task`、`steady_checksquare_item`、`steady_checksquare_stat_summary`、`steady_checksquare_detail`
|
||||
|
||||
通用请求头:
|
||||
|
||||
```http
|
||||
Authorization: Bearer <token>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
通用返回结构:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
> 实际 `code`、`message` 字段以项目公共 `HttpResult` 封装为准,下面示例重点展示 `data` 内容。
|
||||
|
||||
## 2. 查询数据校验历史记录
|
||||
|
||||
- 方法:`POST`
|
||||
- 路径:`/steady/data-view/checksquare/query`
|
||||
- 返回:`HttpResult<Page<SteadyChecksquareTaskVO>>`
|
||||
- 说明:分页查询已落库的数据校验任务,按创建时间倒序返回。
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"pageNum": 1,
|
||||
"pageSize": 10,
|
||||
"lineId": "line-001",
|
||||
"indicatorCode": "V_RMS",
|
||||
"timeStart": "2026-05-01 00:00:00",
|
||||
"timeEnd": "2026-05-01 23:59:59",
|
||||
"hasAbnormal": true
|
||||
}
|
||||
```
|
||||
|
||||
请求字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `pageNum` | 否 | 当前页码,继承自 `BaseParam` |
|
||||
| `pageSize` | 否 | 每页数量,继承自 `BaseParam` |
|
||||
| `lineId` | 否 | 监测点 ID,精确匹配 |
|
||||
| `indicatorCode` | 否 | 指标编码,按任务指标集合匹配 |
|
||||
| `timeStart` | 否 | 检测开始时间下限,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
| `timeEnd` | 否 | 检测结束时间上限,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
| `hasAbnormal` | 否 | 是否只查询存在异常项的任务;`true` 表示 `abnormalItemCount > 0` |
|
||||
|
||||
返回示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"records": [
|
||||
{
|
||||
"taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001",
|
||||
"taskNo": "CS202606101630001",
|
||||
"lineId": "line-001",
|
||||
"lineName": "进线一",
|
||||
"timeStart": "2026-05-01 00:00:00",
|
||||
"timeEnd": "2026-05-01 23:59:59",
|
||||
"intervalMinutes": 1,
|
||||
"taskStatus": "SUCCESS",
|
||||
"itemCount": 2,
|
||||
"abnormalItemCount": 1,
|
||||
"minDataIntegrity": 0.999306,
|
||||
"createTime": "2026-06-10 16:30:00"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"size": 10,
|
||||
"current": 1,
|
||||
"pages": 1
|
||||
}
|
||||
```
|
||||
|
||||
cURL 示例:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:18192/steady/data-view/checksquare/query" \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"pageNum":1,"pageSize":10,"lineId":"line-001","indicatorCode":"V_RMS","hasAbnormal":true}'
|
||||
```
|
||||
|
||||
## 3. 新增数据校验记录
|
||||
|
||||
- 方法:`POST`
|
||||
- 路径:`/steady/data-view/checksquare/create`
|
||||
- 返回:`HttpResult<SteadyChecksquareCreateVO>`
|
||||
- 说明:按监测点、指标和时间范围实时查询 InfluxDB,执行缺数校验、指标值大小关系校验、谐波奇偶关系校验,并将任务、检测项、统计摘要和明细写入 MySQL。
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"lineId": "line-001",
|
||||
"indicatorCodes": ["V_RMS", "FREQ", "V_HARMONIC"],
|
||||
"timeStart": "2026-05-01 00:00:00",
|
||||
"timeEnd": "2026-05-01 23:59:59"
|
||||
}
|
||||
```
|
||||
|
||||
请求字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `lineId` | 是 | 监测点 ID,需要能在台账中找到且处于可用状态 |
|
||||
| `indicatorCodes` | 是 | 指标编码列表,来自 `/steady/data-view/indicator-tree` |
|
||||
| `timeStart` | 是 | 检测开始时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
| `timeEnd` | 是 | 检测结束时间,格式 `yyyy-MM-dd HH:mm:ss` |
|
||||
|
||||
返回示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001",
|
||||
"taskNo": "CS202606101630001",
|
||||
"lineId": "line-001",
|
||||
"lineName": "进线一",
|
||||
"timeStart": "2026-05-01 00:00:00",
|
||||
"timeEnd": "2026-05-01 23:59:59",
|
||||
"intervalMinutes": 1,
|
||||
"itemCount": 3,
|
||||
"abnormalItemCount": 1
|
||||
}
|
||||
```
|
||||
|
||||
cURL 示例:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:18192/steady/data-view/checksquare/create" \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"lineId":"line-001","indicatorCodes":["V_RMS","FREQ","V_HARMONIC"],"timeStart":"2026-05-01 00:00:00","timeEnd":"2026-05-01 23:59:59"}'
|
||||
```
|
||||
|
||||
调试建议:
|
||||
|
||||
- 新增接口会实际写入 MySQL,重复调用会生成新的任务记录。
|
||||
- 时间范围越大、指标越多,InfluxDB 查询和明细落库耗时越高。
|
||||
- 谐波类指标固定按 2-50 次聚合检测,不需要在请求体传 `harmonicOrders`。
|
||||
|
||||
## 4. 查询数据校验任务详情
|
||||
|
||||
- 方法:`GET`
|
||||
- 路径:`/steady/data-view/checksquare/detail`
|
||||
- 返回:`HttpResult<SteadyChecksquareQueryVO>`
|
||||
- 说明:按任务 ID 查询任务基础信息、检测项列表和各检测项统计摘要。检测项的明细列表通过 `/item-detail` 按需查询。
|
||||
|
||||
请求参数:
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `taskId` | 是 | 数据校验任务 ID,即 `/query` 或 `/create` 返回的 `taskId` |
|
||||
|
||||
请求示例:
|
||||
|
||||
```http
|
||||
GET /steady/data-view/checksquare/detail?taskId=8f7a4d6d1f3145a88b6f9d7a8e6c1001
|
||||
```
|
||||
|
||||
返回示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"taskId": "8f7a4d6d1f3145a88b6f9d7a8e6c1001",
|
||||
"taskNo": "CS202606101630001",
|
||||
"lineId": "line-001",
|
||||
"lineName": "进线一",
|
||||
"timeStart": "2026-05-01 00:00:00",
|
||||
"timeEnd": "2026-05-01 23:59:59",
|
||||
"intervalMinutes": 1,
|
||||
"items": [
|
||||
{
|
||||
"itemId": "0f4b6d6c3e9d400a902a2df101d10001",
|
||||
"itemKey": "line-001|V_RMS",
|
||||
"indicatorCode": "V_RMS",
|
||||
"indicatorName": "相电压有效值",
|
||||
"harmonicOrder": null,
|
||||
"intervalMinutes": 1,
|
||||
"hasData": true,
|
||||
"expectedPointCount": 5760,
|
||||
"actualPointCount": 5756,
|
||||
"missingPointCount": 4,
|
||||
"dataIntegrity": 0.999306,
|
||||
"dataIntegrityText": "99.93%",
|
||||
"abnormal": true,
|
||||
"abnormalPointCount": 2,
|
||||
"harmonicParityAbnormal": false,
|
||||
"harmonicParityAbnormalPointCount": 0,
|
||||
"statSummaries": [
|
||||
{
|
||||
"statType": "AVG",
|
||||
"supported": true,
|
||||
"hasData": true,
|
||||
"expectedPointCount": 1440,
|
||||
"actualPointCount": 1439,
|
||||
"missingPointCount": 1,
|
||||
"dataIntegrity": 0.999306,
|
||||
"dataIntegrityText": "99.93%",
|
||||
}
|
||||
],
|
||||
"statDetails": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
cURL 示例:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:18192/steady/data-view/checksquare/detail?taskId=8f7a4d6d1f3145a88b6f9d7a8e6c1001" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
## 5. 查询检测项明细
|
||||
|
||||
- 方法:`GET`
|
||||
- 路径:`/steady/data-view/checksquare/item-detail`
|
||||
- 返回:`HttpResult<SteadyChecksquareItemDetailVO>`
|
||||
- 说明:按检测项 ID、明细类型查询缺数连续区间、指标值大小关系异常点或谐波奇偶关系异常点。`pageNum` 和 `pageSize` 未同时传入时保持全量返回;两者同时传入且均大于 0 时返回当前页明细,并在结果中带回分页元数据。
|
||||
|
||||
请求参数:
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `itemId` | 是 | 检测项 ID,即任务详情中每个 `items[].itemId` |
|
||||
| `detailType` | 是 | 明细类型:`SEGMENT`、`VALUE_ORDER`、`HARMONIC_PARITY` |
|
||||
| `statType` | 否 | 统计类型:`AVG`、`MAX`、`MIN`、`CP95`;查询缺数区间时建议传入 |
|
||||
| `pageNum` | 否 | 明细分页页码;与 `pageSize` 同时传入且大于 0 时启用分页 |
|
||||
| `pageSize` | 否 | 明细分页条数;与 `pageNum` 同时传入且大于 0 时启用分页 |
|
||||
|
||||
### 5.1 查询缺数连续区间
|
||||
|
||||
请求示例:
|
||||
|
||||
```http
|
||||
GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=SEGMENT&statType=AVG
|
||||
```
|
||||
|
||||
返回示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"itemId": "0f4b6d6c3e9d400a902a2df101d10001",
|
||||
"detailType": "SEGMENT",
|
||||
"statType": "AVG",
|
||||
"pageNum": null,
|
||||
"pageSize": null,
|
||||
"total": null,
|
||||
"segments": [
|
||||
{
|
||||
"startTime": "2026-05-01 00:00:00",
|
||||
"endTime": "2026-05-01 00:09:00",
|
||||
"status": "NORMAL",
|
||||
"harmonicOrder": null,
|
||||
"missingPointCount": 0,
|
||||
"durationMinutes": 10
|
||||
},
|
||||
{
|
||||
"startTime": "2026-05-01 00:10:00",
|
||||
"endTime": "2026-05-01 00:10:00",
|
||||
"status": "MISSING",
|
||||
"harmonicOrder": null,
|
||||
"missingPointCount": 1,
|
||||
"durationMinutes": 1
|
||||
}
|
||||
],
|
||||
"valueOrderDetails": [],
|
||||
"harmonicParityDetails": []
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 查询指标值大小关系异常明细
|
||||
|
||||
请求示例:
|
||||
|
||||
```http
|
||||
GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER&pageNum=1&pageSize=20
|
||||
```
|
||||
|
||||
返回示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"itemId": "0f4b6d6c3e9d400a902a2df101d10001",
|
||||
"detailType": "VALUE_ORDER",
|
||||
"statType": null,
|
||||
"pageNum": 1,
|
||||
"pageSize": 20,
|
||||
"total": 1,
|
||||
"segments": [],
|
||||
"valueOrderDetails": [
|
||||
{
|
||||
"time": "2026-05-01 00:10:00",
|
||||
"phase": "A",
|
||||
"harmonicOrder": null,
|
||||
"maxValue": 219.8,
|
||||
"minValue": 218.1,
|
||||
"avgValue": 219.0,
|
||||
"cp95Value": 219.8
|
||||
}
|
||||
],
|
||||
"harmonicParityDetails": []
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 查询谐波奇偶关系异常明细
|
||||
|
||||
请求示例:
|
||||
|
||||
```http
|
||||
GET /steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10002&detailType=HARMONIC_PARITY&pageNum=1&pageSize=20
|
||||
```
|
||||
|
||||
返回示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"itemId": "0f4b6d6c3e9d400a902a2df101d10002",
|
||||
"detailType": "HARMONIC_PARITY",
|
||||
"statType": null,
|
||||
"pageNum": 1,
|
||||
"pageSize": 20,
|
||||
"total": 1,
|
||||
"segments": [],
|
||||
"valueOrderDetails": [],
|
||||
"harmonicParityDetails": [
|
||||
{
|
||||
"time": "2026-05-01 00:10:00",
|
||||
"phase": "A",
|
||||
"statType": "AVG",
|
||||
"evenHarmonicOrder": 4,
|
||||
"evenValue": 0.32,
|
||||
"oddHarmonicOrders": [3, 5],
|
||||
"oddValues": [0.08, 0.09],
|
||||
"oddMedianValue": 0.085,
|
||||
"thresholdMultiplier": 3.0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
cURL 示例:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:18192/steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
分页 cURL 示例:
|
||||
|
||||
```bash
|
||||
curl -X GET "http://localhost:18192/steady/data-view/checksquare/item-detail?itemId=0f4b6d6c3e9d400a902a2df101d10001&detailType=VALUE_ORDER&pageNum=1&pageSize=20" \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
## 6. 删除数据校验任务
|
||||
|
||||
- 方法:`POST`
|
||||
- 路径:`/steady/data-view/checksquare/delete`
|
||||
- 返回:`HttpResult<Boolean>`
|
||||
- 说明:按任务 ID 批量逻辑删除数据校验任务,并同步将任务下检测项置为删除态。删除后历史查询不再返回该任务,详情和检测项明细会按不存在处理。
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
[
|
||||
"8f7a4d6d1f3145a88b6f9d7a8e6c1001"
|
||||
]
|
||||
```
|
||||
|
||||
请求字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 请求体 | 是 | 数据校验任务 ID 数组 |
|
||||
|
||||
返回示例:
|
||||
|
||||
```json
|
||||
true
|
||||
```
|
||||
|
||||
cURL 示例:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:18192/steady/data-view/checksquare/delete" \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '["8f7a4d6d1f3145a88b6f9d7a8e6c1001"]'
|
||||
```
|
||||
|
||||
## 7. 校验规则说明
|
||||
|
||||
### 7.1 缺数校验
|
||||
|
||||
- 按检测项支持的统计类型分别查询实际存在的时间点。
|
||||
- 返回期望点数、实际点数、缺失点数、数据完整性和最大连续缺失时长。
|
||||
- `SEGMENT` 明细按连续区间返回,`status` 可取 `NORMAL`、`MISSING`。
|
||||
- `FLUC`、`PST` 固定按 10 分钟间隔校验,`PLT` 固定按 120 分钟间隔校验,其余指标按监测点统计间隔校验。
|
||||
|
||||
### 7.2 指标值大小关系校验
|
||||
|
||||
- 同一时间点、同一指标、同一相别需要满足 `MAX >= CP95 >= AVG >= MIN`。
|
||||
- 缺少 `MAX`、`CP95`、`AVG`、`MIN` 任一值的时间点不计入大小关系异常,由缺数校验体现。
|
||||
- 异常点写入 `VALUE_ORDER` 明细,前端可通过 `item-detail` 拉取后弹窗展示。
|
||||
|
||||
### 7.3 谐波奇偶关系校验
|
||||
|
||||
- 谐波指标固定聚合 2-50 次检测。
|
||||
- 偶次谐波值需要大于 `0.1` 才继续参与奇偶关系检测。
|
||||
- 异常点写入 `HARMONIC_PARITY` 明细,包含偶次谐波值、参与比较的奇次谐波值、中位数和阈值倍数。
|
||||
|
||||
## 8. 字段说明
|
||||
|
||||
### 8.1 任务字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `taskId` | 数据校验任务 ID |
|
||||
| `taskNo` | 数据校验任务编号 |
|
||||
| `lineId` | 监测点 ID |
|
||||
| `lineName` | 监测点名称 |
|
||||
| `timeStart` | 检测开始时间 |
|
||||
| `timeEnd` | 检测结束时间 |
|
||||
| `intervalMinutes` | 监测点统计间隔,单位分钟 |
|
||||
| `taskStatus` | 任务状态,当前成功任务为 `SUCCESS` |
|
||||
| `itemCount` | 检测项数量 |
|
||||
| `abnormalItemCount` | 存在异常的检测项数量 |
|
||||
| `minDataIntegrity` | 检测项中的最低数据完整性 |
|
||||
| `createTime` | 任务创建时间 |
|
||||
|
||||
### 8.2 检测项字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `itemId` | 检测项 ID |
|
||||
| `itemKey` | 检测项唯一键 |
|
||||
| `indicatorCode` | 指标编码 |
|
||||
| `indicatorName` | 指标名称 |
|
||||
| `harmonicOrder` | 谐波次数;聚合检测项为空 |
|
||||
| `hasData` | 当前检测项是否存在任意数据 |
|
||||
| `expectedPointCount` | 期望点数 |
|
||||
| `actualPointCount` | 实际点数 |
|
||||
| `missingPointCount` | 缺失点数 |
|
||||
| `dataIntegrity` | 数据完整性数值 |
|
||||
| `dataIntegrityText` | 数据完整性文本 |
|
||||
| `abnormal` | 指标值大小关系是否异常 |
|
||||
| `abnormalPointCount` | 指标值大小关系异常点数 |
|
||||
| `harmonicParityAbnormal` | 谐波奇偶关系是否异常 |
|
||||
| `harmonicParityAbnormalPointCount` | 谐波奇偶关系异常点数 |
|
||||
| `statSummaries` | 各统计类型缺数摘要 |
|
||||
| `statDetails` | 兼容字段;明细建议通过 `/item-detail` 按需查询 |
|
||||
|
||||
### 8.3 检测项明细字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `itemId` | 检测项 ID |
|
||||
| `detailType` | 明细类型:`SEGMENT`、`VALUE_ORDER`、`HARMONIC_PARITY` |
|
||||
| `statType` | 统计类型;未传入或当前明细类型不按统计类型过滤时为空 |
|
||||
| `pageNum` | 当前页码;未启用分页时为空 |
|
||||
| `pageSize` | 每页条数;未启用分页时为空 |
|
||||
| `total` | 当前查询条件下的总明细数;未启用分页时为空 |
|
||||
| `segments` | 缺数连续区间,仅 `SEGMENT` 查询返回数据 |
|
||||
| `valueOrderDetails` | 指标值大小关系异常点,仅 `VALUE_ORDER` 查询返回数据 |
|
||||
| `harmonicParityDetails` | 谐波奇偶关系异常点,仅 `HARMONIC_PARITY` 查询返回数据 |
|
||||
|
||||
## 9. 常见错误场景
|
||||
|
||||
| 场景 | 后端提示 |
|
||||
| --- | --- |
|
||||
| 新增时 `lineId` 为空 | `监测点 ID 不能为空` |
|
||||
| 新增时 `indicatorCodes` 为空 | `指标不能为空` |
|
||||
| 新增时开始时间为空 | `开始时间不能为空` |
|
||||
| 新增时结束时间为空 | `结束时间不能为空` |
|
||||
| 开始时间大于结束时间 | `开始时间不能大于结束时间` |
|
||||
| 时间格式不正确 | `时间格式不正确,仅支持 yyyy-MM-dd HH:mm:ss` |
|
||||
| 监测点不存在或不可用 | `监测点不存在或不可用` |
|
||||
| 指标编码不支持 | `稳态指标不支持:xxx` |
|
||||
| 任务不存在或已删除 | `数据校验任务不存在` |
|
||||
| 检测项不存在或已删除 | `数据校验检测项不存在` |
|
||||
| 删除时任务 ID 为空 | `数据校验任务 ID 不能为空` |
|
||||
| 删除时任务不存在或已删除 | `数据校验任务不存在或已删除` |
|
||||
| 明细类型为空 | `明细类型不能为空` |
|
||||
| 明细类型不支持 | `明细类型不支持:xxx` |
|
||||
|
||||
## 10. 调试流程建议
|
||||
|
||||
1. 调用 `/steady/data-view/ledger-tree` 获取可用监测点,取叶子节点 `lineId`。
|
||||
2. 调用 `/steady/data-view/indicator-tree` 获取可用指标编码。
|
||||
3. 调用 `/steady/data-view/checksquare/create` 新增校验任务。
|
||||
4. 使用返回的 `taskId` 调用 `/steady/data-view/checksquare/detail` 查看任务详情和检测项列表。
|
||||
5. 使用 `items[].itemId` 调用 `/steady/data-view/checksquare/item-detail` 查看缺数区间或异常点明细。
|
||||
6. 调用 `/steady/data-view/checksquare/query` 验证任务已落库,并按监测点、指标、时间和异常状态筛选历史记录。
|
||||
7. 调用 `/steady/data-view/checksquare/delete` 删除任务后,再调用 `/query`、`/detail` 验证任务已不可见。
|
||||
|
||||
## 11. 环境依赖
|
||||
|
||||
- MySQL:需要已初始化 4 张 `steady_checksquare_*` 结果表。
|
||||
- InfluxDB:新增校验任务时需要可访问 `steady.influxdb` 配置的时序库。
|
||||
- 台账:新增校验任务时需要 `lineId` 能在台账中解析到监测点路径和统计间隔。
|
||||
- 指标目录:`indicatorCodes` 必须来自后端趋势指标目录。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user