最近新增的工作
This commit is contained in:
@@ -14,10 +14,15 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓
|
||||
|
||||
- `entrance`
|
||||
- `system`
|
||||
- `systemmonitor`
|
||||
- `user`
|
||||
- `detection`
|
||||
- `tools`
|
||||
|
||||
其中 `systemmonitor` 当前包含:
|
||||
|
||||
- `disk-monitor`
|
||||
|
||||
其中 `tools` 当前包含:
|
||||
|
||||
- `activate-tool`
|
||||
@@ -30,7 +35,7 @@ CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓
|
||||
|
||||
- `entrance/src/main/java/com/njcn/gather/EntranceApplication.java`
|
||||
|
||||
`entrance` 模块聚合了 `system`、`user`、`detection`、`activate-tool`、`wave-tool`、`mms-mapping`,是当前运行时主入口。
|
||||
`entrance` 模块聚合了 `system`、`disk-monitor`、`user`、`detection`、`activate-tool`、`wave-tool`、`mms-mapping`,是当前运行时主入口。
|
||||
|
||||
## 技术基线
|
||||
|
||||
@@ -71,6 +76,8 @@ P0 已补齐基线文档,建议按以下顺序阅读:
|
||||
- 负责认证、用户、角色、菜单资源相关能力
|
||||
- `system`
|
||||
- 负责字典、日志、系统配置、注册资源相关能力
|
||||
- `systemmonitor/disk-monitor`
|
||||
- 负责磁盘监控相关能力的独立扩展实现
|
||||
- `detection`
|
||||
- 当前以通信基础设施为主,包含 WebSocket / Netty 相关组件
|
||||
- `tools/activate-tool`
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
<artifactId>system</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njcn.gather</groupId>
|
||||
<artifactId>disk-monitor</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.njcn.gather</groupId>
|
||||
<artifactId>detection</artifactId>
|
||||
|
||||
1
pom.xml
1
pom.xml
@@ -12,6 +12,7 @@
|
||||
<modules>
|
||||
<module>entrance</module>
|
||||
<module>system</module>
|
||||
<module>systemmonitor</module>
|
||||
<module>user</module>
|
||||
<module>detection</module>
|
||||
<module>tools</module>
|
||||
|
||||
29
systemmonitor/README.md
Normal file
29
systemmonitor/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# System Monitor 模块说明
|
||||
|
||||
## 当前状态
|
||||
|
||||
`systemmonitor` 当前作为系统监控能力聚合模块使用。
|
||||
|
||||
当前真实保留的子模块有:
|
||||
|
||||
- `disk-monitor`
|
||||
|
||||
## 当前结构
|
||||
|
||||
```text
|
||||
systemmonitor/
|
||||
└── disk-monitor/
|
||||
```
|
||||
|
||||
## disk-monitor 的职责
|
||||
|
||||
`disk-monitor` 预留用于承载磁盘监控相关能力,后续可在该模块内继续补充:
|
||||
|
||||
- 磁盘监控配置管理
|
||||
- 磁盘巡检任务
|
||||
- 磁盘容量计算与阈值判定
|
||||
- 磁盘预警与告警留痕
|
||||
|
||||
## 模块定位
|
||||
|
||||
当前将磁盘监控拆分到 `systemmonitor/disk-monitor`,目的是避免将系统监控扩展逻辑直接混入 `system` 公共基础模块,便于后续继续扩展 CPU、内存或进程级监控能力。
|
||||
11
systemmonitor/disk-monitor/README.md
Normal file
11
systemmonitor/disk-monitor/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# disk-monitor
|
||||
|
||||
`disk-monitor` 模块用于承载磁盘监控相关代码。
|
||||
|
||||
当前这一版只完成模块骨架接入,后续可以在该模块内继续补充:
|
||||
|
||||
- Controller
|
||||
- Service
|
||||
- 定时巡检任务
|
||||
- 磁盘容量采集与阈值判断
|
||||
- 日志留痕与 SQL 脚本
|
||||
37
systemmonitor/disk-monitor/pom.xml
Normal file
37
systemmonitor/disk-monitor/pom.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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>systemmonitor</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>disk-monitor</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>disk-monitor</name>
|
||||
|
||||
<dependencies>
|
||||
<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>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,250 @@
|
||||
package com.njcn.gather.systemmonitor.disk.component;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.constant.DiskMonitorConstant;
|
||||
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorNotifyLogMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyHttpItem;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyPathItem;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorJob;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorNotifyLog;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorResult;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorTarget;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 磁盘监控通知发送组件。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DiskMonitorNotificationComponent {
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final TypeReference<List<DiskMonitorNotifyPathItem>> PATH_LIST_TYPE = new TypeReference<List<DiskMonitorNotifyPathItem>>() {
|
||||
};
|
||||
private static final TypeReference<List<DiskMonitorNotifyHttpItem>> HTTP_LIST_TYPE = new TypeReference<List<DiskMonitorNotifyHttpItem>>() {
|
||||
};
|
||||
|
||||
private final DiskMonitorNotifyLogMapper notifyLogMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public void sendNotifications(DiskMonitorJob job, DiskMonitorTarget target, DiskMonitorResult result,
|
||||
BigDecimal usedPercent, String currentStatus, String notifyReason,
|
||||
String notifyLevel, LocalDateTime scanTime, String message) {
|
||||
List<DiskMonitorNotifyPathItem> pathItems;
|
||||
List<DiskMonitorNotifyHttpItem> httpItems;
|
||||
try {
|
||||
pathItems = parsePathItems(target.getNotifyPathListJson());
|
||||
httpItems = parseHttpItems(target.getNotifyHttpListJson());
|
||||
} catch (Exception exception) {
|
||||
insertFailedNotifyLog(job, result, target, notifyLevel, "N/A", "通知配置解析失败: " + exception.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
boolean pathConfigured = isEnabled(target.getNotifyPathEnabled()) && pathItems.stream().anyMatch(item -> Boolean.TRUE.equals(item.getEnabled()));
|
||||
boolean httpConfigured = isEnabled(target.getNotifyHttpEnabled()) && httpItems.stream().anyMatch(item -> Boolean.TRUE.equals(item.getEnabled()));
|
||||
if (!pathConfigured && !httpConfigured) {
|
||||
insertFailedNotifyLog(job, result, target, notifyLevel, "N/A", "当前盘符未配置可用通知通道");
|
||||
return;
|
||||
}
|
||||
|
||||
String notifyTitle = buildNotifyTitle(target.getDriveLetter(), notifyLevel, usedPercent);
|
||||
String notifyContent = buildNotifyContent(job, target, usedPercent, currentStatus, notifyLevel, scanTime, message);
|
||||
|
||||
if (pathConfigured) {
|
||||
for (DiskMonitorNotifyPathItem item : pathItems) {
|
||||
if (!Boolean.TRUE.equals(item.getEnabled())) {
|
||||
continue;
|
||||
}
|
||||
sendPathNotification(job, target, result, notifyReason, notifyLevel, notifyTitle, notifyContent, item,
|
||||
usedPercent, currentStatus, scanTime, message);
|
||||
}
|
||||
}
|
||||
if (httpConfigured) {
|
||||
for (DiskMonitorNotifyHttpItem item : httpItems) {
|
||||
if (!Boolean.TRUE.equals(item.getEnabled())) {
|
||||
continue;
|
||||
}
|
||||
sendHttpNotification(job, target, result, notifyReason, notifyLevel, notifyTitle, notifyContent, item,
|
||||
usedPercent, currentStatus, scanTime, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPathNotification(DiskMonitorJob job, DiskMonitorTarget target, DiskMonitorResult result,
|
||||
String notifyReason, String notifyLevel, String notifyTitle, String notifyContent,
|
||||
DiskMonitorNotifyPathItem item, BigDecimal usedPercent, String currentStatus,
|
||||
LocalDateTime scanTime, String message) {
|
||||
DiskMonitorNotifyLog notifyLog = createNotifyLog(job, result, target, notifyLevel, notifyTitle, notifyContent,
|
||||
DiskMonitorConstant.CHANNEL_TYPE_PATH, item.getPath());
|
||||
try {
|
||||
Path directoryPath = Paths.get(item.getPath());
|
||||
Files.createDirectories(directoryPath);
|
||||
String fileName = String.format("disk-monitor-%s-%s-%s.json",
|
||||
job.getJobNo(), target.getDriveLetter().replace(":", ""),
|
||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
|
||||
Path filePath = directoryPath.resolve(fileName);
|
||||
Map<String, Object> payload = buildNotifyPayload(job, target, usedPercent, currentStatus, notifyReason, notifyLevel, scanTime, message);
|
||||
Files.write(filePath, objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(payload));
|
||||
notifyLog.setSendStatus(DiskMonitorConstant.SEND_STATUS_SUCCESS);
|
||||
notifyLog.setResponseMessage("写入成功:" + filePath.toString());
|
||||
} catch (Exception exception) {
|
||||
log.error("磁盘监控路径通知发送失败,driveLetter={}, path={}", target.getDriveLetter(), item.getPath(), exception);
|
||||
notifyLog.setSendStatus(DiskMonitorConstant.SEND_STATUS_FAILED);
|
||||
notifyLog.setResponseMessage(exception.getMessage());
|
||||
}
|
||||
notifyLogMapper.insert(notifyLog);
|
||||
}
|
||||
|
||||
private void sendHttpNotification(DiskMonitorJob job, DiskMonitorTarget target, DiskMonitorResult result,
|
||||
String notifyReason, String notifyLevel, String notifyTitle, String notifyContent,
|
||||
DiskMonitorNotifyHttpItem item, BigDecimal usedPercent, String currentStatus,
|
||||
LocalDateTime scanTime, String message) {
|
||||
DiskMonitorNotifyLog notifyLog = createNotifyLog(job, result, target, notifyLevel, notifyTitle, notifyContent,
|
||||
DiskMonitorConstant.CHANNEL_TYPE_HTTP, item.getUrl());
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
Map<String, Object> payload = buildNotifyPayload(job, target, usedPercent, currentStatus, notifyReason, notifyLevel, scanTime, message);
|
||||
byte[] body = objectMapper.writeValueAsBytes(payload);
|
||||
URL url = new URL(item.getUrl());
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod(StrUtil.blankToDefault(item.getMethod(), DiskMonitorConstant.HTTP_METHOD_POST).trim().toUpperCase(Locale.ROOT));
|
||||
connection.setConnectTimeout(resolveTimeout(item.getTimeoutMs()));
|
||||
connection.setReadTimeout(resolveTimeout(item.getTimeoutMs()));
|
||||
connection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
|
||||
connection.setDoInput(true);
|
||||
if (!"GET".equalsIgnoreCase(connection.getRequestMethod())) {
|
||||
connection.setDoOutput(true);
|
||||
try (OutputStream outputStream = connection.getOutputStream()) {
|
||||
outputStream.write(body);
|
||||
}
|
||||
}
|
||||
int statusCode = connection.getResponseCode();
|
||||
notifyLog.setSendStatus(statusCode >= 200 && statusCode < 300
|
||||
? DiskMonitorConstant.SEND_STATUS_SUCCESS
|
||||
: DiskMonitorConstant.SEND_STATUS_FAILED);
|
||||
notifyLog.setResponseMessage(statusCode + " " + connection.getResponseMessage());
|
||||
} catch (Exception exception) {
|
||||
log.error("磁盘监控HTTP通知发送失败,driveLetter={}, url={}", target.getDriveLetter(), item.getUrl(), exception);
|
||||
notifyLog.setSendStatus(DiskMonitorConstant.SEND_STATUS_FAILED);
|
||||
notifyLog.setResponseMessage(exception.getMessage());
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
notifyLogMapper.insert(notifyLog);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildNotifyPayload(DiskMonitorJob job, DiskMonitorTarget target, BigDecimal usedPercent,
|
||||
String currentStatus, String notifyReason, String notifyLevel,
|
||||
LocalDateTime scanTime, String message) {
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("jobNo", job.getJobNo());
|
||||
payload.put("driveLetter", target.getDriveLetter());
|
||||
payload.put("currentStatus", currentStatus);
|
||||
payload.put("notifyReason", notifyReason);
|
||||
payload.put("notifyLevel", notifyLevel);
|
||||
payload.put("usedPercent", usedPercent);
|
||||
payload.put("warningUsagePercent", target.getWarningUsagePercent());
|
||||
payload.put("alarmUsagePercent", target.getAlarmUsagePercent());
|
||||
payload.put("scanTime", formatDateTime(scanTime));
|
||||
payload.put("message", message);
|
||||
return payload;
|
||||
}
|
||||
|
||||
private String buildNotifyTitle(String driveLetter, String notifyLevel, BigDecimal usedPercent) {
|
||||
return String.format("磁盘监控通知[%s] %s 使用率%s%%",
|
||||
notifyLevel, driveLetter, usedPercent == null ? "0.00" : usedPercent.toPlainString());
|
||||
}
|
||||
|
||||
private String buildNotifyContent(DiskMonitorJob job, DiskMonitorTarget target, BigDecimal usedPercent,
|
||||
String currentStatus, String notifyLevel, LocalDateTime scanTime, String message) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("任务编号:").append(job.getJobNo())
|
||||
.append(";盘符:").append(target.getDriveLetter())
|
||||
.append(";当前状态:").append(currentStatus)
|
||||
.append(";通知级别:").append(notifyLevel)
|
||||
.append(";使用率:").append(usedPercent == null ? "0.00" : usedPercent.toPlainString()).append("%")
|
||||
.append(";预警阈值:").append(target.getWarningUsagePercent()).append("%")
|
||||
.append(";告警阈值:").append(target.getAlarmUsagePercent()).append("%")
|
||||
.append(";扫描时间:").append(formatDateTime(scanTime));
|
||||
if (StrUtil.isNotBlank(message)) {
|
||||
builder.append(";说明:").append(message);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private DiskMonitorNotifyLog createNotifyLog(DiskMonitorJob job, DiskMonitorResult result, DiskMonitorTarget target,
|
||||
String notifyLevel, String notifyTitle, String notifyContent,
|
||||
String channelType, String channelTarget) {
|
||||
DiskMonitorNotifyLog notifyLog = new DiskMonitorNotifyLog();
|
||||
notifyLog.setJobId(job.getId());
|
||||
notifyLog.setResultId(result.getId());
|
||||
notifyLog.setTargetId(target.getId());
|
||||
notifyLog.setDriveLetter(target.getDriveLetter());
|
||||
notifyLog.setNotifyLevel(notifyLevel);
|
||||
notifyLog.setChannelType(channelType);
|
||||
notifyLog.setChannelTarget(channelTarget);
|
||||
notifyLog.setNotifyTitle(notifyTitle);
|
||||
notifyLog.setNotifyContent(notifyContent);
|
||||
notifyLog.setSentAt(LocalDateTime.now());
|
||||
return notifyLog;
|
||||
}
|
||||
|
||||
private void insertFailedNotifyLog(DiskMonitorJob job, DiskMonitorResult result, DiskMonitorTarget target,
|
||||
String notifyLevel, String channelTarget, String responseMessage) {
|
||||
DiskMonitorNotifyLog notifyLog = createNotifyLog(job, result, target, notifyLevel,
|
||||
"磁盘监控通知失败", responseMessage, DiskMonitorConstant.CHANNEL_TYPE_HTTP, channelTarget);
|
||||
notifyLog.setSendStatus(DiskMonitorConstant.SEND_STATUS_FAILED);
|
||||
notifyLog.setResponseMessage(responseMessage);
|
||||
notifyLogMapper.insert(notifyLog);
|
||||
}
|
||||
|
||||
private List<DiskMonitorNotifyPathItem> parsePathItems(String json) throws Exception {
|
||||
return readJsonList(json, PATH_LIST_TYPE);
|
||||
}
|
||||
|
||||
private List<DiskMonitorNotifyHttpItem> parseHttpItems(String json) throws Exception {
|
||||
return readJsonList(json, HTTP_LIST_TYPE);
|
||||
}
|
||||
|
||||
private <T> List<T> readJsonList(String json, TypeReference<List<T>> typeReference) throws Exception {
|
||||
if (StrUtil.isBlank(json)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
List<T> result = objectMapper.readValue(json, typeReference);
|
||||
return result == null ? new ArrayList<>() : result;
|
||||
}
|
||||
|
||||
private boolean isEnabled(Integer value) {
|
||||
return value != null && value == 1;
|
||||
}
|
||||
|
||||
private int resolveTimeout(Integer timeoutMs) {
|
||||
return timeoutMs == null || timeoutMs <= 0 ? DiskMonitorConstant.DEFAULT_HTTP_TIMEOUT_MS : timeoutMs;
|
||||
}
|
||||
|
||||
private String formatDateTime(LocalDateTime dateTime) {
|
||||
return dateTime == null ? null : dateTime.format(DATE_TIME_FORMATTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemmonitor.disk.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 磁盘监控模块基础配置入口,后续磁盘监控相关 Bean 统一放在该模块下扩展。
|
||||
*/
|
||||
@Configuration
|
||||
public class DiskMonitorModuleConfig {
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.njcn.gather.systemmonitor.disk.constant;
|
||||
|
||||
/**
|
||||
* 磁盘监控模块常量。
|
||||
*/
|
||||
public interface DiskMonitorConstant {
|
||||
|
||||
String DEFAULT_POLICY_NAME = "默认磁盘监控策略";
|
||||
String DEFAULT_DAILY_RUN_TIME = "08:30:00";
|
||||
String WARNING_NOTIFY_MODE_STATUS_CHANGE = "STATUS_CHANGE";
|
||||
String ALARM_NOTIFY_MODE_EVERY_TIME = "EVERY_TIME";
|
||||
|
||||
String STATUS_UNKNOWN = "UNKNOWN";
|
||||
String STATUS_NORMAL = "NORMAL";
|
||||
String STATUS_WARNING = "WARNING";
|
||||
String STATUS_ALARM = "ALARM";
|
||||
|
||||
String JOB_SOURCE_APP_START = "APP_START";
|
||||
String JOB_SOURCE_DAILY_SCHEDULE = "DAILY_SCHEDULE";
|
||||
String JOB_SOURCE_MANUAL = "MANUAL";
|
||||
|
||||
String JOB_STATUS_RUNNING = "RUNNING";
|
||||
String JOB_STATUS_SUCCESS = "SUCCESS";
|
||||
String JOB_STATUS_PARTIAL_SUCCESS = "PARTIAL_SUCCESS";
|
||||
String JOB_STATUS_FAILED = "FAILED";
|
||||
|
||||
String NOTIFY_REASON_ALARM_EVERY_TIME = "ALARM_EVERY_TIME";
|
||||
String NOTIFY_REASON_STATUS_CHANGED = "STATUS_CHANGED";
|
||||
String NOTIFY_REASON_NO_NOTIFY = "NO_NOTIFY";
|
||||
|
||||
String NOTIFY_LEVEL_WARNING = "WARNING";
|
||||
String NOTIFY_LEVEL_ALARM = "ALARM";
|
||||
String NOTIFY_LEVEL_RECOVER = "RECOVER";
|
||||
|
||||
String CHANNEL_TYPE_PATH = "PATH";
|
||||
String CHANNEL_TYPE_HTTP = "HTTP";
|
||||
|
||||
String SEND_STATUS_SUCCESS = "SUCCESS";
|
||||
String SEND_STATUS_FAILED = "FAILED";
|
||||
|
||||
String HTTP_METHOD_POST = "POST";
|
||||
int DEFAULT_HTTP_TIMEOUT_MS = 5000;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.njcn.gather.systemmonitor.disk.constant;
|
||||
|
||||
/**
|
||||
* 磁盘监控参数校验提示。
|
||||
*/
|
||||
public interface DiskMonitorValidMessage {
|
||||
|
||||
String POLICY_NOT_NULL = "policy不能为空,请检查policy参数";
|
||||
String TARGETS_NOT_NULL = "targets不能为空,请检查targets参数";
|
||||
String DRIVE_LETTER_NOT_BLANK = "盘符不能为空,请检查driveLetter参数";
|
||||
String DAILY_RUN_TIME_NOT_BLANK = "每日统一执行时间不能为空,请检查dailyRunTime参数";
|
||||
String DAILY_RUN_TIME_FORMAT_ERROR = "每日统一执行时间格式错误,请使用HH:mm:ss";
|
||||
String WARNING_USAGE_PERCENT_NOT_NULL = "预警使用率不能为空,请检查warningUsagePercent参数";
|
||||
String ALARM_USAGE_PERCENT_NOT_NULL = "告警使用率不能为空,请检查alarmUsagePercent参数";
|
||||
String USAGE_PERCENT_FORMAT_ERROR = "阈值范围必须在1-100之间,请检查阈值参数";
|
||||
String JOB_SOURCE_NOT_BLANK = "任务来源不能为空,请检查jobSource参数";
|
||||
String JOB_ID_NOT_NULL = "jobId不能为空,请检查jobId参数";
|
||||
String POLICY_NAME_NOT_BLANK = "策略名称不能为空,请检查policyName参数";
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.njcn.gather.systemmonitor.disk.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
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.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.vo.DiskMonitorVO;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorJobService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 磁盘监控任务接口。
|
||||
*/
|
||||
@Validated
|
||||
@Slf4j
|
||||
@Api(tags = "磁盘监控任务")
|
||||
@RestController
|
||||
@RequestMapping("/disk-monitor")
|
||||
@RequiredArgsConstructor
|
||||
public class DiskMonitorJobController extends BaseController {
|
||||
|
||||
private final IDiskMonitorJobService diskMonitorJobService;
|
||||
|
||||
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||
@PostMapping("/job/run")
|
||||
@ApiOperation("手动执行磁盘监控")
|
||||
@ApiImplicitParam(name = "param", value = "手动执行参数", required = true)
|
||||
public HttpResult<DiskMonitorVO.JobRunVO> run(@RequestBody @Valid DiskMonitorParam.JobRunParam param) {
|
||||
String methodDescribe = getMethodDescribe("run");
|
||||
LogUtil.njcnDebug(log, "{},执行磁盘监控任务,source={}", methodDescribe, param.getJobSource());
|
||||
return HttpResultUtil.assembleResult(CommonResponseEnum.SUCCESS.getCode(), diskMonitorJobService.runJob(param), "任务已启动");
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||
@PostMapping("/job/list")
|
||||
@ApiOperation("分页查询磁盘监控任务列表")
|
||||
@ApiImplicitParam(name = "param", value = "任务分页查询参数", required = true)
|
||||
public HttpResult<Page<DiskMonitorVO.JobListVO>> list(@RequestBody @Valid DiskMonitorParam.JobListParam param) {
|
||||
String methodDescribe = getMethodDescribe("list");
|
||||
LogUtil.njcnDebug(log, "{},分页查询磁盘监控任务列表", methodDescribe);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, diskMonitorJobService.listJobs(param), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||
@GetMapping("/job/{jobId}/detail")
|
||||
@ApiOperation("查询磁盘监控任务详情")
|
||||
@ApiImplicitParam(name = "jobId", value = "任务ID", required = true)
|
||||
public HttpResult<DiskMonitorVO.JobDetailVO> jobDetail(@PathVariable("jobId") Long jobId) {
|
||||
String methodDescribe = getMethodDescribe("jobDetail");
|
||||
LogUtil.njcnDebug(log, "{},查询磁盘监控任务详情,jobId={}", methodDescribe, jobId);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, diskMonitorJobService.getJobDetail(jobId), methodDescribe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.njcn.gather.systemmonitor.disk.controller;
|
||||
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
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.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorNotifyService;
|
||||
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.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 磁盘监控通知接口。
|
||||
*/
|
||||
@Validated
|
||||
@Slf4j
|
||||
@Api(tags = "磁盘监控通知")
|
||||
@RestController
|
||||
@RequestMapping("/disk-monitor")
|
||||
@RequiredArgsConstructor
|
||||
public class DiskMonitorNotifyController extends BaseController {
|
||||
|
||||
private final IDiskMonitorNotifyService diskMonitorNotifyService;
|
||||
|
||||
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||
@PostMapping("/notify/test")
|
||||
@ApiOperation("测试磁盘监控通知")
|
||||
@ApiImplicitParam(name = "param", value = "通知测试参数", required = true)
|
||||
public HttpResult<Boolean> testNotify(@RequestBody @Valid DiskMonitorParam.NotifyTestParam param) {
|
||||
String methodDescribe = getMethodDescribe("testNotify");
|
||||
LogUtil.njcnDebug(log, "{},测试磁盘监控通知,driveLetter={}", methodDescribe, param.getDriveLetter());
|
||||
boolean result = diskMonitorNotifyService.testNotify(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.njcn.gather.systemmonitor.disk.controller;
|
||||
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.constant.OperateType;
|
||||
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.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.vo.DiskMonitorVO;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorPolicyService;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import com.njcn.web.utils.HttpResultUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 磁盘监控配置接口。
|
||||
*/
|
||||
@Validated
|
||||
@Slf4j
|
||||
@Api(tags = "磁盘监控配置")
|
||||
@RestController
|
||||
@RequestMapping("/disk-monitor")
|
||||
@RequiredArgsConstructor
|
||||
public class DiskMonitorPolicyController extends BaseController {
|
||||
|
||||
private final IDiskMonitorPolicyService diskMonitorPolicyService;
|
||||
|
||||
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||
@GetMapping("/policy/detail")
|
||||
@ApiOperation("查询磁盘监控配置详情")
|
||||
public HttpResult<DiskMonitorVO.PolicyDetailVO> detail() {
|
||||
String methodDescribe = getMethodDescribe("detail");
|
||||
LogUtil.njcnDebug(log, "{},查询磁盘监控配置详情", methodDescribe);
|
||||
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, diskMonitorPolicyService.getPolicyDetail(), methodDescribe);
|
||||
}
|
||||
|
||||
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||
@PostMapping("/policy/save")
|
||||
@ApiOperation("保存磁盘监控配置")
|
||||
@ApiImplicitParam(name = "param", value = "磁盘监控配置", required = true)
|
||||
public HttpResult<Boolean> save(@RequestBody @Valid DiskMonitorParam.PolicySaveParam param) {
|
||||
String methodDescribe = getMethodDescribe("save");
|
||||
LogUtil.njcnDebug(log, "{},保存磁盘监控配置", methodDescribe);
|
||||
boolean result = diskMonitorPolicyService.savePolicy(param);
|
||||
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL, result, methodDescribe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.njcn.gather.systemmonitor.disk.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 磁盘监控模块业务响应码。
|
||||
*/
|
||||
@Getter
|
||||
public enum DiskMonitorResponseEnum {
|
||||
|
||||
POLICY_NOT_FOUND("A01060", "磁盘监控策略不存在"),
|
||||
TARGET_NOT_FOUND("A01061", "磁盘监控盘符配置不存在"),
|
||||
DRIVE_LETTER_REPEAT("A01062", "盘符重复,请检查driveLetter参数"),
|
||||
DRIVE_LETTER_FORMAT_ERROR("A01063", "盘符格式错误,请使用类似 C: 的格式"),
|
||||
DAILY_RUN_TIME_FORMAT_ERROR("A01064", "每日统一执行时间格式错误,请使用HH:mm:ss"),
|
||||
USAGE_PERCENT_INVALID("A01065", "告警使用率必须大于等于预警使用率"),
|
||||
NOTIFY_PATH_EMPTY("A01066", "路径通知目标不能为空"),
|
||||
NOTIFY_HTTP_EMPTY("A01067", "HTTP通知目标不能为空"),
|
||||
NOTIFY_PATH_VALUE_EMPTY("A01068", "通知路径不能为空"),
|
||||
NOTIFY_HTTP_URL_INVALID("A01069", "HTTP通知目标格式错误,请检查url参数"),
|
||||
NO_ENABLED_TARGET("A01070", "暂无启用的磁盘监控盘符配置"),
|
||||
JOB_NOT_FOUND("A01071", "监控任务不存在"),
|
||||
POLICY_SAVE_FAILED("A01072", "磁盘监控配置保存失败"),
|
||||
DRIVE_SCAN_FAILED("A01073", "磁盘扫描失败"),
|
||||
NOTIFY_TARGET_NOT_FOUND("A01074", "通知测试盘符不存在"),
|
||||
NOTIFY_CHANNEL_MISSING("A01075", "当前盘符未配置可用通知通道"),
|
||||
POLICY_MODE_INVALID("A01076", "通知模式配置非法"),
|
||||
JOB_SOURCE_INVALID("A01077", "任务来源非法");
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
DiskMonitorResponseEnum(String code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.njcn.gather.systemmonitor.disk.event;
|
||||
|
||||
/**
|
||||
* 磁盘监控策略变更事件。
|
||||
*/
|
||||
public class DiskMonitorPolicyChangedEvent {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemmonitor.disk.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorJob;
|
||||
|
||||
/**
|
||||
* 磁盘监控任务 Mapper。
|
||||
*/
|
||||
public interface DiskMonitorJobMapper extends MPJBaseMapper<DiskMonitorJob> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemmonitor.disk.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorNotifyLog;
|
||||
|
||||
/**
|
||||
* 磁盘监控通知日志 Mapper。
|
||||
*/
|
||||
public interface DiskMonitorNotifyLogMapper extends MPJBaseMapper<DiskMonitorNotifyLog> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemmonitor.disk.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorPolicy;
|
||||
|
||||
/**
|
||||
* 磁盘监控策略 Mapper。
|
||||
*/
|
||||
public interface DiskMonitorPolicyMapper extends MPJBaseMapper<DiskMonitorPolicy> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemmonitor.disk.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorResult;
|
||||
|
||||
/**
|
||||
* 磁盘监控结果 Mapper。
|
||||
*/
|
||||
public interface DiskMonitorResultMapper extends MPJBaseMapper<DiskMonitorResult> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.njcn.gather.systemmonitor.disk.mapper;
|
||||
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorTarget;
|
||||
|
||||
/**
|
||||
* 磁盘监控盘符配置 Mapper。
|
||||
*/
|
||||
public interface DiskMonitorTargetMapper extends MPJBaseMapper<DiskMonitorTarget> {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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.systemmonitor.disk.mapper.DiskMonitorJobMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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.systemmonitor.disk.mapper.DiskMonitorNotifyLogMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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.systemmonitor.disk.mapper.DiskMonitorPolicyMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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.systemmonitor.disk.mapper.DiskMonitorResultMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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.systemmonitor.disk.mapper.DiskMonitorTargetMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* HTTP 通知项。
|
||||
*/
|
||||
@Data
|
||||
public class DiskMonitorNotifyHttpItem {
|
||||
|
||||
@ApiModelProperty("回调地址")
|
||||
@NotBlank(message = "HTTP通知目标不能为空,请检查url参数")
|
||||
private String url;
|
||||
|
||||
@ApiModelProperty("名称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("请求方法")
|
||||
private String method;
|
||||
|
||||
@ApiModelProperty("超时时间")
|
||||
@Min(value = 1, message = "timeoutMs必须大于0")
|
||||
private Integer timeoutMs;
|
||||
|
||||
@ApiModelProperty("是否启用")
|
||||
private Boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 路径通知项。
|
||||
*/
|
||||
@Data
|
||||
public class DiskMonitorNotifyPathItem {
|
||||
|
||||
@ApiModelProperty("路径")
|
||||
@NotBlank(message = "通知路径不能为空,请检查path参数")
|
||||
private String path;
|
||||
|
||||
@ApiModelProperty("名称")
|
||||
private String name;
|
||||
|
||||
@ApiModelProperty("是否启用")
|
||||
private Boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.param;
|
||||
|
||||
import com.njcn.gather.systemmonitor.disk.constant.DiskMonitorValidMessage;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyHttpItem;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyPathItem;
|
||||
import com.njcn.web.pojo.param.BaseParam;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.Max;
|
||||
import javax.validation.constraints.Min;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 磁盘监控接口参数。
|
||||
*/
|
||||
public class DiskMonitorParam {
|
||||
|
||||
@Data
|
||||
public static class PolicySaveParam {
|
||||
|
||||
@ApiModelProperty("全局策略")
|
||||
@NotNull(message = DiskMonitorValidMessage.POLICY_NOT_NULL)
|
||||
@Valid
|
||||
private PolicyParam policy;
|
||||
|
||||
@ApiModelProperty("盘符配置")
|
||||
@NotNull(message = DiskMonitorValidMessage.TARGETS_NOT_NULL)
|
||||
@Valid
|
||||
private List<TargetParam> targets = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PolicyParam {
|
||||
|
||||
@ApiModelProperty("策略ID")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("策略名称")
|
||||
@NotBlank(message = DiskMonitorValidMessage.POLICY_NAME_NOT_BLANK)
|
||||
private String policyName;
|
||||
|
||||
@ApiModelProperty("是否启用监控")
|
||||
private Boolean monitorEnabled;
|
||||
|
||||
@ApiModelProperty("应用启动后立即执行一次")
|
||||
private Boolean runOnAppStart;
|
||||
|
||||
@ApiModelProperty("每日统一执行时间")
|
||||
@NotBlank(message = DiskMonitorValidMessage.DAILY_RUN_TIME_NOT_BLANK)
|
||||
private String dailyRunTime;
|
||||
|
||||
@ApiModelProperty("预警通知模式")
|
||||
private String warningNotifyMode;
|
||||
|
||||
@ApiModelProperty("告警通知模式")
|
||||
private String alarmNotifyMode;
|
||||
|
||||
@ApiModelProperty("备注")
|
||||
private String remark;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TargetParam {
|
||||
|
||||
@ApiModelProperty("盘符配置ID")
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("盘符")
|
||||
@NotBlank(message = DiskMonitorValidMessage.DRIVE_LETTER_NOT_BLANK)
|
||||
private String driveLetter;
|
||||
|
||||
@ApiModelProperty("是否监控")
|
||||
private Boolean monitorEnabled;
|
||||
|
||||
@ApiModelProperty("预警使用率")
|
||||
@NotNull(message = DiskMonitorValidMessage.WARNING_USAGE_PERCENT_NOT_NULL)
|
||||
@Min(value = 1, message = DiskMonitorValidMessage.USAGE_PERCENT_FORMAT_ERROR)
|
||||
@Max(value = 100, message = DiskMonitorValidMessage.USAGE_PERCENT_FORMAT_ERROR)
|
||||
private Integer warningUsagePercent;
|
||||
|
||||
@ApiModelProperty("告警使用率")
|
||||
@NotNull(message = DiskMonitorValidMessage.ALARM_USAGE_PERCENT_NOT_NULL)
|
||||
@Min(value = 1, message = DiskMonitorValidMessage.USAGE_PERCENT_FORMAT_ERROR)
|
||||
@Max(value = 100, message = DiskMonitorValidMessage.USAGE_PERCENT_FORMAT_ERROR)
|
||||
private Integer alarmUsagePercent;
|
||||
|
||||
@ApiModelProperty("是否启用路径通知")
|
||||
private Boolean notifyPathEnabled;
|
||||
|
||||
@ApiModelProperty("路径通知列表")
|
||||
@Valid
|
||||
private List<DiskMonitorNotifyPathItem> notifyPathList = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("是否启用HTTP通知")
|
||||
private Boolean notifyHttpEnabled;
|
||||
|
||||
@ApiModelProperty("HTTP通知列表")
|
||||
@Valid
|
||||
private List<DiskMonitorNotifyHttpItem> notifyHttpList = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty("备注")
|
||||
private String remark;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class JobRunParam {
|
||||
|
||||
@ApiModelProperty("任务来源")
|
||||
@NotBlank(message = DiskMonitorValidMessage.JOB_SOURCE_NOT_BLANK)
|
||||
private String jobSource;
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class JobListParam extends BaseParam {
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class NotifyTestParam {
|
||||
|
||||
@ApiModelProperty("盘符")
|
||||
@NotBlank(message = DiskMonitorValidMessage.DRIVE_LETTER_NOT_BLANK)
|
||||
private String driveLetter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 磁盘监控任务批次表。
|
||||
*/
|
||||
@Data
|
||||
@TableName("disk_monitor_job")
|
||||
public class DiskMonitorJob implements Serializable {
|
||||
private static final long serialVersionUID = 2956749403195165256L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("job_no")
|
||||
private String jobNo;
|
||||
|
||||
@TableField("job_source")
|
||||
private String jobSource;
|
||||
|
||||
@TableField("planned_time")
|
||||
private LocalDateTime plannedTime;
|
||||
|
||||
@TableField("started_at")
|
||||
private LocalDateTime startedAt;
|
||||
|
||||
@TableField("finished_at")
|
||||
private LocalDateTime finishedAt;
|
||||
|
||||
@TableField("job_status")
|
||||
private String jobStatus;
|
||||
|
||||
@TableField("target_count")
|
||||
private Integer targetCount;
|
||||
|
||||
@TableField("success_count")
|
||||
private Integer successCount;
|
||||
|
||||
@TableField("warning_count")
|
||||
private Integer warningCount;
|
||||
|
||||
@TableField("alarm_count")
|
||||
private Integer alarmCount;
|
||||
|
||||
@TableField("message")
|
||||
private String message;
|
||||
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 磁盘监控通知日志表。
|
||||
*/
|
||||
@Data
|
||||
@TableName("disk_monitor_notify_log")
|
||||
public class DiskMonitorNotifyLog implements Serializable {
|
||||
private static final long serialVersionUID = -8873612319853902451L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("job_id")
|
||||
private Long jobId;
|
||||
|
||||
@TableField("result_id")
|
||||
private Long resultId;
|
||||
|
||||
@TableField("target_id")
|
||||
private Long targetId;
|
||||
|
||||
@TableField("drive_letter")
|
||||
private String driveLetter;
|
||||
|
||||
@TableField("notify_level")
|
||||
private String notifyLevel;
|
||||
|
||||
@TableField("channel_type")
|
||||
private String channelType;
|
||||
|
||||
@TableField("channel_target")
|
||||
private String channelTarget;
|
||||
|
||||
@TableField("notify_title")
|
||||
private String notifyTitle;
|
||||
|
||||
@TableField("notify_content")
|
||||
private String notifyContent;
|
||||
|
||||
@TableField("send_status")
|
||||
private String sendStatus;
|
||||
|
||||
@TableField("response_message")
|
||||
private String responseMessage;
|
||||
|
||||
@TableField("sent_at")
|
||||
private LocalDateTime sentAt;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
|
||||
/**
|
||||
* 磁盘监控全局策略表。
|
||||
*/
|
||||
@Data
|
||||
@TableName("disk_monitor_policy")
|
||||
public class DiskMonitorPolicy implements Serializable {
|
||||
private static final long serialVersionUID = -2789228200584651740L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("policy_name")
|
||||
private String policyName;
|
||||
|
||||
@TableField("monitor_enabled")
|
||||
private Integer monitorEnabled;
|
||||
|
||||
@TableField("run_on_app_start")
|
||||
private Integer runOnAppStart;
|
||||
|
||||
@TableField("daily_run_time")
|
||||
private LocalTime dailyRunTime;
|
||||
|
||||
@TableField("warning_notify_mode")
|
||||
private String warningNotifyMode;
|
||||
|
||||
@TableField("alarm_notify_mode")
|
||||
private String alarmNotifyMode;
|
||||
|
||||
@TableField("last_job_id")
|
||||
private Long lastJobId;
|
||||
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
@TableField("created_by")
|
||||
private String createdBy;
|
||||
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField("updated_by")
|
||||
private String updatedBy;
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 磁盘监控结果表。
|
||||
*/
|
||||
@Data
|
||||
@TableName("disk_monitor_result")
|
||||
public class DiskMonitorResult implements Serializable {
|
||||
private static final long serialVersionUID = 5557836396879348480L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("job_id")
|
||||
private Long jobId;
|
||||
|
||||
@TableField("target_id")
|
||||
private Long targetId;
|
||||
|
||||
@TableField("drive_letter")
|
||||
private String driveLetter;
|
||||
|
||||
@TableField("total_bytes")
|
||||
private Long totalBytes;
|
||||
|
||||
@TableField("used_bytes")
|
||||
private Long usedBytes;
|
||||
|
||||
@TableField("free_bytes")
|
||||
private Long freeBytes;
|
||||
|
||||
@TableField("used_percent")
|
||||
private BigDecimal usedPercent;
|
||||
|
||||
@TableField("current_status")
|
||||
private String currentStatus;
|
||||
|
||||
@TableField("previous_status")
|
||||
private String previousStatus;
|
||||
|
||||
@TableField("status_changed")
|
||||
private Integer statusChanged;
|
||||
|
||||
@TableField("should_notify")
|
||||
private Integer shouldNotify;
|
||||
|
||||
@TableField("notify_reason")
|
||||
private String notifyReason;
|
||||
|
||||
@TableField("scan_time")
|
||||
private LocalDateTime scanTime;
|
||||
|
||||
@TableField("message")
|
||||
private String message;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.po;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 磁盘监控盘符配置表。
|
||||
*/
|
||||
@Data
|
||||
@TableName("disk_monitor_target")
|
||||
public class DiskMonitorTarget implements Serializable {
|
||||
private static final long serialVersionUID = -3567440027709476481L;
|
||||
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("policy_id")
|
||||
private Long policyId;
|
||||
|
||||
@TableField("drive_letter")
|
||||
private String driveLetter;
|
||||
|
||||
@TableField("monitor_enabled")
|
||||
private Integer monitorEnabled;
|
||||
|
||||
@TableField("warning_usage_percent")
|
||||
private Integer warningUsagePercent;
|
||||
|
||||
@TableField("alarm_usage_percent")
|
||||
private Integer alarmUsagePercent;
|
||||
|
||||
@TableField("notify_path_enabled")
|
||||
private Integer notifyPathEnabled;
|
||||
|
||||
@TableField("notify_path_list_json")
|
||||
private String notifyPathListJson;
|
||||
|
||||
@TableField("notify_http_enabled")
|
||||
private Integer notifyHttpEnabled;
|
||||
|
||||
@TableField("notify_http_list_json")
|
||||
private String notifyHttpListJson;
|
||||
|
||||
@TableField("last_status")
|
||||
private String lastStatus;
|
||||
|
||||
@TableField("last_scan_time")
|
||||
private LocalDateTime lastScanTime;
|
||||
|
||||
@TableField("last_used_percent")
|
||||
private BigDecimal lastUsedPercent;
|
||||
|
||||
@TableField("remark")
|
||||
private String remark;
|
||||
|
||||
@TableField("created_by")
|
||||
private String createdBy;
|
||||
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField("updated_by")
|
||||
private String updatedBy;
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.njcn.gather.systemmonitor.disk.pojo.vo;
|
||||
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyHttpItem;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyPathItem;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 磁盘监控接口返回对象。
|
||||
*/
|
||||
public class DiskMonitorVO {
|
||||
|
||||
@Data
|
||||
public static class PolicyDetailVO {
|
||||
private PolicyVO policy;
|
||||
private List<TargetVO> targets = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PolicyVO {
|
||||
private Long id;
|
||||
private String policyName;
|
||||
private Boolean monitorEnabled;
|
||||
private Boolean runOnAppStart;
|
||||
private String dailyRunTime;
|
||||
private String warningNotifyMode;
|
||||
private String alarmNotifyMode;
|
||||
private Long lastJobId;
|
||||
private String remark;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class TargetVO {
|
||||
private Long id;
|
||||
private String driveLetter;
|
||||
private Boolean monitorEnabled;
|
||||
private Integer warningUsagePercent;
|
||||
private Integer alarmUsagePercent;
|
||||
private Boolean notifyPathEnabled;
|
||||
private List<DiskMonitorNotifyPathItem> notifyPathList = new ArrayList<>();
|
||||
private Boolean notifyHttpEnabled;
|
||||
private List<DiskMonitorNotifyHttpItem> notifyHttpList = new ArrayList<>();
|
||||
private String lastStatus;
|
||||
private String lastScanTime;
|
||||
private BigDecimal lastUsedPercent;
|
||||
private String remark;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class JobRunVO {
|
||||
private Long jobId;
|
||||
private String jobNo;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class JobListVO {
|
||||
private Long jobId;
|
||||
private String jobNo;
|
||||
private String jobSource;
|
||||
private String startedAt;
|
||||
private String finishedAt;
|
||||
private String jobStatus;
|
||||
private Integer targetCount;
|
||||
private Integer warningCount;
|
||||
private Integer alarmCount;
|
||||
private String message;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class JobDetailVO {
|
||||
private JobInfoVO job;
|
||||
private List<ResultVO> results = new ArrayList<>();
|
||||
private List<NotifyLogVO> notifyLogs = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class JobInfoVO {
|
||||
private Long id;
|
||||
private String jobNo;
|
||||
private String jobSource;
|
||||
private String startedAt;
|
||||
private String finishedAt;
|
||||
private String jobStatus;
|
||||
private Integer targetCount;
|
||||
private Integer successCount;
|
||||
private Integer warningCount;
|
||||
private Integer alarmCount;
|
||||
private String message;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class ResultVO {
|
||||
private Long resultId;
|
||||
private Long targetId;
|
||||
private String driveLetter;
|
||||
private Long totalBytes;
|
||||
private Long usedBytes;
|
||||
private Long freeBytes;
|
||||
private BigDecimal usedPercent;
|
||||
private String currentStatus;
|
||||
private String previousStatus;
|
||||
private Boolean statusChanged;
|
||||
private Boolean shouldNotify;
|
||||
private String notifyReason;
|
||||
private String scanTime;
|
||||
private String message;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class NotifyLogVO {
|
||||
private Long id;
|
||||
private Long resultId;
|
||||
private String driveLetter;
|
||||
private String notifyLevel;
|
||||
private String channelType;
|
||||
private String channelTarget;
|
||||
private String sendStatus;
|
||||
private String responseMessage;
|
||||
private String sentAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.njcn.gather.systemmonitor.disk.schedule;
|
||||
|
||||
import com.njcn.gather.systemmonitor.disk.event.DiskMonitorPolicyChangedEvent;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorJobService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 磁盘监控启动与定时调度管理。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class DiskMonitorScheduleManager implements ApplicationRunner {
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final IDiskMonitorJobService diskMonitorJobService;
|
||||
private final AtomicInteger threadIndex = new AtomicInteger(1);
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(createThreadFactory());
|
||||
private ScheduledFuture<?> scheduledFuture;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
try {
|
||||
diskMonitorJobService.executeAppStartMonitor();
|
||||
} catch (Exception exception) {
|
||||
log.error("应用启动后执行磁盘监控失败", exception);
|
||||
}
|
||||
refreshSchedule();
|
||||
}
|
||||
|
||||
@EventListener(DiskMonitorPolicyChangedEvent.class)
|
||||
public void onPolicyChanged() {
|
||||
refreshSchedule();
|
||||
}
|
||||
|
||||
public synchronized void refreshSchedule() {
|
||||
if (scheduledFuture != null) {
|
||||
scheduledFuture.cancel(false);
|
||||
scheduledFuture = null;
|
||||
}
|
||||
LocalDateTime nextRunTime = diskMonitorJobService.getNextDailyRunTime();
|
||||
if (nextRunTime == null) {
|
||||
log.info("磁盘监控每日调度未启用或未配置执行时间,跳过注册");
|
||||
return;
|
||||
}
|
||||
long delayMs = Math.max(Duration.between(LocalDateTime.now(), nextRunTime).toMillis(), 0L);
|
||||
scheduledFuture = scheduler.schedule(this::executeDailyTask, delayMs, TimeUnit.MILLISECONDS);
|
||||
log.info("磁盘监控每日调度已注册,下次执行时间:{}", nextRunTime.format(DATE_TIME_FORMATTER));
|
||||
}
|
||||
|
||||
private void executeDailyTask() {
|
||||
try {
|
||||
diskMonitorJobService.executeDailyScheduleMonitor();
|
||||
} catch (Exception exception) {
|
||||
log.error("每日磁盘监控执行失败", exception);
|
||||
} finally {
|
||||
refreshSchedule();
|
||||
}
|
||||
}
|
||||
|
||||
private ThreadFactory createThreadFactory() {
|
||||
return runnable -> {
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.setName("disk-monitor-schedule-" + threadIndex.getAndIncrement());
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
};
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public synchronized void destroy() {
|
||||
if (scheduledFuture != null) {
|
||||
scheduledFuture.cancel(false);
|
||||
}
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.njcn.gather.systemmonitor.disk.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.vo.DiskMonitorVO;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 磁盘监控任务服务。
|
||||
*/
|
||||
public interface IDiskMonitorJobService {
|
||||
|
||||
DiskMonitorVO.JobRunVO runJob(DiskMonitorParam.JobRunParam param);
|
||||
|
||||
Page<DiskMonitorVO.JobListVO> listJobs(DiskMonitorParam.JobListParam param);
|
||||
|
||||
DiskMonitorVO.JobDetailVO getJobDetail(Long jobId);
|
||||
|
||||
void executeAppStartMonitor();
|
||||
|
||||
void executeDailyScheduleMonitor();
|
||||
|
||||
LocalDateTime getNextDailyRunTime();
|
||||
|
||||
void executeNotifyTest(String driveLetter);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.njcn.gather.systemmonitor.disk.service;
|
||||
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
|
||||
/**
|
||||
* 磁盘监控通知服务。
|
||||
*/
|
||||
public interface IDiskMonitorNotifyService {
|
||||
|
||||
boolean testNotify(DiskMonitorParam.NotifyTestParam param);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.njcn.gather.systemmonitor.disk.service;
|
||||
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorPolicy;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorTarget;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.vo.DiskMonitorVO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 磁盘监控配置服务。
|
||||
*/
|
||||
public interface IDiskMonitorPolicyService {
|
||||
|
||||
DiskMonitorVO.PolicyDetailVO getPolicyDetail();
|
||||
|
||||
boolean savePolicy(DiskMonitorParam.PolicySaveParam param);
|
||||
|
||||
DiskMonitorPolicy getCurrentPolicy();
|
||||
|
||||
DiskMonitorPolicy getOrCreatePolicy();
|
||||
|
||||
List<DiskMonitorTarget> listEnabledTargets();
|
||||
|
||||
DiskMonitorTarget getTargetByDriveLetter(String driveLetter);
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
package com.njcn.gather.systemmonitor.disk.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemmonitor.disk.component.DiskMonitorNotificationComponent;
|
||||
import com.njcn.gather.systemmonitor.disk.constant.DiskMonitorConstant;
|
||||
import com.njcn.gather.systemmonitor.disk.enums.DiskMonitorResponseEnum;
|
||||
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorJobMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorNotifyLogMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorPolicyMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorResultMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorTargetMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorJob;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorNotifyLog;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorPolicy;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorResult;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorTarget;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.vo.DiskMonitorVO;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorJobService;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorPolicyService;
|
||||
import com.njcn.web.factory.PageFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 磁盘监控任务服务实现。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DiskMonitorJobServiceImpl implements IDiskMonitorJobService {
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final IDiskMonitorPolicyService diskMonitorPolicyService;
|
||||
private final DiskMonitorNotificationComponent diskMonitorNotificationComponent;
|
||||
private final DiskMonitorJobMapper jobMapper;
|
||||
private final DiskMonitorResultMapper resultMapper;
|
||||
private final DiskMonitorNotifyLogMapper notifyLogMapper;
|
||||
private final DiskMonitorTargetMapper targetMapper;
|
||||
private final DiskMonitorPolicyMapper policyMapper;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DiskMonitorVO.JobRunVO runJob(DiskMonitorParam.JobRunParam param) {
|
||||
String jobSource = StrUtil.blankToDefault(param.getJobSource(), DiskMonitorConstant.JOB_SOURCE_MANUAL).trim().toUpperCase();
|
||||
if (!DiskMonitorConstant.JOB_SOURCE_MANUAL.equals(jobSource)) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.JOB_SOURCE_INVALID);
|
||||
}
|
||||
return executeMonitorJob(jobSource, null, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<DiskMonitorVO.JobListVO> listJobs(DiskMonitorParam.JobListParam param) {
|
||||
Page<DiskMonitorJob> page = jobMapper.selectPage(
|
||||
new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)),
|
||||
new QueryWrapper<DiskMonitorJob>().orderByDesc("started_at", "id")
|
||||
);
|
||||
Page<DiskMonitorVO.JobListVO> resultPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
|
||||
resultPage.setRecords(page.getRecords().stream().map(this::toJobListVO).collect(Collectors.toList()));
|
||||
return resultPage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiskMonitorVO.JobDetailVO getJobDetail(Long jobId) {
|
||||
DiskMonitorJob job = jobMapper.selectById(jobId);
|
||||
if (job == null) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.JOB_NOT_FOUND);
|
||||
}
|
||||
DiskMonitorVO.JobDetailVO detailVO = new DiskMonitorVO.JobDetailVO();
|
||||
detailVO.setJob(toJobInfoVO(job));
|
||||
List<DiskMonitorResult> results = resultMapper.selectList(new QueryWrapper<DiskMonitorResult>()
|
||||
.eq("job_id", jobId)
|
||||
.orderByAsc("scan_time", "id"));
|
||||
detailVO.setResults(results.stream().map(this::toResultVO).collect(Collectors.toList()));
|
||||
List<DiskMonitorNotifyLog> notifyLogs = notifyLogMapper.selectList(new QueryWrapper<DiskMonitorNotifyLog>()
|
||||
.eq("job_id", jobId)
|
||||
.orderByAsc("sent_at", "id"));
|
||||
detailVO.setNotifyLogs(notifyLogs.stream().map(this::toNotifyLogVO).collect(Collectors.toList()));
|
||||
return detailVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void executeAppStartMonitor() {
|
||||
DiskMonitorPolicy policy = diskMonitorPolicyService.getOrCreatePolicy();
|
||||
if (!isEnabled(policy.getMonitorEnabled()) || !isEnabled(policy.getRunOnAppStart())) {
|
||||
return;
|
||||
}
|
||||
executeMonitorJob(DiskMonitorConstant.JOB_SOURCE_APP_START, null, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void executeDailyScheduleMonitor() {
|
||||
DiskMonitorPolicy policy = diskMonitorPolicyService.getOrCreatePolicy();
|
||||
if (!isEnabled(policy.getMonitorEnabled()) || policy.getDailyRunTime() == null) {
|
||||
return;
|
||||
}
|
||||
executeMonitorJob(DiskMonitorConstant.JOB_SOURCE_DAILY_SCHEDULE, null, false, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDateTime getNextDailyRunTime() {
|
||||
DiskMonitorPolicy policy = diskMonitorPolicyService.getOrCreatePolicy();
|
||||
if (!isEnabled(policy.getMonitorEnabled()) || policy.getDailyRunTime() == null) {
|
||||
return null;
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime candidate = LocalDate.now().atTime(policy.getDailyRunTime());
|
||||
if (!candidate.isAfter(now)) {
|
||||
candidate = candidate.plusDays(1);
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void executeNotifyTest(String driveLetter) {
|
||||
executeMonitorJob(DiskMonitorConstant.JOB_SOURCE_MANUAL, driveLetter, true, true);
|
||||
}
|
||||
|
||||
private DiskMonitorVO.JobRunVO executeMonitorJob(String jobSource, String driveLetter, boolean forceNotify, boolean failWhenNoTarget) {
|
||||
validateJobSource(jobSource);
|
||||
List<DiskMonitorTarget> targets = resolveExecutionTargets(driveLetter);
|
||||
if (targets.isEmpty()) {
|
||||
logNoExecutionTargets(jobSource, driveLetter);
|
||||
if (failWhenNoTarget) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.NO_ENABLED_TARGET);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
DiskMonitorJob job = new DiskMonitorJob();
|
||||
job.setJobNo(generateJobNo());
|
||||
job.setJobSource(jobSource);
|
||||
job.setPlannedTime(now);
|
||||
job.setStartedAt(now);
|
||||
job.setJobStatus(DiskMonitorConstant.JOB_STATUS_RUNNING);
|
||||
job.setTargetCount(targets.size());
|
||||
job.setSuccessCount(0);
|
||||
job.setWarningCount(0);
|
||||
job.setAlarmCount(0);
|
||||
job.setMessage(forceNotify ? "通知测试任务" : null);
|
||||
jobMapper.insert(job);
|
||||
|
||||
int successCount = 0;
|
||||
int warningCount = 0;
|
||||
int alarmCount = 0;
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
DiskMonitorPolicy policy = diskMonitorPolicyService.getOrCreatePolicy();
|
||||
|
||||
for (DiskMonitorTarget target : targets) {
|
||||
try {
|
||||
ScanSnapshot snapshot = resolveSnapshotStatus(target, scanDrive(target.getDriveLetter()));
|
||||
if (snapshot.isSuccess()) {
|
||||
successCount++;
|
||||
}
|
||||
if (DiskMonitorConstant.STATUS_WARNING.equals(snapshot.getCurrentStatus())) {
|
||||
warningCount++;
|
||||
}
|
||||
if (DiskMonitorConstant.STATUS_ALARM.equals(snapshot.getCurrentStatus())) {
|
||||
alarmCount++;
|
||||
}
|
||||
|
||||
NotifyDecision notifyDecision = resolveNotifyDecision(target, snapshot.getCurrentStatus(), forceNotify);
|
||||
DiskMonitorResult result = buildResult(job, target, snapshot, notifyDecision);
|
||||
resultMapper.insert(result);
|
||||
|
||||
if (notifyDecision.isShouldNotify()) {
|
||||
diskMonitorNotificationComponent.sendNotifications(job, target, result,
|
||||
snapshot.getUsedPercent(), snapshot.getCurrentStatus(),
|
||||
notifyDecision.getNotifyReason(), notifyDecision.getNotifyLevel(),
|
||||
snapshot.getScanTime(), snapshot.getMessage());
|
||||
}
|
||||
|
||||
updateTargetLastState(target, snapshot);
|
||||
} catch (Exception exception) {
|
||||
log.error("磁盘监控执行异常,driveLetter={}", target.getDriveLetter(), exception);
|
||||
errorMessages.add(target.getDriveLetter() + ":" + exception.getMessage());
|
||||
ScanSnapshot failedSnapshot = ScanSnapshot.failed(exception.getMessage());
|
||||
DiskMonitorResult result = buildResult(job, target, failedSnapshot,
|
||||
new NotifyDecision(false, DiskMonitorConstant.NOTIFY_REASON_NO_NOTIFY, resolveNotifyLevel(failedSnapshot.getCurrentStatus())));
|
||||
resultMapper.insert(result);
|
||||
updateTargetLastState(target, failedSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
finishJob(job, policy, successCount, warningCount, alarmCount, errorMessages);
|
||||
DiskMonitorVO.JobRunVO runVO = new DiskMonitorVO.JobRunVO();
|
||||
runVO.setJobId(job.getId());
|
||||
runVO.setJobNo(job.getJobNo());
|
||||
return runVO;
|
||||
}
|
||||
|
||||
private void finishJob(DiskMonitorJob job, DiskMonitorPolicy policy, int successCount, int warningCount,
|
||||
int alarmCount, List<String> errorMessages) {
|
||||
job.setSuccessCount(successCount);
|
||||
job.setWarningCount(warningCount);
|
||||
job.setAlarmCount(alarmCount);
|
||||
job.setFinishedAt(LocalDateTime.now());
|
||||
if (successCount == job.getTargetCount()) {
|
||||
job.setJobStatus(DiskMonitorConstant.JOB_STATUS_SUCCESS);
|
||||
} else if (successCount == 0) {
|
||||
job.setJobStatus(DiskMonitorConstant.JOB_STATUS_FAILED);
|
||||
} else {
|
||||
job.setJobStatus(DiskMonitorConstant.JOB_STATUS_PARTIAL_SUCCESS);
|
||||
}
|
||||
if (!errorMessages.isEmpty()) {
|
||||
job.setMessage(String.join(";", errorMessages));
|
||||
}
|
||||
jobMapper.updateById(job);
|
||||
|
||||
policy.setLastJobId(job.getId());
|
||||
policyMapper.updateById(policy);
|
||||
}
|
||||
|
||||
private DiskMonitorResult buildResult(DiskMonitorJob job, DiskMonitorTarget target, ScanSnapshot snapshot, NotifyDecision notifyDecision) {
|
||||
DiskMonitorResult result = new DiskMonitorResult();
|
||||
result.setJobId(job.getId());
|
||||
result.setTargetId(target.getId());
|
||||
result.setDriveLetter(target.getDriveLetter());
|
||||
result.setTotalBytes(snapshot.getTotalBytes());
|
||||
result.setUsedBytes(snapshot.getUsedBytes());
|
||||
result.setFreeBytes(snapshot.getFreeBytes());
|
||||
result.setUsedPercent(snapshot.getUsedPercent());
|
||||
result.setCurrentStatus(snapshot.getCurrentStatus());
|
||||
result.setPreviousStatus(resolvePreviousStatus(target.getLastStatus()));
|
||||
result.setStatusChanged(snapshot.getCurrentStatus().equals(resolvePreviousStatus(target.getLastStatus())) ? 0 : 1);
|
||||
result.setShouldNotify(notifyDecision.isShouldNotify() ? 1 : 0);
|
||||
result.setNotifyReason(notifyDecision.getNotifyReason());
|
||||
result.setScanTime(snapshot.getScanTime());
|
||||
result.setMessage(snapshot.getMessage());
|
||||
return result;
|
||||
}
|
||||
|
||||
private void updateTargetLastState(DiskMonitorTarget target, ScanSnapshot snapshot) {
|
||||
target.setLastStatus(snapshot.getCurrentStatus());
|
||||
target.setLastScanTime(snapshot.getScanTime());
|
||||
target.setLastUsedPercent(snapshot.getUsedPercent());
|
||||
targetMapper.updateById(target);
|
||||
}
|
||||
|
||||
private NotifyDecision resolveNotifyDecision(DiskMonitorTarget target, String currentStatus, boolean forceNotify) {
|
||||
String previousStatus = resolvePreviousStatus(target.getLastStatus());
|
||||
if (forceNotify) {
|
||||
return new NotifyDecision(true, DiskMonitorConstant.NOTIFY_REASON_STATUS_CHANGED, resolveNotifyLevel(currentStatus));
|
||||
}
|
||||
if (DiskMonitorConstant.STATUS_ALARM.equals(currentStatus)) {
|
||||
return new NotifyDecision(true, DiskMonitorConstant.NOTIFY_REASON_ALARM_EVERY_TIME, DiskMonitorConstant.NOTIFY_LEVEL_ALARM);
|
||||
}
|
||||
if (DiskMonitorConstant.STATUS_WARNING.equals(currentStatus) && !DiskMonitorConstant.STATUS_WARNING.equals(previousStatus)) {
|
||||
return new NotifyDecision(true, DiskMonitorConstant.NOTIFY_REASON_STATUS_CHANGED, DiskMonitorConstant.NOTIFY_LEVEL_WARNING);
|
||||
}
|
||||
if (DiskMonitorConstant.STATUS_NORMAL.equals(currentStatus)
|
||||
&& (DiskMonitorConstant.STATUS_WARNING.equals(previousStatus) || DiskMonitorConstant.STATUS_ALARM.equals(previousStatus))) {
|
||||
return new NotifyDecision(true, DiskMonitorConstant.NOTIFY_REASON_STATUS_CHANGED, DiskMonitorConstant.NOTIFY_LEVEL_RECOVER);
|
||||
}
|
||||
return new NotifyDecision(false, DiskMonitorConstant.NOTIFY_REASON_NO_NOTIFY, resolveNotifyLevel(currentStatus));
|
||||
}
|
||||
|
||||
private String resolveNotifyLevel(String currentStatus) {
|
||||
if (DiskMonitorConstant.STATUS_ALARM.equals(currentStatus)) {
|
||||
return DiskMonitorConstant.NOTIFY_LEVEL_ALARM;
|
||||
}
|
||||
if (DiskMonitorConstant.STATUS_WARNING.equals(currentStatus)) {
|
||||
return DiskMonitorConstant.NOTIFY_LEVEL_WARNING;
|
||||
}
|
||||
return DiskMonitorConstant.NOTIFY_LEVEL_RECOVER;
|
||||
}
|
||||
|
||||
private ScanSnapshot scanDrive(String driveLetter) {
|
||||
File driveRoot = new File(driveLetter + File.separator);
|
||||
if (!driveRoot.exists()) {
|
||||
log.warn("磁盘监控扫描跳过,监控盘符不存在或当前运行账号无访问权限,driveLetter={}", driveLetter);
|
||||
return ScanSnapshot.failed("盘符不存在或当前运行账户无访问权限");
|
||||
}
|
||||
long totalBytes = driveRoot.getTotalSpace();
|
||||
if (totalBytes <= 0) {
|
||||
log.warn("磁盘监控扫描失败,未读取到有效磁盘容量,driveLetter={}", driveLetter);
|
||||
return ScanSnapshot.failed("未读取到有效磁盘容量");
|
||||
}
|
||||
long freeBytes = driveRoot.getUsableSpace();
|
||||
long usedBytes = totalBytes - freeBytes;
|
||||
BigDecimal usedPercent = BigDecimal.valueOf(usedBytes)
|
||||
.multiply(BigDecimal.valueOf(100))
|
||||
.divide(BigDecimal.valueOf(totalBytes), 2, RoundingMode.HALF_UP);
|
||||
return new ScanSnapshot(true, totalBytes, usedBytes, freeBytes, usedPercent, LocalDateTime.now(),
|
||||
DiskMonitorConstant.STATUS_UNKNOWN, null);
|
||||
}
|
||||
|
||||
private ScanSnapshot resolveSnapshotStatus(DiskMonitorTarget target, ScanSnapshot snapshot) {
|
||||
if (!snapshot.isSuccess()) {
|
||||
return snapshot;
|
||||
}
|
||||
String currentStatus;
|
||||
if (snapshot.getUsedPercent().compareTo(BigDecimal.valueOf(target.getAlarmUsagePercent())) >= 0) {
|
||||
currentStatus = DiskMonitorConstant.STATUS_ALARM;
|
||||
} else if (snapshot.getUsedPercent().compareTo(BigDecimal.valueOf(target.getWarningUsagePercent())) >= 0) {
|
||||
currentStatus = DiskMonitorConstant.STATUS_WARNING;
|
||||
} else {
|
||||
currentStatus = DiskMonitorConstant.STATUS_NORMAL;
|
||||
}
|
||||
return new ScanSnapshot(true, snapshot.getTotalBytes(), snapshot.getUsedBytes(), snapshot.getFreeBytes(),
|
||||
snapshot.getUsedPercent(), snapshot.getScanTime(), currentStatus, snapshot.getMessage());
|
||||
}
|
||||
|
||||
private List<DiskMonitorTarget> resolveExecutionTargets(String driveLetter) {
|
||||
if (StrUtil.isNotBlank(driveLetter)) {
|
||||
DiskMonitorTarget target = diskMonitorPolicyService.getTargetByDriveLetter(driveLetter);
|
||||
return target == null ? Collections.emptyList() : Collections.singletonList(target);
|
||||
}
|
||||
return diskMonitorPolicyService.listEnabledTargets();
|
||||
}
|
||||
|
||||
private void logNoExecutionTargets(String jobSource, String driveLetter) {
|
||||
if (StrUtil.isNotBlank(driveLetter)) {
|
||||
log.warn("磁盘监控执行跳过,未找到可执行的盘符配置,jobSource={}, driveLetter={}", jobSource, driveLetter);
|
||||
return;
|
||||
}
|
||||
log.warn("磁盘监控执行跳过,暂无启用的磁盘监控盘符配置,jobSource={}", jobSource);
|
||||
}
|
||||
|
||||
private void validateJobSource(String jobSource) {
|
||||
if (!DiskMonitorConstant.JOB_SOURCE_APP_START.equals(jobSource)
|
||||
&& !DiskMonitorConstant.JOB_SOURCE_DAILY_SCHEDULE.equals(jobSource)
|
||||
&& !DiskMonitorConstant.JOB_SOURCE_MANUAL.equals(jobSource)) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.JOB_SOURCE_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnabled(Integer value) {
|
||||
return value != null && value == 1;
|
||||
}
|
||||
|
||||
private String resolvePreviousStatus(String previousStatus) {
|
||||
return StrUtil.isBlank(previousStatus) ? DiskMonitorConstant.STATUS_UNKNOWN : previousStatus;
|
||||
}
|
||||
|
||||
private String formatDateTime(LocalDateTime dateTime) {
|
||||
return dateTime == null ? null : dateTime.format(DATE_TIME_FORMATTER);
|
||||
}
|
||||
|
||||
private String generateJobNo() {
|
||||
return "DM" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
|
||||
}
|
||||
|
||||
private DiskMonitorVO.JobListVO toJobListVO(DiskMonitorJob job) {
|
||||
DiskMonitorVO.JobListVO jobListVO = new DiskMonitorVO.JobListVO();
|
||||
jobListVO.setJobId(job.getId());
|
||||
jobListVO.setJobNo(job.getJobNo());
|
||||
jobListVO.setJobSource(job.getJobSource());
|
||||
jobListVO.setStartedAt(formatDateTime(job.getStartedAt()));
|
||||
jobListVO.setFinishedAt(formatDateTime(job.getFinishedAt()));
|
||||
jobListVO.setJobStatus(job.getJobStatus());
|
||||
jobListVO.setTargetCount(job.getTargetCount());
|
||||
jobListVO.setWarningCount(job.getWarningCount());
|
||||
jobListVO.setAlarmCount(job.getAlarmCount());
|
||||
jobListVO.setMessage(job.getMessage());
|
||||
return jobListVO;
|
||||
}
|
||||
|
||||
private DiskMonitorVO.JobInfoVO toJobInfoVO(DiskMonitorJob job) {
|
||||
DiskMonitorVO.JobInfoVO jobInfoVO = new DiskMonitorVO.JobInfoVO();
|
||||
jobInfoVO.setId(job.getId());
|
||||
jobInfoVO.setJobNo(job.getJobNo());
|
||||
jobInfoVO.setJobSource(job.getJobSource());
|
||||
jobInfoVO.setStartedAt(formatDateTime(job.getStartedAt()));
|
||||
jobInfoVO.setFinishedAt(formatDateTime(job.getFinishedAt()));
|
||||
jobInfoVO.setJobStatus(job.getJobStatus());
|
||||
jobInfoVO.setTargetCount(job.getTargetCount());
|
||||
jobInfoVO.setSuccessCount(job.getSuccessCount());
|
||||
jobInfoVO.setWarningCount(job.getWarningCount());
|
||||
jobInfoVO.setAlarmCount(job.getAlarmCount());
|
||||
jobInfoVO.setMessage(job.getMessage());
|
||||
return jobInfoVO;
|
||||
}
|
||||
|
||||
private DiskMonitorVO.ResultVO toResultVO(DiskMonitorResult result) {
|
||||
DiskMonitorVO.ResultVO resultVO = new DiskMonitorVO.ResultVO();
|
||||
resultVO.setResultId(result.getId());
|
||||
resultVO.setTargetId(result.getTargetId());
|
||||
resultVO.setDriveLetter(result.getDriveLetter());
|
||||
resultVO.setTotalBytes(result.getTotalBytes());
|
||||
resultVO.setUsedBytes(result.getUsedBytes());
|
||||
resultVO.setFreeBytes(result.getFreeBytes());
|
||||
resultVO.setUsedPercent(result.getUsedPercent());
|
||||
resultVO.setCurrentStatus(result.getCurrentStatus());
|
||||
resultVO.setPreviousStatus(result.getPreviousStatus());
|
||||
resultVO.setStatusChanged(Objects.equals(result.getStatusChanged(), 1));
|
||||
resultVO.setShouldNotify(Objects.equals(result.getShouldNotify(), 1));
|
||||
resultVO.setNotifyReason(result.getNotifyReason());
|
||||
resultVO.setScanTime(formatDateTime(result.getScanTime()));
|
||||
resultVO.setMessage(result.getMessage());
|
||||
return resultVO;
|
||||
}
|
||||
|
||||
private DiskMonitorVO.NotifyLogVO toNotifyLogVO(DiskMonitorNotifyLog notifyLog) {
|
||||
DiskMonitorVO.NotifyLogVO notifyLogVO = new DiskMonitorVO.NotifyLogVO();
|
||||
notifyLogVO.setId(notifyLog.getId());
|
||||
notifyLogVO.setResultId(notifyLog.getResultId());
|
||||
notifyLogVO.setDriveLetter(notifyLog.getDriveLetter());
|
||||
notifyLogVO.setNotifyLevel(notifyLog.getNotifyLevel());
|
||||
notifyLogVO.setChannelType(notifyLog.getChannelType());
|
||||
notifyLogVO.setChannelTarget(notifyLog.getChannelTarget());
|
||||
notifyLogVO.setSendStatus(notifyLog.getSendStatus());
|
||||
notifyLogVO.setResponseMessage(notifyLog.getResponseMessage());
|
||||
notifyLogVO.setSentAt(formatDateTime(notifyLog.getSentAt()));
|
||||
return notifyLogVO;
|
||||
}
|
||||
|
||||
private static class NotifyDecision {
|
||||
private final boolean shouldNotify;
|
||||
private final String notifyReason;
|
||||
private final String notifyLevel;
|
||||
|
||||
private NotifyDecision(boolean shouldNotify, String notifyReason, String notifyLevel) {
|
||||
this.shouldNotify = shouldNotify;
|
||||
this.notifyReason = notifyReason;
|
||||
this.notifyLevel = notifyLevel;
|
||||
}
|
||||
|
||||
public boolean isShouldNotify() {
|
||||
return shouldNotify;
|
||||
}
|
||||
|
||||
public String getNotifyReason() {
|
||||
return notifyReason;
|
||||
}
|
||||
|
||||
public String getNotifyLevel() {
|
||||
return notifyLevel;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ScanSnapshot {
|
||||
private final boolean success;
|
||||
private final Long totalBytes;
|
||||
private final Long usedBytes;
|
||||
private final Long freeBytes;
|
||||
private final BigDecimal usedPercent;
|
||||
private final LocalDateTime scanTime;
|
||||
private final String currentStatus;
|
||||
private final String message;
|
||||
|
||||
private ScanSnapshot(boolean success, Long totalBytes, Long usedBytes, Long freeBytes,
|
||||
BigDecimal usedPercent, LocalDateTime scanTime, String currentStatus, String message) {
|
||||
this.success = success;
|
||||
this.totalBytes = totalBytes;
|
||||
this.usedBytes = usedBytes;
|
||||
this.freeBytes = freeBytes;
|
||||
this.usedPercent = usedPercent == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : usedPercent;
|
||||
this.scanTime = scanTime == null ? LocalDateTime.now() : scanTime;
|
||||
this.currentStatus = StrUtil.blankToDefault(currentStatus, DiskMonitorConstant.STATUS_UNKNOWN);
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static ScanSnapshot failed(String message) {
|
||||
return new ScanSnapshot(false, 0L, 0L, 0L, BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP),
|
||||
LocalDateTime.now(), DiskMonitorConstant.STATUS_UNKNOWN, message);
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public Long getTotalBytes() {
|
||||
return totalBytes;
|
||||
}
|
||||
|
||||
public Long getUsedBytes() {
|
||||
return usedBytes;
|
||||
}
|
||||
|
||||
public Long getFreeBytes() {
|
||||
return freeBytes;
|
||||
}
|
||||
|
||||
public BigDecimal getUsedPercent() {
|
||||
return usedPercent;
|
||||
}
|
||||
|
||||
public LocalDateTime getScanTime() {
|
||||
return scanTime;
|
||||
}
|
||||
|
||||
public String getCurrentStatus() {
|
||||
if (!success || totalBytes == null || totalBytes <= 0) {
|
||||
return DiskMonitorConstant.STATUS_UNKNOWN;
|
||||
}
|
||||
return currentStatus == null ? DiskMonitorConstant.STATUS_NORMAL : currentStatus;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.njcn.gather.systemmonitor.disk.service.impl;
|
||||
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemmonitor.disk.enums.DiskMonitorResponseEnum;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorTarget;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorJobService;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorNotifyService;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorPolicyService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* 磁盘监控通知服务实现。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DiskMonitorNotifyServiceImpl implements IDiskMonitorNotifyService {
|
||||
|
||||
private final IDiskMonitorPolicyService diskMonitorPolicyService;
|
||||
private final IDiskMonitorJobService diskMonitorJobService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean testNotify(DiskMonitorParam.NotifyTestParam param) {
|
||||
DiskMonitorTarget target = diskMonitorPolicyService.getTargetByDriveLetter(param.getDriveLetter());
|
||||
if (target == null) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_TARGET_NOT_FOUND);
|
||||
}
|
||||
diskMonitorJobService.executeNotifyTest(target.getDriveLetter());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,341 @@
|
||||
package com.njcn.gather.systemmonitor.disk.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||
import com.njcn.common.pojo.exception.BusinessException;
|
||||
import com.njcn.gather.systemmonitor.disk.constant.DiskMonitorConstant;
|
||||
import com.njcn.gather.systemmonitor.disk.enums.DiskMonitorResponseEnum;
|
||||
import com.njcn.gather.systemmonitor.disk.event.DiskMonitorPolicyChangedEvent;
|
||||
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorPolicyMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.mapper.DiskMonitorTargetMapper;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyHttpItem;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.dto.DiskMonitorNotifyPathItem;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.param.DiskMonitorParam;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorPolicy;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.po.DiskMonitorTarget;
|
||||
import com.njcn.gather.systemmonitor.disk.pojo.vo.DiskMonitorVO;
|
||||
import com.njcn.gather.systemmonitor.disk.service.IDiskMonitorPolicyService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 磁盘监控配置服务实现。
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DiskMonitorPolicyServiceImpl implements IDiskMonitorPolicyService {
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
private static final TypeReference<List<DiskMonitorNotifyPathItem>> PATH_LIST_TYPE = new TypeReference<List<DiskMonitorNotifyPathItem>>() {
|
||||
};
|
||||
private static final TypeReference<List<DiskMonitorNotifyHttpItem>> HTTP_LIST_TYPE = new TypeReference<List<DiskMonitorNotifyHttpItem>>() {
|
||||
};
|
||||
|
||||
private final DiskMonitorPolicyMapper policyMapper;
|
||||
private final DiskMonitorTargetMapper targetMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DiskMonitorVO.PolicyDetailVO getPolicyDetail() {
|
||||
DiskMonitorPolicy policy = getOrCreatePolicy();
|
||||
DiskMonitorVO.PolicyDetailVO detailVO = new DiskMonitorVO.PolicyDetailVO();
|
||||
detailVO.setPolicy(toPolicyVO(policy));
|
||||
detailVO.setTargets(listTargetsByPolicyId(policy.getId()).stream().map(this::toTargetVO).collect(Collectors.toList()));
|
||||
return detailVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean savePolicy(DiskMonitorParam.PolicySaveParam param) {
|
||||
validateSaveParam(param);
|
||||
DiskMonitorPolicy currentPolicy = getOrCreatePolicy();
|
||||
DiskMonitorParam.PolicyParam policyParam = param.getPolicy();
|
||||
|
||||
currentPolicy.setPolicyName(policyParam.getPolicyName().trim());
|
||||
currentPolicy.setMonitorEnabled(boolToInt(policyParam.getMonitorEnabled(), true));
|
||||
currentPolicy.setRunOnAppStart(boolToInt(policyParam.getRunOnAppStart(), true));
|
||||
currentPolicy.setDailyRunTime(parseTime(policyParam.getDailyRunTime()));
|
||||
currentPolicy.setWarningNotifyMode(resolveWarningNotifyMode(policyParam.getWarningNotifyMode()));
|
||||
currentPolicy.setAlarmNotifyMode(resolveAlarmNotifyMode(policyParam.getAlarmNotifyMode()));
|
||||
currentPolicy.setRemark(policyParam.getRemark());
|
||||
if (policyMapper.updateById(currentPolicy) <= 0) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.POLICY_SAVE_FAILED);
|
||||
}
|
||||
|
||||
List<DiskMonitorTarget> existingTargets = listTargetsByPolicyId(currentPolicy.getId());
|
||||
Map<Long, DiskMonitorTarget> existingById = existingTargets.stream()
|
||||
.collect(Collectors.toMap(DiskMonitorTarget::getId, item -> item, (left, right) -> left, LinkedHashMap::new));
|
||||
Map<String, DiskMonitorTarget> existingByDrive = existingTargets.stream()
|
||||
.collect(Collectors.toMap(item -> normalizeDriveLetter(item.getDriveLetter()), item -> item, (left, right) -> left, LinkedHashMap::new));
|
||||
|
||||
List<Long> keepIds = new ArrayList<>();
|
||||
for (DiskMonitorParam.TargetParam targetParam : param.getTargets()) {
|
||||
String driveLetter = normalizeDriveLetter(targetParam.getDriveLetter());
|
||||
DiskMonitorTarget target = targetParam.getId() == null ? null : existingById.get(targetParam.getId());
|
||||
if (target == null) {
|
||||
target = existingByDrive.get(driveLetter);
|
||||
}
|
||||
if (target == null) {
|
||||
target = new DiskMonitorTarget();
|
||||
target.setPolicyId(currentPolicy.getId());
|
||||
target.setDriveLetter(driveLetter);
|
||||
target.setLastStatus(DiskMonitorConstant.STATUS_UNKNOWN);
|
||||
}
|
||||
fillTarget(target, targetParam, driveLetter);
|
||||
if (target.getId() == null) {
|
||||
targetMapper.insert(target);
|
||||
} else {
|
||||
targetMapper.updateById(target);
|
||||
}
|
||||
keepIds.add(target.getId());
|
||||
}
|
||||
|
||||
List<Long> deleteIds = existingTargets.stream()
|
||||
.map(DiskMonitorTarget::getId)
|
||||
.filter(id -> !keepIds.contains(id))
|
||||
.collect(Collectors.toList());
|
||||
if (!deleteIds.isEmpty()) {
|
||||
targetMapper.deleteBatchIds(deleteIds);
|
||||
}
|
||||
|
||||
applicationEventPublisher.publishEvent(new DiskMonitorPolicyChangedEvent());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiskMonitorPolicy getCurrentPolicy() {
|
||||
return policyMapper.selectOne(new QueryWrapper<DiskMonitorPolicy>().orderByAsc("id").last("LIMIT 1"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DiskMonitorPolicy getOrCreatePolicy() {
|
||||
DiskMonitorPolicy policy = getCurrentPolicy();
|
||||
if (policy != null) {
|
||||
return policy;
|
||||
}
|
||||
policy = new DiskMonitorPolicy();
|
||||
policy.setPolicyName(DiskMonitorConstant.DEFAULT_POLICY_NAME);
|
||||
policy.setMonitorEnabled(1);
|
||||
policy.setRunOnAppStart(1);
|
||||
policy.setDailyRunTime(parseTime(DiskMonitorConstant.DEFAULT_DAILY_RUN_TIME));
|
||||
policy.setWarningNotifyMode(DiskMonitorConstant.WARNING_NOTIFY_MODE_STATUS_CHANGE);
|
||||
policy.setAlarmNotifyMode(DiskMonitorConstant.ALARM_NOTIFY_MODE_EVERY_TIME);
|
||||
policyMapper.insert(policy);
|
||||
return policy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DiskMonitorTarget> listEnabledTargets() {
|
||||
return targetMapper.selectList(new QueryWrapper<DiskMonitorTarget>()
|
||||
.eq("monitor_enabled", 1)
|
||||
.orderByAsc("drive_letter", "id"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DiskMonitorTarget getTargetByDriveLetter(String driveLetter) {
|
||||
return targetMapper.selectOne(new QueryWrapper<DiskMonitorTarget>()
|
||||
.eq("drive_letter", normalizeDriveLetter(driveLetter))
|
||||
.last("LIMIT 1"));
|
||||
}
|
||||
|
||||
private List<DiskMonitorTarget> listTargetsByPolicyId(Long policyId) {
|
||||
return targetMapper.selectList(new QueryWrapper<DiskMonitorTarget>()
|
||||
.eq("policy_id", policyId)
|
||||
.orderByAsc("drive_letter", "id"));
|
||||
}
|
||||
|
||||
private void fillTarget(DiskMonitorTarget target, DiskMonitorParam.TargetParam targetParam, String driveLetter) {
|
||||
target.setDriveLetter(driveLetter);
|
||||
target.setMonitorEnabled(boolToInt(targetParam.getMonitorEnabled(), true));
|
||||
target.setWarningUsagePercent(targetParam.getWarningUsagePercent());
|
||||
target.setAlarmUsagePercent(targetParam.getAlarmUsagePercent());
|
||||
target.setNotifyPathEnabled(boolToInt(targetParam.getNotifyPathEnabled(), false));
|
||||
target.setNotifyPathListJson(writeValueAsJson(targetParam.getNotifyPathList()));
|
||||
target.setNotifyHttpEnabled(boolToInt(targetParam.getNotifyHttpEnabled(), false));
|
||||
target.setNotifyHttpListJson(writeValueAsJson(targetParam.getNotifyHttpList()));
|
||||
target.setRemark(targetParam.getRemark());
|
||||
}
|
||||
|
||||
private void validateSaveParam(DiskMonitorParam.PolicySaveParam param) {
|
||||
DiskMonitorParam.PolicyParam policyParam = param.getPolicy();
|
||||
parseTime(policyParam.getDailyRunTime());
|
||||
resolveWarningNotifyMode(policyParam.getWarningNotifyMode());
|
||||
resolveAlarmNotifyMode(policyParam.getAlarmNotifyMode());
|
||||
|
||||
Map<String, Boolean> driveMap = new LinkedHashMap<>();
|
||||
for (DiskMonitorParam.TargetParam targetParam : param.getTargets()) {
|
||||
String driveLetter = normalizeDriveLetter(targetParam.getDriveLetter());
|
||||
if (driveMap.put(driveLetter, Boolean.TRUE) != null) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.DRIVE_LETTER_REPEAT);
|
||||
}
|
||||
if (targetParam.getAlarmUsagePercent() < targetParam.getWarningUsagePercent()) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.USAGE_PERCENT_INVALID);
|
||||
}
|
||||
if (Boolean.TRUE.equals(targetParam.getNotifyPathEnabled())
|
||||
&& (targetParam.getNotifyPathList() == null || targetParam.getNotifyPathList().isEmpty())) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_PATH_EMPTY);
|
||||
}
|
||||
if (Boolean.TRUE.equals(targetParam.getNotifyHttpEnabled())
|
||||
&& (targetParam.getNotifyHttpList() == null || targetParam.getNotifyHttpList().isEmpty())) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_HTTP_EMPTY);
|
||||
}
|
||||
if (targetParam.getNotifyPathList() != null) {
|
||||
for (DiskMonitorNotifyPathItem item : targetParam.getNotifyPathList()) {
|
||||
if (StrUtil.isBlank(item.getPath())) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_PATH_VALUE_EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetParam.getNotifyHttpList() != null) {
|
||||
for (DiskMonitorNotifyHttpItem item : targetParam.getNotifyHttpList()) {
|
||||
validateHttpUrl(item.getUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveWarningNotifyMode(String mode) {
|
||||
String value = StrUtil.blankToDefault(mode, DiskMonitorConstant.WARNING_NOTIFY_MODE_STATUS_CHANGE).trim().toUpperCase(Locale.ROOT);
|
||||
if (!DiskMonitorConstant.WARNING_NOTIFY_MODE_STATUS_CHANGE.equals(value)) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.POLICY_MODE_INVALID);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private String resolveAlarmNotifyMode(String mode) {
|
||||
String value = StrUtil.blankToDefault(mode, DiskMonitorConstant.ALARM_NOTIFY_MODE_EVERY_TIME).trim().toUpperCase(Locale.ROOT);
|
||||
if (!DiskMonitorConstant.ALARM_NOTIFY_MODE_EVERY_TIME.equals(value)) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.POLICY_MODE_INVALID);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void validateHttpUrl(String url) {
|
||||
try {
|
||||
URL httpUrl = new URL(url);
|
||||
httpUrl.toURI();
|
||||
String protocol = httpUrl.getProtocol();
|
||||
if (!"http".equalsIgnoreCase(protocol) && !"https".equalsIgnoreCase(protocol)) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_HTTP_URL_INVALID);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.NOTIFY_HTTP_URL_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private String normalizeDriveLetter(String driveLetter) {
|
||||
if (StrUtil.isBlank(driveLetter)) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.DRIVE_LETTER_FORMAT_ERROR);
|
||||
}
|
||||
String normalized = driveLetter.trim().toUpperCase(Locale.ROOT);
|
||||
if (!normalized.matches("^[A-Z]:$")) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.DRIVE_LETTER_FORMAT_ERROR);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private LocalTime parseTime(String time) {
|
||||
try {
|
||||
return LocalTime.parse(time, TIME_FORMATTER);
|
||||
} catch (DateTimeParseException exception) {
|
||||
throw new BusinessException(DiskMonitorResponseEnum.DAILY_RUN_TIME_FORMAT_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private int boolToInt(Boolean value, boolean defaultValue) {
|
||||
return Boolean.TRUE.equals(value == null ? defaultValue : value) ? 1 : 0;
|
||||
}
|
||||
|
||||
private boolean isEnabled(Integer value) {
|
||||
return value != null && value == 1;
|
||||
}
|
||||
|
||||
private String writeValueAsJson(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (JsonProcessingException exception) {
|
||||
throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private List<DiskMonitorNotifyPathItem> parsePathItems(String json) {
|
||||
return readJsonList(json, PATH_LIST_TYPE);
|
||||
}
|
||||
|
||||
private List<DiskMonitorNotifyHttpItem> parseHttpItems(String json) {
|
||||
return readJsonList(json, HTTP_LIST_TYPE);
|
||||
}
|
||||
|
||||
private <T> List<T> readJsonList(String json, TypeReference<List<T>> typeReference) {
|
||||
if (StrUtil.isBlank(json)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
List<T> result = objectMapper.readValue(json, typeReference);
|
||||
return result == null ? new ArrayList<>() : result;
|
||||
} catch (Exception exception) {
|
||||
throw new BusinessException(CommonResponseEnum.JSON_CONVERT_EXCEPTION, exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private DiskMonitorVO.PolicyVO toPolicyVO(DiskMonitorPolicy policy) {
|
||||
DiskMonitorVO.PolicyVO policyVO = new DiskMonitorVO.PolicyVO();
|
||||
policyVO.setId(policy.getId());
|
||||
policyVO.setPolicyName(policy.getPolicyName());
|
||||
policyVO.setMonitorEnabled(isEnabled(policy.getMonitorEnabled()));
|
||||
policyVO.setRunOnAppStart(isEnabled(policy.getRunOnAppStart()));
|
||||
policyVO.setDailyRunTime(policy.getDailyRunTime() == null ? null : policy.getDailyRunTime().format(TIME_FORMATTER));
|
||||
policyVO.setWarningNotifyMode(policy.getWarningNotifyMode());
|
||||
policyVO.setAlarmNotifyMode(policy.getAlarmNotifyMode());
|
||||
policyVO.setLastJobId(policy.getLastJobId());
|
||||
policyVO.setRemark(policy.getRemark());
|
||||
return policyVO;
|
||||
}
|
||||
|
||||
private DiskMonitorVO.TargetVO toTargetVO(DiskMonitorTarget target) {
|
||||
DiskMonitorVO.TargetVO targetVO = new DiskMonitorVO.TargetVO();
|
||||
targetVO.setId(target.getId());
|
||||
targetVO.setDriveLetter(target.getDriveLetter());
|
||||
targetVO.setMonitorEnabled(isEnabled(target.getMonitorEnabled()));
|
||||
targetVO.setWarningUsagePercent(target.getWarningUsagePercent());
|
||||
targetVO.setAlarmUsagePercent(target.getAlarmUsagePercent());
|
||||
targetVO.setNotifyPathEnabled(isEnabled(target.getNotifyPathEnabled()));
|
||||
targetVO.setNotifyPathList(parsePathItems(target.getNotifyPathListJson()));
|
||||
targetVO.setNotifyHttpEnabled(isEnabled(target.getNotifyHttpEnabled()));
|
||||
targetVO.setNotifyHttpList(parseHttpItems(target.getNotifyHttpListJson()));
|
||||
targetVO.setLastStatus(target.getLastStatus());
|
||||
targetVO.setLastScanTime(formatDateTime(target.getLastScanTime()));
|
||||
targetVO.setLastUsedPercent(target.getLastUsedPercent());
|
||||
targetVO.setRemark(target.getRemark());
|
||||
return targetVO;
|
||||
}
|
||||
|
||||
private String formatDateTime(LocalDateTime dateTime) {
|
||||
return dateTime == null ? null : dateTime.format(DATE_TIME_FORMATTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
CREATE TABLE IF NOT EXISTS `disk_monitor_policy` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`policy_name` VARCHAR(100) NOT NULL DEFAULT '默认磁盘监控策略' COMMENT '策略名称',
|
||||
`monitor_enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用监控:0否 1是',
|
||||
`run_on_app_start` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '应用启动后是否执行一次:0否 1是',
|
||||
`daily_run_time` TIME NOT NULL COMMENT '每日统一执行时间',
|
||||
`warning_notify_mode` VARCHAR(32) NOT NULL DEFAULT 'STATUS_CHANGE' COMMENT '预警通知模式',
|
||||
`alarm_notify_mode` VARCHAR(32) NOT NULL DEFAULT 'EVERY_TIME' COMMENT '告警通知模式',
|
||||
`last_job_id` BIGINT NULL COMMENT '最近一次任务ID',
|
||||
`remark` VARCHAR(500) NULL COMMENT '备注',
|
||||
`created_by` VARCHAR(64) NULL COMMENT '创建人',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_by` VARCHAR(64) NULL COMMENT '更新人',
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='磁盘监控全局策略表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `disk_monitor_target` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`policy_id` BIGINT NOT NULL COMMENT '策略ID',
|
||||
`drive_letter` VARCHAR(10) NOT NULL COMMENT '盘符,例如 C:',
|
||||
`monitor_enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用监控:0否 1是',
|
||||
`warning_usage_percent` TINYINT UNSIGNED NOT NULL COMMENT '预警使用率阈值',
|
||||
`alarm_usage_percent` TINYINT UNSIGNED NOT NULL COMMENT '告警使用率阈值',
|
||||
`notify_path_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用路径通知:0否 1是',
|
||||
`notify_path_list_json` JSON NULL COMMENT '路径通知目标列表JSON',
|
||||
`notify_http_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用HTTP通知:0否 1是',
|
||||
`notify_http_list_json` JSON NULL COMMENT 'HTTP通知目标列表JSON',
|
||||
`last_status` VARCHAR(32) NOT NULL DEFAULT 'UNKNOWN' COMMENT '最近一次状态',
|
||||
`last_scan_time` DATETIME NULL COMMENT '最近扫描时间',
|
||||
`last_used_percent` DECIMAL(5,2) NULL COMMENT '最近一次使用率',
|
||||
`remark` VARCHAR(500) NULL COMMENT '备注',
|
||||
`created_by` VARCHAR(64) NULL COMMENT '创建人',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updated_by` VARCHAR(64) NULL COMMENT '更新人',
|
||||
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_drive_letter` (`drive_letter`),
|
||||
KEY `idx_policy_id` (`policy_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='磁盘监控盘符配置表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `disk_monitor_job` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`job_no` VARCHAR(64) NOT NULL COMMENT '任务编号',
|
||||
`job_source` VARCHAR(32) NOT NULL COMMENT '任务来源',
|
||||
`planned_time` DATETIME NULL COMMENT '计划执行时间',
|
||||
`started_at` DATETIME NOT NULL COMMENT '开始时间',
|
||||
`finished_at` DATETIME NULL COMMENT '结束时间',
|
||||
`job_status` VARCHAR(32) NOT NULL COMMENT '任务状态',
|
||||
`target_count` INT NOT NULL DEFAULT 0 COMMENT '计划扫描盘符数量',
|
||||
`success_count` INT NOT NULL DEFAULT 0 COMMENT '成功扫描数量',
|
||||
`warning_count` INT NOT NULL DEFAULT 0 COMMENT '预警数量',
|
||||
`alarm_count` INT NOT NULL DEFAULT 0 COMMENT '告警数量',
|
||||
`message` VARCHAR(1000) NULL COMMENT '结果说明',
|
||||
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_job_no` (`job_no`),
|
||||
KEY `idx_job_source` (`job_source`),
|
||||
KEY `idx_started_at` (`started_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='磁盘监控任务批次表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `disk_monitor_result` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`job_id` BIGINT NOT NULL COMMENT '任务ID',
|
||||
`target_id` BIGINT NOT NULL COMMENT '盘符配置ID',
|
||||
`drive_letter` VARCHAR(10) NOT NULL COMMENT '盘符',
|
||||
`total_bytes` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '总容量字节数',
|
||||
`used_bytes` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已使用字节数',
|
||||
`free_bytes` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '剩余字节数',
|
||||
`used_percent` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '使用率',
|
||||
`current_status` VARCHAR(32) NOT NULL COMMENT '当前状态',
|
||||
`previous_status` VARCHAR(32) NOT NULL DEFAULT 'UNKNOWN' COMMENT '上一次状态',
|
||||
`status_changed` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '状态是否变化:0否 1是',
|
||||
`should_notify` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '本次是否通知:0否 1是',
|
||||
`notify_reason` VARCHAR(32) NOT NULL DEFAULT 'NO_NOTIFY' COMMENT '通知原因',
|
||||
`scan_time` DATETIME NOT NULL COMMENT '扫描时间',
|
||||
`message` VARCHAR(1000) NULL COMMENT '扫描说明',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_job_id` (`job_id`),
|
||||
KEY `idx_target_id` (`target_id`),
|
||||
KEY `idx_drive_letter` (`drive_letter`),
|
||||
KEY `idx_scan_time` (`scan_time`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='磁盘监控结果表';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `disk_monitor_notify_log` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`job_id` BIGINT NOT NULL COMMENT '任务ID',
|
||||
`result_id` BIGINT NOT NULL COMMENT '结果ID',
|
||||
`target_id` BIGINT NOT NULL COMMENT '盘符配置ID',
|
||||
`drive_letter` VARCHAR(10) NOT NULL COMMENT '盘符',
|
||||
`notify_level` VARCHAR(32) NOT NULL COMMENT '通知级别',
|
||||
`channel_type` VARCHAR(32) NOT NULL COMMENT '通知通道类型',
|
||||
`channel_target` VARCHAR(1000) NOT NULL COMMENT '通知目标',
|
||||
`notify_title` VARCHAR(255) NOT NULL COMMENT '通知标题',
|
||||
`notify_content` TEXT NOT NULL COMMENT '通知内容',
|
||||
`send_status` VARCHAR(32) NOT NULL COMMENT '发送状态',
|
||||
`response_message` VARCHAR(2000) NULL COMMENT '响应结果或异常信息',
|
||||
`sent_at` DATETIME NOT NULL COMMENT '发送时间',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_job_id` (`job_id`),
|
||||
KEY `idx_result_id` (`result_id`),
|
||||
KEY `idx_target_id` (`target_id`),
|
||||
KEY `idx_sent_at` (`sent_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='磁盘监控通知日志表';
|
||||
22
systemmonitor/pom.xml
Normal file
22
systemmonitor/pom.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?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>CN_Tool</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>systemmonitor</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<name>systemmonitor</name>
|
||||
<description>System monitor capability aggregator.</description>
|
||||
|
||||
<modules>
|
||||
<module>disk-monitor</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
371
tools/mms-mapping/API-getIcdMmsJson.md
Normal file
371
tools/mms-mapping/API-getIcdMmsJson.md
Normal file
@@ -0,0 +1,371 @@
|
||||
# getIcdMmsJson 标准 API 调试文档
|
||||
|
||||
## 1. 文档范围
|
||||
|
||||
本文档用于说明 `mms-mapping` 模块统一调试接口 `getIcdMmsJson` 的标准调用方式、请求结构、响应规则和联调注意事项。
|
||||
|
||||
本文档内容以当前源码为准,主要对照以下实现:
|
||||
|
||||
- `tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/controller/MappingController.java`
|
||||
- `tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/component/MappingRequestConverter.java`
|
||||
- `tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/component/MappingResponseConverter.java`
|
||||
- `tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/service/impl/MappingTaskServiceImpl.java`
|
||||
- `tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/component/MappingGenerationService.java`
|
||||
- `tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/param/GenerateMappingFromIcdRequest.java`
|
||||
- `tools/mms-mapping/src/main/java/com/njcn/gather/icd/mapping/pojo/vo/MappingTaskResponse.java`
|
||||
|
||||
说明:
|
||||
|
||||
- 本文档仅描述接口契约和调试方式,不改动业务代码。
|
||||
- 本次未执行 `mvn` 编译、打包或真实接口联调。
|
||||
- 如文档与运行结果冲突,以源码和实际部署配置为准。
|
||||
|
||||
## 2. 接口基本信息
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| 接口名称 | `getIcdMmsJson` |
|
||||
| 请求方法 | `POST` |
|
||||
| 请求路径 | `/api/mms-mapping/get-icd-mms-json` |
|
||||
| Content-Type | `multipart/form-data` |
|
||||
| 控制器入口 | `MappingController#getIcdMmsJson` |
|
||||
| 请求组成 | `icdFile` 文件 Part + `request` JSON Part |
|
||||
| 正常业务响应体 | `MappingTaskResponse` |
|
||||
|
||||
## 3. 接口职责
|
||||
|
||||
该接口是 `mms-mapping` 模块的统一调试入口,串联以下两个阶段:
|
||||
|
||||
1. 上传 ICD 文件并完成解析,生成 `icdDocument` 和 `indexCandidates`
|
||||
2. 根据 `request.indexSelection` 判断是否继续生成正式 `mappingJson`
|
||||
|
||||
接口行为分为三种典型结果:
|
||||
|
||||
1. `request.indexSelection` 未传或为空
|
||||
返回 `NEED_INDEX_SELECTION`,用于引导前端或调试人员先确认标签与 `lnInst` 的绑定关系。
|
||||
2. `request.indexSelection` 已传但校验不通过
|
||||
返回 `NEED_INDEX_SELECTION`,同时通过 `problems` 给出不合法原因,要求重新选择。
|
||||
3. `request.indexSelection` 校验通过
|
||||
返回 `SUCCESS`,输出正式 `mappingJson`,必要时同时落盘并返回 `savedPath`。
|
||||
|
||||
补充说明:
|
||||
|
||||
- 该接口每次都会重新解析上传的 ICD 文件,因此第二次调试仍然必须重新上传 ICD 文件。
|
||||
- 该接口正常进入业务编排后,返回体类型为 `MappingTaskResponse`。
|
||||
- 如果异常发生在控制器参数绑定或请求转换阶段,例如文件为空、Part 缺失、JSON Part 解析失败,则由全局异常处理器统一包装为 `HttpResult<String>`,而不是 `MappingTaskResponse`。
|
||||
|
||||
## 4. 请求规范
|
||||
|
||||
### 4.1 multipart/form-data Part 说明
|
||||
|
||||
| Part 名称 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `icdFile` | File | 是 | ICD 文件,不能为空 |
|
||||
| `request` | JSON Part | 是 | 生成参数,必须按 `application/json` 发送 |
|
||||
|
||||
说明:
|
||||
|
||||
- `request` Part 不能省略。即使第一次只想拿候选结果,也必须传一个最小 JSON。
|
||||
- `request.indexSelection` 可以省略或传空数组,此时接口只返回候选结果,不生成正式映射。
|
||||
|
||||
### 4.2 request JSON 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "2026-04-22",
|
||||
"author": "debug-user",
|
||||
"saveToDisk": false,
|
||||
"prettyJson": true,
|
||||
"outputDir": "D:/temp/mms-output",
|
||||
"indexSelection": [
|
||||
{
|
||||
"groupKey": "harm",
|
||||
"groupDesc": "谐波数据",
|
||||
"bindings": [
|
||||
{
|
||||
"reportName": "brcbStHarm",
|
||||
"dataSetName": "dsStHarm",
|
||||
"label": "A相",
|
||||
"lnInst": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 request 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `version` | String | 否 | 输出版本号。未传或空白时,后端按当天日期补齐,格式为 `yyyy-MM-dd` |
|
||||
| `author` | String | 否 | 作者。未传或空白时,回退到配置项 `icd.mapping.default-author`,默认值为 `system` |
|
||||
| `saveToDisk` | boolean | 否 | 是否将生成结果写入磁盘 |
|
||||
| `prettyJson` | boolean | 否 | 是否输出格式化 JSON。`true` 为美化 JSON,`false` 为紧凑 JSON |
|
||||
| `outputDir` | String | 否 | 输出目录。未传或空白时,先回退到配置项 `icd.mapping.default-output-dir`;如果配置也为空,最终落到当前工作目录 |
|
||||
| `indexSelection` | Array | 否 | 标签与 `lnInst` 的最终绑定关系。未传或为空时,只返回候选结果 |
|
||||
|
||||
### 4.4 indexSelection 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `groupKey` | String | 是 | 分组唯一键,必须使用第一次响应里返回的原值 |
|
||||
| `groupDesc` | String | 否 | 分组中文描述,便于调试查看 |
|
||||
| `bindings` | Array | 是 | 当前业务分组下最终确认的绑定关系列表 |
|
||||
|
||||
### 4.5 bindings 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `reportName` | String | 是 | 绑定发生在哪个报告上,例如 `brcbStHarm` |
|
||||
| `dataSetName` | String | 是 | 绑定发生在哪个数据集上,例如 `dsStHarm` |
|
||||
| `label` | String | 是 | 业务标签,例如 `A相`、`最大值`、`实时数据` |
|
||||
| `lnInst` | String | 是 | 标签最终绑定到的逻辑节点实例值,例如 `1`、`2`、`3` |
|
||||
|
||||
## 5. 标准调试流程
|
||||
|
||||
### 5.1 第一次调试:只获取候选结果
|
||||
|
||||
用途:
|
||||
|
||||
- 上传 ICD 文件
|
||||
- 获取 `icdDocument`
|
||||
- 获取 `indexCandidates`
|
||||
- 确认每个业务分组下可选的 `reportName`、`dataSetName` 和 `availableLnInstValues`
|
||||
|
||||
调用要求:
|
||||
|
||||
- `request` Part 仍然必须传
|
||||
- `request.indexSelection` 可以不传,或传空数组
|
||||
|
||||
预期结果:
|
||||
|
||||
- `status = NEED_INDEX_SELECTION`
|
||||
- 响应中返回 `icdDocument`
|
||||
- 响应中返回 `indexCandidates`
|
||||
|
||||
### 5.2 第二次调试:带索引绑定生成正式结果
|
||||
|
||||
用途:
|
||||
|
||||
- 根据第一次返回的 `indexCandidates` 组装 `request.indexSelection`
|
||||
- 再次上传同一个 ICD 文件
|
||||
- 生成正式 `mappingJson`
|
||||
|
||||
调用要求:
|
||||
|
||||
- 必须继续上传 `icdFile`
|
||||
- `groupKey` 必须沿用第一次返回值
|
||||
- `reportName`、`dataSetName`、`lnInst` 必须与第一次返回的候选结果匹配
|
||||
|
||||
预期结果:
|
||||
|
||||
- `status = SUCCESS`
|
||||
- 响应中返回 `mappingJson`
|
||||
- 当 `saveToDisk = true` 时,响应中额外返回 `savedPath`
|
||||
|
||||
### 5.3 第二次调试但绑定不合法
|
||||
|
||||
适用场景:
|
||||
|
||||
- `groupKey` 与候选结果不匹配
|
||||
- `reportName` 或 `dataSetName` 不在候选集中
|
||||
- `lnInst` 不在 `availableLnInstValues` 内
|
||||
- 绑定关系缺失、不完整或结构错误
|
||||
|
||||
预期结果:
|
||||
|
||||
- `status = NEED_INDEX_SELECTION`
|
||||
- 响应中仍然返回 `icdDocument` 和 `indexCandidates`
|
||||
- `problems` 返回具体问题列表,要求重新确认绑定关系
|
||||
|
||||
## 6. 响应规范
|
||||
|
||||
### 6.1 正常业务响应体
|
||||
|
||||
接口正常进入业务编排后,统一返回 `MappingTaskResponse`。该对象使用了 `@JsonInclude(JsonInclude.Include.NON_EMPTY)`,空字段和空集合不会参与序列化。
|
||||
|
||||
基础字段说明:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `status` | Enum | 本次处理状态,可能为 `SUCCESS`、`NEED_INDEX_SELECTION`、`FAILED` |
|
||||
| `message` | String | 状态说明或错误提示 |
|
||||
| `icdDocument` | Object | 需要重新选择索引时返回的 ICD 解析结果 |
|
||||
| `mappingJson` | String | 正式生成成功后的映射 JSON 文本 |
|
||||
| `savedPath` | String | 结果已落盘时返回的绝对路径 |
|
||||
| `indexCandidates` | Array | 待绑定状态下返回的索引候选分组 |
|
||||
| `problems` | Array | 模板校验、候选分析或绑定校验问题 |
|
||||
|
||||
字段出现规则:
|
||||
|
||||
| 状态 | 必有字段 | 可能出现字段 |
|
||||
| --- | --- | --- |
|
||||
| `SUCCESS` | `status`、`message`、`mappingJson` | `savedPath`、`problems` |
|
||||
| `NEED_INDEX_SELECTION` | `status`、`message`、`icdDocument`、`indexCandidates` | `problems` |
|
||||
| `FAILED` | `status`、`message` | `problems` |
|
||||
|
||||
### 6.2 NEED_INDEX_SELECTION 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "NEED_INDEX_SELECTION",
|
||||
"message": "索引配置缺失,请根据候选信息完成标签与数字索引的绑定后重新提交",
|
||||
"icdDocument": {
|
||||
"fileName": "demo.icd",
|
||||
"iedName": "IED1",
|
||||
"ldInst": "LD0",
|
||||
"ldPrefix": "LD",
|
||||
"logicalNodes": [
|
||||
{
|
||||
"lnInst": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"indexCandidates": [
|
||||
{
|
||||
"groupKey": "harm",
|
||||
"groupDesc": "谐波数据",
|
||||
"reportCount": 1,
|
||||
"templateLabels": [
|
||||
"A相",
|
||||
"B相",
|
||||
"C相"
|
||||
],
|
||||
"reports": [
|
||||
{
|
||||
"reportName": "brcbStHarm",
|
||||
"dataSetName": "dsStHarm",
|
||||
"reportDesc": "谐波报告",
|
||||
"availableLnInstValues": [
|
||||
"1",
|
||||
"2",
|
||||
"3"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `icdDocument` 实际字段可能比示例更多。
|
||||
- 如果本次是“索引配置不合法”而不是“索引配置缺失”,通常还会返回 `problems`。
|
||||
|
||||
### 6.3 SUCCESS 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"message": "映射生成成功",
|
||||
"mappingJson": "{\n \"version\": \"2026-04-22\",\n \"author\": \"debug-user\",\n \"ied\": \"IED1\",\n \"ld\": \"LD\",\n \"instList\": []\n}"
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `mappingJson` 是字符串字段,字段值本身是一段 JSON 文本。
|
||||
- 当 `saveToDisk = true` 时,响应中还会额外返回 `savedPath`。
|
||||
|
||||
### 6.4 FAILED 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "FAILED",
|
||||
"message": "映射生成失败:加载 DefaultCfg.txt 失败:默认模板文件不存在:template/DefaultCfg.txt",
|
||||
"problems": [
|
||||
"加载 DefaultCfg.txt 失败:默认模板文件不存在:template/DefaultCfg.txt"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `FAILED` 主要对应服务编排阶段捕获到的运行异常,例如 ICD 解析、模板加载、映射生成、序列化或落盘失败。
|
||||
- 并非所有错误都会进入 `FAILED`。如果异常发生在控制器参数绑定或请求转换阶段,会走全局异常处理器,而不是这里的业务响应结构。
|
||||
|
||||
## 7. 全局异常响应说明
|
||||
|
||||
以下场景通常不会返回 `MappingTaskResponse`,而是由 `GlobalBusinessExceptionHandler` 统一包装:
|
||||
|
||||
- `icdFile` 缺失或为空
|
||||
- `request` Part 缺失
|
||||
- `request` Part 的 `Content-Type` 不是 `application/json`
|
||||
- `multipart/form-data` 结构不合法
|
||||
- JSON 反序列化失败或框架参数绑定失败
|
||||
|
||||
这类异常最终会包装为统一的 `HttpResult<String>` 响应,具体字段结构以全局公共响应定义为准,本文不展开其完整协议,只强调:
|
||||
|
||||
- 不能把这类错误等同理解为 `MappingTaskResponse.status = FAILED`
|
||||
- 联调时应先区分“业务响应体”与“全局异常包装”
|
||||
|
||||
## 8. 调试示例
|
||||
|
||||
### 8.1 curl 示例:第一次调用,只获取候选结果
|
||||
|
||||
```powershell
|
||||
curl.exe -X POST "http://localhost:8080/api/mms-mapping/get-icd-mms-json" `
|
||||
-H "Accept: application/json" `
|
||||
-F 'icdFile=@D:/data/demo.icd' `
|
||||
-F 'request={"prettyJson":true,"saveToDisk":false};type=application/json'
|
||||
```
|
||||
|
||||
### 8.2 curl 示例:第二次调用,带索引绑定直接生成 MMS JSON
|
||||
|
||||
```powershell
|
||||
curl.exe -X POST "http://localhost:8080/api/mms-mapping/get-icd-mms-json" `
|
||||
-H "Accept: application/json" `
|
||||
-F 'icdFile=@D:/data/demo.icd' `
|
||||
-F 'request={"version":"2026-04-22","author":"debug-user","prettyJson":true,"saveToDisk":false,"indexSelection":[{"groupKey":"harm","groupDesc":"谐波数据","bindings":[{"reportName":"brcbStHarm","dataSetName":"dsStHarm","label":"A相","lnInst":"1"},{"reportName":"brcbStHarm","dataSetName":"dsStHarm","label":"B相","lnInst":"2"},{"reportName":"brcbStHarm","dataSetName":"dsStHarm","label":"C相","lnInst":"3"}]}]};type=application/json'
|
||||
```
|
||||
|
||||
## 9. Postman 调试要点
|
||||
|
||||
1. `Body` 选择 `form-data`
|
||||
2. `icdFile` 类型选择 `File`
|
||||
3. `request` 保持文本输入,但该 Part 的 `Content-Type` 必须显式设置为 `application/json`
|
||||
4. 第一次调试不要省略 `request` Part,只是不传 `indexSelection`
|
||||
5. 第二次调试时必须继续上传 ICD 文件,并严格按第一次返回的候选结果组装绑定关系
|
||||
|
||||
## 10. 常见问题
|
||||
|
||||
### 10.1 为什么第一次调试也必须传 `request`
|
||||
|
||||
因为控制器方法签名使用的是 `@RequestPart("request") GenerateMappingFromIcdRequest request`,该 Part 本身就是必填参数。第一次调试可以只传最小 JSON,但不能完全省略。
|
||||
|
||||
### 10.2 为什么没有传 `indexSelection`,却没有返回 `FAILED`
|
||||
|
||||
这是接口设计的正常行为。`indexSelection` 缺失或为空时,业务语义不是“接口执行失败”,而是“还需要前端继续确认索引绑定”,因此返回的是 `NEED_INDEX_SELECTION`。
|
||||
|
||||
### 10.3 `saveToDisk=true` 但没有传 `outputDir`,结果会保存到哪里
|
||||
|
||||
处理顺序如下:
|
||||
|
||||
1. 先读取请求中的 `outputDir`
|
||||
2. 如果请求空白,则回退到配置项 `icd.mapping.default-output-dir`
|
||||
3. 如果配置项也为空,则最终落到当前工作目录
|
||||
|
||||
### 10.4 `version` 不传时会变成什么
|
||||
|
||||
后端在正式生成映射文档时,会把空白 `version` 自动补成当天日期,格式为 `yyyy-MM-dd`。
|
||||
|
||||
### 10.5 `mappingJson` 为什么是字符串,不是嵌套对象
|
||||
|
||||
因为当前响应结构中 `mappingJson` 定义为 `String`,接口返回的是一段已经序列化好的 JSON 文本,而不是再次展开后的对象结构。
|
||||
|
||||
### 10.6 什么情况下会返回 `problems`
|
||||
|
||||
`problems` 主要用于承载以下问题:
|
||||
|
||||
- 默认模板校验问题
|
||||
- 索引候选分析问题
|
||||
- `indexSelection` 绑定校验问题
|
||||
- 服务编排阶段捕获到的异常原因
|
||||
|
||||
## 11. 当前边界
|
||||
|
||||
- 当前文档仅覆盖 `getIcdMmsJson` 接口,不覆盖 `get-icd` 与 `get-mms-json` 的独立接口文档
|
||||
- 当前文档重点描述业务返回体与调试方式,不展开全局 `HttpResult` 的完整协议
|
||||
- 示例中的 `icdDocument`、`indexCandidates` 和 `mappingJson` 为结构化示意,实际字段数量与内容以运行结果为准
|
||||
221
tools/mms-mapping/README.md
Normal file
221
tools/mms-mapping/README.md
Normal file
@@ -0,0 +1,221 @@
|
||||
# mms-mapping
|
||||
|
||||
`mms-mapping` 模块负责解析 ICD 文件并生成 MMS 映射数据。当前统一调试入口为 `getIcdMmsJson`,该接口同时覆盖“先解析 ICD 获取索引候选”和“确认索引后生成正式 MMS JSON”两类场景。
|
||||
|
||||
## 1. 接口信息
|
||||
|
||||
- 路径:`POST /api/mms-mapping/get-icd-mms-json`
|
||||
- Content-Type:`multipart/form-data`
|
||||
- 控制器:`MappingController#getIcdMmsJson`
|
||||
- 说明:上传 ICD 文件后,后端先解析 ICD,再根据 `request.indexSelection` 决定返回候选结果还是正式映射结果
|
||||
|
||||
说明:
|
||||
|
||||
- `request.indexSelection` 为空时,接口返回 `NEED_INDEX_SELECTION`,用于指导前端或调试人员完成标签与 `lnInst` 绑定。
|
||||
- `request.indexSelection` 有效时,接口返回 `SUCCESS`,直接给出 `mappingJson`,必要时同时落盘。
|
||||
- 该接口每次都会重新解析上传的 ICD 文件,因此二次调试时仍需要重新上传同一个 ICD 文件。
|
||||
|
||||
## 2. 调试流程
|
||||
|
||||
### 2.1 第一次调试
|
||||
|
||||
用途:只上传 ICD,先拿到 `icdDocument` 和 `indexCandidates`。
|
||||
|
||||
预期结果:
|
||||
|
||||
- `status = NEED_INDEX_SELECTION`
|
||||
- 返回 `icdDocument`
|
||||
- 返回 `indexCandidates`
|
||||
|
||||
### 2.2 第二次调试
|
||||
|
||||
用途:根据第一次返回的 `indexCandidates` 组装 `request.indexSelection`,再次调用同一个接口生成正式 MMS JSON。
|
||||
|
||||
预期结果:
|
||||
|
||||
- `status = SUCCESS`
|
||||
- 返回 `mappingJson`
|
||||
- 当 `saveToDisk=true` 时返回 `savedPath`
|
||||
|
||||
## 3. 请求参数
|
||||
|
||||
### 3.1 form-data 参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `icdFile` | File | 是 | ICD 文件 |
|
||||
| `request` | JSON Part | 是 | 生成参数,必须按 `application/json` 发送 |
|
||||
|
||||
### 3.2 request JSON 结构
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "20260421",
|
||||
"author": "debug-user",
|
||||
"saveToDisk": false,
|
||||
"prettyJson": true,
|
||||
"outputDir": "D:/temp/mms-output",
|
||||
"indexSelection": [
|
||||
{
|
||||
"groupKey": "harm",
|
||||
"groupDesc": "谐波数据",
|
||||
"bindings": [
|
||||
{
|
||||
"reportName": "brcbStHarm",
|
||||
"dataSetName": "dsStHarm",
|
||||
"label": "A相",
|
||||
"lnInst": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 request 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `version` | String | 否 | 映射版本号;为空时由后端按当前日期补齐 |
|
||||
| `author` | String | 否 | 作者;为空时使用模块默认作者 |
|
||||
| `saveToDisk` | boolean | 否 | 是否将生成结果写入磁盘 |
|
||||
| `prettyJson` | boolean | 否 | 是否生成格式化 JSON |
|
||||
| `outputDir` | String | 否 | 输出目录;仅 `saveToDisk=true` 时生效 |
|
||||
| `indexSelection` | Array | 否 | 标签与 `lnInst` 的最终绑定关系;为空时只返回候选结果 |
|
||||
|
||||
### 3.4 indexSelection 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `groupKey` | String | 是 | 分组唯一键,必须使用第一次响应里返回的原值 |
|
||||
| `groupDesc` | String | 否 | 分组描述,便于调试查看 |
|
||||
| `bindings` | Array | 是 | 当前分组下最终确认的绑定列表 |
|
||||
|
||||
### 3.5 bindings 字段说明
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `reportName` | String | 是 | 报告名称 |
|
||||
| `dataSetName` | String | 是 | 数据集名称 |
|
||||
| `label` | String | 是 | 模板标签 |
|
||||
| `lnInst` | String | 是 | 实际绑定的逻辑节点实例值 |
|
||||
|
||||
## 4. 调试示例
|
||||
|
||||
### 4.1 第一次调用,只获取候选结果
|
||||
|
||||
```powershell
|
||||
curl.exe -X POST "http://localhost:8080/api/mms-mapping/get-icd-mms-json" `
|
||||
-H "Accept: application/json" `
|
||||
-F 'icdFile=@D:/data/demo.icd' `
|
||||
-F 'request={"version":"20260421","author":"debug-user","prettyJson":true,"saveToDisk":false};type=application/json'
|
||||
```
|
||||
|
||||
### 4.2 第二次调用,带索引绑定直接生成 MMS JSON
|
||||
|
||||
```powershell
|
||||
curl.exe -X POST "http://localhost:8080/api/mms-mapping/get-icd-mms-json" `
|
||||
-H "Accept: application/json" `
|
||||
-F 'icdFile=@D:/data/demo.icd' `
|
||||
-F 'request={"version":"20260421","author":"debug-user","prettyJson":true,"saveToDisk":false,"indexSelection":[{"groupKey":"harm","groupDesc":"谐波数据","bindings":[{"reportName":"brcbStHarm","dataSetName":"dsStHarm","label":"A相","lnInst":"1"},{"reportName":"brcbStHarm","dataSetName":"dsStHarm","label":"B相","lnInst":"2"},{"reportName":"brcbStHarm","dataSetName":"dsStHarm","label":"C相","lnInst":"3"}]}]};type=application/json'
|
||||
```
|
||||
|
||||
## 5. 响应说明
|
||||
|
||||
接口统一返回 `MappingTaskResponse`。由于响应对象使用了 `@JsonInclude(JsonInclude.Include.NON_EMPTY)`,空字段和空集合默认不会出现在最终 JSON 中。
|
||||
|
||||
### 5.1 NEED_INDEX_SELECTION
|
||||
|
||||
适用场景:未传 `indexSelection`,或绑定关系不完整、不合法。
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "NEED_INDEX_SELECTION",
|
||||
"message": "索引配置缺失,请根据候选信息完成标签与数字索引的绑定后重新提交",
|
||||
"icdDocument": {
|
||||
"fileName": "demo.icd",
|
||||
"iedName": "IED1",
|
||||
"ldInst": "LD0",
|
||||
"ldPrefix": "LD",
|
||||
"logicalNodes": [
|
||||
{
|
||||
"lnInst": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"indexCandidates": [
|
||||
{
|
||||
"groupKey": "harm",
|
||||
"groupDesc": "谐波数据",
|
||||
"reportCount": 1,
|
||||
"templateLabels": [
|
||||
"A相",
|
||||
"B相",
|
||||
"C相"
|
||||
],
|
||||
"reports": [
|
||||
{
|
||||
"reportName": "brcbStHarm",
|
||||
"dataSetName": "dsStHarm",
|
||||
"reportDesc": "谐波报告",
|
||||
"availableLnInstValues": [
|
||||
"1",
|
||||
"2",
|
||||
"3"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `icdDocument` 实际返回内容会比示例更大,这里只保留关键字段用于说明结构。
|
||||
- 如果绑定值非法,还会额外返回 `problems`,提示缺失或不匹配的绑定项。
|
||||
|
||||
### 5.2 SUCCESS
|
||||
|
||||
适用场景:`indexSelection` 校验通过,映射成功生成。
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "SUCCESS",
|
||||
"message": "映射生成成功",
|
||||
"mappingJson": "{\n \"ied\": \"IED1\",\n \"ld\": \"LD\",\n \"instList\": []\n}"
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `mappingJson` 是字符串字段,字段值本身也是一段 JSON 文本。
|
||||
- 当 `saveToDisk=true` 时,响应中还会返回 `savedPath`。
|
||||
|
||||
### 5.3 FAILED
|
||||
|
||||
适用场景:ICD 解析失败、模板校验失败、文件读取失败或其他运行异常。
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "FAILED",
|
||||
"message": "映射生成失败:ICD 文件不能为空",
|
||||
"problems": [
|
||||
"ICD 文件不能为空"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Postman 调试注意事项
|
||||
|
||||
- `Body` 选择 `form-data`。
|
||||
- `icdFile` 类型选择 `File`。
|
||||
- `request` 类型保持文本,但该 Part 的 `Content-Type` 需要设置为 `application/json`。
|
||||
- 第一次调试建议不要传 `indexSelection`,先观察 `indexCandidates` 和 `availableLnInstValues`。
|
||||
- 第二次调试时必须继续上传 ICD 文件,并按第一次返回的 `groupKey`、`reportName`、`dataSetName` 和 `availableLnInstValues` 组装绑定关系。
|
||||
|
||||
## 7. 当前限制
|
||||
|
||||
- 当前仅补充调试文档,未改动 `mms-mapping` 业务代码。
|
||||
- 当前未执行 `mvn` 编译、打包或接口联调验证。
|
||||
- 示例响应中的 `icdDocument` 和 `mappingJson` 为结构化示意,实际字段数量以运行结果为准。
|
||||
@@ -1,135 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.application;
|
||||
|
||||
import com.njcn.gather.icd.mapping.application.command.GenerateFromIcdCommand;
|
||||
import com.njcn.gather.icd.mapping.application.result.GenerateMappingResult;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.ValidationResult;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.MappingDocument;
|
||||
import com.njcn.gather.icd.mapping.domain.model.template.DefaultTemplate;
|
||||
import com.njcn.gather.icd.mapping.domain.service.DefaultTemplateLoader;
|
||||
import com.njcn.gather.icd.mapping.domain.service.IcdParserService;
|
||||
import com.njcn.gather.icd.mapping.domain.service.IndexAnalysisService;
|
||||
import com.njcn.gather.icd.mapping.domain.service.IndexValidationService;
|
||||
import com.njcn.gather.icd.mapping.domain.service.MappingGenerationService;
|
||||
import com.njcn.gather.icd.mapping.enums.GenerateStatus;
|
||||
import com.njcn.gather.icd.mapping.infrastructure.serializer.MappingDocumentSerializer;
|
||||
import com.njcn.gather.icd.mapping.infrastructure.storage.FileStorageService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 生成任务应用服务。
|
||||
*
|
||||
* 完整流程:
|
||||
* 1. 解析 ICD;
|
||||
* 2. 读取 DefaultCfg.txt;
|
||||
* 3. 按业务分组生成候选项;
|
||||
* 4. 如果用户未提交绑定关系,返回 NEED_INDEX_SELECTION;
|
||||
* 5. 如果提交了绑定关系,先做合法性校验;
|
||||
* 6. 校验通过后生成正式 MappingDocument;
|
||||
* 7. 序列化并按需落盘。
|
||||
*/
|
||||
@Service
|
||||
public class MappingTaskAppService {
|
||||
|
||||
private final IcdParserService icdParserService;
|
||||
private final DefaultTemplateLoader defaultTemplateLoader;
|
||||
private final IndexAnalysisService indexAnalysisService;
|
||||
private final IndexValidationService indexValidationService;
|
||||
private final MappingGenerationService mappingGenerationService;
|
||||
private final MappingDocumentSerializer mappingDocumentSerializer;
|
||||
private final FileStorageService fileStorageService;
|
||||
|
||||
public MappingTaskAppService(IcdParserService icdParserService,
|
||||
DefaultTemplateLoader defaultTemplateLoader,
|
||||
IndexAnalysisService indexAnalysisService,
|
||||
IndexValidationService indexValidationService,
|
||||
MappingGenerationService mappingGenerationService,
|
||||
MappingDocumentSerializer mappingDocumentSerializer,
|
||||
FileStorageService fileStorageService) {
|
||||
this.icdParserService = icdParserService;
|
||||
this.defaultTemplateLoader = defaultTemplateLoader;
|
||||
this.indexAnalysisService = indexAnalysisService;
|
||||
this.indexValidationService = indexValidationService;
|
||||
this.mappingGenerationService = mappingGenerationService;
|
||||
this.mappingDocumentSerializer = mappingDocumentSerializer;
|
||||
this.fileStorageService = fileStorageService;
|
||||
}
|
||||
|
||||
public GenerateMappingResult generateFromIcd(GenerateFromIcdCommand command) {
|
||||
GenerateMappingResult result = new GenerateMappingResult();
|
||||
try {
|
||||
// 1. 解析 ICD
|
||||
IcdDocument icdDocument = icdParserService.parse(command.getFileBytes(), command.getFileName());
|
||||
result.setIedName(icdDocument.getIedName());
|
||||
result.setLdInst(icdDocument.getLdInst());
|
||||
|
||||
// 2. 加载 DefaultCfg.txt
|
||||
DefaultTemplate template = defaultTemplateLoader.load();
|
||||
result.getProblems().addAll(template.verify());
|
||||
|
||||
// 3. 分析索引候选
|
||||
IndexAnalysis indexAnalysis = indexAnalysisService.analyze(icdDocument, template);
|
||||
result.setIndexAnalysis(indexAnalysis);
|
||||
result.getProblems().addAll(indexAnalysis.getProblems());
|
||||
|
||||
// 4. 如果没有提交任何绑定关系,则直接返回待匹配项
|
||||
if (command.getIndexSelection() == null || command.getIndexSelection().isEmpty()) {
|
||||
result.setStatus(GenerateStatus.NEED_INDEX_SELECTION);
|
||||
result.setMessage("索引配置缺失或不合法,请根据候选信息完成标签与数字索引的绑定后重新提交");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 5. 校验用户提交的绑定关系
|
||||
ValidationResult validationResult = indexValidationService.validate(indexAnalysis, command.getIndexSelection());
|
||||
if (!validationResult.isValid()) {
|
||||
result.setStatus(GenerateStatus.NEED_INDEX_SELECTION);
|
||||
result.setMessage("索引配置缺失或不合法,请根据候选信息完成标签与数字索引的绑定后重新提交");
|
||||
result.getProblems().addAll(validationResult.getProblems());
|
||||
return result;
|
||||
}
|
||||
|
||||
// 6. 生成正式映射结构
|
||||
MappingDocument mappingDocument = mappingGenerationService.generate(
|
||||
icdDocument,
|
||||
template,
|
||||
indexAnalysis,
|
||||
command.getIndexSelection(),
|
||||
command.getVersion(),
|
||||
command.getAuthor()
|
||||
);
|
||||
result.setMappingDocument(mappingDocument);
|
||||
|
||||
// 7. 序列化输出
|
||||
String mappingJson = command.isPrettyJson()
|
||||
? mappingDocumentSerializer.toPrettyJson(mappingDocument)
|
||||
: mappingDocumentSerializer.toCompactJson(mappingDocument);
|
||||
result.setMappingJson(mappingJson);
|
||||
|
||||
if (command.isSaveToDisk()) {
|
||||
String fileName = buildOutputFileName(icdDocument, command.isPrettyJson());
|
||||
String savedPath = fileStorageService.save(fileName, mappingJson, command.getOutputDir());
|
||||
result.setSavedPath(savedPath);
|
||||
}
|
||||
|
||||
result.setStatus(GenerateStatus.SUCCESS);
|
||||
result.setMessage("映射生成成功");
|
||||
return result;
|
||||
} catch (Exception ex) {
|
||||
result.setStatus(GenerateStatus.FAILED);
|
||||
result.setMessage("映射生成失败:" + ex.getMessage());
|
||||
result.getProblems().add(ex.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private String buildOutputFileName(IcdDocument icdDocument, boolean prettyJson) {
|
||||
String baseName = icdDocument.getIedName() == null ? "mapping" : icdDocument.getIedName();
|
||||
// 落盘文件名只保留安全字符,避免 IED 名称携带路径分隔符导致越界写入。
|
||||
String safeBaseName = baseName.replaceAll("[\\\\/:*?\"<>|]+", "_").trim();
|
||||
if (safeBaseName.isEmpty()) {
|
||||
safeBaseName = "mapping";
|
||||
}
|
||||
return safeBaseName + (prettyJson ? "-mapping-pretty.json" : "-mapping.json");
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.application.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 生成命令对象。
|
||||
*
|
||||
* 说明:
|
||||
* controller 层不要把 MultipartFile 和 request 直接传进领域服务;
|
||||
* 统一转成 command,便于应用层做流程编排。
|
||||
*/
|
||||
public class GenerateFromIcdCommand {
|
||||
|
||||
/** 原始文件名。 */
|
||||
private String fileName;
|
||||
|
||||
/** ICD 文件字节数组。 */
|
||||
private byte[] fileBytes;
|
||||
|
||||
/** 输出版本号。 */
|
||||
private String version;
|
||||
|
||||
/** 作者。 */
|
||||
private String author;
|
||||
|
||||
/** 是否保存到磁盘。 */
|
||||
private boolean saveToDisk;
|
||||
|
||||
/** 是否输出美化 JSON。 */
|
||||
private boolean prettyJson;
|
||||
|
||||
/** 输出目录。 */
|
||||
private String outputDir;
|
||||
|
||||
/** 用户上送的索引选择结果。 */
|
||||
private List<IndexSelectionGroupCommand> indexSelection = new ArrayList<IndexSelectionGroupCommand>();
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public byte[] getFileBytes() {
|
||||
return fileBytes;
|
||||
}
|
||||
|
||||
public void setFileBytes(byte[] fileBytes) {
|
||||
this.fileBytes = fileBytes;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public boolean isSaveToDisk() {
|
||||
return saveToDisk;
|
||||
}
|
||||
|
||||
public void setSaveToDisk(boolean saveToDisk) {
|
||||
this.saveToDisk = saveToDisk;
|
||||
}
|
||||
|
||||
public boolean isPrettyJson() {
|
||||
return prettyJson;
|
||||
}
|
||||
|
||||
public void setPrettyJson(boolean prettyJson) {
|
||||
this.prettyJson = prettyJson;
|
||||
}
|
||||
|
||||
public String getOutputDir() {
|
||||
return outputDir;
|
||||
}
|
||||
|
||||
public void setOutputDir(String outputDir) {
|
||||
this.outputDir = outputDir;
|
||||
}
|
||||
|
||||
public List<IndexSelectionGroupCommand> getIndexSelection() {
|
||||
return indexSelection;
|
||||
}
|
||||
|
||||
public void setIndexSelection(List<IndexSelectionGroupCommand> indexSelection) {
|
||||
this.indexSelection = indexSelection;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.application.command;
|
||||
|
||||
/**
|
||||
* 应用层单条绑定命令。
|
||||
*/
|
||||
public class IndexBindingCommand {
|
||||
|
||||
/** 报告名。 */
|
||||
private String reportName;
|
||||
|
||||
/** 数据集名。 */
|
||||
private String dataSetName;
|
||||
|
||||
/** 标签。 */
|
||||
private String label;
|
||||
|
||||
/** 绑定到的 lnInst 数字。 */
|
||||
private String lnInst;
|
||||
|
||||
public String getReportName() {
|
||||
return reportName;
|
||||
}
|
||||
|
||||
public void setReportName(String reportName) {
|
||||
this.reportName = reportName;
|
||||
}
|
||||
|
||||
public String getDataSetName() {
|
||||
return dataSetName;
|
||||
}
|
||||
|
||||
public void setDataSetName(String dataSetName) {
|
||||
this.dataSetName = dataSetName;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLnInst() {
|
||||
return lnInst;
|
||||
}
|
||||
|
||||
public void setLnInst(String lnInst) {
|
||||
this.lnInst = lnInst;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.application.command;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 应用层分组选择命令。
|
||||
*/
|
||||
public class IndexSelectionGroupCommand {
|
||||
|
||||
/** 分组唯一键。 */
|
||||
private String groupKey;
|
||||
|
||||
/** 分组中文描述。 */
|
||||
private String groupDesc;
|
||||
|
||||
/** 当前分组下的多条绑定关系。 */
|
||||
private List<IndexBindingCommand> bindings = new ArrayList<IndexBindingCommand>();
|
||||
|
||||
public String getGroupKey() {
|
||||
return groupKey;
|
||||
}
|
||||
|
||||
public void setGroupKey(String groupKey) {
|
||||
this.groupKey = groupKey;
|
||||
}
|
||||
|
||||
public String getGroupDesc() {
|
||||
return groupDesc;
|
||||
}
|
||||
|
||||
public void setGroupDesc(String groupDesc) {
|
||||
this.groupDesc = groupDesc;
|
||||
}
|
||||
|
||||
public List<IndexBindingCommand> getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public void setBindings(List<IndexBindingCommand> bindings) {
|
||||
this.bindings = bindings;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.application.result;
|
||||
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.MappingDocument;
|
||||
import com.njcn.gather.icd.mapping.enums.GenerateStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 应用层返回对象。统一封装成功、需要选择索引、失败三类结果。
|
||||
*/
|
||||
public class GenerateMappingResult {
|
||||
private GenerateStatus status;
|
||||
private String message;
|
||||
private String iedName;
|
||||
private String ldInst;
|
||||
private IndexAnalysis indexAnalysis;
|
||||
private MappingDocument mappingDocument;
|
||||
private String mappingJson;
|
||||
private String savedPath;
|
||||
private List<String> problems = new ArrayList<String>();
|
||||
|
||||
public GenerateStatus getStatus() { return status; }
|
||||
public void setStatus(GenerateStatus status) { this.status = status; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public String getIedName() { return iedName; }
|
||||
public void setIedName(String iedName) { this.iedName = iedName; }
|
||||
public String getLdInst() { return ldInst; }
|
||||
public void setLdInst(String ldInst) { this.ldInst = ldInst; }
|
||||
public IndexAnalysis getIndexAnalysis() { return indexAnalysis; }
|
||||
public void setIndexAnalysis(IndexAnalysis indexAnalysis) { this.indexAnalysis = indexAnalysis; }
|
||||
public MappingDocument getMappingDocument() { return mappingDocument; }
|
||||
public void setMappingDocument(MappingDocument mappingDocument) { this.mappingDocument = mappingDocument; }
|
||||
public String getMappingJson() { return mappingJson; }
|
||||
public void setMappingJson(String mappingJson) { this.mappingJson = mappingJson; }
|
||||
public String getSavedPath() { return savedPath; }
|
||||
public void setSavedPath(String savedPath) { this.savedPath = savedPath; }
|
||||
public List<String> getProblems() { return problems; }
|
||||
public void setProblems(List<String> problems) { this.problems = problems; }
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.njcn.gather.icd.mapping.domain.service;
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.njcn.gather.icd.mapping.config.MappingModuleConfig;
|
||||
import com.njcn.gather.icd.mapping.domain.model.template.DefaultTemplate;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.template.DefaultTemplate;
|
||||
import com.njcn.gather.icd.mapping.utils.JsonUtils;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -23,7 +23,10 @@ import java.util.List;
|
||||
@Service
|
||||
public class DefaultTemplateLoader {
|
||||
|
||||
/** 模块配置,提供默认模板路径等运行参数。 */
|
||||
private final MappingModuleConfig moduleConfig;
|
||||
|
||||
/** 模板反序列化使用的 Jackson 实例。 */
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public DefaultTemplateLoader(MappingModuleConfig moduleConfig) {
|
||||
@@ -32,6 +35,11 @@ public class DefaultTemplateLoader {
|
||||
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载并校验默认映射模板。
|
||||
*
|
||||
* 返回值会直接参与索引候选分析和最终 MappingDocument 生成。
|
||||
*/
|
||||
public DefaultTemplate load() {
|
||||
try {
|
||||
ClassPathResource resource = new ClassPathResource(moduleConfig.getDefaultTemplatePath());
|
||||
@@ -55,6 +63,9 @@ public class DefaultTemplateLoader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 classpath 资源的完整字节内容。
|
||||
*/
|
||||
private byte[] readAllBytes(ClassPathResource resource) throws Exception {
|
||||
try (InputStream inputStream = resource.getInputStream();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.njcn.gather.icd.mapping.infrastructure.storage;
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -11,8 +11,14 @@ import java.nio.charset.StandardCharsets;
|
||||
*/
|
||||
@Component
|
||||
public class FileStorageService {
|
||||
/**
|
||||
* 将映射 JSON 写入目标目录。
|
||||
*
|
||||
* outputDir 为空时使用当前工作目录,返回最终文件绝对路径。
|
||||
*/
|
||||
public String save(String fileName, String content, String outputDir) {
|
||||
try {
|
||||
// 输出目录允许由请求覆盖,未传时落到当前工作目录。
|
||||
File dir = outputDir == null || outputDir.trim().isEmpty() ? new File(".") : new File(outputDir);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
throw new IllegalStateException("输出目录创建失败:" + dir.getAbsolutePath());
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.njcn.gather.icd.mapping.domain.service;
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.infrastructure.parser.SclParserAdapter;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -11,12 +11,16 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class IcdParserService {
|
||||
|
||||
/** SCL 底层解析适配器,封装 JAXB generated 模型处理。 */
|
||||
private final SclParserAdapter parserAdapter;
|
||||
|
||||
public IcdParserService(SclParserAdapter parserAdapter) {
|
||||
this.parserAdapter = parserAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 ICD 文件内容为模块内部统一领域模型。
|
||||
*/
|
||||
public IcdDocument parse(byte[] bytes, String fileName) {
|
||||
return parserAdapter.parse(bytes, fileName);
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.njcn.gather.icd.mapping.domain.service;
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidate;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidateReportItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.DataSetNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.FcdaNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.ReportControlNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.template.DefaultTemplate;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexCandidate;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexCandidateReportItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.DataSetNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.FcdaNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.ReportControlNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.template.DefaultTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -21,17 +21,15 @@ import java.util.Set;
|
||||
/**
|
||||
* 索引候选分析服务。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 新版不再按“单个报告”平铺返回,而是按 DefaultCfg.ReportList 的业务配置项聚合。
|
||||
* 2. 一个业务配置项下可能包含多个报告,因此这里会计算 reportCount,并返回 reports 子列表。
|
||||
* 3. templateLabels 只是模板参考,不要求与 ICD 解析到的 lnInst 数量完全一一对应。
|
||||
* 4. 关键修正:
|
||||
* 在这里就把 DefaultCfg.ReportList 的 inst / Select / TrgOps 一并带入 IndexCandidate,
|
||||
* 后续正式生成阶段直接使用,不再重新查模板。
|
||||
* 按 `DefaultCfg.ReportList` 的业务分组聚合 ICD 中的报告信息,
|
||||
* 同时把后续正式生成阶段需要的模板字段一并带入候选结果。
|
||||
*/
|
||||
@Service
|
||||
public class IndexAnalysisService {
|
||||
|
||||
/**
|
||||
* 根据 ICD 报告控制块和 DefaultCfg.ReportList 生成索引候选分组。
|
||||
*/
|
||||
public IndexAnalysis analyze(IcdDocument icdDocument, DefaultTemplate template) {
|
||||
IndexAnalysis analysis = new IndexAnalysis();
|
||||
if (icdDocument == null) {
|
||||
@@ -43,7 +41,7 @@ public class IndexAnalysisService {
|
||||
return analysis;
|
||||
}
|
||||
|
||||
// 先按模板分组聚合
|
||||
// 先按模板分组聚合候选项,而不是按单个报告平铺返回。
|
||||
for (DefaultTemplate.ReportCfgItem reportCfg : template.getReportList()) {
|
||||
List<ReportControlNode> matchedReports = collectMatchedReports(icdDocument, reportCfg);
|
||||
if (matchedReports.isEmpty()) {
|
||||
@@ -55,7 +53,7 @@ public class IndexAnalysisService {
|
||||
candidate.setGroupDesc(reportCfg.getDesc());
|
||||
candidate.setReportCount(matchedReports.size());
|
||||
|
||||
// 关键:把 DefaultCfg.ReportList 的配置项直接带入候选对象
|
||||
// 把模板中的关键配置直接带入候选对象,避免正式生成阶段再次回查模板。
|
||||
candidate.setReportInst(reportCfg.getInst());
|
||||
candidate.setSelect(reportCfg.getSelect());
|
||||
candidate.setTrgOps(reportCfg.getTrgOps());
|
||||
@@ -76,7 +74,7 @@ public class IndexAnalysisService {
|
||||
analysis.getCandidates().add(candidate);
|
||||
}
|
||||
|
||||
// 再检查是否有 ICD 报告没有被模板覆盖
|
||||
// 检查 ICD 中是否存在未被模板覆盖的报告配置。
|
||||
if (icdDocument.getReportControls() != null) {
|
||||
for (ReportControlNode reportControl : icdDocument.getReportControls()) {
|
||||
if (!isCoveredByTemplate(reportControl, template)) {
|
||||
@@ -91,6 +89,9 @@ public class IndexAnalysisService {
|
||||
return analysis;
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集命中当前模板报告分组 DataSetList 的报告控制块。
|
||||
*/
|
||||
private List<ReportControlNode> collectMatchedReports(IcdDocument icdDocument, DefaultTemplate.ReportCfgItem reportCfg) {
|
||||
List<ReportControlNode> result = new ArrayList<ReportControlNode>();
|
||||
if (icdDocument.getReportControls() == null || reportCfg.getDataSetList() == null) {
|
||||
@@ -104,6 +105,9 @@ public class IndexAnalysisService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 ICD 中的报告是否已经被模板 ReportList 覆盖。
|
||||
*/
|
||||
private boolean isCoveredByTemplate(ReportControlNode reportControl, DefaultTemplate template) {
|
||||
if (template == null || template.getReportList() == null) {
|
||||
return false;
|
||||
@@ -116,6 +120,9 @@ public class IndexAnalysisService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从指定 DataSet 的 FCDA 中提取可选 lnInst 数字,并按数字优先排序。
|
||||
*/
|
||||
private List<String> collectLnInstValues(IcdDocument icdDocument, String dataSetName) {
|
||||
if (icdDocument.getDataSets() == null) {
|
||||
return Collections.emptyList();
|
||||
@@ -151,6 +158,9 @@ public class IndexAnalysisService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造前端回传使用的稳定分组 key。
|
||||
*/
|
||||
private String buildGroupKey(DefaultTemplate.ReportCfgItem reportCfg) {
|
||||
String desc = reportCfg.getDesc() == null ? "GROUP" : reportCfg.getDesc();
|
||||
String firstDataSet = (reportCfg.getDataSetList() == null || reportCfg.getDataSetList().isEmpty())
|
||||
@@ -158,6 +168,9 @@ public class IndexAnalysisService {
|
||||
return normalize(desc) + "__" + normalize(firstDataSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将中文描述和 DataSet 名转换为可比较的 key 片段。
|
||||
*/
|
||||
private String normalize(String value) {
|
||||
return value.replaceAll("[^0-9A-Za-z\\u4e00-\\u9fa5]+", "_")
|
||||
.replaceAll("_+", "_")
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.njcn.gather.icd.mapping.domain.service;
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import com.njcn.gather.icd.mapping.application.command.IndexBindingCommand;
|
||||
import com.njcn.gather.icd.mapping.application.command.IndexSelectionGroupCommand;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidate;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidateReportItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.ValidationResult;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexCandidate;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexCandidateReportItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.ValidationResult;
|
||||
import com.njcn.gather.icd.mapping.pojo.dto.IndexBindingCommand;
|
||||
import com.njcn.gather.icd.mapping.pojo.dto.IndexSelectionGroupCommand;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
@@ -15,13 +15,16 @@ import java.util.List;
|
||||
*
|
||||
* 校验原则:
|
||||
* 1. 只校验用户明确提交的绑定项;
|
||||
* 2. 不要求模板里的所有标签都必须配置;
|
||||
* 2. 不强制模板中的所有标签都必须配置;
|
||||
* 3. label 必须属于当前业务分组的 templateLabels;
|
||||
* 4. lnInst 必须属于当前报告对应的 ICD 候选数字。
|
||||
*/
|
||||
@Service
|
||||
public class IndexValidationService {
|
||||
|
||||
/**
|
||||
* 校验前端提交的分组绑定是否能落到当前 ICD 候选范围内。
|
||||
*/
|
||||
public ValidationResult validate(IndexAnalysis analysis, List<IndexSelectionGroupCommand> selections) {
|
||||
ValidationResult result = new ValidationResult();
|
||||
|
||||
@@ -33,7 +36,7 @@ public class IndexValidationService {
|
||||
if (selections == null || selections.isEmpty()) {
|
||||
for (IndexCandidate candidate : analysis.getCandidates()) {
|
||||
result.getProblems().add(
|
||||
"报告组【" + candidate.getGroupDesc() + "】未提交绑定关系,请根据 templateLabels 与 reports[*].availableLnInstValues 完成配置"
|
||||
"分组【" + candidate.getGroupDesc() + "】未提交绑定关系,请根据 templateLabels 与 reports[*].availableLnInstValues 完成配置"
|
||||
);
|
||||
}
|
||||
return result;
|
||||
@@ -62,6 +65,9 @@ public class IndexValidationService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单条 label + report + lnInst 绑定。
|
||||
*/
|
||||
private void validateBinding(IndexCandidate candidate, IndexBindingCommand binding, ValidationResult result) {
|
||||
if (binding == null) {
|
||||
result.getProblems().add("存在空的绑定项");
|
||||
@@ -93,6 +99,9 @@ public class IndexValidationService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先按 groupKey 匹配候选分组,兼容只传 groupDesc 的旧调用。
|
||||
*/
|
||||
private IndexCandidate findCandidate(IndexAnalysis analysis, String groupKey, String groupDesc) {
|
||||
for (IndexCandidate candidate : analysis.getCandidates()) {
|
||||
if (same(candidate.getGroupKey(), groupKey)) {
|
||||
@@ -105,6 +114,9 @@ public class IndexValidationService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在候选分组内定位具体报告,dataSetName 为空时只按报告名匹配。
|
||||
*/
|
||||
private IndexCandidateReportItem findReport(IndexCandidate candidate, String reportName, String dataSetName) {
|
||||
if (candidate.getReports() == null) {
|
||||
return null;
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.njcn.gather.icd.mapping.infrastructure.serializer;
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.MappingDocument;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.MappingDocument;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -12,7 +12,10 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
public class MappingDocumentSerializer {
|
||||
|
||||
/** 紧凑 JSON 输出器,用于减少响应体体积。 */
|
||||
private final ObjectMapper compactMapper;
|
||||
|
||||
/** 格式化 JSON 输出器,用于人工查看和落盘审阅。 */
|
||||
private final ObjectMapper prettyMapper;
|
||||
|
||||
public MappingDocumentSerializer() {
|
||||
@@ -24,6 +27,9 @@ public class MappingDocumentSerializer {
|
||||
prettyMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为紧凑 JSON。
|
||||
*/
|
||||
public String toCompactJson(MappingDocument document) {
|
||||
try {
|
||||
return compactMapper.writeValueAsString(document);
|
||||
@@ -32,6 +38,9 @@ public class MappingDocumentSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化为格式化 JSON。
|
||||
*/
|
||||
public String toPrettyJson(MappingDocument document) {
|
||||
try {
|
||||
return prettyMapper.writeValueAsString(document);
|
||||
@@ -1,29 +1,29 @@
|
||||
package com.njcn.gather.icd.mapping.domain.service;
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import com.njcn.gather.icd.mapping.application.command.IndexBindingCommand;
|
||||
import com.njcn.gather.icd.mapping.application.command.IndexSelectionGroupCommand;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidate;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidateReportItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.DataSetNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.DoiElementNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.DoiNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.FcdaNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.LnNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.ReportControlNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.intermediate.DataSetSelectionState;
|
||||
import com.njcn.gather.icd.mapping.domain.model.intermediate.ReportAndDataSetState;
|
||||
import com.njcn.gather.icd.mapping.domain.model.intermediate.ReportBindingState;
|
||||
import com.njcn.gather.icd.mapping.domain.model.intermediate.ReportGroupState;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.DataSetGroupItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.DoiItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.InstItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.MappingDocument;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.ReportMapItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.SdiItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.mapping.TypeItem;
|
||||
import com.njcn.gather.icd.mapping.domain.model.template.DefaultTemplate;
|
||||
import com.njcn.gather.icd.mapping.pojo.dto.IndexBindingCommand;
|
||||
import com.njcn.gather.icd.mapping.pojo.dto.IndexSelectionGroupCommand;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexCandidate;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexCandidateReportItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.DataSetNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.DoiElementNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.DoiNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.FcdaNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.LnNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.ReportControlNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.state.DataSetSelectionState;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.state.ReportAndDataSetState;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.state.ReportBindingState;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.state.ReportGroupState;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.DataSetGroupItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.DoiItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.InstItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.MappingDocument;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.ReportMapItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.SdiItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.TypeItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.template.DefaultTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -46,6 +46,9 @@ import java.time.format.DateTimeFormatter;
|
||||
@Service
|
||||
public class MappingGenerationService {
|
||||
|
||||
/**
|
||||
* 根据 ICD、默认模板和用户索引绑定生成最终映射文档。
|
||||
*/
|
||||
public MappingDocument generate(IcdDocument icdDocument,
|
||||
DefaultTemplate template,
|
||||
IndexAnalysis analysis,
|
||||
@@ -103,6 +106,7 @@ public class MappingGenerationService {
|
||||
}
|
||||
|
||||
// 2. 生成 DataSetList
|
||||
// DataSetList 按 desc + lnClass 聚合,保持输出顺序稳定。
|
||||
Map<String, DataSetGroupItem> dataSetGroupMap = new LinkedHashMap<String, DataSetGroupItem>();
|
||||
for (DataSetSelectionState selectionState : state.getDataSetSelections()) {
|
||||
if (selectionState.getLnNodes() == null || selectionState.getLnNodes().isEmpty()) {
|
||||
@@ -140,6 +144,11 @@ public class MappingGenerationService {
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建正式生成前的中间态。
|
||||
*
|
||||
* 中间态把报告分组、用户绑定和命中的 LN 节点拆开保存,避免生成 JSON 时重复查找。
|
||||
*/
|
||||
private ReportAndDataSetState buildState(IcdDocument icdDocument,
|
||||
DefaultTemplate template,
|
||||
IndexAnalysis analysis,
|
||||
@@ -263,6 +272,9 @@ public class MappingGenerationService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 lnClass 分组下查找或创建指定 lnInst 的输出节点。
|
||||
*/
|
||||
private InstItem findOrCreateInst(DataSetGroupItem groupItem, String inst, String desc) {
|
||||
for (InstItem item : groupItem.getInstList()) {
|
||||
if (equalsTrim(item.getInst(), inst)) {
|
||||
@@ -372,6 +384,9 @@ public class MappingGenerationService {
|
||||
instItem.getDoiList().add(doiItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断 queueList 命中的对象是否应按原 C# 规则跳过。
|
||||
*/
|
||||
private boolean shouldSkipQueueItem(boolean queueMode, int icdCount, DefaultTemplate.ObjectCfgItem objectCfg) {
|
||||
if (!queueMode || objectCfg == null) {
|
||||
return false;
|
||||
@@ -418,6 +433,9 @@ public class MappingGenerationService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据用户绑定标签查找对应的 DataObjectsList 配置项。
|
||||
*/
|
||||
private List<DefaultTemplate.DataObjectCfgItem> findDataObjectCfgItems(DefaultTemplate template, String label) {
|
||||
List<DefaultTemplate.DataObjectCfgItem> result = new ArrayList<DefaultTemplate.DataObjectCfgItem>();
|
||||
if (template == null || template.getDataObjectsList() == null) {
|
||||
@@ -639,6 +657,11 @@ public class MappingGenerationService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 DOI 的倍率系数。
|
||||
*
|
||||
* 找不到模板倍率配置时默认返回 1.0。
|
||||
*/
|
||||
private float resolveCoefficient(DoiNode doiNode, DefaultTemplate template) {
|
||||
Set<String> values = collectAllLeafValues(doiNode.getChildren());
|
||||
for (String value : values) {
|
||||
@@ -837,6 +860,9 @@ public class MappingGenerationService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从模板 LnClassList 中查找当前 LN 的业务分组配置。
|
||||
*/
|
||||
private DefaultTemplate.LnClassCfgItem findLnClassCfg(DefaultTemplate template, String lnClass) {
|
||||
if (template == null || template.getLnClassList() == null) {
|
||||
return null;
|
||||
@@ -850,6 +876,9 @@ public class MappingGenerationService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在候选分析结果中定位当前前端回传的业务分组。
|
||||
*/
|
||||
private IndexCandidate findCandidate(IndexAnalysis analysis, String groupKey, String groupDesc) {
|
||||
if (analysis == null || analysis.getCandidates() == null) {
|
||||
return null;
|
||||
@@ -862,6 +891,9 @@ public class MappingGenerationService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集 DOI 子树下所有 Val 文本,用于匹配倍率等模板配置。
|
||||
*/
|
||||
private Set<String> collectAllLeafValues(List<DoiElementNode> nodes) {
|
||||
Set<String> result = new LinkedHashSet<String>();
|
||||
if (nodes == null) {
|
||||
@@ -880,6 +912,9 @@ public class MappingGenerationService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从模板 UnitList 中查找指定 DAI 值对应的单位配置。
|
||||
*/
|
||||
private DefaultTemplate.UnitCfgItem findUnitCfg(DefaultTemplate template, String value) {
|
||||
if (template == null || template.getUnitList() == null) {
|
||||
return null;
|
||||
@@ -892,6 +927,9 @@ public class MappingGenerationService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从模板 MultiplierList 中查找指定 DAI 值对应的倍率配置。
|
||||
*/
|
||||
private DefaultTemplate.MultiplierCfgItem findMultiplierCfg(DefaultTemplate template, String value) {
|
||||
if (template == null || template.getMultiplierList() == null) {
|
||||
return null;
|
||||
@@ -904,6 +942,9 @@ public class MappingGenerationService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将相别节点名称转换为模板中的中文描述。
|
||||
*/
|
||||
private String resolvePhaseDesc(DefaultTemplate template, String value) {
|
||||
if (template == null || template.getPhaseList() == null) {
|
||||
return value;
|
||||
@@ -916,6 +957,9 @@ public class MappingGenerationService {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将类型节点名称转换为模板中的中文描述。
|
||||
*/
|
||||
private String resolveTypeDesc(DefaultTemplate template, String value) {
|
||||
if (template == null || template.getTypeList() == null) {
|
||||
return value;
|
||||
@@ -929,6 +973,9 @@ public class MappingGenerationService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 空安全、去首尾空白的字符串比较。
|
||||
*/
|
||||
private boolean equalsTrim(String left, String right) {
|
||||
if (left == null && right == null) {
|
||||
return true;
|
||||
@@ -1002,6 +1049,9 @@ public class MappingGenerationService {
|
||||
return "96";
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析输出版本号;请求未传时使用当天日期。
|
||||
*/
|
||||
private String resolveVersion(String version) {
|
||||
if (version != null && !version.trim().isEmpty()) {
|
||||
return version.trim();
|
||||
@@ -1009,6 +1059,9 @@ public class MappingGenerationService {
|
||||
return LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按原 C# 规则转换 reportCount:单报告写 0,多报告写实际数量。
|
||||
*/
|
||||
private int resolveReportCount(int size) {
|
||||
return size <= 1 ? 0 : size;
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import com.njcn.gather.icd.mapping.config.MappingModuleConfig;
|
||||
import com.njcn.gather.icd.mapping.pojo.dto.GenerateFromIcdCommand;
|
||||
import com.njcn.gather.icd.mapping.pojo.dto.IndexBindingCommand;
|
||||
import com.njcn.gather.icd.mapping.pojo.dto.IndexSelectionGroupCommand;
|
||||
import com.njcn.gather.icd.mapping.pojo.param.GenerateMappingFromIcdRequest;
|
||||
import com.njcn.gather.icd.mapping.pojo.param.IndexBindingRequest;
|
||||
import com.njcn.gather.icd.mapping.pojo.param.IndexSelectionGroupRequest;
|
||||
import com.njcn.gather.icd.mapping.pojo.param.SubmitIndexSelectionRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 请求转换器。
|
||||
*
|
||||
* 作用:
|
||||
* 1. 将接口层 request 转成应用层 command;
|
||||
* 2. 统一处理 null 和空集合,避免后续业务层到处判空。
|
||||
*/
|
||||
@Component
|
||||
public class MappingRequestConverter {
|
||||
|
||||
/** 模块默认配置,用于补齐作者、输出目录等缺省值。 */
|
||||
private final MappingModuleConfig moduleConfig;
|
||||
|
||||
public MappingRequestConverter(MappingModuleConfig moduleConfig) {
|
||||
this.moduleConfig = moduleConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将候选接口上传文件转换为应用层命令,其他参数走模块默认值。
|
||||
*/
|
||||
public GenerateFromIcdCommand toCommand(MultipartFile icdFile) {
|
||||
return toCommand(icdFile, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将上传文件和请求体转换为应用层命令。
|
||||
*/
|
||||
public GenerateFromIcdCommand toCommand(MultipartFile icdFile, GenerateMappingFromIcdRequest request) {
|
||||
try {
|
||||
if (icdFile == null || icdFile.isEmpty()) {
|
||||
throw new IllegalArgumentException("ICD 文件不能为空");
|
||||
}
|
||||
GenerateFromIcdCommand command = new GenerateFromIcdCommand();
|
||||
command.setFileName(icdFile.getOriginalFilename());
|
||||
command.setFileBytes(icdFile.getBytes());
|
||||
command.setVersion(request == null ? null : request.getVersion());
|
||||
command.setAuthor(resolveText(request == null ? null : request.getAuthor(), moduleConfig.getDefaultAuthor()));
|
||||
command.setSaveToDisk(request != null && request.isSaveToDisk());
|
||||
command.setPrettyJson(request != null && request.isPrettyJson());
|
||||
command.setOutputDir(resolveText(request == null ? null : request.getOutputDir(), moduleConfig.getDefaultOutputDir()));
|
||||
fillIndexSelection(command, request == null ? null : request.getIndexSelection());
|
||||
return command;
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("请求转换失败:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将提交绑定接口请求体转换为应用层命令。
|
||||
*/
|
||||
public GenerateFromIcdCommand toCommand(SubmitIndexSelectionRequest request) {
|
||||
try {
|
||||
if (request == null) {
|
||||
throw new IllegalArgumentException("请求体不能为空");
|
||||
}
|
||||
if (request.getIcdDocument() == null) {
|
||||
throw new IllegalArgumentException("ICD 解析结果不能为空");
|
||||
}
|
||||
|
||||
GenerateFromIcdCommand command = new GenerateFromIcdCommand();
|
||||
command.setIcdDocument(request.getIcdDocument());
|
||||
command.setFileName(request.getIcdDocument().getFileName());
|
||||
command.setVersion(request.getVersion());
|
||||
command.setAuthor(resolveText(request.getAuthor(), moduleConfig.getDefaultAuthor()));
|
||||
command.setSaveToDisk(request.isSaveToDisk());
|
||||
command.setPrettyJson(request.isPrettyJson());
|
||||
command.setOutputDir(resolveText(request.getOutputDir(), moduleConfig.getDefaultOutputDir()));
|
||||
fillIndexSelection(command, request.getIndexSelection());
|
||||
return command;
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("请求转换失败:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制前端提交的索引绑定结构。
|
||||
*/
|
||||
private void fillIndexSelection(GenerateFromIcdCommand command, List<IndexSelectionGroupRequest> indexSelection) {
|
||||
if (indexSelection == null) {
|
||||
return;
|
||||
}
|
||||
for (IndexSelectionGroupRequest groupRequest : indexSelection) {
|
||||
if (groupRequest == null) {
|
||||
continue;
|
||||
}
|
||||
IndexSelectionGroupCommand groupCommand = new IndexSelectionGroupCommand();
|
||||
groupCommand.setGroupKey(groupRequest.getGroupKey());
|
||||
groupCommand.setGroupDesc(groupRequest.getGroupDesc());
|
||||
|
||||
if (groupRequest.getBindings() != null) {
|
||||
for (IndexBindingRequest bindingRequest : groupRequest.getBindings()) {
|
||||
if (bindingRequest == null) {
|
||||
continue;
|
||||
}
|
||||
IndexBindingCommand bindingCommand = new IndexBindingCommand();
|
||||
bindingCommand.setReportName(bindingRequest.getReportName());
|
||||
bindingCommand.setDataSetName(bindingRequest.getDataSetName());
|
||||
bindingCommand.setLabel(bindingRequest.getLabel());
|
||||
bindingCommand.setLnInst(bindingRequest.getLnInst());
|
||||
groupCommand.getBindings().add(bindingCommand);
|
||||
}
|
||||
}
|
||||
|
||||
command.getIndexSelection().add(groupCommand);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一处理文本参数的 trim 和默认值回退。
|
||||
*/
|
||||
private String resolveText(String value, String defaultValue) {
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
return value.trim();
|
||||
}
|
||||
return defaultValue == null ? null : defaultValue.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.njcn.gather.icd.mapping.component;
|
||||
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.GenerateMappingResult;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexCandidate;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexCandidateReportItem;
|
||||
import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus;
|
||||
import com.njcn.gather.icd.mapping.pojo.vo.IndexCandidateReportItemResponse;
|
||||
import com.njcn.gather.icd.mapping.pojo.vo.IndexCandidateResponse;
|
||||
import com.njcn.gather.icd.mapping.pojo.vo.MappingTaskResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 接口响应转换器。
|
||||
*
|
||||
* 按接口阶段仅组装当前场景必需字段,避免返回冗余信息。
|
||||
*/
|
||||
@Component
|
||||
public class MappingResponseConverter {
|
||||
|
||||
/**
|
||||
* 候选接口响应。
|
||||
*/
|
||||
public MappingTaskResponse fromCandidateResult(GenerateMappingResult result) {
|
||||
MappingTaskResponse response = initBaseResponse(result);
|
||||
response.setIcdDocument(result.getIcdDocument());
|
||||
fillIndexCandidates(response, result);
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* 正式提交类接口响应。
|
||||
*
|
||||
* SUCCESS:仅返回最终结果;
|
||||
* NEED_INDEX_SELECTION:返回重新选择所需的候选信息;
|
||||
* FAILED:仅返回错误信息。
|
||||
*/
|
||||
public MappingTaskResponse fromSubmitResult(GenerateMappingResult result) {
|
||||
MappingTaskResponse response = initBaseResponse(result);
|
||||
if (result.getStatus() == GenerateStatus.SUCCESS) {
|
||||
response.setMappingJson(result.getMappingJson());
|
||||
response.setSavedPath(result.getSavedPath());
|
||||
return response;
|
||||
}
|
||||
|
||||
if (result.getStatus() == GenerateStatus.NEED_INDEX_SELECTION) {
|
||||
response.setIcdDocument(result.getIcdDocument());
|
||||
fillIndexCandidates(response, result);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private MappingTaskResponse initBaseResponse(GenerateMappingResult result) {
|
||||
MappingTaskResponse response = new MappingTaskResponse();
|
||||
response.setStatus(result.getStatus());
|
||||
response.setMessage(result.getMessage());
|
||||
response.getProblems().addAll(result.getProblems());
|
||||
return response;
|
||||
}
|
||||
|
||||
private void fillIndexCandidates(MappingTaskResponse response, GenerateMappingResult result) {
|
||||
if (result.getIndexAnalysis() == null || result.getIndexAnalysis().getCandidates() == null) {
|
||||
return;
|
||||
}
|
||||
for (IndexCandidate candidate : result.getIndexAnalysis().getCandidates()) {
|
||||
IndexCandidateResponse candidateResponse = new IndexCandidateResponse();
|
||||
candidateResponse.setGroupKey(candidate.getGroupKey());
|
||||
candidateResponse.setGroupDesc(candidate.getGroupDesc());
|
||||
candidateResponse.setReportCount(candidate.getReportCount());
|
||||
candidateResponse.getTemplateLabels().addAll(candidate.getTemplateLabels());
|
||||
|
||||
if (candidate.getReports() != null) {
|
||||
for (IndexCandidateReportItem item : candidate.getReports()) {
|
||||
IndexCandidateReportItemResponse itemResponse = new IndexCandidateReportItemResponse();
|
||||
itemResponse.setReportName(item.getReportName());
|
||||
itemResponse.setDataSetName(item.getDataSetName());
|
||||
itemResponse.setReportDesc(item.getReportDesc());
|
||||
itemResponse.getAvailableLnInstValues().addAll(item.getAvailableLnInstValues());
|
||||
candidateResponse.getReports().add(itemResponse);
|
||||
}
|
||||
}
|
||||
|
||||
response.getIndexCandidates().add(candidateResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
package com.njcn.gather.icd.mapping.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 模块配置。
|
||||
* 模块配置类。集中管理默认作者、默认模板路径等可配置项。
|
||||
* `mms-mapping` 模块配置。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 这里把模板路径、输出目录、默认作者等集中管理。
|
||||
* 2. 当前先用 @Value + 默认值,后续你也可以改成 @ConfigurationProperties。
|
||||
* 统一管理默认模板路径、默认作者和默认输出目录等配置项。
|
||||
*/
|
||||
@Component
|
||||
@Getter
|
||||
@Setter
|
||||
public class MappingModuleConfig {
|
||||
|
||||
/** 默认模板资源路径。 */
|
||||
@@ -25,28 +26,4 @@ public class MappingModuleConfig {
|
||||
/** 默认输出目录。 */
|
||||
@Value("${icd.mapping.default-output-dir:}")
|
||||
private String defaultOutputDir;
|
||||
|
||||
public String getDefaultTemplatePath() {
|
||||
return defaultTemplatePath;
|
||||
}
|
||||
|
||||
public void setDefaultTemplatePath(String defaultTemplatePath) {
|
||||
this.defaultTemplatePath = defaultTemplatePath;
|
||||
}
|
||||
|
||||
public String getDefaultAuthor() {
|
||||
return defaultAuthor;
|
||||
}
|
||||
|
||||
public void setDefaultAuthor(String defaultAuthor) {
|
||||
this.defaultAuthor = defaultAuthor;
|
||||
}
|
||||
|
||||
public String getDefaultOutputDir() {
|
||||
return defaultOutputDir;
|
||||
}
|
||||
|
||||
public void setDefaultOutputDir(String defaultOutputDir) {
|
||||
this.defaultOutputDir = defaultOutputDir;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,92 @@
|
||||
package com.njcn.gather.icd.mapping.controller;
|
||||
|
||||
import com.njcn.gather.icd.mapping.application.MappingTaskAppService;
|
||||
import com.njcn.gather.icd.mapping.application.command.GenerateFromIcdCommand;
|
||||
import com.njcn.gather.icd.mapping.application.result.GenerateMappingResult;
|
||||
import com.njcn.gather.icd.mapping.controller.request.GenerateMappingFromIcdRequest;
|
||||
import com.njcn.gather.icd.mapping.controller.response.MappingTaskResponse;
|
||||
import com.njcn.gather.icd.mapping.converter.MappingRequestConverter;
|
||||
import com.njcn.gather.icd.mapping.converter.MappingResponseConverter;
|
||||
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||
import com.njcn.common.utils.LogUtil;
|
||||
import com.njcn.gather.icd.mapping.component.MappingRequestConverter;
|
||||
import com.njcn.gather.icd.mapping.component.MappingResponseConverter;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.GenerateMappingResult;
|
||||
import com.njcn.gather.icd.mapping.pojo.dto.GenerateFromIcdCommand;
|
||||
import com.njcn.gather.icd.mapping.pojo.param.GenerateMappingFromIcdRequest;
|
||||
import com.njcn.gather.icd.mapping.pojo.param.SubmitIndexSelectionRequest;
|
||||
import com.njcn.gather.icd.mapping.pojo.vo.MappingTaskResponse;
|
||||
import com.njcn.gather.icd.mapping.service.MappingTaskService;
|
||||
import com.njcn.gather.icd.mapping.utils.DateUtils;
|
||||
import com.njcn.web.controller.BaseController;
|
||||
import io.swagger.annotations.Api;
|
||||
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.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;
|
||||
|
||||
/**
|
||||
* ICD 映射接口。
|
||||
* ICD 映射接口入口。
|
||||
*/
|
||||
@Slf4j
|
||||
@Api(tags = "ICD 映射")
|
||||
@RestController
|
||||
@RequestMapping("/api/mms-mapping")
|
||||
public class MappingController {
|
||||
@RequiredArgsConstructor
|
||||
public class MappingController extends BaseController {
|
||||
|
||||
private final MappingTaskAppService mappingTaskAppService;
|
||||
/** 映射任务编排服务,负责从 ICD 解析到映射生成的完整链路。 */
|
||||
private final MappingTaskService mappingTaskService;
|
||||
|
||||
/** 请求参数转换器,将接口入参转换为应用层命令。 */
|
||||
private final MappingRequestConverter requestConverter;
|
||||
|
||||
/** 响应转换器,按接口阶段裁剪最小返回字段。 */
|
||||
private final MappingResponseConverter responseConverter;
|
||||
|
||||
public MappingController(MappingTaskAppService mappingTaskAppService,
|
||||
MappingRequestConverter requestConverter,
|
||||
MappingResponseConverter responseConverter) {
|
||||
this.mappingTaskAppService = mappingTaskAppService;
|
||||
this.requestConverter = requestConverter;
|
||||
this.responseConverter = responseConverter;
|
||||
/**
|
||||
* 上传 ICD 文件,返回候选结果和可编辑的 ICD 解析结果。
|
||||
*/
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("上传 ICD 文件并生成索引候选")
|
||||
@PostMapping(value = "/get-icd", consumes = {"multipart/form-data"})
|
||||
public MappingTaskResponse getICD(@RequestPart("icdFile") MultipartFile icdFile) {
|
||||
String methodDescribe = getMethodDescribe("getICD");
|
||||
LogUtil.njcnDebug(log, "{},开始解析 ICD 文件并生成索引候选,fileName={}", methodDescribe, DateUtils.resolveFileName(icdFile));
|
||||
GenerateFromIcdCommand command = requestConverter.toCommand(icdFile);
|
||||
GenerateMappingResult result = mappingTaskService.getICD(command);
|
||||
return responseConverter.fromCandidateResult(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传 ICD 并生成映射。
|
||||
*
|
||||
* 表单参数:
|
||||
* 1. icdFile:ICD 文件
|
||||
* 2. request:JSON 请求体
|
||||
* 根据前端确认后的索引绑定关系生成 MMS JSON。
|
||||
*/
|
||||
@PostMapping(value = "/generate-from-icd", consumes = {"multipart/form-data"})
|
||||
public MappingTaskResponse generateFromIcd(@RequestPart("icdFile") MultipartFile icdFile,
|
||||
@Validated @RequestPart("request") GenerateMappingFromIcdRequest request) {
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("获取 MMS JSON")
|
||||
@PostMapping("/get-mms-json")
|
||||
public MappingTaskResponse getMmsJson(@Validated @RequestBody SubmitIndexSelectionRequest request) {
|
||||
String methodDescribe = getMethodDescribe("getMmsJson");
|
||||
LogUtil.njcnDebug(log, "{},开始生成 MMS JSON,indexSelectionCount={}",
|
||||
methodDescribe, DateUtils.resolveSelectionCount(request == null ? null : request.getIndexSelection()));
|
||||
GenerateFromIcdCommand command = requestConverter.toCommand(request);
|
||||
GenerateMappingResult result = mappingTaskService.getMmsJson(command);
|
||||
return responseConverter.fromSubmitResult(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传 ICD 后直接串联候选生成和索引提交,统一返回正式提交阶段结果。
|
||||
*/
|
||||
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||
@ApiOperation("上传 ICD 后直接获取 MMS JSON")
|
||||
@PostMapping(value = "/get-icd-mms-json", consumes = {"multipart/form-data"})
|
||||
public MappingTaskResponse getIcdMmsJson(@RequestPart("icdFile") MultipartFile icdFile,
|
||||
@Validated @RequestPart("request") GenerateMappingFromIcdRequest request) {
|
||||
String methodDescribe = getMethodDescribe("getIcdMmsJson");
|
||||
LogUtil.njcnDebug(log, "{},开始获取 ICD MMS JSON,fileName={}, indexSelectionCount={}",
|
||||
methodDescribe, DateUtils.resolveFileName(icdFile),
|
||||
DateUtils.resolveSelectionCount(request == null ? null : request.getIndexSelection()));
|
||||
GenerateFromIcdCommand command = requestConverter.toCommand(icdFile, request);
|
||||
GenerateMappingResult result = mappingTaskAppService.generateFromIcd(command);
|
||||
return responseConverter.fromResult(result);
|
||||
GenerateMappingResult result = mappingTaskService.getIcdMmsJson(command);
|
||||
return responseConverter.fromSubmitResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.controller.request;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 生成映射接口请求体。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 旧版结构中,indexSelection 是 Map<String, String>,只能表达“一个报告对应一个值”,
|
||||
* 无法表达“一个业务分组下有多个报告、一个报告下又有多条标签绑定”的真实场景。
|
||||
* 2. 新版结构改成 List<IndexSelectionGroupRequest>,用来完整承载用户在前端完成的绑定结果。
|
||||
* 3. 第一次只上传 ICD 时,这个字段可以为空;第二次用户确认绑定后再把完整结构上送即可。
|
||||
*/
|
||||
public class GenerateMappingFromIcdRequest {
|
||||
|
||||
/** 输出版本号。为空时后端默认补 1.0。 */
|
||||
private String version;
|
||||
|
||||
/** 作者。为空时默认空字符串。 */
|
||||
private String author;
|
||||
|
||||
/** 是否保存到磁盘。 */
|
||||
private boolean saveToDisk;
|
||||
|
||||
/** 是否返回美化 JSON。 */
|
||||
private boolean prettyJson;
|
||||
|
||||
/** 输出目录。saveToDisk=true 时才会用到。 */
|
||||
private String outputDir;
|
||||
|
||||
/**
|
||||
* 索引选择结果。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 每一个元素代表一个“业务分组”,例如:实时数据、统计数据、波动闪变。
|
||||
* 2. 每个业务分组下面又包含多条绑定关系。
|
||||
* 3. 允许为空;为空时后端返回 NEED_INDEX_SELECTION,给前端候选参考项。
|
||||
*/
|
||||
private List<IndexSelectionGroupRequest> indexSelection = new ArrayList<IndexSelectionGroupRequest>();
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public boolean isSaveToDisk() {
|
||||
return saveToDisk;
|
||||
}
|
||||
|
||||
public void setSaveToDisk(boolean saveToDisk) {
|
||||
this.saveToDisk = saveToDisk;
|
||||
}
|
||||
|
||||
public boolean isPrettyJson() {
|
||||
return prettyJson;
|
||||
}
|
||||
|
||||
public void setPrettyJson(boolean prettyJson) {
|
||||
this.prettyJson = prettyJson;
|
||||
}
|
||||
|
||||
public String getOutputDir() {
|
||||
return outputDir;
|
||||
}
|
||||
|
||||
public void setOutputDir(String outputDir) {
|
||||
this.outputDir = outputDir;
|
||||
}
|
||||
|
||||
public List<IndexSelectionGroupRequest> getIndexSelection() {
|
||||
return indexSelection;
|
||||
}
|
||||
|
||||
public void setIndexSelection(List<IndexSelectionGroupRequest> indexSelection) {
|
||||
this.indexSelection = indexSelection;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.controller.request;
|
||||
|
||||
/**
|
||||
* 单条索引绑定请求。
|
||||
*
|
||||
* 一条绑定只表达一个最小关系:
|
||||
* 某个报告(reportName)下,使用某个标签(label)与某个 lnInst 数字做绑定。
|
||||
*
|
||||
* 这样做的好处:
|
||||
* 1. 一个报告可以出现多次,对应多个标签。
|
||||
* 2. 一个业务分组下也可以有多个报告。
|
||||
* 3. 后端校验、生成映射时都更容易逐条处理。
|
||||
*/
|
||||
public class IndexBindingRequest {
|
||||
|
||||
/** 绑定发生在哪个报告上,例如:brcbStHarm。 */
|
||||
private String reportName;
|
||||
|
||||
/** 绑定发生在哪个数据集上,例如:dsStHarm。 */
|
||||
private String dataSetName;
|
||||
|
||||
/** 业务标签,例如:最大值、最小值、实时数据。 */
|
||||
private String label;
|
||||
|
||||
/** 当前标签最终绑定到的 lnInst 数字,例如:1、2、8。 */
|
||||
private String lnInst;
|
||||
|
||||
public String getReportName() {
|
||||
return reportName;
|
||||
}
|
||||
|
||||
public void setReportName(String reportName) {
|
||||
this.reportName = reportName;
|
||||
}
|
||||
|
||||
public String getDataSetName() {
|
||||
return dataSetName;
|
||||
}
|
||||
|
||||
public void setDataSetName(String dataSetName) {
|
||||
this.dataSetName = dataSetName;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLnInst() {
|
||||
return lnInst;
|
||||
}
|
||||
|
||||
public void setLnInst(String lnInst) {
|
||||
this.lnInst = lnInst;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.controller.request;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 单个业务分组的索引选择请求。
|
||||
*
|
||||
* 例如:
|
||||
* - groupKey = REALTIME_DATA
|
||||
* - groupDesc = 实时数据
|
||||
* - bindings = 多条“报告 + 标签 + lnInst”的绑定关系
|
||||
*/
|
||||
public class IndexSelectionGroupRequest {
|
||||
|
||||
/**
|
||||
* 分组唯一键。
|
||||
*
|
||||
* 说明:
|
||||
* 这个值由后端在 NEED_INDEX_SELECTION 时返回,前端原样带回即可,避免仅靠中文描述做匹配。
|
||||
*/
|
||||
private String groupKey;
|
||||
|
||||
/** 分组中文描述,例如:实时数据、统计数据。 */
|
||||
private String groupDesc;
|
||||
|
||||
/** 当前业务分组下,用户最终确认的绑定关系。 */
|
||||
private List<IndexBindingRequest> bindings = new ArrayList<IndexBindingRequest>();
|
||||
|
||||
public String getGroupKey() {
|
||||
return groupKey;
|
||||
}
|
||||
|
||||
public void setGroupKey(String groupKey) {
|
||||
this.groupKey = groupKey;
|
||||
}
|
||||
|
||||
public String getGroupDesc() {
|
||||
return groupDesc;
|
||||
}
|
||||
|
||||
public void setGroupDesc(String groupDesc) {
|
||||
this.groupDesc = groupDesc;
|
||||
}
|
||||
|
||||
public List<IndexBindingRequest> getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public void setBindings(List<IndexBindingRequest> bindings) {
|
||||
this.bindings = bindings;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.controller.response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 索引候选分组下的单个报告响应。
|
||||
*/
|
||||
public class IndexCandidateReportItemResponse {
|
||||
|
||||
/** 报告名称。 */
|
||||
private String reportName;
|
||||
|
||||
/** 数据集名称。 */
|
||||
private String dataSetName;
|
||||
|
||||
/** 报告描述。 */
|
||||
private String reportDesc;
|
||||
|
||||
/** 当前报告可选的 lnInst 数字。 */
|
||||
private List<String> availableLnInstValues = new ArrayList<String>();
|
||||
|
||||
public String getReportName() {
|
||||
return reportName;
|
||||
}
|
||||
|
||||
public void setReportName(String reportName) {
|
||||
this.reportName = reportName;
|
||||
}
|
||||
|
||||
public String getDataSetName() {
|
||||
return dataSetName;
|
||||
}
|
||||
|
||||
public void setDataSetName(String dataSetName) {
|
||||
this.dataSetName = dataSetName;
|
||||
}
|
||||
|
||||
public String getReportDesc() {
|
||||
return reportDesc;
|
||||
}
|
||||
|
||||
public void setReportDesc(String reportDesc) {
|
||||
this.reportDesc = reportDesc;
|
||||
}
|
||||
|
||||
public List<String> getAvailableLnInstValues() {
|
||||
return availableLnInstValues;
|
||||
}
|
||||
|
||||
public void setAvailableLnInstValues(List<String> availableLnInstValues) {
|
||||
this.availableLnInstValues = availableLnInstValues;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.controller.response;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 索引候选返回对象。
|
||||
*
|
||||
* 说明:
|
||||
* 这是给前端“待匹配界面”使用的正式结构:
|
||||
* - 一个候选就是一个业务分组;
|
||||
* - 分组下面再挂多个报告;
|
||||
* - 前端根据 templateLabels 与 reports[*].availableLnInstValues 做人工绑定。
|
||||
*/
|
||||
public class IndexCandidateResponse {
|
||||
|
||||
/** 分组唯一键。 */
|
||||
private String groupKey;
|
||||
|
||||
/** 分组中文描述。 */
|
||||
private String groupDesc;
|
||||
|
||||
/** 当前分组包含的报告数。 */
|
||||
private int reportCount;
|
||||
|
||||
/** 模板里配置的可选标签。 */
|
||||
private List<String> templateLabels = new ArrayList<String>();
|
||||
|
||||
/** 当前分组下的报告候选列表。 */
|
||||
private List<IndexCandidateReportItemResponse> reports = new ArrayList<IndexCandidateReportItemResponse>();
|
||||
|
||||
public String getGroupKey() {
|
||||
return groupKey;
|
||||
}
|
||||
|
||||
public void setGroupKey(String groupKey) {
|
||||
this.groupKey = groupKey;
|
||||
}
|
||||
|
||||
public String getGroupDesc() {
|
||||
return groupDesc;
|
||||
}
|
||||
|
||||
public void setGroupDesc(String groupDesc) {
|
||||
this.groupDesc = groupDesc;
|
||||
}
|
||||
|
||||
public int getReportCount() {
|
||||
return reportCount;
|
||||
}
|
||||
|
||||
public void setReportCount(int reportCount) {
|
||||
this.reportCount = reportCount;
|
||||
}
|
||||
|
||||
public List<String> getTemplateLabels() {
|
||||
return templateLabels;
|
||||
}
|
||||
|
||||
public void setTemplateLabels(List<String> templateLabels) {
|
||||
this.templateLabels = templateLabels;
|
||||
}
|
||||
|
||||
public List<IndexCandidateReportItemResponse> getReports() {
|
||||
return reports;
|
||||
}
|
||||
|
||||
public void setReports(List<IndexCandidateReportItemResponse> reports) {
|
||||
this.reports = reports;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.controller.response;
|
||||
|
||||
/**
|
||||
* 映射摘要响应。
|
||||
* 映射摘要响应 DTO。用于把最终映射的关键信息单独封装返回。
|
||||
*/
|
||||
public class MappingDocumentResponse {
|
||||
private String version;
|
||||
private String author;
|
||||
private String ied;
|
||||
private String ld;
|
||||
private int reportCount;
|
||||
private int dataSetCount;
|
||||
public String getVersion() { return version; }
|
||||
public void setVersion(String version) { this.version = version; }
|
||||
public String getAuthor() { return author; }
|
||||
public void setAuthor(String author) { this.author = author; }
|
||||
public String getIed() { return ied; }
|
||||
public void setIed(String ied) { this.ied = ied; }
|
||||
public String getLd() { return ld; }
|
||||
public void setLd(String ld) { this.ld = ld; }
|
||||
public int getReportCount() { return reportCount; }
|
||||
public void setReportCount(int reportCount) { this.reportCount = reportCount; }
|
||||
public int getDataSetCount() { return dataSetCount; }
|
||||
public void setDataSetCount(int dataSetCount) { this.dataSetCount = dataSetCount; }
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.controller.response;
|
||||
|
||||
import com.njcn.gather.icd.mapping.enums.GenerateStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 第一个接口统一响应。
|
||||
* 接口统一响应 DTO。返回生成状态、映射内容、候选索引、问题列表等。
|
||||
*/
|
||||
public class MappingTaskResponse {
|
||||
private GenerateStatus status;
|
||||
private String message;
|
||||
private String iedName;
|
||||
private String ldInst;
|
||||
private String mappingJson;
|
||||
private String savedPath;
|
||||
private MappingDocumentResponse mappingDocument;
|
||||
private List<IndexCandidateResponse> indexCandidates = new ArrayList<IndexCandidateResponse>();
|
||||
private List<String> problems = new ArrayList<String>();
|
||||
public GenerateStatus getStatus() { return status; }
|
||||
public void setStatus(GenerateStatus status) { this.status = status; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public String getIedName() { return iedName; }
|
||||
public void setIedName(String iedName) { this.iedName = iedName; }
|
||||
public String getLdInst() { return ldInst; }
|
||||
public void setLdInst(String ldInst) { this.ldInst = ldInst; }
|
||||
public String getMappingJson() { return mappingJson; }
|
||||
public void setMappingJson(String mappingJson) { this.mappingJson = mappingJson; }
|
||||
public String getSavedPath() { return savedPath; }
|
||||
public void setSavedPath(String savedPath) { this.savedPath = savedPath; }
|
||||
public MappingDocumentResponse getMappingDocument() { return mappingDocument; }
|
||||
public void setMappingDocument(MappingDocumentResponse mappingDocument) { this.mappingDocument = mappingDocument; }
|
||||
public List<IndexCandidateResponse> getIndexCandidates() { return indexCandidates; }
|
||||
public void setIndexCandidates(List<IndexCandidateResponse> indexCandidates) { this.indexCandidates = indexCandidates; }
|
||||
public List<String> getProblems() { return problems; }
|
||||
public void setProblems(List<String> problems) { this.problems = problems; }
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.converter;
|
||||
|
||||
import com.njcn.gather.icd.mapping.application.command.GenerateFromIcdCommand;
|
||||
import com.njcn.gather.icd.mapping.application.command.IndexBindingCommand;
|
||||
import com.njcn.gather.icd.mapping.application.command.IndexSelectionGroupCommand;
|
||||
import com.njcn.gather.icd.mapping.config.MappingModuleConfig;
|
||||
import com.njcn.gather.icd.mapping.controller.request.GenerateMappingFromIcdRequest;
|
||||
import com.njcn.gather.icd.mapping.controller.request.IndexBindingRequest;
|
||||
import com.njcn.gather.icd.mapping.controller.request.IndexSelectionGroupRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 请求转换器。
|
||||
*
|
||||
* 作用:
|
||||
* 1. 把接口层 request 转成应用层 command。
|
||||
* 2. 统一处理 null 和空集合,避免后面业务层到处判空。
|
||||
*/
|
||||
@Component
|
||||
public class MappingRequestConverter {
|
||||
|
||||
private final MappingModuleConfig moduleConfig;
|
||||
|
||||
public MappingRequestConverter(MappingModuleConfig moduleConfig) {
|
||||
this.moduleConfig = moduleConfig;
|
||||
}
|
||||
|
||||
public GenerateFromIcdCommand toCommand(MultipartFile icdFile, GenerateMappingFromIcdRequest request) {
|
||||
try {
|
||||
if (icdFile == null || icdFile.isEmpty()) {
|
||||
throw new IllegalArgumentException("ICD 文件不能为空");
|
||||
}
|
||||
GenerateFromIcdCommand command = new GenerateFromIcdCommand();
|
||||
command.setFileName(icdFile.getOriginalFilename());
|
||||
command.setFileBytes(icdFile.getBytes());
|
||||
command.setVersion(request == null ? null : request.getVersion());
|
||||
command.setAuthor(resolveText(request == null ? null : request.getAuthor(), moduleConfig.getDefaultAuthor()));
|
||||
command.setSaveToDisk(request != null && request.isSaveToDisk());
|
||||
command.setPrettyJson(request == null || request.isPrettyJson());
|
||||
command.setOutputDir(resolveText(request == null ? null : request.getOutputDir(), moduleConfig.getDefaultOutputDir()));
|
||||
|
||||
if (request != null && request.getIndexSelection() != null) {
|
||||
for (IndexSelectionGroupRequest groupRequest : request.getIndexSelection()) {
|
||||
if (groupRequest == null) {
|
||||
continue;
|
||||
}
|
||||
IndexSelectionGroupCommand groupCommand = new IndexSelectionGroupCommand();
|
||||
groupCommand.setGroupKey(groupRequest.getGroupKey());
|
||||
groupCommand.setGroupDesc(groupRequest.getGroupDesc());
|
||||
|
||||
if (groupRequest.getBindings() != null) {
|
||||
for (IndexBindingRequest bindingRequest : groupRequest.getBindings()) {
|
||||
if (bindingRequest == null) {
|
||||
continue;
|
||||
}
|
||||
IndexBindingCommand bindingCommand = new IndexBindingCommand();
|
||||
bindingCommand.setReportName(bindingRequest.getReportName());
|
||||
bindingCommand.setDataSetName(bindingRequest.getDataSetName());
|
||||
bindingCommand.setLabel(bindingRequest.getLabel());
|
||||
bindingCommand.setLnInst(bindingRequest.getLnInst());
|
||||
groupCommand.getBindings().add(bindingCommand);
|
||||
}
|
||||
}
|
||||
|
||||
command.getIndexSelection().add(groupCommand);
|
||||
}
|
||||
}
|
||||
|
||||
return command;
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalArgumentException("请求转换失败:" + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String resolveText(String value, String defaultValue) {
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
return value.trim();
|
||||
}
|
||||
return defaultValue == null ? null : defaultValue.trim();
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.converter;
|
||||
|
||||
import com.njcn.gather.icd.mapping.application.result.GenerateMappingResult;
|
||||
import com.njcn.gather.icd.mapping.controller.response.IndexCandidateReportItemResponse;
|
||||
import com.njcn.gather.icd.mapping.controller.response.IndexCandidateResponse;
|
||||
import com.njcn.gather.icd.mapping.controller.response.MappingDocumentResponse;
|
||||
import com.njcn.gather.icd.mapping.controller.response.MappingTaskResponse;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidate;
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidateReportItem;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 响应转换器。
|
||||
*
|
||||
* 作用:
|
||||
* 1. 把应用层结果转换成接口层响应对象;
|
||||
* 2. 对待匹配索引场景,输出新的“按业务分组返回”的结构。
|
||||
*/
|
||||
@Component
|
||||
public class MappingResponseConverter {
|
||||
|
||||
public MappingTaskResponse fromResult(GenerateMappingResult result) {
|
||||
MappingTaskResponse response = new MappingTaskResponse();
|
||||
response.setStatus(result.getStatus());
|
||||
response.setMessage(result.getMessage());
|
||||
response.setIedName(result.getIedName());
|
||||
response.setLdInst(result.getLdInst());
|
||||
response.setMappingJson(result.getMappingJson());
|
||||
response.setSavedPath(result.getSavedPath());
|
||||
response.getProblems().addAll(result.getProblems());
|
||||
|
||||
if (result.getIndexAnalysis() != null && result.getIndexAnalysis().getCandidates() != null) {
|
||||
for (IndexCandidate candidate : result.getIndexAnalysis().getCandidates()) {
|
||||
IndexCandidateResponse candidateResponse = new IndexCandidateResponse();
|
||||
candidateResponse.setGroupKey(candidate.getGroupKey());
|
||||
candidateResponse.setGroupDesc(candidate.getGroupDesc());
|
||||
candidateResponse.setReportCount(candidate.getReportCount());
|
||||
candidateResponse.getTemplateLabels().addAll(candidate.getTemplateLabels());
|
||||
|
||||
if (candidate.getReports() != null) {
|
||||
for (IndexCandidateReportItem item : candidate.getReports()) {
|
||||
IndexCandidateReportItemResponse itemResponse = new IndexCandidateReportItemResponse();
|
||||
itemResponse.setReportName(item.getReportName());
|
||||
itemResponse.setDataSetName(item.getDataSetName());
|
||||
itemResponse.setReportDesc(item.getReportDesc());
|
||||
itemResponse.getAvailableLnInstValues().addAll(item.getAvailableLnInstValues());
|
||||
candidateResponse.getReports().add(itemResponse);
|
||||
}
|
||||
}
|
||||
|
||||
response.getIndexCandidates().add(candidateResponse);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.getMappingDocument() != null) {
|
||||
MappingDocumentResponse doc = new MappingDocumentResponse();
|
||||
doc.setVersion(result.getMappingDocument().getVersion());
|
||||
doc.setAuthor(result.getMappingDocument().getAuthor());
|
||||
doc.setIed(result.getMappingDocument().getIed());
|
||||
doc.setLd(result.getMappingDocument().getLd());
|
||||
doc.setReportCount(result.getMappingDocument().getReportMap() == null
|
||||
? 0 : result.getMappingDocument().getReportMap().size());
|
||||
doc.setDataSetCount(result.getMappingDocument().getDataSetList() == null
|
||||
? 0 : result.getMappingDocument().getDataSetList().size());
|
||||
response.setMappingDocument(doc);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.analysis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 索引候选分组。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 一条候选对应一个业务分组,例如:统计数据、实时数据;
|
||||
* 2. 一个业务分组下可以包含多个报告;
|
||||
* 3. 这里不仅保存返回给前端的候选项,也保存从 DefaultCfg.ReportList 带下来的配置项,
|
||||
* 供后续 MappingGenerationService 直接使用,避免“二次查模板”失败。
|
||||
*/
|
||||
public class IndexCandidate {
|
||||
|
||||
/** 分组唯一键。 */
|
||||
private String groupKey;
|
||||
|
||||
/** 分组描述。 */
|
||||
private String groupDesc;
|
||||
|
||||
/** 该分组下实际匹配到的报告数量。 */
|
||||
private int reportCount;
|
||||
|
||||
/** DefaultCfg.txt 中该分组可用的标签模板。 */
|
||||
private List<String> templateLabels = new ArrayList<String>();
|
||||
|
||||
/** 当前分组下匹配到的报告列表。 */
|
||||
private List<IndexCandidateReportItem> reports = new ArrayList<IndexCandidateReportItem>();
|
||||
|
||||
/**
|
||||
* DefaultCfg.ReportList.inst
|
||||
* 例如:01 / 02 / 03 / 04
|
||||
*/
|
||||
private String reportInst;
|
||||
|
||||
/**
|
||||
* DefaultCfg.ReportList.Select
|
||||
* 例如:DataStatFileMap / DataRealFileMap / FlickerFileMap
|
||||
*/
|
||||
private String select;
|
||||
|
||||
/**
|
||||
* DefaultCfg.ReportList.TrgOps
|
||||
* 例如:40 / 96
|
||||
*/
|
||||
private String trgOps;
|
||||
|
||||
public String getGroupKey() {
|
||||
return groupKey;
|
||||
}
|
||||
|
||||
public void setGroupKey(String groupKey) {
|
||||
this.groupKey = groupKey;
|
||||
}
|
||||
|
||||
public String getGroupDesc() {
|
||||
return groupDesc;
|
||||
}
|
||||
|
||||
public void setGroupDesc(String groupDesc) {
|
||||
this.groupDesc = groupDesc;
|
||||
}
|
||||
|
||||
public int getReportCount() {
|
||||
return reportCount;
|
||||
}
|
||||
|
||||
public void setReportCount(int reportCount) {
|
||||
this.reportCount = reportCount;
|
||||
}
|
||||
|
||||
public List<String> getTemplateLabels() {
|
||||
return templateLabels;
|
||||
}
|
||||
|
||||
public void setTemplateLabels(List<String> templateLabels) {
|
||||
this.templateLabels = templateLabels;
|
||||
}
|
||||
|
||||
public List<IndexCandidateReportItem> getReports() {
|
||||
return reports;
|
||||
}
|
||||
|
||||
public void setReports(List<IndexCandidateReportItem> reports) {
|
||||
this.reports = reports;
|
||||
}
|
||||
|
||||
public String getReportInst() {
|
||||
return reportInst;
|
||||
}
|
||||
|
||||
public void setReportInst(String reportInst) {
|
||||
this.reportInst = reportInst;
|
||||
}
|
||||
|
||||
public String getSelect() {
|
||||
return select;
|
||||
}
|
||||
|
||||
public void setSelect(String select) {
|
||||
this.select = select;
|
||||
}
|
||||
|
||||
public String getTrgOps() {
|
||||
return trgOps;
|
||||
}
|
||||
|
||||
public void setTrgOps(String trgOps) {
|
||||
this.trgOps = trgOps;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.analysis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 候选分组下的单个报告项。
|
||||
*
|
||||
* 这个对象专门给前端展示“这个分组下面有哪些报告,以及每个报告对应哪些可用 lnInst 数字”。
|
||||
*/
|
||||
public class IndexCandidateReportItem {
|
||||
|
||||
/** 报告名称。 */
|
||||
private String reportName;
|
||||
|
||||
/** 数据集名称。 */
|
||||
private String dataSetName;
|
||||
|
||||
/** 报告中文描述。一般和分组描述相同,保留它是为了前端渲染更灵活。 */
|
||||
private String reportDesc;
|
||||
|
||||
/** 当前报告在 ICD 中解析出来的所有可选 lnInst 数字。 */
|
||||
private List<String> availableLnInstValues = new ArrayList<String>();
|
||||
|
||||
public String getReportName() {
|
||||
return reportName;
|
||||
}
|
||||
|
||||
public void setReportName(String reportName) {
|
||||
this.reportName = reportName;
|
||||
}
|
||||
|
||||
public String getDataSetName() {
|
||||
return dataSetName;
|
||||
}
|
||||
|
||||
public void setDataSetName(String dataSetName) {
|
||||
this.dataSetName = dataSetName;
|
||||
}
|
||||
|
||||
public String getReportDesc() {
|
||||
return reportDesc;
|
||||
}
|
||||
|
||||
public void setReportDesc(String reportDesc) {
|
||||
this.reportDesc = reportDesc;
|
||||
}
|
||||
|
||||
public List<String> getAvailableLnInstValues() {
|
||||
return availableLnInstValues;
|
||||
}
|
||||
|
||||
public void setAvailableLnInstValues(List<String> availableLnInstValues) {
|
||||
this.availableLnInstValues = availableLnInstValues;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.analysis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 校验结果。
|
||||
* 索引校验结果模型。保存是否通过以及问题列表。
|
||||
*/
|
||||
public class ValidationResult {
|
||||
private boolean valid;
|
||||
private List<String> problems = new ArrayList<String>();
|
||||
|
||||
public boolean isValid() { return valid; }
|
||||
public void setValid(boolean valid) { this.valid = valid; }
|
||||
public List<String> getProblems() { return problems; }
|
||||
public void setProblems(List<String> problems) { this.problems = problems; }
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DataSet 节点。
|
||||
* 数据集模型。用于承接 DataSet 下的 FCDA 列表。
|
||||
*/
|
||||
public class DataSetNode {
|
||||
private String name;
|
||||
private List<FcdaNode> fcdas = new ArrayList<FcdaNode>();
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public List<FcdaNode> getFcdas() { return fcdas; }
|
||||
public void setFcdas(List<FcdaNode> fcdas) { this.fcdas = fcdas; }
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DOI/SDI/DAI 统一节点。
|
||||
* DOI/SDI/DAI 细项模型。用于递归承接 DOI 树明细。
|
||||
*
|
||||
* kind 常见值:
|
||||
* - SDI
|
||||
* - DAI
|
||||
*/
|
||||
public class DoiElementNode {
|
||||
private String kind;
|
||||
private String name;
|
||||
private Long ix;
|
||||
private List<String> values = new ArrayList<String>();
|
||||
private List<DoiElementNode> children = new ArrayList<DoiElementNode>();
|
||||
|
||||
public String getKind() { return kind; }
|
||||
public void setKind(String kind) { this.kind = kind; }
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public Long getIx() { return ix; }
|
||||
public void setIx(Long ix) { this.ix = ix; }
|
||||
public List<String> getValues() { return values; }
|
||||
public void setValues(List<String> values) { this.values = values; }
|
||||
public List<DoiElementNode> getChildren() { return children; }
|
||||
public void setChildren(List<DoiElementNode> children) { this.children = children; }
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* DOI 节点。
|
||||
* DOI 模型。表示逻辑节点下的一个数据对象节点。
|
||||
*/
|
||||
public class DoiNode {
|
||||
private String name;
|
||||
private Long ix;
|
||||
private String lnClass;
|
||||
private String lnInst;
|
||||
private int sequenceCount;
|
||||
private List<DoiElementNode> children = new ArrayList<DoiElementNode>();
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public Long getIx() { return ix; }
|
||||
public void setIx(Long ix) { this.ix = ix; }
|
||||
public String getLnClass() { return lnClass; }
|
||||
public void setLnClass(String lnClass) { this.lnClass = lnClass; }
|
||||
public String getLnInst() { return lnInst; }
|
||||
public void setLnInst(String lnInst) { this.lnInst = lnInst; }
|
||||
public int getSequenceCount() { return sequenceCount; }
|
||||
public void setSequenceCount(int sequenceCount) { this.sequenceCount = sequenceCount; }
|
||||
public List<DoiElementNode> getChildren() { return children; }
|
||||
public void setChildren(List<DoiElementNode> children) { this.children = children; }
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
/**
|
||||
* FCDA 节点。
|
||||
* FCDA 模型。保存 lnClass、lnInst、doName、daName、fc、ix 等信息。
|
||||
*/
|
||||
public class FcdaNode {
|
||||
private String ldInst;
|
||||
private String prefix;
|
||||
private String lnClass;
|
||||
private String lnInst;
|
||||
private String doName;
|
||||
private String daName;
|
||||
private String fc;
|
||||
private Long ix;
|
||||
private int sequenceCount;
|
||||
|
||||
public String getLdInst() { return ldInst; }
|
||||
public void setLdInst(String ldInst) { this.ldInst = ldInst; }
|
||||
public String getPrefix() { return prefix; }
|
||||
public void setPrefix(String prefix) { this.prefix = prefix; }
|
||||
public String getLnClass() { return lnClass; }
|
||||
public void setLnClass(String lnClass) { this.lnClass = lnClass; }
|
||||
public String getLnInst() { return lnInst; }
|
||||
public void setLnInst(String lnInst) { this.lnInst = lnInst; }
|
||||
public String getDoName() { return doName; }
|
||||
public void setDoName(String doName) { this.doName = doName; }
|
||||
public String getDaName() { return daName; }
|
||||
public void setDaName(String daName) { this.daName = daName; }
|
||||
public String getFc() { return fc; }
|
||||
public void setFc(String fc) { this.fc = fc; }
|
||||
public Long getIx() { return ix; }
|
||||
public void setIx(Long ix) { this.ix = ix; }
|
||||
public int getSequenceCount() { return sequenceCount; }
|
||||
public void setSequenceCount(int sequenceCount) { this.sequenceCount = sequenceCount; }
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* ICD 文档聚合根。
|
||||
* ICD 统一领域模型的聚合根。承接 IED、LDevice、ReportControl、DataSet、LN 等解析结果。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 这是系统内部统一的 ICD 模型。
|
||||
* 2. 外部 JAXB generated 类只在 parser 层使用。
|
||||
* 3. 业务层全部依赖这个标准化模型,便于后续替换解析实现。
|
||||
*/
|
||||
public class IcdDocument {
|
||||
private String fileName;
|
||||
private String iedName;
|
||||
private String ldInst;
|
||||
private String ldPrefix;
|
||||
private IedNode primaryIed;
|
||||
private LogicalDeviceNode logicalDevice;
|
||||
private List<LnNode> logicalNodes = new ArrayList<LnNode>();
|
||||
private List<ReportControlNode> reportControls = new ArrayList<ReportControlNode>();
|
||||
private Map<String, DataSetNode> dataSets = new LinkedHashMap<String, DataSetNode>();
|
||||
|
||||
public String getFileName() { return fileName; }
|
||||
public void setFileName(String fileName) { this.fileName = fileName; }
|
||||
public String getIedName() { return iedName; }
|
||||
public void setIedName(String iedName) { this.iedName = iedName; }
|
||||
public String getLdInst() { return ldInst; }
|
||||
public void setLdInst(String ldInst) { this.ldInst = ldInst; }
|
||||
public String getLdPrefix() { return ldPrefix; }
|
||||
public void setLdPrefix(String ldPrefix) { this.ldPrefix = ldPrefix; }
|
||||
public IedNode getPrimaryIed() { return primaryIed; }
|
||||
public void setPrimaryIed(IedNode primaryIed) { this.primaryIed = primaryIed; }
|
||||
public LogicalDeviceNode getLogicalDevice() { return logicalDevice; }
|
||||
public void setLogicalDevice(LogicalDeviceNode logicalDevice) { this.logicalDevice = logicalDevice; }
|
||||
public List<LnNode> getLogicalNodes() { return logicalNodes; }
|
||||
public void setLogicalNodes(List<LnNode> logicalNodes) { this.logicalNodes = logicalNodes; }
|
||||
public List<ReportControlNode> getReportControls() { return reportControls; }
|
||||
public void setReportControls(List<ReportControlNode> reportControls) { this.reportControls = reportControls; }
|
||||
public Map<String, DataSetNode> getDataSets() { return dataSets; }
|
||||
public void setDataSets(Map<String, DataSetNode> dataSets) { this.dataSets = dataSets; }
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IED 节点。
|
||||
* IED 节点模型。保存 IED 名称以及其下逻辑设备引用。
|
||||
*/
|
||||
public class IedNode {
|
||||
private String name;
|
||||
private List<String> accessPointNames = new ArrayList<String>();
|
||||
private List<String> lDeviceInstList = new ArrayList<String>();
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public List<String> getAccessPointNames() { return accessPointNames; }
|
||||
public void setAccessPointNames(List<String> accessPointNames) { this.accessPointNames = accessPointNames; }
|
||||
public List<String> getLDeviceInstList() { return lDeviceInstList; }
|
||||
public void setLDeviceInstList(List<String> lDeviceInstList) { this.lDeviceInstList = lDeviceInstList; }
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 逻辑节点。
|
||||
* 逻辑节点模型。保存 lnClass、lnInst、prefix、DOI 等信息。
|
||||
*
|
||||
* LN0 和 LN 最终都统一抽象为这个模型。
|
||||
*/
|
||||
public class LnNode {
|
||||
private boolean ln0;
|
||||
private String prefix;
|
||||
private String lnClass;
|
||||
private String lnInst;
|
||||
private String lnType;
|
||||
private List<DoiNode> doiList = new ArrayList<DoiNode>();
|
||||
|
||||
public boolean isLn0() { return ln0; }
|
||||
public void setLn0(boolean ln0) { this.ln0 = ln0; }
|
||||
public String getPrefix() { return prefix; }
|
||||
public void setPrefix(String prefix) { this.prefix = prefix; }
|
||||
public String getLnClass() { return lnClass; }
|
||||
public void setLnClass(String lnClass) { this.lnClass = lnClass; }
|
||||
public String getLnInst() { return lnInst; }
|
||||
public void setLnInst(String lnInst) { this.lnInst = lnInst; }
|
||||
public String getLnType() { return lnType; }
|
||||
public void setLnType(String lnType) { this.lnType = lnType; }
|
||||
public List<DoiNode> getDoiList() { return doiList; }
|
||||
public void setDoiList(List<DoiNode> doiList) { this.doiList = doiList; }
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
/**
|
||||
* 逻辑设备节点。
|
||||
* 逻辑设备模型。保存 inst、prefix 以及其下 LN/LN0 列表。
|
||||
*/
|
||||
public class LogicalDeviceNode {
|
||||
private String inst;
|
||||
private String ldName;
|
||||
public String getInst() { return inst; }
|
||||
public void setInst(String inst) { this.inst = inst; }
|
||||
public String getLdName() { return ldName; }
|
||||
public void setLdName(String ldName) { this.ldName = ldName; }
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.icd;
|
||||
|
||||
/**
|
||||
* ReportControl 节点。
|
||||
* 报告控制块模型。用于保存报告名称、关联 DataSet、缓冲属性等。
|
||||
*/
|
||||
public class ReportControlNode {
|
||||
private String name;
|
||||
private String rptId;
|
||||
private boolean buffered;
|
||||
private String dataSetName;
|
||||
private String trgOps;
|
||||
private String confRev;
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
public String getRptId() { return rptId; }
|
||||
public void setRptId(String rptId) { this.rptId = rptId; }
|
||||
public boolean isBuffered() { return buffered; }
|
||||
public void setBuffered(boolean buffered) { this.buffered = buffered; }
|
||||
public String getDataSetName() { return dataSetName; }
|
||||
public void setDataSetName(String dataSetName) { this.dataSetName = dataSetName; }
|
||||
public String getTrgOps() { return trgOps; }
|
||||
public void setTrgOps(String trgOps) { this.trgOps = trgOps; }
|
||||
public String getConfRev() { return confRev; }
|
||||
public void setConfRev(String confRev) { this.confRev = confRev; }
|
||||
public Boolean getBuffered() { return buffered; }
|
||||
public void setBuffered(Boolean buffered) { this.buffered = buffered; }
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.intermediate;
|
||||
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.LnNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 最终参与生成 DataSetList 的选择状态。
|
||||
*
|
||||
* 关键修正:
|
||||
* 旧版本一个绑定只保存一个 LnNode,导致:
|
||||
* - MSQI 整组丢失
|
||||
* - MHAI 只生成一半
|
||||
*
|
||||
* 新版本改成:
|
||||
* 一个绑定可以关联多个 LnNode,后续生成阶段再逐个展开。
|
||||
*/
|
||||
public class DataSetSelectionState {
|
||||
|
||||
/** 所属分组 key。 */
|
||||
private String groupKey;
|
||||
|
||||
/** 所属分组 desc。 */
|
||||
private String groupDesc;
|
||||
|
||||
/** 报告名。 */
|
||||
private String reportName;
|
||||
|
||||
/** 数据集名。 */
|
||||
private String dataSetName;
|
||||
|
||||
/** 绑定标签。 */
|
||||
private String label;
|
||||
|
||||
/** 绑定的 lnInst。 */
|
||||
private String lnInst;
|
||||
|
||||
/**
|
||||
* 当前绑定最终命中的 LN 节点列表。
|
||||
*
|
||||
* 说明:
|
||||
* 同一个 lnInst 在不同 lnClass 下可能都需要展开,
|
||||
* 例如:MMXU / MSQI / MHAI。
|
||||
*/
|
||||
private List<LnNode> lnNodes = new ArrayList<LnNode>();
|
||||
|
||||
public String getGroupKey() {
|
||||
return groupKey;
|
||||
}
|
||||
|
||||
public void setGroupKey(String groupKey) {
|
||||
this.groupKey = groupKey;
|
||||
}
|
||||
|
||||
public String getGroupDesc() {
|
||||
return groupDesc;
|
||||
}
|
||||
|
||||
public void setGroupDesc(String groupDesc) {
|
||||
this.groupDesc = groupDesc;
|
||||
}
|
||||
|
||||
public String getReportName() {
|
||||
return reportName;
|
||||
}
|
||||
|
||||
public void setReportName(String reportName) {
|
||||
this.reportName = reportName;
|
||||
}
|
||||
|
||||
public String getDataSetName() {
|
||||
return dataSetName;
|
||||
}
|
||||
|
||||
public void setDataSetName(String dataSetName) {
|
||||
this.dataSetName = dataSetName;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLnInst() {
|
||||
return lnInst;
|
||||
}
|
||||
|
||||
public void setLnInst(String lnInst) {
|
||||
this.lnInst = lnInst;
|
||||
}
|
||||
|
||||
public List<LnNode> getLnNodes() {
|
||||
return lnNodes;
|
||||
}
|
||||
|
||||
public void setLnNodes(List<LnNode> lnNodes) {
|
||||
this.lnNodes = lnNodes;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.intermediate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 中间态总对象。
|
||||
*
|
||||
* 作用:
|
||||
* 1. 对应原 C# 里“先形成中间态,再做最终 JSON 组装”的思路;
|
||||
* 2. 把业务分组、用户绑定、最终 DataSet 选择结果集中保存;
|
||||
* 3. 避免在 MappingGenerationService 里直接边遍历边拼 JSON,便于后续扩展和排查。
|
||||
*/
|
||||
public class ReportAndDataSetState {
|
||||
|
||||
/** IED 名称。 */
|
||||
private String iedName;
|
||||
|
||||
/** LD 实例名。 */
|
||||
private String ldInst;
|
||||
|
||||
/** 分组状态列表。 */
|
||||
private List<ReportGroupState> reportGroups = new ArrayList<ReportGroupState>();
|
||||
|
||||
/** 数据集选择状态列表。 */
|
||||
private List<DataSetSelectionState> dataSetSelections = new ArrayList<DataSetSelectionState>();
|
||||
|
||||
public String getIedName() {
|
||||
return iedName;
|
||||
}
|
||||
|
||||
public void setIedName(String iedName) {
|
||||
this.iedName = iedName;
|
||||
}
|
||||
|
||||
public String getLdInst() {
|
||||
return ldInst;
|
||||
}
|
||||
|
||||
public void setLdInst(String ldInst) {
|
||||
this.ldInst = ldInst;
|
||||
}
|
||||
|
||||
public List<ReportGroupState> getReportGroups() {
|
||||
return reportGroups;
|
||||
}
|
||||
|
||||
public void setReportGroups(List<ReportGroupState> reportGroups) {
|
||||
this.reportGroups = reportGroups;
|
||||
}
|
||||
|
||||
public List<DataSetSelectionState> getDataSetSelections() {
|
||||
return dataSetSelections;
|
||||
}
|
||||
|
||||
public void setDataSetSelections(List<DataSetSelectionState> dataSetSelections) {
|
||||
this.dataSetSelections = dataSetSelections;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.intermediate;
|
||||
|
||||
/**
|
||||
* 单条最终有效绑定关系的中间态。
|
||||
*/
|
||||
public class ReportBindingState {
|
||||
|
||||
/** 分组 key。 */
|
||||
private String groupKey;
|
||||
|
||||
/** 分组描述。 */
|
||||
private String groupDesc;
|
||||
|
||||
/** 报告名。 */
|
||||
private String reportName;
|
||||
|
||||
/** 数据集名。 */
|
||||
private String dataSetName;
|
||||
|
||||
/** 标签。 */
|
||||
private String label;
|
||||
|
||||
/** 绑定的 lnInst 数字。 */
|
||||
private String lnInst;
|
||||
|
||||
public String getGroupKey() {
|
||||
return groupKey;
|
||||
}
|
||||
|
||||
public void setGroupKey(String groupKey) {
|
||||
this.groupKey = groupKey;
|
||||
}
|
||||
|
||||
public String getGroupDesc() {
|
||||
return groupDesc;
|
||||
}
|
||||
|
||||
public void setGroupDesc(String groupDesc) {
|
||||
this.groupDesc = groupDesc;
|
||||
}
|
||||
|
||||
public String getReportName() {
|
||||
return reportName;
|
||||
}
|
||||
|
||||
public void setReportName(String reportName) {
|
||||
this.reportName = reportName;
|
||||
}
|
||||
|
||||
public String getDataSetName() {
|
||||
return dataSetName;
|
||||
}
|
||||
|
||||
public void setDataSetName(String dataSetName) {
|
||||
this.dataSetName = dataSetName;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public String getLnInst() {
|
||||
return lnInst;
|
||||
}
|
||||
|
||||
public void setLnInst(String lnInst) {
|
||||
this.lnInst = lnInst;
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.intermediate;
|
||||
|
||||
import com.njcn.gather.icd.mapping.domain.model.analysis.IndexCandidateReportItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 单个业务分组的中间态。
|
||||
*/
|
||||
public class ReportGroupState {
|
||||
|
||||
/** 分组唯一键。 */
|
||||
private String groupKey;
|
||||
|
||||
/** 分组描述。 */
|
||||
private String groupDesc;
|
||||
|
||||
/** 当前分组匹配到的报告数量。 */
|
||||
private int reportCount;
|
||||
|
||||
/** 报表 inst(来自 DefaultCfg.ReportList.inst)。 */
|
||||
private String reportInst;
|
||||
|
||||
/** Select(来自 DefaultCfg.ReportList.Select)。 */
|
||||
private String select;
|
||||
|
||||
/** TrgOps(来自 DefaultCfg.ReportList.TrgOps)。 */
|
||||
private String trgOps;
|
||||
|
||||
/** 当前分组包含的报告列表。 */
|
||||
private List<IndexCandidateReportItem> reportItems = new ArrayList<IndexCandidateReportItem>();
|
||||
|
||||
/** 用户最终确认的绑定关系。 */
|
||||
private List<ReportBindingState> bindings = new ArrayList<ReportBindingState>();
|
||||
|
||||
public String getGroupKey() {
|
||||
return groupKey;
|
||||
}
|
||||
|
||||
public void setGroupKey(String groupKey) {
|
||||
this.groupKey = groupKey;
|
||||
}
|
||||
|
||||
public String getGroupDesc() {
|
||||
return groupDesc;
|
||||
}
|
||||
|
||||
public void setGroupDesc(String groupDesc) {
|
||||
this.groupDesc = groupDesc;
|
||||
}
|
||||
|
||||
public int getReportCount() {
|
||||
return reportCount;
|
||||
}
|
||||
|
||||
public void setReportCount(int reportCount) {
|
||||
this.reportCount = reportCount;
|
||||
}
|
||||
|
||||
public String getReportInst() {
|
||||
return reportInst;
|
||||
}
|
||||
|
||||
public void setReportInst(String reportInst) {
|
||||
this.reportInst = reportInst;
|
||||
}
|
||||
|
||||
public String getSelect() {
|
||||
return select;
|
||||
}
|
||||
|
||||
public void setSelect(String select) {
|
||||
this.select = select;
|
||||
}
|
||||
|
||||
public String getTrgOps() {
|
||||
return trgOps;
|
||||
}
|
||||
|
||||
public void setTrgOps(String trgOps) {
|
||||
this.trgOps = trgOps;
|
||||
}
|
||||
|
||||
public List<IndexCandidateReportItem> getReportItems() {
|
||||
return reportItems;
|
||||
}
|
||||
|
||||
public void setReportItems(List<IndexCandidateReportItem> reportItems) {
|
||||
this.reportItems = reportItems;
|
||||
}
|
||||
|
||||
public List<ReportBindingState> getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public void setBindings(List<ReportBindingState> bindings) {
|
||||
this.bindings = bindings;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 最终映射中的 DataSetList 单项。
|
||||
*/
|
||||
public class DataSetGroupItem {
|
||||
|
||||
/** 分组描述。一般来自 LnClassList.desc。 */
|
||||
private String desc;
|
||||
|
||||
/** lnClass。 */
|
||||
private String lnClass;
|
||||
|
||||
/** 该 lnClass 下的 inst 列表。 */
|
||||
private List<InstItem> instList = new ArrayList<InstItem>();
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getLnClass() {
|
||||
return lnClass;
|
||||
}
|
||||
|
||||
public void setLnClass(String lnClass) {
|
||||
this.lnClass = lnClass;
|
||||
}
|
||||
|
||||
public List<InstItem> getInstList() {
|
||||
return instList;
|
||||
}
|
||||
|
||||
public void setInstList(List<InstItem> instList) {
|
||||
this.instList = instList;
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 最终映射中的 doiList 单项。
|
||||
*/
|
||||
public class DoiItem {
|
||||
|
||||
/** DOI 名称。 */
|
||||
private String name;
|
||||
|
||||
/** DOI 描述。 */
|
||||
private String desc;
|
||||
|
||||
/** 起始序号。 */
|
||||
private int start;
|
||||
|
||||
/** 结束序号。 */
|
||||
private int end;
|
||||
|
||||
/** 单位。 */
|
||||
private String unit;
|
||||
|
||||
/** 系数。 */
|
||||
private float coefficient;
|
||||
|
||||
/** 基波标志。 */
|
||||
private int baseflag;
|
||||
|
||||
/** 基波数量。 */
|
||||
private int basecount;
|
||||
|
||||
/** ICD 实际序列数。 */
|
||||
private int icdcout;
|
||||
|
||||
/** SDI 列表。 */
|
||||
private List<SdiItem> sdiList = new ArrayList<SdiItem>();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
public void setEnd(int end) {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public float getCoefficient() {
|
||||
return coefficient;
|
||||
}
|
||||
|
||||
public void setCoefficient(float coefficient) {
|
||||
this.coefficient = coefficient;
|
||||
}
|
||||
|
||||
public int getBaseflag() {
|
||||
return baseflag;
|
||||
}
|
||||
|
||||
public void setBaseflag(int baseflag) {
|
||||
this.baseflag = baseflag;
|
||||
}
|
||||
|
||||
public int getBasecount() {
|
||||
return basecount;
|
||||
}
|
||||
|
||||
public void setBasecount(int basecount) {
|
||||
this.basecount = basecount;
|
||||
}
|
||||
|
||||
public int getIcdcout() {
|
||||
return icdcout;
|
||||
}
|
||||
|
||||
public void setIcdcout(int icdcout) {
|
||||
this.icdcout = icdcout;
|
||||
}
|
||||
|
||||
public List<SdiItem> getSdiList() {
|
||||
return sdiList;
|
||||
}
|
||||
|
||||
public void setSdiList(List<SdiItem> sdiList) {
|
||||
this.sdiList = sdiList;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 最终映射中的 instList 单项。
|
||||
*/
|
||||
public class InstItem {
|
||||
|
||||
/** lnInst。 */
|
||||
private String inst;
|
||||
|
||||
/** 该 inst 的描述。通常使用当前绑定的 label。 */
|
||||
private String desc;
|
||||
|
||||
/** DOI 列表。 */
|
||||
private List<DoiItem> doiList = new ArrayList<DoiItem>();
|
||||
|
||||
public String getInst() {
|
||||
return inst;
|
||||
}
|
||||
|
||||
public void setInst(String inst) {
|
||||
this.inst = inst;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public List<DoiItem> getDoiList() {
|
||||
return doiList;
|
||||
}
|
||||
|
||||
public void setDoiList(List<DoiItem> doiList) {
|
||||
this.doiList = doiList;
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.mapping;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 最终映射文档。
|
||||
*
|
||||
* 关键说明:
|
||||
* 1. Java 字段统一使用 lowerCamelCase,避免 Jackson 同时输出 ied/IED 这类重复字段。
|
||||
* 2. JSON 输出名通过 @JsonProperty 显式指定,确保与原 C# 输出格式一致。
|
||||
*/
|
||||
public class MappingDocument {
|
||||
|
||||
@JsonProperty("version")
|
||||
private String version;
|
||||
|
||||
@JsonProperty("author")
|
||||
private String author;
|
||||
|
||||
@JsonProperty("IED")
|
||||
private String ied;
|
||||
|
||||
@JsonProperty("LD")
|
||||
private String ld;
|
||||
|
||||
/**
|
||||
* 原 C# mainFrom.txt 固定调用:
|
||||
* Set_WaveTimeFlag("BeiJing")
|
||||
*/
|
||||
@JsonProperty("WaveTimeFlag")
|
||||
private String waveTimeFlag;
|
||||
|
||||
/**
|
||||
* 原 C# mainFrom.txt 固定调用:
|
||||
* Set_DataType("1")
|
||||
*/
|
||||
@JsonProperty("DataType")
|
||||
private String dataType;
|
||||
|
||||
/**
|
||||
* 原 C# mainFrom.txt 固定调用:
|
||||
* Set_unit("1")
|
||||
*/
|
||||
@JsonProperty("unit")
|
||||
private String unit;
|
||||
|
||||
@JsonProperty("ReportMap")
|
||||
private List<ReportMapItem> reportMap = new ArrayList<ReportMapItem>();
|
||||
|
||||
@JsonProperty("DataSetList")
|
||||
private List<DataSetGroupItem> dataSetList = new ArrayList<DataSetGroupItem>();
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getIed() {
|
||||
return ied;
|
||||
}
|
||||
|
||||
public void setIed(String ied) {
|
||||
this.ied = ied;
|
||||
}
|
||||
|
||||
public String getLd() {
|
||||
return ld;
|
||||
}
|
||||
|
||||
public void setLd(String ld) {
|
||||
this.ld = ld;
|
||||
}
|
||||
|
||||
public String getWaveTimeFlag() {
|
||||
return waveTimeFlag;
|
||||
}
|
||||
|
||||
public void setWaveTimeFlag(String waveTimeFlag) {
|
||||
this.waveTimeFlag = waveTimeFlag;
|
||||
}
|
||||
|
||||
public String getDataType() {
|
||||
return dataType;
|
||||
}
|
||||
|
||||
public void setDataType(String dataType) {
|
||||
this.dataType = dataType;
|
||||
}
|
||||
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
public void setUnit(String unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public List<ReportMapItem> getReportMap() {
|
||||
return reportMap;
|
||||
}
|
||||
|
||||
public void setReportMap(List<ReportMapItem> reportMap) {
|
||||
this.reportMap = reportMap;
|
||||
}
|
||||
|
||||
public List<DataSetGroupItem> getDataSetList() {
|
||||
return dataSetList;
|
||||
}
|
||||
|
||||
public void setDataSetList(List<DataSetGroupItem> dataSetList) {
|
||||
this.dataSetList = dataSetList;
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.mapping;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* 最终映射中的 ReportMap 单项。
|
||||
*
|
||||
* 关键说明:
|
||||
* 1. 原 C# 不是把同组报告合并成一条,而是组内每个报告各自输出一条。
|
||||
* 2. 但 reportCount 仍然写该分组总数。
|
||||
* 3. buffered 不是 boolean,而是:
|
||||
* - true -> BR
|
||||
* - false -> RP
|
||||
*/
|
||||
public class ReportMapItem {
|
||||
|
||||
@JsonProperty("desc")
|
||||
private String desc;
|
||||
|
||||
@JsonProperty("reportCount")
|
||||
private int reportCount;
|
||||
|
||||
@JsonProperty("rptID")
|
||||
private String rptId;
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("buffered")
|
||||
private String buffered;
|
||||
|
||||
@JsonProperty("inst")
|
||||
private String inst;
|
||||
|
||||
/**
|
||||
* 原 C# Set_FlickerFlag() 当前固定写 "0"
|
||||
*/
|
||||
@JsonProperty("FlickerFlag")
|
||||
private String flickerFlag;
|
||||
|
||||
/**
|
||||
* 原 C# 来自 DefaultCfg.ReportList.Select
|
||||
*/
|
||||
@JsonProperty("Select")
|
||||
private String select;
|
||||
|
||||
/**
|
||||
* 原 C# 来自 DefaultCfg.ReportList.TrgOps
|
||||
*/
|
||||
@JsonProperty("TrgOps")
|
||||
private String trgOps;
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public int getReportCount() {
|
||||
return reportCount;
|
||||
}
|
||||
|
||||
public void setReportCount(int reportCount) {
|
||||
this.reportCount = reportCount;
|
||||
}
|
||||
|
||||
public String getRptId() {
|
||||
return rptId;
|
||||
}
|
||||
|
||||
public void setRptId(String rptId) {
|
||||
this.rptId = rptId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getBuffered() {
|
||||
return buffered;
|
||||
}
|
||||
|
||||
public void setBuffered(String buffered) {
|
||||
this.buffered = buffered;
|
||||
}
|
||||
|
||||
public String getInst() {
|
||||
return inst;
|
||||
}
|
||||
|
||||
public void setInst(String inst) {
|
||||
this.inst = inst;
|
||||
}
|
||||
|
||||
public String getFlickerFlag() {
|
||||
return flickerFlag;
|
||||
}
|
||||
|
||||
public void setFlickerFlag(String flickerFlag) {
|
||||
this.flickerFlag = flickerFlag;
|
||||
}
|
||||
|
||||
public String getSelect() {
|
||||
return select;
|
||||
}
|
||||
|
||||
public void setSelect(String select) {
|
||||
this.select = select;
|
||||
}
|
||||
|
||||
public String getTrgOps() {
|
||||
return trgOps;
|
||||
}
|
||||
|
||||
public void setTrgOps(String trgOps) {
|
||||
this.trgOps = trgOps;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 最终映射中的 sdiList 单项。
|
||||
*/
|
||||
public class SdiItem {
|
||||
|
||||
/** SDI 名称。 */
|
||||
private String name;
|
||||
|
||||
/** SDI 描述。 */
|
||||
private String desc;
|
||||
|
||||
/** 类型列表。 */
|
||||
private List<TypeItem> typeList = new ArrayList<TypeItem>();
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public List<TypeItem> getTypeList() {
|
||||
return typeList;
|
||||
}
|
||||
|
||||
public void setTypeList(List<TypeItem> typeList) {
|
||||
this.typeList = typeList;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.mapping;
|
||||
|
||||
/**
|
||||
* 最终映射中的 typeList 单项。
|
||||
*/
|
||||
public class TypeItem {
|
||||
|
||||
/** 类型名称。 */
|
||||
private String name;
|
||||
|
||||
/** 类型描述。 */
|
||||
private String desc;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.template;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
/**
|
||||
* 默认模板。
|
||||
* 默认模板模型。把 default-template.json 解析成可直接使用的对象。
|
||||
*
|
||||
* 当前只保留第一个接口真正会用到的部分。
|
||||
*/
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class DefaultTemplate {
|
||||
|
||||
@JsonProperty("ReportList")
|
||||
private List<ReportCfgItem> reportList = new ArrayList<ReportCfgItem>();
|
||||
|
||||
@JsonProperty("LnClassList")
|
||||
private List<LnClassCfgItem> lnClassList = new ArrayList<LnClassCfgItem>();
|
||||
|
||||
@JsonProperty("PhaseList")
|
||||
private List<PhaseCfgItem> phaseList = new ArrayList<PhaseCfgItem>();
|
||||
|
||||
@JsonProperty("MultiplierList")
|
||||
private List<MultiplierCfgItem> multiplierList = new ArrayList<MultiplierCfgItem>();
|
||||
|
||||
@JsonProperty("UnitList")
|
||||
private List<UnitCfgItem> unitList = new ArrayList<UnitCfgItem>();
|
||||
|
||||
@JsonProperty("TypeList")
|
||||
private List<TypeCfgItem> typeList = new ArrayList<TypeCfgItem>();
|
||||
|
||||
@JsonProperty("DataObjectsList")
|
||||
private List<DataObjectCfgItem> dataObjectsList = new ArrayList<DataObjectCfgItem>();
|
||||
|
||||
/**
|
||||
* 基础校验。
|
||||
*
|
||||
* 返回问题列表;为空表示通过。
|
||||
*/
|
||||
public List<String> verify() {
|
||||
List<String> problems = new ArrayList<String>();
|
||||
ensureDuplicateNames("LnClassList", extractNames(lnClassList), problems);
|
||||
ensureDuplicateNames("PhaseList", extractNames(phaseList), problems);
|
||||
ensureDuplicateNames("MultiplierList", extractNames(multiplierList), problems);
|
||||
ensureDuplicateNames("UnitList", extractNames(unitList), problems);
|
||||
ensureDuplicateNames("TypeList", extractNames(typeList), problems);
|
||||
ensureDuplicateObjectNames(problems);
|
||||
if (reportList == null || reportList.isEmpty()) {
|
||||
problems.add("DefaultCfg.ReportList 为空");
|
||||
}
|
||||
return problems;
|
||||
}
|
||||
|
||||
private List<String> extractNames(List<? extends NameListSupport> list) {
|
||||
List<String> names = new ArrayList<String>();
|
||||
if (list == null) {
|
||||
return names;
|
||||
}
|
||||
for (NameListSupport item : list) {
|
||||
if (item.getNameList() != null) {
|
||||
names.addAll(item.getNameList());
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private void ensureDuplicateNames(String section, List<String> names, List<String> problems) {
|
||||
Set<String> set = new HashSet<String>();
|
||||
for (String name : names) {
|
||||
if (name == null) {
|
||||
continue;
|
||||
}
|
||||
String key = name.trim();
|
||||
if (!set.add(key)) {
|
||||
problems.add(section + " 中存在重复配置项:" + key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureDuplicateObjectNames(List<String> problems) {
|
||||
if (dataObjectsList == null) {
|
||||
return;
|
||||
}
|
||||
for (DataObjectCfgItem dataObject : dataObjectsList) {
|
||||
List<String> names = new ArrayList<String>();
|
||||
if (dataObject.getObjectList() != null) {
|
||||
for (ObjectCfgItem object : dataObject.getObjectList()) {
|
||||
if (object.getNameList() != null) {
|
||||
names.addAll(object.getNameList());
|
||||
}
|
||||
}
|
||||
}
|
||||
ensureDuplicateNames("DataObjectsList[" + dataObject.getDesc() + "]", names, problems);
|
||||
}
|
||||
}
|
||||
|
||||
public List<ReportCfgItem> getReportList() {
|
||||
return reportList;
|
||||
}
|
||||
|
||||
public void setReportList(List<ReportCfgItem> reportList) {
|
||||
this.reportList = reportList;
|
||||
}
|
||||
|
||||
public List<LnClassCfgItem> getLnClassList() {
|
||||
return lnClassList;
|
||||
}
|
||||
|
||||
public void setLnClassList(List<LnClassCfgItem> lnClassList) {
|
||||
this.lnClassList = lnClassList;
|
||||
}
|
||||
|
||||
public List<PhaseCfgItem> getPhaseList() {
|
||||
return phaseList;
|
||||
}
|
||||
|
||||
public void setPhaseList(List<PhaseCfgItem> phaseList) {
|
||||
this.phaseList = phaseList;
|
||||
}
|
||||
|
||||
public List<MultiplierCfgItem> getMultiplierList() {
|
||||
return multiplierList;
|
||||
}
|
||||
|
||||
public void setMultiplierList(List<MultiplierCfgItem> multiplierList) {
|
||||
this.multiplierList = multiplierList;
|
||||
}
|
||||
|
||||
public List<UnitCfgItem> getUnitList() {
|
||||
return unitList;
|
||||
}
|
||||
|
||||
public void setUnitList(List<UnitCfgItem> unitList) {
|
||||
this.unitList = unitList;
|
||||
}
|
||||
|
||||
public List<TypeCfgItem> getTypeList() {
|
||||
return typeList;
|
||||
}
|
||||
|
||||
public void setTypeList(List<TypeCfgItem> typeList) {
|
||||
this.typeList = typeList;
|
||||
}
|
||||
|
||||
public List<DataObjectCfgItem> getDataObjectsList() {
|
||||
return dataObjectsList;
|
||||
}
|
||||
|
||||
public void setDataObjectsList(List<DataObjectCfgItem> dataObjectsList) {
|
||||
this.dataObjectsList = dataObjectsList;
|
||||
}
|
||||
|
||||
/** 统一抽象:凡是有 nameList 的配置项都实现它。 */
|
||||
public interface NameListSupport {
|
||||
List<String> getNameList();
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class ReportCfgItem {
|
||||
|
||||
/**
|
||||
* 报告分组描述,例如:统计数据、实时数据、波动闪变
|
||||
*/
|
||||
@JsonProperty("desc")
|
||||
private String desc;
|
||||
|
||||
/**
|
||||
* 报告 inst,例如:01
|
||||
*/
|
||||
@JsonProperty("inst")
|
||||
private String inst;
|
||||
|
||||
/**
|
||||
* 原始配置中的 TrgOps。
|
||||
* 例如:40 / 96
|
||||
*
|
||||
* 这里必须显式加 @JsonProperty("TrgOps"),
|
||||
* 否则在当前项目里很容易反序列化后为 null。
|
||||
*/
|
||||
@JsonProperty("TrgOps")
|
||||
private String trgOps;
|
||||
|
||||
/**
|
||||
* 原始配置中的 Select。
|
||||
* 例如:DataStatFileMap / DataRealFileMap / FlickerFileMap
|
||||
*
|
||||
* 这里必须显式加 @JsonProperty("Select"),
|
||||
* 否则在当前项目里很容易反序列化后为 null。
|
||||
*/
|
||||
@JsonProperty("Select")
|
||||
private String select;
|
||||
|
||||
/**
|
||||
* 该分组覆盖的数据集名称列表
|
||||
*/
|
||||
@JsonProperty("DataSetList")
|
||||
private List<String> dataSetList = new ArrayList<String>();
|
||||
|
||||
/**
|
||||
* 该分组可配置的标签模板列表
|
||||
*/
|
||||
@JsonProperty("LnInstList")
|
||||
private List<String> lnInstList = new ArrayList<String>();
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public void setDesc(String desc) {
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
public String getInst() {
|
||||
return inst;
|
||||
}
|
||||
|
||||
public void setInst(String inst) {
|
||||
this.inst = inst;
|
||||
}
|
||||
|
||||
public String getTrgOps() {
|
||||
return trgOps;
|
||||
}
|
||||
|
||||
public void setTrgOps(String trgOps) {
|
||||
this.trgOps = trgOps;
|
||||
}
|
||||
|
||||
public String getSelect() {
|
||||
return select;
|
||||
}
|
||||
|
||||
public void setSelect(String select) {
|
||||
this.select = select;
|
||||
}
|
||||
|
||||
public List<String> getDataSetList() {
|
||||
return dataSetList;
|
||||
}
|
||||
|
||||
public void setDataSetList(List<String> dataSetList) {
|
||||
this.dataSetList = dataSetList;
|
||||
}
|
||||
|
||||
public List<String> getLnInstList() {
|
||||
return lnInstList;
|
||||
}
|
||||
|
||||
public void setLnInstList(List<String> lnInstList) {
|
||||
this.lnInstList = lnInstList;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class LnClassCfgItem implements NameListSupport {
|
||||
private String desc;
|
||||
private List<String> nameList = new ArrayList<String>();
|
||||
public String getDesc() { return desc; }
|
||||
public void setDesc(String desc) { this.desc = desc; }
|
||||
public List<String> getNameList() { return nameList; }
|
||||
public void setNameList(List<String> nameList) { this.nameList = nameList; }
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class PhaseCfgItem implements NameListSupport {
|
||||
private String desc;
|
||||
private List<String> nameList = new ArrayList<String>();
|
||||
public String getDesc() { return desc; }
|
||||
public void setDesc(String desc) { this.desc = desc; }
|
||||
public List<String> getNameList() { return nameList; }
|
||||
public void setNameList(List<String> nameList) { this.nameList = nameList; }
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class MultiplierCfgItem implements NameListSupport {
|
||||
private int multiplier;
|
||||
private List<String> nameList = new ArrayList<String>();
|
||||
public int getMultiplier() { return multiplier; }
|
||||
public void setMultiplier(int multiplier) { this.multiplier = multiplier; }
|
||||
public List<String> getNameList() { return nameList; }
|
||||
public void setNameList(List<String> nameList) { this.nameList = nameList; }
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class UnitCfgItem implements NameListSupport {
|
||||
private String desc;
|
||||
private List<String> nameList = new ArrayList<String>();
|
||||
public String getDesc() { return desc; }
|
||||
public void setDesc(String desc) { this.desc = desc; }
|
||||
public List<String> getNameList() { return nameList; }
|
||||
public void setNameList(List<String> nameList) { this.nameList = nameList; }
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class TypeCfgItem implements NameListSupport {
|
||||
private String desc;
|
||||
private List<String> nameList = new ArrayList<String>();
|
||||
public String getDesc() { return desc; }
|
||||
public void setDesc(String desc) { this.desc = desc; }
|
||||
public List<String> getNameList() { return nameList; }
|
||||
public void setNameList(List<String> nameList) { this.nameList = nameList; }
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class DataObjectCfgItem {
|
||||
private String desc;
|
||||
@JsonProperty("LnInstList")
|
||||
private List<String> lnInstList = new ArrayList<String>();
|
||||
@JsonProperty("ObjectList")
|
||||
private List<ObjectCfgItem> objectList = new ArrayList<ObjectCfgItem>();
|
||||
public String getDesc() { return desc; }
|
||||
public void setDesc(String desc) { this.desc = desc; }
|
||||
public List<String> getLnInstList() { return lnInstList; }
|
||||
public void setLnInstList(List<String> lnInstList) { this.lnInstList = lnInstList; }
|
||||
public List<ObjectCfgItem> getObjectList() { return objectList; }
|
||||
public void setObjectList(List<ObjectCfgItem> objectList) { this.objectList = objectList; }
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static class ObjectCfgItem implements NameListSupport {
|
||||
private String desc;
|
||||
private int baseflag;
|
||||
private int basecount;
|
||||
private int queuecount;
|
||||
private List<String> nameList = new ArrayList<String>();
|
||||
private List<String> queueList = new ArrayList<String>();
|
||||
public String getDesc() { return desc; }
|
||||
public void setDesc(String desc) { this.desc = desc; }
|
||||
public int getBaseflag() { return baseflag; }
|
||||
public void setBaseflag(int baseflag) { this.baseflag = baseflag; }
|
||||
public int getBasecount() { return basecount; }
|
||||
public void setBasecount(int basecount) { this.basecount = basecount; }
|
||||
public int getQueuecount() { return queuecount; }
|
||||
public void setQueuecount(int queuecount) { this.queuecount = queuecount; }
|
||||
public List<String> getNameList() { return nameList; }
|
||||
public void setNameList(List<String> nameList) { this.nameList = nameList; }
|
||||
public List<String> getQueueList() { return queueList; }
|
||||
public void setQueueList(List<String> queueList) { this.queueList = queueList; }
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
package com.njcn.gather.icd.mapping.infrastructure.parser;
|
||||
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.*;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.*;
|
||||
import com.njcn.gather.icd.mapping.infrastructure.parser.generated.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JAXB generated 模型读取器。
|
||||
* generated 模型读取器。遍历 SCL/IED/LDevice/LN0/LN/ReportControl/DataSet/DOI 树并转换。
|
||||
* JAXB 生成模型读取器。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 这里只负责“把 generated 模型读取出来”。
|
||||
* 2. 不直接做接口编排,也不做 JSON 序列化。
|
||||
* 负责遍历 `SCL/IED/LDevice/LN0/LN/ReportControl/DataSet/DOI` 结构,
|
||||
* 并转换为模块内部使用的 ICD 领域模型。
|
||||
*/
|
||||
public class SclGeneratedModelReader {
|
||||
|
||||
/**
|
||||
* 从 JAXB SCL 根对象中读取当前模块需要的 ICD 结构。
|
||||
*/
|
||||
public IcdDocument read(SCL scl, String fileName) {
|
||||
IcdDocument document = new IcdDocument();
|
||||
document.setFileName(fileName);
|
||||
|
||||
// 当前业务只处理第一个可用的非 LD0/PQLD0 逻辑设备。
|
||||
TIED targetIed = null;
|
||||
TLDevice targetDevice = null;
|
||||
|
||||
@@ -82,15 +85,16 @@ public class SclGeneratedModelReader {
|
||||
document.getLogicalNodes().add(readLogicalNode(ln, false));
|
||||
}
|
||||
}
|
||||
// 关键修正:
|
||||
// C# 原版的 icdcout 优先来自 DataTypeTemplates -> LNodeType -> DO -> DOType -> DA.count,
|
||||
// 不是靠 DataSet/FCDA 重复数来反推。
|
||||
// 这里改成:先按模板 count 回填 DOI.sequenceCount,找不到再退回 FCDA。
|
||||
// `icdcout` 优先取自模板链路 `DataTypeTemplates -> LNodeType -> DO -> DOType -> DA.count`,
|
||||
// 只有模板未提供时才退回到 `DataSet/FCDA` 反查,保持与原 C# 行为一致。
|
||||
syncDoiSequenceCount(scl, document);
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 LN0 或 LN 中读取 DataSet 与 ReportControl 信息。
|
||||
*/
|
||||
private void readReportAndDataSetFromAnyLn(TAnyLN anyLn, IcdDocument document) {
|
||||
List<FcdaNode> allFcdas = new ArrayList<FcdaNode>();
|
||||
if (anyLn.getDataSet() != null) {
|
||||
@@ -127,6 +131,9 @@ public class SclGeneratedModelReader {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 ReportControl.TrgOps 转成后续模板使用的触发编码。
|
||||
*/
|
||||
private String readTrgOps(TReportControl reportControl) {
|
||||
if (reportControl.getTrgOps() == null) {
|
||||
return null;
|
||||
@@ -146,24 +153,22 @@ public class SclGeneratedModelReader {
|
||||
/**
|
||||
* 读取逻辑节点。
|
||||
*
|
||||
* 注意:
|
||||
* 当前这套 JAXB 生成类中,TAnyLN 只提供了通用能力,
|
||||
* prefix / lnClass / inst 这些属性实际定义在子类 TLN / TLN0 上,
|
||||
* 所以这里不能直接对 TAnyLN 调用 getPrefix()/getLnClass()/getInst()。
|
||||
* `TAnyLN` 只暴露通用能力,`prefix / lnClass / inst` 实际定义在 `TLN / TLN0` 子类上,
|
||||
* 因此这里需要按具体类型分别读取。
|
||||
*/
|
||||
private LnNode readLogicalNode(TAnyLN anyLn, boolean isLn0) {
|
||||
LnNode node = new LnNode();
|
||||
node.setLn0(isLn0);
|
||||
|
||||
// lnType 在 TAnyLN 上是存在的,可以直接读取
|
||||
// `lnType` 定义在 `TAnyLN` 上,可直接读取。
|
||||
node.setLnType(anyLn.getLnType());
|
||||
|
||||
// prefix / lnClass / inst 需要根据具体子类读取
|
||||
// `prefix / lnClass / inst` 需要根据具体子类读取。
|
||||
node.setPrefix(resolveLnPrefix(anyLn));
|
||||
node.setLnClass(resolveLnClass(anyLn));
|
||||
node.setLnInst(resolveLnInst(anyLn));
|
||||
|
||||
// DOI 是定义在 TAnyLN 上的,可以直接读取
|
||||
// `DOI` 定义在 `TAnyLN` 上,可直接读取。
|
||||
if (anyLn.getDOI() != null) {
|
||||
for (TDOI doi : anyLn.getDOI()) {
|
||||
DoiNode doiNode = new DoiNode();
|
||||
@@ -188,10 +193,9 @@ public class SclGeneratedModelReader {
|
||||
return node;
|
||||
}
|
||||
/**
|
||||
* 解析逻辑节点 prefix。
|
||||
* 解析逻辑节点的 `prefix`。
|
||||
*
|
||||
* TLN 有 prefix;
|
||||
* TLN0 按这套生成类没有单独 prefix 字段,返回空字符串即可。
|
||||
* `TLN` 有 `prefix` 字段,`TLN0` 在当前生成模型中没有单独字段,统一返回空字符串。
|
||||
*/
|
||||
private String resolveLnPrefix(TAnyLN anyLn) {
|
||||
if (anyLn instanceof TLN) {
|
||||
@@ -201,11 +205,9 @@ public class SclGeneratedModelReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析逻辑节点 lnClass。
|
||||
* 解析逻辑节点的 `lnClass`。
|
||||
*
|
||||
* 当前生成类里:
|
||||
* - TLN.getLnClass() 返回 List<String>
|
||||
* - TLN0.getLnClass() 返回 List<String>
|
||||
* 当前生成模型中,`TLN` 和 `TLN0` 都返回 `List<String>`,这里只取首个值。
|
||||
*/
|
||||
private String resolveLnClass(TAnyLN anyLn) {
|
||||
if (anyLn instanceof TLN) {
|
||||
@@ -218,11 +220,9 @@ public class SclGeneratedModelReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析逻辑节点 inst。
|
||||
* 解析逻辑节点的 `inst`。
|
||||
*
|
||||
* 当前生成类里:
|
||||
* - TLN.getInst() 存在
|
||||
* - TLN0.getInst() 也存在
|
||||
* 当前生成模型中,`TLN` 和 `TLN0` 都提供了该字段。
|
||||
*/
|
||||
private String resolveLnInst(TAnyLN anyLn) {
|
||||
if (anyLn instanceof TLN) {
|
||||
@@ -234,6 +234,9 @@ public class SclGeneratedModelReader {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归读取 DOI 下的 SDI/DAI 混合子节点。
|
||||
*/
|
||||
private DoiElementNode readUnNamingNode(TUnNaming source) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
@@ -272,6 +275,9 @@ public class SclGeneratedModelReader {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JAXB FCDA 节点转换为内部 FCDA 模型。
|
||||
*/
|
||||
private FcdaNode toFcdaNode(TFCDA fcda) {
|
||||
FcdaNode node = new FcdaNode();
|
||||
node.setLdInst(fcda.getLdInst());
|
||||
@@ -285,10 +291,16 @@ public class SclGeneratedModelReader {
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 JAXB 列表型字段的首个值。
|
||||
*/
|
||||
private String first(List<String> values) {
|
||||
return values == null || values.isEmpty() ? null : values.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 LD 实例名中提取非数字前缀,例如 PQM1 -> PQM。
|
||||
*/
|
||||
private String extractLdPrefix(String ldInst) {
|
||||
if (ldInst == null) {
|
||||
return null;
|
||||
@@ -305,19 +317,16 @@ public class SclGeneratedModelReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 DOI 的 sequenceCount 同步出来。
|
||||
* 回填 DOI 的 `sequenceCount`。
|
||||
*
|
||||
* 规则严格贴近原 C#:
|
||||
* 1. 优先从 DataTypeTemplates -> LNodeType -> DO -> DOType -> DA.count 取值;
|
||||
* 2. 如果模板里没有 count,再退回 DataSet/FCDA 反查;
|
||||
* 3. 这样后续 MappingGenerationService 里的 icdcout 才会和 C# 一致。
|
||||
* 优先按模板链路取值,模板缺失时再退回 `DataSet/FCDA` 反查,确保后续生成结果与原 C# 逻辑一致。
|
||||
*/
|
||||
private void syncDoiSequenceCount(SCL scl, IcdDocument document) {
|
||||
if (document == null || document.getLogicalNodes() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先把 “lnType + doName -> count” 建好缓存
|
||||
// 先构建 `lnType + doName -> count` 缓存。
|
||||
Map<String, Integer> templateSequenceCountMap = buildTemplateSequenceCountMap(scl);
|
||||
|
||||
for (LnNode lnNode : document.getLogicalNodes()) {
|
||||
@@ -338,23 +347,17 @@ public class SclGeneratedModelReader {
|
||||
|
||||
int fcdaCount = findDoiSequenceCountFromFcda(document, doiNode);
|
||||
|
||||
// 关键:优先使用模板 count,保持与 C# 原版一致
|
||||
// 优先使用模板中的 `count`,保持与原 C# 行为一致。
|
||||
doiNode.setSequenceCount(templateCount > 0 ? templateCount : fcdaCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建 “lnType + doName -> 序列数量” 缓存。
|
||||
* 构建 `lnType + doName -> 序列数量` 缓存。
|
||||
*
|
||||
* 来源:
|
||||
* DataTypeTemplates
|
||||
* -> LNodeType(id = lnType)
|
||||
* -> DO(name/type)
|
||||
* -> DOType(id = type)
|
||||
* -> DA.count
|
||||
*
|
||||
* C# 原版本质上就是沿这条链把 doi.tNUM 算出来。
|
||||
* 数据来源为 `DataTypeTemplates -> LNodeType -> DO -> DOType -> DA.count`,
|
||||
* 原 C# 版本也是沿着这条链路计算 `doi.tNUM`。
|
||||
*/
|
||||
private Map<String, Integer> buildTemplateSequenceCountMap(SCL scl) {
|
||||
Map<String, Integer> result = new LinkedHashMap<String, Integer>();
|
||||
@@ -367,7 +370,7 @@ public class SclGeneratedModelReader {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 1. 先建 DOType.id -> count
|
||||
// 1. 先构建 `DOType.id -> count`。
|
||||
Map<String, Integer> doTypeCountMap = new LinkedHashMap<String, Integer>();
|
||||
for (TDOType doType : templates.getDOType()) {
|
||||
if (doType == null || doType.getId() == null) {
|
||||
@@ -378,7 +381,7 @@ public class SclGeneratedModelReader {
|
||||
doTypeCountMap.put(doType.getId(), count);
|
||||
}
|
||||
|
||||
// 2. 再建 lnType + doName -> count
|
||||
// 2. 再构建 `lnType + doName -> count`。
|
||||
for (TLNodeType lNodeType : templates.getLNodeType()) {
|
||||
if (lNodeType == null || lNodeType.getId() == null || lNodeType.getDO() == null) {
|
||||
continue;
|
||||
@@ -402,10 +405,9 @@ public class SclGeneratedModelReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从一个 DOType 中提取序列数量。
|
||||
* 从单个 `DOType` 中提取序列数量。
|
||||
*
|
||||
* C# 原版用的是 DOType 下 DA.count。
|
||||
* 这里取所有顶层 DA 里最大的正整数 count。
|
||||
* 这里取顶层 `DA.count` 中最大的正整数,贴近原 C# 的处理方式。
|
||||
*/
|
||||
private int extractDoTypeSequenceCount(TDOType doType) {
|
||||
int max = 0;
|
||||
@@ -429,10 +431,9 @@ public class SclGeneratedModelReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 DA.count。
|
||||
* 解析 `DA.count`。
|
||||
*
|
||||
* JAXB 这套生成类把 count 生成为 List<String>,
|
||||
* 所以这里要做一次安全转换。
|
||||
* 当前 JAXB 生成类把该字段定义为 `List<String>`,这里统一做安全转换。
|
||||
*/
|
||||
private int parseDaCount(TAbstractDataAttribute dataAttribute) {
|
||||
if (dataAttribute == null || dataAttribute.getCount() == null || dataAttribute.getCount().isEmpty()) {
|
||||
@@ -456,16 +457,16 @@ public class SclGeneratedModelReader {
|
||||
max = value;
|
||||
}
|
||||
} catch (NumberFormatException ignore) {
|
||||
// 非法 count 直接忽略,保持容错
|
||||
// 非法 `count` 直接忽略,保持容错。
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 退回到 DataSet/FCDA 的 sequenceCount 反查。
|
||||
* 从 `DataSet/FCDA` 反查 `sequenceCount`。
|
||||
*
|
||||
* 这里只作为模板 count 找不到时的兜底逻辑。
|
||||
* 这里只在模板未提供 `count` 时作为兜底逻辑使用。
|
||||
*/
|
||||
private int findDoiSequenceCountFromFcda(IcdDocument document, DoiNode doiNode) {
|
||||
int max = 0;
|
||||
@@ -501,6 +502,9 @@ public class SclGeneratedModelReader {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造模板序列数量缓存使用的复合 key。
|
||||
*/
|
||||
private String buildLnTypeDoKey(String lnType, String doName) {
|
||||
String left = lnType == null ? "" : lnType.trim();
|
||||
String right = doName == null ? "" : doName.trim();
|
||||
@@ -508,7 +512,7 @@ public class SclGeneratedModelReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* 空安全字符串比较。
|
||||
* 空安全的字符串比较。
|
||||
*/
|
||||
private boolean equalsTrim(String left, String right) {
|
||||
if (left == null && right == null) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.njcn.gather.icd.mapping.infrastructure.parser;
|
||||
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.infrastructure.parser.generated.SCL;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -11,18 +11,18 @@ import java.io.ByteArrayInputStream;
|
||||
|
||||
/**
|
||||
* SCL 解析适配器。
|
||||
* JAXB 解析适配器。负责把 ICD XML 反序列化为 SCL 根对象,再转成内部模型。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 这是真正会用到的 JAXB 版解析入口。
|
||||
* 2. 这里只负责把 ICD XML 反序列化成 SCL 根对象,再交给 reader 转成内部模型。
|
||||
* 3. 业务层不会直接依赖 JAXB generated 类。
|
||||
* 负责把 ICD XML 反序列化为 SCL 根对象,再交给读取器转换为内部模型。
|
||||
*/
|
||||
@Component
|
||||
public class SclParserAdapter {
|
||||
|
||||
/** JAXB generated 模型读取器,负责转成内部 ICD 领域模型。 */
|
||||
private final SclGeneratedModelReader modelReader = new SclGeneratedModelReader();
|
||||
|
||||
/**
|
||||
* 解析 ICD/SCL XML 字节内容。
|
||||
*/
|
||||
public IcdDocument parse(byte[] content, String fileName) {
|
||||
if (content == null || content.length == 0) {
|
||||
throw new IllegalArgumentException("ICD 文件内容不能为空");
|
||||
@@ -38,6 +38,9 @@ public class SclParserAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容 JAXB 直接返回 SCL 或 JAXBElement<SCL> 两种根对象形式。
|
||||
*/
|
||||
private SCL resolveSclRoot(Object raw) {
|
||||
if (raw instanceof SCL) {
|
||||
return (SCL) raw;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package com.njcn.gather.icd.mapping.infrastructure.parser;
|
||||
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.DoiElementNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.DoiNode;
|
||||
import com.njcn.gather.icd.mapping.domain.model.icd.FcdaNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.DoiElementNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.DoiNode;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.FcdaNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SCL 遍历辅助工具。
|
||||
*
|
||||
* 提供序列数量统计和 DOI 叶子节点展开等公共能力。
|
||||
*/
|
||||
public final class SclTraversalSupport {
|
||||
|
||||
@@ -16,7 +18,7 @@ public final class SclTraversalSupport {
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计同一个数据对象在同数据集里出现的次数,作为 ICD 实际序列个数参考。
|
||||
* 统计同一数据对象在数据集中的出现次数,作为 ICD 实际序列数量的参考值。
|
||||
*/
|
||||
public static int calculateSequenceCount(List<FcdaNode> allFcdas, FcdaNode current) {
|
||||
int count = 0;
|
||||
@@ -30,6 +32,9 @@ public final class SclTraversalSupport {
|
||||
return count <= 1 ? 0 : count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开 DOI 树下所有 DAI 节点,便于后续读取单位、倍率等叶子值。
|
||||
*/
|
||||
public static List<DoiElementNode> flattenLeafDai(DoiNode doi) {
|
||||
List<DoiElementNode> result = new ArrayList<DoiElementNode>();
|
||||
if (doi == null || doi.getChildren() == null) {
|
||||
@@ -41,6 +46,9 @@ public final class SclTraversalSupport {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归收集 DAI 叶子节点。
|
||||
*/
|
||||
private static void collectLeafDai(DoiElementNode node, List<DoiElementNode> result) {
|
||||
if (node == null) {
|
||||
return;
|
||||
@@ -55,6 +63,9 @@ public final class SclTraversalSupport {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 空安全字符串比较,供解析阶段统计序列数量使用。
|
||||
*/
|
||||
public static boolean safeEquals(String a, String b) {
|
||||
return a == null ? b == null : a.equals(b);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.njcn.gather.icd.mapping.pojo.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexAnalysis;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.icd.IcdDocument;
|
||||
import com.njcn.gather.icd.mapping.pojo.bo.mapping.MappingDocument;
|
||||
import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 应用层返回对象。
|
||||
*
|
||||
* 统一封装成功、需选择索引、失败三类结果。
|
||||
*/
|
||||
@Data
|
||||
public class GenerateMappingResult {
|
||||
/** 本次生成流程状态。 */
|
||||
private GenerateStatus status;
|
||||
|
||||
/** 给前端或调用方展示的处理结果说明。 */
|
||||
private String message;
|
||||
|
||||
/** ICD 中解析到的 IED 名称。 */
|
||||
private String iedName;
|
||||
|
||||
/** ICD 中解析到的 LD 实例名。 */
|
||||
private String ldInst;
|
||||
|
||||
/** 前端可编辑的 ICD 解析结果。 */
|
||||
private IcdDocument icdDocument;
|
||||
|
||||
/** 需要人工绑定索引时返回的候选分析结果。 */
|
||||
private IndexAnalysis indexAnalysis;
|
||||
|
||||
/** 生成成功后的结构化映射文档。 */
|
||||
private MappingDocument mappingDocument;
|
||||
|
||||
/** 生成成功后的 JSON 字符串。 */
|
||||
private String mappingJson;
|
||||
|
||||
/** saveToDisk=true 时的文件保存路径。 */
|
||||
private String savedPath;
|
||||
|
||||
/** 解析、校验或生成过程中收集到的问题。 */
|
||||
private List<String> problems = new ArrayList<String>();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.njcn.gather.icd.mapping.domain.model.analysis;
|
||||
package com.njcn.gather.icd.mapping.pojo.bo.analysis;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -9,12 +10,11 @@ import java.util.List;
|
||||
*
|
||||
* key = reportName
|
||||
*/
|
||||
@Data
|
||||
public class IndexAnalysis {
|
||||
/** 按 DefaultCfg.ReportList 分组后的索引候选。 */
|
||||
private List<IndexCandidate> candidates = new ArrayList<IndexCandidate>();
|
||||
private List<String> problems = new ArrayList<String>();
|
||||
|
||||
public List<IndexCandidate> getCandidates() { return candidates; }
|
||||
public void setCandidates(List<IndexCandidate> candidates) { this.candidates = candidates; }
|
||||
public List<String> getProblems() { return problems; }
|
||||
public void setProblems(List<String> problems) { this.problems = problems; }
|
||||
/** 分析过程中发现但不一定阻断流程的问题。 */
|
||||
private List<String> problems = new ArrayList<String>();
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.njcn.gather.icd.mapping.pojo.bo.analysis;
|
||||
|
||||
import lombok.Data;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 索引候选分组。
|
||||
*
|
||||
* 说明:
|
||||
* 1. 一条候选对应一个业务分组,例如:统计数据、实时数据;
|
||||
* 2. 一个业务分组下可以包含多个报告;
|
||||
* 3. 这里不仅保存返回给前端的候选项,也保存从 DefaultCfg.ReportList 带下来的配置项,
|
||||
* 供后续 MappingGenerationService 直接使用,避免“二次查模板”失败。
|
||||
*/
|
||||
@Data
|
||||
public class IndexCandidate {
|
||||
|
||||
/** 分组唯一键。 */
|
||||
private String groupKey;
|
||||
|
||||
/** 分组描述。 */
|
||||
private String groupDesc;
|
||||
|
||||
/** 该分组下实际匹配到的报告数量。 */
|
||||
private int reportCount;
|
||||
|
||||
/** DefaultCfg.txt 中该分组可用的标签模板。 */
|
||||
private List<String> templateLabels = new ArrayList<String>();
|
||||
|
||||
/** 当前分组下匹配到的报告列表。 */
|
||||
private List<IndexCandidateReportItem> reports = new ArrayList<IndexCandidateReportItem>();
|
||||
|
||||
/**
|
||||
* DefaultCfg.ReportList.inst
|
||||
* 例如:01 / 02 / 03 / 04
|
||||
*/
|
||||
private String reportInst;
|
||||
|
||||
/**
|
||||
* DefaultCfg.ReportList.Select
|
||||
* 例如:DataStatFileMap / DataRealFileMap / FlickerFileMap
|
||||
*/
|
||||
private String select;
|
||||
|
||||
/**
|
||||
* DefaultCfg.ReportList.TrgOps
|
||||
* 例如:40 / 96
|
||||
*/
|
||||
private String trgOps;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user