Compare commits

...

3 Commits

Author SHA1 Message Date
2c1d926261 调整了ICDCOUNT计数的逻辑,去除了重复性FCDA计数判断 2026-05-08 11:40:00 +08:00
34b1a81c70 refactor(mms-mapping): 重构ICD到XML转换服务及响应结构
- 移除API调试文档,精简工具模块
- 重构IcdToXmlGenerateResult数据结构,新增methodDescribe和xmlContent字段
- 更新IcdToXmlResponse响应对象,移除savedPath字段,新增XmlFileResponse容器
- 重构IcdToXmlTaskAppService,引入任务编排上下文和统一异常处理
- 新增XmlFileResponse和XmlResourceContext辅助类
- 优化JsonToXmlConversionService,支持直接返回XML内容而不落地文件
- 添加索引绑定验证和错误处理机制
- 引入函数式接口简化任务执行逻辑
2026-05-08 09:52:44 +08:00
81e4ff4009 feat(mms-mapping): 添加索引确认和选择构建功能
- 新增 buildIndexConfirmData 和 buildIndexSelection 两个调试接口
- 添加完整的请求参数和响应结果的数据结构定义
- 实现索引候选数据到确认模型的转换逻辑
- 实现确认结果到正式索引选择的展开生成功能
- 添加详细的 API 调试文档和使用示例
- 集成参数校验和业务规则验证机制
2026-05-06 13:24:18 +08:00
25 changed files with 1319 additions and 271 deletions

View File

@@ -5,6 +5,7 @@
## 0. 调试文档 ## 0. 调试文档
- `getIcdMmsJson``API-getIcdMmsJson.md` - `getIcdMmsJson``API-getIcdMmsJson.md`
- `getXmlFromJson``API-getXmlFromJson.md`
- `buildIndexConfirmData` / `buildIndexSelection``API-buildIndexDebug.md` - `buildIndexConfirmData` / `buildIndexSelection``API-buildIndexDebug.md`
## 1. 接口信息 ## 1. 接口信息

View File

@@ -7,18 +7,26 @@ import com.njcn.gather.icd.mapping.pojo.vo.IcdToXmlResponse;
import com.njcn.gather.icd.mapping.pojo.vo.IndexCandidateReportItemResponse; 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.IndexCandidateResponse;
import com.njcn.gather.icd.mapping.pojo.vo.MappingDocumentResponse; import com.njcn.gather.icd.mapping.pojo.vo.MappingDocumentResponse;
import com.njcn.gather.icd.mapping.pojo.vo.XmlFileResponse;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@Component @Component
public class IcdToXmlResponseConverter { public class IcdToXmlResponseConverter {
/** 默认 XML 展示文件名。 */
private static final String DEFAULT_XML_FILE_NAME = "mapping.xml";
/** XML 内容类型。 */
private static final String XML_CONTENT_TYPE = "application/xml";
/** XML 文件编码。 */
private static final String XML_ENCODING = "UTF-8";
public IcdToXmlResponse fromResult(IcdToXmlGenerateResult result) { public IcdToXmlResponse fromResult(IcdToXmlGenerateResult result) {
IcdToXmlResponse response = new IcdToXmlResponse(); IcdToXmlResponse response = new IcdToXmlResponse();
response.setStatus(result.getStatus()); response.setStatus(result.getStatus());
response.setMessage(result.getMessage()); response.setMessage(result.getMessage());
response.setIedName(result.getIedName()); response.setIedName(result.getIedName());
response.setLdInst(result.getLdInst()); response.setLdInst(result.getLdInst());
response.setSavedPath(result.getSavedPath()); response.setXmlFile(buildXmlFile(result));
response.getProblems().addAll(result.getProblems()); response.getProblems().addAll(result.getProblems());
if (result.getIndexAnalysis() != null && result.getIndexAnalysis().getCandidates() != null) { if (result.getIndexAnalysis() != null && result.getIndexAnalysis().getCandidates() != null) {
@@ -59,4 +67,36 @@ public class IcdToXmlResponseConverter {
return response; return response;
} }
/**
* 将 XML 内容包装为前端展示用的标准文件容器。
*/
private XmlFileResponse buildXmlFile(IcdToXmlGenerateResult result) {
if (result.getXmlContent() == null || result.getXmlContent().isEmpty()) {
return null;
}
XmlFileResponse xmlFile = new XmlFileResponse();
xmlFile.setFileName(resolveXmlFileName(result));
xmlFile.setContentType(XML_CONTENT_TYPE);
xmlFile.setEncoding(XML_ENCODING);
xmlFile.setContent(result.getXmlContent());
return xmlFile;
}
/**
* 优先使用 IED 名称构造文件名,缺失时回退默认名。
*/
private String resolveXmlFileName(IcdToXmlGenerateResult result) {
String iedName = result.getIedName();
if (iedName == null || iedName.trim().isEmpty()) {
return DEFAULT_XML_FILE_NAME;
}
String safeName = iedName.replaceAll("[\\\\/:*?\"<>|]+", "_").trim();
if (safeName.isEmpty()) {
return DEFAULT_XML_FILE_NAME;
}
return safeName + ".xml";
}
} }

View File

@@ -0,0 +1,464 @@
package com.njcn.gather.icd.mapping.component;
import com.njcn.gather.icd.mapping.pojo.param.BuildIndexSelectionRequest;
import com.njcn.gather.icd.mapping.pojo.param.ConfirmedIndexGroupRequest;
import com.njcn.gather.icd.mapping.pojo.param.ConfirmedLabelItemRequest;
import com.njcn.gather.icd.mapping.pojo.param.IndexCandidateReportItemRequest;
import com.njcn.gather.icd.mapping.pojo.param.IndexCandidateRequest;
import com.njcn.gather.icd.mapping.pojo.param.IndexConfirmGroupRequest;
import com.njcn.gather.icd.mapping.pojo.param.IndexConfirmLabelItemRequest;
import com.njcn.gather.icd.mapping.pojo.param.IndexConfirmTargetRequest;
import com.njcn.gather.icd.mapping.pojo.vo.IndexBindingResponse;
import com.njcn.gather.icd.mapping.pojo.vo.IndexConfirmGroupResponse;
import com.njcn.gather.icd.mapping.pojo.vo.IndexConfirmLabelItemResponse;
import com.njcn.gather.icd.mapping.pojo.vo.IndexConfirmTargetResponse;
import com.njcn.gather.icd.mapping.pojo.vo.IndexSelectionGroupResponse;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* ICD 结构确认弹窗编排服务。
* 第一阶段:把 indexCandidates 整理成前端可确认的 label 级模型。
* 第二阶段:根据前端确认结果展开为最终 indexSelection。
*/
@Service
public class IndexSelectionBuildService {
private static final String GROUP_KEY_STATISTIC = "统计数据__DSSTATISTICDATA";
private static final String GROUP_KEY_REALTIME = "实时数据__DSREALTIMEDATA";
private static final String DATA_SET_STATISTIC_UNGROUPED = "dsStatisticData";
private static final String DATA_SET_STATISTIC_INTER = "dsStIHarm";
private static final String DATA_SET_REALTIME_UNGROUPED = "dsRealTimeData";
private static final String DATA_SET_REALTIME_INTER = "dsRtIHarm";
private static final String LABEL_REALTIME_INTER = "间谐波实时数据";
private static final Comparator<IndexBindingResponse> INDEX_BINDING_COMPARATOR = (left, right) -> {
int result = normalizeSortValue(left == null ? null : left.getReportName())
.compareTo(normalizeSortValue(right == null ? null : right.getReportName()));
if (result != 0) {
return result;
}
result = normalizeSortValue(left == null ? null : left.getDataSetName())
.compareTo(normalizeSortValue(right == null ? null : right.getDataSetName()));
if (result != 0) {
return result;
}
return compareLnInstValues(left == null ? null : left.getLnInst(), right == null ? null : right.getLnInst());
};
/**
* 根据页面回传的 indexCandidates 生成弹窗确认模型。
*/
public List<IndexConfirmGroupResponse> buildConfirmData(List<IndexCandidateRequest> indexCandidates) {
if (indexCandidates == null || indexCandidates.isEmpty()) {
return Collections.emptyList();
}
List<IndexConfirmGroupResponse> result = new ArrayList<IndexConfirmGroupResponse>();
for (IndexCandidateRequest candidate : indexCandidates) {
if (candidate == null) {
continue;
}
IndexConfirmGroupResponse groupResponse = new IndexConfirmGroupResponse();
groupResponse.setGroupKey(candidate.getGroupKey());
groupResponse.setGroupDesc(candidate.getGroupDesc());
if (candidate.getTemplateLabels() != null) {
Set<String> uniqueLabels = new LinkedHashSet<String>(candidate.getTemplateLabels());
for (String label : uniqueLabels) {
if (isBlank(label)) {
continue;
}
List<IndexCandidateReportItemRequest> targets = resolveTargets(candidate, label);
if (targets.isEmpty()) {
continue;
}
IndexConfirmLabelItemResponse labelItem = new IndexConfirmLabelItemResponse();
labelItem.setLabel(label);
labelItem.setRequired(false);
labelItem.setConfigurableOnce(true);
List<List<String>> targetLnInstLists = new ArrayList<List<String>>();
for (IndexCandidateReportItemRequest target : targets) {
IndexConfirmTargetResponse targetResponse = new IndexConfirmTargetResponse();
targetResponse.setReportName(target.getReportName());
targetResponse.setDataSetName(target.getDataSetName());
targetResponse.setReportDesc(target.getReportDesc());
List<String> labelSpecificValues = resolveAvailableLnInstValues(candidate, target, label);
targetResponse.getAvailableLnInstValues().addAll(labelSpecificValues);
targetLnInstLists.add(labelSpecificValues);
labelItem.getTargets().add(targetResponse);
}
List<String> commonLnInstValues = intersectLnInstValues(targetLnInstLists);
labelItem.getCommonLnInstValues().addAll(commonLnInstValues);
if (commonLnInstValues.size() == 1) {
labelItem.setDefaultLnInst(commonLnInstValues.get(0));
}
if (commonLnInstValues.isEmpty()) {
labelItem.setConfigurableOnce(false);
}
groupResponse.getLabelItems().add(labelItem);
}
}
result.add(groupResponse);
}
return result;
}
/**
* 根据确认模型和前端最终确认结果展开生成 indexSelection。
*/
public List<IndexSelectionGroupResponse> buildIndexSelection(BuildIndexSelectionRequest request) {
if (request == null) {
throw new IllegalArgumentException("请求体不能为空");
}
if (request.getConfirmData() == null || request.getConfirmData().isEmpty()) {
throw new IllegalArgumentException("confirmData 不能为空");
}
Map<String, IndexConfirmGroupRequest> confirmGroupMap = toConfirmGroupMap(request.getConfirmData());
Map<String, ConfirmedIndexGroupRequest> confirmedGroupMap = toConfirmedGroupMap(request.getConfirmedData());
validateConfirmedGroups(confirmGroupMap, confirmedGroupMap);
List<IndexSelectionGroupResponse> result = new ArrayList<IndexSelectionGroupResponse>();
for (IndexConfirmGroupRequest confirmGroup : request.getConfirmData()) {
if (confirmGroup == null || isBlank(confirmGroup.getGroupKey())) {
continue;
}
ConfirmedIndexGroupRequest confirmedGroup = confirmedGroupMap.get(confirmGroup.getGroupKey());
if (confirmedGroup == null) {
continue;
}
Map<String, ConfirmedLabelItemRequest> confirmedLabelMap = toConfirmedLabelMap(confirmedGroup.getLabelItems());
validateConfirmedLabels(confirmGroup, confirmedLabelMap);
IndexSelectionGroupResponse selectionGroup = new IndexSelectionGroupResponse();
selectionGroup.setGroupKey(confirmGroup.getGroupKey());
selectionGroup.setGroupDesc(confirmGroup.getGroupDesc());
if (confirmGroup.getLabelItems() != null) {
for (IndexConfirmLabelItemRequest confirmLabel : confirmGroup.getLabelItems()) {
if (confirmLabel == null || isBlank(confirmLabel.getLabel())) {
continue;
}
ConfirmedLabelItemRequest confirmedLabel = confirmedLabelMap.get(confirmLabel.getLabel());
if (confirmedLabel == null || !confirmedLabel.isEnabled()) {
continue;
}
validateConfirmedLabel(confirmGroupMap, confirmGroup, confirmLabel, confirmedLabel);
expandBindings(selectionGroup, confirmLabel, confirmedLabel.getLnInst());
}
}
if (!selectionGroup.getBindings().isEmpty()) {
// 返回前统一排序,避免前端展示顺序受展开过程影响。
selectionGroup.getBindings().sort(INDEX_BINDING_COMPARATOR);
result.add(selectionGroup);
}
}
return result;
}
private void validateConfirmedGroups(Map<String, IndexConfirmGroupRequest> confirmGroupMap,
Map<String, ConfirmedIndexGroupRequest> confirmedGroupMap) {
for (String groupKey : confirmedGroupMap.keySet()) {
if (!confirmGroupMap.containsKey(groupKey)) {
throw new IllegalArgumentException("confirmedData 中存在未知分组:" + groupKey);
}
}
}
private void validateConfirmedLabels(IndexConfirmGroupRequest confirmGroup,
Map<String, ConfirmedLabelItemRequest> confirmedLabelMap) {
if (confirmedLabelMap.isEmpty()) {
return;
}
Set<String> allowedLabels = new LinkedHashSet<String>();
if (confirmGroup.getLabelItems() != null) {
for (IndexConfirmLabelItemRequest labelItem : confirmGroup.getLabelItems()) {
if (labelItem != null && !isBlank(labelItem.getLabel())) {
allowedLabels.add(labelItem.getLabel());
}
}
}
for (String label : confirmedLabelMap.keySet()) {
if (!allowedLabels.contains(label)) {
throw new IllegalArgumentException("分组【" + confirmGroup.getGroupDesc() + "】中存在未知标签:" + label);
}
}
}
private Map<String, IndexConfirmGroupRequest> toConfirmGroupMap(List<IndexConfirmGroupRequest> confirmData) {
Map<String, IndexConfirmGroupRequest> result = new LinkedHashMap<String, IndexConfirmGroupRequest>();
if (confirmData == null) {
return result;
}
for (IndexConfirmGroupRequest group : confirmData) {
if (group != null && !isBlank(group.getGroupKey())) {
result.put(group.getGroupKey(), group);
}
}
return result;
}
private Map<String, ConfirmedIndexGroupRequest> toConfirmedGroupMap(List<ConfirmedIndexGroupRequest> confirmedData) {
Map<String, ConfirmedIndexGroupRequest> result = new LinkedHashMap<String, ConfirmedIndexGroupRequest>();
if (confirmedData == null) {
return result;
}
for (ConfirmedIndexGroupRequest group : confirmedData) {
if (group != null && !isBlank(group.getGroupKey())) {
result.put(group.getGroupKey(), group);
}
}
return result;
}
private Map<String, ConfirmedLabelItemRequest> toConfirmedLabelMap(List<ConfirmedLabelItemRequest> labelItems) {
Map<String, ConfirmedLabelItemRequest> result = new LinkedHashMap<String, ConfirmedLabelItemRequest>();
if (labelItems == null) {
return result;
}
for (ConfirmedLabelItemRequest item : labelItems) {
if (item != null && !isBlank(item.getLabel())) {
result.put(item.getLabel(), item);
}
}
return result;
}
/**
* 统计数据、实时数据存在“同一 label 只配一次”的特殊归并规则,
* 其他分组默认把同名 label 展开到当前分组下全部报告。
*/
private List<IndexCandidateReportItemRequest> resolveTargets(IndexCandidateRequest candidate, String label) {
List<IndexCandidateReportItemRequest> reports = candidate.getReports();
if (reports == null || reports.isEmpty()) {
return Collections.emptyList();
}
boolean hasRealtimeInterReport = hasDataSet(reports, DATA_SET_REALTIME_INTER);
List<IndexCandidateReportItemRequest> result = new ArrayList<IndexCandidateReportItemRequest>();
for (IndexCandidateReportItemRequest report : reports) {
if (report == null) {
continue;
}
if (GROUP_KEY_STATISTIC.equals(candidate.getGroupKey())) {
if (isInterHarmonicLabel(label)) {
if (DATA_SET_STATISTIC_INTER.equals(report.getDataSetName()) || reports.size() == 1) {
result.add(report);
}
continue;
}
if (!DATA_SET_STATISTIC_INTER.equals(report.getDataSetName())) {
result.add(report);
}
continue;
}
if (GROUP_KEY_REALTIME.equals(candidate.getGroupKey())) {
if (LABEL_REALTIME_INTER.equals(label)) {
if (DATA_SET_REALTIME_INTER.equals(report.getDataSetName())) {
result.add(report);
} else if (!hasRealtimeInterReport && DATA_SET_REALTIME_UNGROUPED.equals(report.getDataSetName())) {
result.add(report);
}
continue;
}
if (!DATA_SET_REALTIME_INTER.equals(report.getDataSetName())) {
result.add(report);
}
continue;
}
result.add(report);
}
return result;
}
private boolean hasDataSet(List<IndexCandidateReportItemRequest> reports, String dataSetName) {
if (reports == null || reports.isEmpty() || isBlank(dataSetName)) {
return false;
}
for (IndexCandidateReportItemRequest report : reports) {
if (report != null && dataSetName.equals(report.getDataSetName())) {
return true;
}
}
return false;
}
/**
* 计算多个目标报告可共同接受的 lnInst 候选,保持首个目标报告的顺序。
*/
private List<String> intersectLnInstValues(List<List<String>> targetLnInstLists) {
if (targetLnInstLists == null || targetLnInstLists.isEmpty()) {
return Collections.emptyList();
}
List<String> firstValues = targetLnInstLists.get(0);
if (firstValues == null || firstValues.isEmpty()) {
return Collections.emptyList();
}
List<String> result = new ArrayList<String>();
for (String value : firstValues) {
if (isBlank(value)) {
continue;
}
boolean existsInAllTargets = true;
for (int i = 1; i < targetLnInstLists.size(); i++) {
List<String> currentValues = targetLnInstLists.get(i);
if (currentValues == null || !currentValues.contains(value)) {
existsInAllTargets = false;
break;
}
}
if (existsInAllTargets) {
result.add(value);
}
}
return result;
}
/**
* 某些未分组报告会把多种 label 的候选值拼在同一个 availableLnInstValues 里,
* 这里按当前业务规则先拆开后再返回给前端。
*/
private List<String> resolveAvailableLnInstValues(IndexCandidateRequest candidate,
IndexCandidateReportItemRequest target,
String label) {
if (target == null || target.getAvailableLnInstValues() == null) {
return Collections.emptyList();
}
List<String> values = target.getAvailableLnInstValues();
if (GROUP_KEY_STATISTIC.equals(candidate.getGroupKey())
&& DATA_SET_STATISTIC_UNGROUPED.equals(target.getDataSetName())
&& values.size() > 1) {
int splitIndex = values.size() / 2;
return isInterHarmonicLabel(label)
? new ArrayList<String>(values.subList(splitIndex, values.size()))
: new ArrayList<String>(values.subList(0, splitIndex));
}
if (GROUP_KEY_REALTIME.equals(candidate.getGroupKey())
&& DATA_SET_REALTIME_UNGROUPED.equals(target.getDataSetName())
&& values.size() > 1) {
return LABEL_REALTIME_INTER.equals(label)
? new ArrayList<String>(values.subList(1, values.size()))
: new ArrayList<String>(values.subList(0, 1));
}
return new ArrayList<String>(values);
}
private void validateConfirmedLabel(Map<String, IndexConfirmGroupRequest> confirmGroupMap,
IndexConfirmGroupRequest confirmGroup,
IndexConfirmLabelItemRequest confirmLabel,
ConfirmedLabelItemRequest confirmedLabel) {
if (!confirmGroupMap.containsKey(confirmGroup.getGroupKey())) {
throw new IllegalArgumentException("未找到确认分组:" + confirmGroup.getGroupKey());
}
if (isBlank(confirmedLabel.getLnInst())) {
throw new IllegalArgumentException("分组【" + confirmGroup.getGroupDesc() + "】的标签【"
+ confirmLabel.getLabel() + "】未选择 lnInst");
}
if (confirmLabel.getCommonLnInstValues() == null || !confirmLabel.getCommonLnInstValues().contains(confirmedLabel.getLnInst())) {
throw new IllegalArgumentException(
"分组【" + confirmGroup.getGroupDesc() + "】的标签【" + confirmLabel.getLabel()
+ "】选择的 lnInst【" + confirmedLabel.getLnInst() + "】不在共同候选范围内:"
+ confirmLabel.getCommonLnInstValues()
);
}
if (confirmLabel.getTargets() != null) {
for (IndexConfirmTargetRequest target : confirmLabel.getTargets()) {
if (target == null || target.getAvailableLnInstValues() == null) {
continue;
}
if (!target.getAvailableLnInstValues().contains(confirmedLabel.getLnInst())) {
throw new IllegalArgumentException(
"标签【" + confirmLabel.getLabel() + "】在目标报告【" + target.getReportName()
+ "】下不支持 lnInst【" + confirmedLabel.getLnInst() + ""
);
}
}
}
}
private void expandBindings(IndexSelectionGroupResponse selectionGroup,
IndexConfirmLabelItemRequest confirmLabel,
String lnInst) {
if (confirmLabel.getTargets() == null) {
return;
}
for (IndexConfirmTargetRequest target : confirmLabel.getTargets()) {
if (target == null) {
continue;
}
IndexBindingResponse binding = new IndexBindingResponse();
binding.setReportName(target.getReportName());
binding.setDataSetName(target.getDataSetName());
binding.setLabel(confirmLabel.getLabel());
binding.setLnInst(lnInst);
selectionGroup.getBindings().add(binding);
}
}
private boolean isInterHarmonicLabel(String label) {
return label != null && label.startsWith("间谐波");
}
private static String normalizeSortValue(String value) {
return value == null ? "" : value;
}
private static int compareLnInstValues(String left, String right) {
Long leftNumeric = parseNumericLnInst(left);
Long rightNumeric = parseNumericLnInst(right);
if (leftNumeric != null && rightNumeric != null) {
int result = leftNumeric.compareTo(rightNumeric);
if (result != 0) {
return result;
}
}
return normalizeSortValue(left).compareTo(normalizeSortValue(right));
}
private static Long parseNumericLnInst(String value) {
if (value == null) {
return null;
}
String trimmedValue = value.trim();
if (trimmedValue.isEmpty()) {
return null;
}
try {
return Long.valueOf(trimmedValue);
} catch (NumberFormatException ex) {
return null;
}
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
}

View File

@@ -54,14 +54,7 @@ public class JsonToXmlConversionService {
InputStream templateStream, InputStream templateStream,
List<InputStream> ruleStreams, List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception { IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
String xmlContent = buildXmlContentFromJson(mappingJson, templateStream, ruleStreams, indexMapping);
// 1. 反序列化JSON为MappingDocument对象
MappingDocument mappingDocument = objectMapper.readValue(mappingJson, MappingDocument.class);
// 2. 使用现有的规则引擎生成XML
// 注意这里复用RuleBasedXmlMappingService的核心逻辑
// 但数据来源从ICD直接解析改为从MappingDocument提取
String xmlContent = buildXmlFromMapping(mappingDocument, templateStream, ruleStreams, indexMapping);
// 3. 保存为临时文件 // 3. 保存为临时文件
Path tempPath = Files.createTempFile("converted_", ".xml"); Path tempPath = Files.createTempFile("converted_", ".xml");
@@ -70,13 +63,41 @@ public class JsonToXmlConversionService {
return tempPath.toString(); return tempPath.toString();
} }
/**
* 从 JSON 字符串转换为 XML 内容,供前端直接展示。
*
* @param mappingJson JSON 格式的映射文档
* @param templateStream XML 模板流
* @param ruleStreams 规则文件流列表
* @param indexMapping 索引映射配置
* @return 生成后的 XML 内容
* @throws Exception 转换异常
*/
public String buildXmlContentFromJson(String mappingJson,
InputStream templateStream,
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception {
return buildXmlContentFromJson(mappingJson, templateStream, ruleStreams, indexMapping, null);
}
public String buildXmlContentFromJson(String mappingJson,
InputStream templateStream,
List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping,
List<String> methodDescribeList) throws Exception {
// 反序列化 JSON 后复用现有规则引擎生成 XML避免文件输出链路重复实现。
MappingDocument mappingDocument = objectMapper.readValue(mappingJson, MappingDocument.class);
return buildXmlFromMapping(mappingDocument, templateStream, ruleStreams, indexMapping, methodDescribeList);
}
/** /**
* 从MappingDocument构建XML内容 * 从MappingDocument构建XML内容
*/ */
private String buildXmlFromMapping(MappingDocument mappingDocument, private String buildXmlFromMapping(MappingDocument mappingDocument,
InputStream templateStream, InputStream templateStream,
List<InputStream> ruleStreams, List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception { IcdToXmlMappingService.IndexMappingConfig indexMapping,
List<String> methodDescribeList) throws Exception {
String templateContent = readInputStreamToString(templateStream); String templateContent = readInputStreamToString(templateStream);
@@ -84,7 +105,7 @@ public class JsonToXmlConversionService {
xmlContent = fillReportControlsFromMapping(xmlContent, mappingDocument); xmlContent = fillReportControlsFromMapping(xmlContent, mappingDocument);
xmlContent = applyRulesFromMapping(xmlContent, mappingDocument, ruleStreams, indexMapping); xmlContent = applyRulesFromMapping(xmlContent, mappingDocument, ruleStreams, indexMapping, methodDescribeList);
return xmlContent; return xmlContent;
} }
@@ -217,21 +238,22 @@ public class JsonToXmlConversionService {
private String applyRulesFromMapping(String xmlContent, private String applyRulesFromMapping(String xmlContent,
MappingDocument mappingDocument, MappingDocument mappingDocument,
List<InputStream> ruleStreams, List<InputStream> ruleStreams,
IcdToXmlMappingService.IndexMappingConfig indexMapping) throws Exception { IcdToXmlMappingService.IndexMappingConfig indexMapping,
List<String> methodDescribeList) throws Exception {
var mergedRules = mergeAllRulesDesc(ruleStreams, indexMapping); var mergedRules = mergeAllRulesDesc(ruleStreams, indexMapping);
var mappingMetrics = extractMetricsFromMapping(mappingDocument); var mappingMetrics = extractMetricsFromMapping(mappingDocument);
System.out.println("========== 开始从JSON匹配规则 =========="); addMethodDescribe(methodDescribeList, "========== 开始从JSON匹配规则 ==========");
System.out.println("规则总数: " + mergedRules.size()); addMethodDescribe(methodDescribeList, "规则总数: " + mergedRules.size());
System.out.println("JSON中指标总数: " + mappingMetrics.size()); addMethodDescribe(methodDescribeList, "JSON中指标总数: " + mappingMetrics.size());
var applicableRules = findApplicableRulesDesc(mergedRules, mappingMetrics); var applicableRules = findApplicableRulesDesc(mergedRules, mappingMetrics, methodDescribeList);
System.out.println("匹配成功: " + applicableRules.size() + " 条规则"); addMethodDescribe(methodDescribeList, "匹配成功: " + applicableRules.size() + " 条规则");
System.out.println("匹配失败: " + (mergedRules.size() - applicableRules.size()) + " 条规则"); addMethodDescribe(methodDescribeList, "匹配失败: " + (mergedRules.size() - applicableRules.size()) + " 条规则");
System.out.println("========== JSON规则匹配结束 ==========\n"); addMethodDescribe(methodDescribeList, "========== JSON规则匹配结束 ==========");
return applyRulesToXml(xmlContent, applicableRules); return applyRulesToXml(xmlContent, applicableRules);
} }
@@ -731,7 +753,8 @@ public class JsonToXmlConversionService {
*/ */
private java.util.Map<String, RuleBasedXmlMappingService.ValueRule> findApplicableRulesDesc( private java.util.Map<String, RuleBasedXmlMappingService.ValueRule> findApplicableRulesDesc(
java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> allRules, java.util.Map<String, List<RuleBasedXmlMappingService.ValueRule>> allRules,
java.util.Map<String, MetricInfo> mappingMetrics) { java.util.Map<String, MetricInfo> mappingMetrics,
List<String> methodDescribeList) {
java.util.Map<String, RuleBasedXmlMappingService.ValueRule> applicable = new java.util.HashMap<>(); java.util.Map<String, RuleBasedXmlMappingService.ValueRule> applicable = new java.util.HashMap<>();
List<String> failedRules = new ArrayList<>(); List<String> failedRules = new ArrayList<>();
@@ -753,7 +776,7 @@ public class JsonToXmlConversionService {
rule.setDaPath(metric.getDaPath()); rule.setDaPath(metric.getDaPath());
applicable.put(ruleKey, rule); applicable.put(ruleKey, rule);
matched = true; matched = true;
System.out.println("✓ 规则匹配成功: " + ruleKey + " -> DO=" + rule.getDoPath() + ", DA=" + rule.getDaPath()); addMethodDescribe(methodDescribeList, "✓ 规则匹配成功: " + ruleKey + " -> DO=" + rule.getDoPath() + ", DA=" + rule.getDaPath());
} }
@@ -765,22 +788,22 @@ public class JsonToXmlConversionService {
if (!matched) { if (!matched) {
failedRules.add(ruleKey); failedRules.add(ruleKey);
System.out.println("✗ 规则匹配失败: " + ruleKey + " (共" + ruleVariants.size() + "个候选)"); addMethodDescribe(methodDescribeList, "✗ 规则匹配失败: " + ruleKey + " (共" + ruleVariants.size() + "个候选)");
for (RuleBasedXmlMappingService.ValueRule rule : ruleVariants) { for (RuleBasedXmlMappingService.ValueRule rule : ruleVariants) {
System.out.println(" 候选指标名: " + rule.getName()); addMethodDescribe(methodDescribeList, " 候选指标名: " + rule.getName());
} }
} }
} }
System.out.println("\n========== 详细匹配结果 =========="); addMethodDescribe(methodDescribeList, "========== 详细匹配结果 ==========");
System.out.println("成功匹配的规则 (" + applicable.size() + " 条):"); addMethodDescribe(methodDescribeList, "成功匹配的规则 (" + applicable.size() + " 条):");
for (String key : applicable.keySet()) { for (String key : applicable.keySet()) {
System.out.println(" - " + key); addMethodDescribe(methodDescribeList, " - " + key);
} }
System.out.println("\n失败匹配的规则 (" + failedRules.size() + " 条):"); addMethodDescribe(methodDescribeList, "失败匹配的规则 (" + failedRules.size() + " 条):");
for (String key : failedRules) { for (String key : failedRules) {
System.out.println(" - " + key); addMethodDescribe(methodDescribeList, " - " + key);
} }
return applicable; return applicable;
@@ -818,7 +841,7 @@ public class JsonToXmlConversionService {
if (matchesPattern(ruleDo, icdDo) && matchesPattern(ruleDa, icdDa)) { if (matchesPattern(ruleDo, icdDo) && matchesPattern(ruleDa, icdDa)) {
applicable.put(ruleKey, rule); applicable.put(ruleKey, rule);
matched = true; matched = true;
System.out.println("✓ 规则匹配成功: " + ruleKey + " -> DO=" + rule.getDoPath() + ", DA=" + rule.getDaPath()); addMethodDescribe(null, "✓ 规则匹配成功: " + ruleKey + " -> DO=" + rule.getDoPath() + ", DA=" + rule.getDaPath());
break; break;
} }
} }
@@ -830,22 +853,22 @@ public class JsonToXmlConversionService {
if (!matched) { if (!matched) {
failedRules.add(ruleKey); failedRules.add(ruleKey);
System.out.println("✗ 规则匹配失败: " + ruleKey + " (共" + ruleVariants.size() + "个候选)"); addMethodDescribe(null, "✗ 规则匹配失败: " + ruleKey + " (共" + ruleVariants.size() + "个候选)");
for (RuleBasedXmlMappingService.ValueRule rule : ruleVariants) { for (RuleBasedXmlMappingService.ValueRule rule : ruleVariants) {
System.out.println(" 候选: DO=" + rule.getDoPath() + ", DA=" + rule.getDaPath()); addMethodDescribe(null, " 候选: DO=" + rule.getDoPath() + ", DA=" + rule.getDaPath());
} }
} }
} }
System.out.println("\n========== 详细匹配结果 =========="); addMethodDescribe(null, "========== 详细匹配结果 ==========");
System.out.println("成功匹配的规则 (" + applicable.size() + " 条):"); addMethodDescribe(null, "成功匹配的规则 (" + applicable.size() + " 条):");
for (String key : applicable.keySet()) { for (String key : applicable.keySet()) {
System.out.println(" - " + key); addMethodDescribe(null, " - " + key);
} }
System.out.println("\n失败匹配的规则 (" + failedRules.size() + " 条):"); addMethodDescribe(null, "失败匹配的规则 (" + failedRules.size() + " 条):");
for (String key : failedRules) { for (String key : failedRules) {
System.out.println(" - " + key); addMethodDescribe(null, " - " + key);
} }
return applicable; return applicable;
@@ -953,6 +976,13 @@ public class JsonToXmlConversionService {
.replace("'", "&apos;"); .replace("'", "&apos;");
} }
private void addMethodDescribe(List<String> methodDescribeList, String message) {
if (methodDescribeList == null || message == null || message.trim().isEmpty()) {
return;
}
methodDescribeList.add(message);
}
/** /**
* 指标信息内部类 * 指标信息内部类
*/ */

View File

@@ -38,6 +38,7 @@ public class MappingResponseConverter {
MappingTaskResponse response = initBaseResponse(result); MappingTaskResponse response = initBaseResponse(result);
if (result.getStatus() == GenerateStatus.SUCCESS) { if (result.getStatus() == GenerateStatus.SUCCESS) {
response.setMappingJson(result.getMappingJson()); response.setMappingJson(result.getMappingJson());
response.setMappingDocument(result.getMappingDocument());
response.setSavedPath(result.getSavedPath()); response.setSavedPath(result.getSavedPath());
return response; return response;
} }

View File

@@ -5,7 +5,6 @@ import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum; import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult; import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.LogUtil; import com.njcn.common.utils.LogUtil;
import com.njcn.gather.icd.mapping.component.IcdToXmlRequestConverter;
import com.njcn.gather.icd.mapping.component.IcdToXmlResponseConverter; import com.njcn.gather.icd.mapping.component.IcdToXmlResponseConverter;
import com.njcn.gather.icd.mapping.component.IndexSelectionBuildService; import com.njcn.gather.icd.mapping.component.IndexSelectionBuildService;
import com.njcn.gather.icd.mapping.component.MappingRequestConverter; import com.njcn.gather.icd.mapping.component.MappingRequestConverter;
@@ -13,10 +12,8 @@ import com.njcn.gather.icd.mapping.component.MappingResponseConverter;
import com.njcn.gather.icd.mapping.pojo.bo.GenerateMappingResult; import com.njcn.gather.icd.mapping.pojo.bo.GenerateMappingResult;
import com.njcn.gather.icd.mapping.pojo.bo.IcdToXmlGenerateResult; import com.njcn.gather.icd.mapping.pojo.bo.IcdToXmlGenerateResult;
import com.njcn.gather.icd.mapping.pojo.dto.GenerateFromIcdCommand; import com.njcn.gather.icd.mapping.pojo.dto.GenerateFromIcdCommand;
import com.njcn.gather.icd.mapping.pojo.dto.IcdToXmlGenerateCommand;
import com.njcn.gather.icd.mapping.pojo.param.BuildIndexSelectionRequest; import com.njcn.gather.icd.mapping.pojo.param.BuildIndexSelectionRequest;
import com.njcn.gather.icd.mapping.pojo.param.GenerateMappingFromIcdRequest; import com.njcn.gather.icd.mapping.pojo.param.GenerateMappingFromIcdRequest;
import com.njcn.gather.icd.mapping.pojo.param.IcdToXmlGenerateRequest;
import com.njcn.gather.icd.mapping.pojo.param.IndexCandidateRequest; import com.njcn.gather.icd.mapping.pojo.param.IndexCandidateRequest;
import com.njcn.gather.icd.mapping.pojo.param.JsonToXmlRequest; import com.njcn.gather.icd.mapping.pojo.param.JsonToXmlRequest;
import com.njcn.gather.icd.mapping.pojo.param.SubmitIndexSelectionRequest; import com.njcn.gather.icd.mapping.pojo.param.SubmitIndexSelectionRequest;
@@ -67,9 +64,6 @@ public class MappingController extends BaseController {
/** 请求参数转换器,将接口入参转换为应用层命令。 */ /** 请求参数转换器,将接口入参转换为应用层命令。 */
private final IcdToXmlResponseConverter icdResponseConverter; private final IcdToXmlResponseConverter icdResponseConverter;
/** 响应转换器,按接口阶段裁剪最小返回字段。 */
private final IcdToXmlRequestConverter icdRequestConverter;
/** 响应转换器,按接口阶段裁剪最小返回字段。 */ /** 响应转换器,按接口阶段裁剪最小返回字段。 */
private final IcdToXmlTaskAppService icdToXmlTaskAppService; private final IcdToXmlTaskAppService icdToXmlTaskAppService;
@@ -161,7 +155,7 @@ public class MappingController extends BaseController {
} }
/** /**
* 直接将 MMS JSON 转换为 XML 文件 * 直接将 MMS JSON 转换为 XML 内容
* 适用于已经通过 getIcdMmsJson 获得 JSON 后,单独进行 XML 转换的场景。 * 适用于已经通过 getIcdMmsJson 获得 JSON 后,单独进行 XML 转换的场景。
*/ */
@OperateInfo(info = LogEnum.BUSINESS_COMMON) @OperateInfo(info = LogEnum.BUSINESS_COMMON)
@@ -169,18 +163,18 @@ public class MappingController extends BaseController {
@ApiImplicitParam(name = "request", value = "MMS JSON 转 XML 参数", required = true, dataType = "JsonToXmlRequest") @ApiImplicitParam(name = "request", value = "MMS JSON 转 XML 参数", required = true, dataType = "JsonToXmlRequest")
@PostMapping("/get-xml-from-json") @PostMapping("/get-xml-from-json")
public IcdToXmlResponse getXmlFromJson(@Validated @RequestPart("request") JsonToXmlRequest request) { public IcdToXmlResponse getXmlFromJson(@Validated @RequestPart("request") JsonToXmlRequest request) {
String methodDescribe = getMethodDescribe("getXmlFromJson"); String methodDescribe = getMethodDescribe("getXmlFromJson") + ",开始将 MMS JSON 转换为 XML";
LogUtil.njcnDebug(log, "{},开始将 MMS JSON 转换为 XML", methodDescribe);
// // 将请求参数转换为 Command // 直接返回 XML 内容给前端展示,不再输出临时文件。
// IcdToXmlGenerateCommand command =
// icdRequestConverter.toCommand(request);
//
// 调用服务生成 XML
IcdToXmlGenerateResult result = IcdToXmlGenerateResult result =
icdToXmlTaskAppService.generateXmlFromJson(request.getMappingJson()); icdToXmlTaskAppService.generateXmlFromJson(request == null ? null : request.getMappingJson());
if (result.getMethodDescribe() != null && !result.getMethodDescribe().trim().isEmpty()) {
methodDescribe = methodDescribe + "\n" + result.getMethodDescribe();
}
return icdResponseConverter.fromResult(result); IcdToXmlResponse response = icdResponseConverter.fromResult(result);
response.setMethodDescribe(methodDescribe);
return response;
} }
} }

View File

@@ -96,7 +96,7 @@ public class SclGeneratedModelReader {
* 从 LN0 或 LN 中读取 DataSet 与 ReportControl 信息。 * 从 LN0 或 LN 中读取 DataSet 与 ReportControl 信息。
*/ */
private void readReportAndDataSetFromAnyLn(TAnyLN anyLn, IcdDocument document) { private void readReportAndDataSetFromAnyLn(TAnyLN anyLn, IcdDocument document) {
List<FcdaNode> allFcdas = new ArrayList<FcdaNode>(); //List<FcdaNode> allFcdas = new ArrayList<FcdaNode>();
if (anyLn.getDataSet() != null) { if (anyLn.getDataSet() != null) {
for (TDataSet dataSet : anyLn.getDataSet()) { for (TDataSet dataSet : anyLn.getDataSet()) {
DataSetNode dataSetNode = new DataSetNode(); DataSetNode dataSetNode = new DataSetNode();
@@ -105,7 +105,7 @@ public class SclGeneratedModelReader {
for (TFCDA fcda : dataSet.getFCDA()) { for (TFCDA fcda : dataSet.getFCDA()) {
FcdaNode node = toFcdaNode(fcda); FcdaNode node = toFcdaNode(fcda);
dataSetNode.getFcdas().add(node); dataSetNode.getFcdas().add(node);
allFcdas.add(node); //allFcdas.add(node);
} }
} }
document.getDataSets().put(dataSetNode.getName(), dataSetNode); document.getDataSets().put(dataSetNode.getName(), dataSetNode);
@@ -113,7 +113,7 @@ public class SclGeneratedModelReader {
} }
for (DataSetNode dataSet : document.getDataSets().values()) { for (DataSetNode dataSet : document.getDataSets().values()) {
for (FcdaNode fcda : dataSet.getFcdas()) { for (FcdaNode fcda : dataSet.getFcdas()) {
fcda.setSequenceCount(SclTraversalSupport.calculateSequenceCount(allFcdas, fcda)); fcda.setSequenceCount(SclTraversalSupport.calculateSequenceCount(dataSet.getFcdas(), fcda));
} }
} }

View File

@@ -3,36 +3,42 @@ package com.njcn.gather.icd.mapping.pojo.bo;
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexAnalysis; import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexAnalysis;
import com.njcn.gather.icd.mapping.pojo.bo.mapping.MappingDocument; import com.njcn.gather.icd.mapping.pojo.bo.mapping.MappingDocument;
import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus; import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* XML 生成应用层返回对象。
*
* 统一封装成功、需选择索引、失败三类结果。
*/
@Data
public class IcdToXmlGenerateResult { public class IcdToXmlGenerateResult {
/** 本次生成流程状态。 */
private GenerateStatus status; private GenerateStatus status;
private String message;
private String iedName;
private String ldInst;
private IndexAnalysis indexAnalysis;
private MappingDocument mappingDocument;
private String savedPath;
private List<String> problems = new ArrayList<String>();
public GenerateStatus getStatus() { return status; } /** 给前端或调用方展示的处理结果说明。 */
public void setStatus(GenerateStatus status) { this.status = status; } private String message;
public String getMessage() { return message; }
public void setMessage(String message) { this.message = message; } /** 生成过程中需要返回的方法描述信息。 */
public String getIedName() { return iedName; } private String methodDescribe;
public void setIedName(String iedName) { this.iedName = iedName; }
public String getLdInst() { return ldInst; } /** ICD 中解析到的 IED 名称。 */
public void setLdInst(String ldInst) { this.ldInst = ldInst; } private String iedName;
public IndexAnalysis getIndexAnalysis() { return indexAnalysis; }
public void setIndexAnalysis(IndexAnalysis indexAnalysis) { this.indexAnalysis = indexAnalysis; } /** ICD 中解析到的 LD 实例名。 */
public MappingDocument getMappingDocument() { return mappingDocument; } private String ldInst;
public void setMappingDocument(MappingDocument mappingDocument) { this.mappingDocument = mappingDocument; }
public String getSavedPath() { return savedPath; } /** 需要人工绑定索引时返回的候选分析结果。 */
public void setSavedPath(String savedPath) { this.savedPath = savedPath; } private IndexAnalysis indexAnalysis;
public List<String> getProblems() { return problems; }
public void setProblems(List<String> problems) { this.problems = problems; } /** 生成成功后的结构化映射文档。 */
private MappingDocument mappingDocument;
/** 生成成功后的 XML 内容,用于前端直接展示。 */
private String xmlContent;
/** 解析、校验或生成过程中收集到的问题。 */
private List<String> problems = new ArrayList<String>();
} }

View File

@@ -0,0 +1,24 @@
package com.njcn.gather.icd.mapping.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 根据确认结果生成 indexSelection 的请求参数。
*/
@Data
@ApiModel("根据确认结果生成 indexSelection 的请求参数")
public class BuildIndexSelectionRequest {
/** 接口返回的确认模型数据。 */
@ApiModelProperty("接口返回的确认模型数据")
private List<IndexConfirmGroupRequest> confirmData = new ArrayList<IndexConfirmGroupRequest>();
/** 前端最终确认后的选择结果。 */
@ApiModelProperty("前端最终确认后的选择结果")
private List<ConfirmedIndexGroupRequest> confirmedData = new ArrayList<ConfirmedIndexGroupRequest>();
}

View File

@@ -0,0 +1,24 @@
package com.njcn.gather.icd.mapping.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 前端提交的确认分组结果。
*/
@Data
@ApiModel("前端提交的确认分组结果")
public class ConfirmedIndexGroupRequest {
/** 分组唯一键。 */
@ApiModelProperty("分组唯一键")
private String groupKey;
/** 当前分组下按 label 汇总后的确认项。 */
@ApiModelProperty("当前分组下按 label 汇总后的确认项")
private List<ConfirmedLabelItemRequest> labelItems = new ArrayList<ConfirmedLabelItemRequest>();
}

View File

@@ -0,0 +1,25 @@
package com.njcn.gather.icd.mapping.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 前端提交的单个 label 确认结果。
*/
@Data
@ApiModel("前端提交的单个 label 确认结果")
public class ConfirmedLabelItemRequest {
/** 模板标签。 */
@ApiModelProperty("模板标签")
private String label;
/** 是否启用当前配置。 */
@ApiModelProperty("是否启用当前配置")
private boolean enabled;
/** 当前 label 选中的 lnInst。 */
@ApiModelProperty("当前 label 选中的 lnInst")
private String lnInst;
}

View File

@@ -0,0 +1,32 @@
package com.njcn.gather.icd.mapping.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 索引候选中的单个报告请求项。
*/
@Data
@ApiModel("索引候选中的单个报告请求项")
public class IndexCandidateReportItemRequest {
/** 报告名称。 */
@ApiModelProperty("报告名称")
private String reportName;
/** 数据集名称。 */
@ApiModelProperty("数据集名称")
private String dataSetName;
/** 报告描述。 */
@ApiModelProperty("报告描述")
private String reportDesc;
/** 当前报告允许选择的 lnInst 值列表。 */
@ApiModelProperty("当前报告允许选择的 lnInst 值列表")
private List<String> availableLnInstValues = new ArrayList<String>();
}

View File

@@ -0,0 +1,36 @@
package com.njcn.gather.icd.mapping.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 索引候选分组请求参数。
*/
@Data
@ApiModel("索引候选分组请求参数")
public class IndexCandidateRequest {
/** 分组唯一键。 */
@ApiModelProperty("分组唯一键")
private String groupKey;
/** 分组中文描述。 */
@ApiModelProperty("分组中文描述")
private String groupDesc;
/** 当前分组包含的报告数量。 */
@ApiModelProperty("当前分组包含的报告数量")
private int reportCount;
/** 当前分组展示的模板标签列表。 */
@ApiModelProperty("当前分组展示的模板标签列表")
private List<String> templateLabels = new ArrayList<String>();
/** 当前分组下的报告候选列表。 */
@ApiModelProperty("当前分组下的报告候选列表")
private List<IndexCandidateReportItemRequest> reports = new ArrayList<IndexCandidateReportItemRequest>();
}

View File

@@ -0,0 +1,28 @@
package com.njcn.gather.icd.mapping.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 索引确认分组请求参数。
*/
@Data
@ApiModel("索引确认分组请求参数")
public class IndexConfirmGroupRequest {
/** 分组唯一键。 */
@ApiModelProperty("分组唯一键")
private String groupKey;
/** 分组中文描述。 */
@ApiModelProperty("分组中文描述")
private String groupDesc;
/** 当前分组下按 label 聚合后的确认项。 */
@ApiModelProperty("当前分组下按 label 聚合后的确认项")
private List<IndexConfirmLabelItemRequest> labelItems = new ArrayList<IndexConfirmLabelItemRequest>();
}

View File

@@ -0,0 +1,40 @@
package com.njcn.gather.icd.mapping.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 索引确认模型中的单个 label 请求项。
*/
@Data
@ApiModel("索引确认模型中的单个 label 请求项")
public class IndexConfirmLabelItemRequest {
/** 模板标签。 */
@ApiModelProperty("模板标签")
private String label;
/** 是否必配。 */
@ApiModelProperty("是否必配")
private boolean required;
/** 是否支持一次配置后展开到多个报告。 */
@ApiModelProperty("是否支持一次配置后展开到多个报告")
private boolean configurableOnce;
/** 所有目标报告共同支持的 lnInst 候选值。 */
@ApiModelProperty("所有目标报告共同支持的 lnInst 候选值")
private List<String> commonLnInstValues = new ArrayList<String>();
/** 候选值唯一时的默认 lnInst。 */
@ApiModelProperty("候选值唯一时的默认 lnInst")
private String defaultLnInst;
/** 当前 label 影响的目标报告列表。 */
@ApiModelProperty("当前 label 影响的目标报告列表")
private List<IndexConfirmTargetRequest> targets = new ArrayList<IndexConfirmTargetRequest>();
}

View File

@@ -0,0 +1,32 @@
package com.njcn.gather.icd.mapping.pojo.param;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 索引确认模型中的目标报告请求项。
*/
@Data
@ApiModel("索引确认模型中的目标报告请求项")
public class IndexConfirmTargetRequest {
/** 报告名称。 */
@ApiModelProperty("报告名称")
private String reportName;
/** 数据集名称。 */
@ApiModelProperty("数据集名称")
private String dataSetName;
/** 报告描述。 */
@ApiModelProperty("报告描述")
private String reportDesc;
/** 当前目标报告可接受的 lnInst 列表。 */
@ApiModelProperty("当前目标报告可接受的 lnInst 列表")
private List<String> availableLnInstValues = new ArrayList<String>();
}

View File

@@ -1,8 +1,10 @@
package com.njcn.gather.icd.mapping.pojo.vo; package com.njcn.gather.icd.mapping.pojo.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus; import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -10,6 +12,8 @@ import java.util.List;
/** /**
* MMS JSON 转 XML 的响应。 * MMS JSON 转 XML 的响应。
*/ */
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel("MMS JSON 转 XML 的响应") @ApiModel("MMS JSON 转 XML 的响应")
public class IcdToXmlResponse { public class IcdToXmlResponse {
@@ -19,14 +23,17 @@ public class IcdToXmlResponse {
@ApiModelProperty("状态说明或错误提示") @ApiModelProperty("状态说明或错误提示")
private String message; private String message;
@ApiModelProperty("接口方法描述")
private String methodDescribe;
@ApiModelProperty("IED 名称") @ApiModelProperty("IED 名称")
private String iedName; private String iedName;
@ApiModelProperty("逻辑设备实例名") @ApiModelProperty("逻辑设备实例名")
private String ldInst; private String ldInst;
@ApiModelProperty("落盘后的绝对路径") @ApiModelProperty("生成后的 XML 文件展示容器")
private String savedPath; private XmlFileResponse xmlFile;
@ApiModelProperty("生成后的映射文档摘要") @ApiModelProperty("生成后的映射文档摘要")
private MappingDocumentResponse mappingDocument; private MappingDocumentResponse mappingDocument;
@@ -37,67 +44,4 @@ public class IcdToXmlResponse {
@ApiModelProperty("处理过程中发现的问题列表") @ApiModelProperty("处理过程中发现的问题列表")
private List<String> problems = new ArrayList<String>(); 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 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;
}
} }

View File

@@ -0,0 +1,31 @@
package com.njcn.gather.icd.mapping.pojo.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 单条索引绑定响应项。
*/
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel("单条索引绑定响应项")
public class IndexBindingResponse {
/** 报告名称。 */
@ApiModelProperty("报告名称")
private String reportName;
/** 数据集名称。 */
@ApiModelProperty("数据集名称")
private String dataSetName;
/** 模板标签。 */
@ApiModelProperty("模板标签")
private String label;
/** 当前标签对应的 lnInst。 */
@ApiModelProperty("当前标签对应的 lnInst")
private String lnInst;
}

View File

@@ -0,0 +1,30 @@
package com.njcn.gather.icd.mapping.pojo.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 索引确认分组响应。
*/
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel("索引确认分组响应")
public class IndexConfirmGroupResponse {
/** 分组唯一键。 */
@ApiModelProperty("分组唯一键")
private String groupKey;
/** 分组中文描述。 */
@ApiModelProperty("分组中文描述")
private String groupDesc;
/** 当前分组下按 label 聚合后的确认项。 */
@ApiModelProperty("当前分组下按 label 聚合后的确认项")
private List<IndexConfirmLabelItemResponse> labelItems = new ArrayList<IndexConfirmLabelItemResponse>();
}

View File

@@ -0,0 +1,42 @@
package com.njcn.gather.icd.mapping.pojo.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 索引确认模型中的单个 label 响应项。
*/
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel("索引确认模型中的单个 label 响应项")
public class IndexConfirmLabelItemResponse {
/** 模板标签。 */
@ApiModelProperty("模板标签")
private String label;
/** 是否必配。 */
@ApiModelProperty("是否必配")
private boolean required;
/** 是否支持一次配置后展开到多个报告。 */
@ApiModelProperty("是否支持一次配置后展开到多个报告")
private boolean configurableOnce;
/** 所有目标报告共同支持的 lnInst 候选值。 */
@ApiModelProperty("所有目标报告共同支持的 lnInst 候选值")
private List<String> commonLnInstValues = new ArrayList<String>();
/** 候选值唯一时的默认 lnInst。 */
@ApiModelProperty("候选值唯一时的默认 lnInst")
private String defaultLnInst;
/** 当前 label 影响的全部目标报告。 */
@ApiModelProperty("当前 label 影响的全部目标报告")
private List<IndexConfirmTargetResponse> targets = new ArrayList<IndexConfirmTargetResponse>();
}

View File

@@ -0,0 +1,34 @@
package com.njcn.gather.icd.mapping.pojo.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 索引确认模型中的目标报告响应项。
*/
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel("索引确认模型中的目标报告响应项")
public class IndexConfirmTargetResponse {
/** 报告名称。 */
@ApiModelProperty("报告名称")
private String reportName;
/** 数据集名称。 */
@ApiModelProperty("数据集名称")
private String dataSetName;
/** 报告描述。 */
@ApiModelProperty("报告描述")
private String reportDesc;
/** 当前目标报告可接受的 lnInst 列表。 */
@ApiModelProperty("当前目标报告可接受的 lnInst 列表")
private List<String> availableLnInstValues = new ArrayList<String>();
}

View File

@@ -0,0 +1,30 @@
package com.njcn.gather.icd.mapping.pojo.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 生成后的索引选择分组响应。
*/
@Data
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@ApiModel("生成后的索引选择分组响应")
public class IndexSelectionGroupResponse {
/** 分组唯一键。 */
@ApiModelProperty("分组唯一键")
private String groupKey;
/** 分组中文描述。 */
@ApiModelProperty("分组中文描述")
private String groupDesc;
/** 当前分组最终生成的绑定关系。 */
@ApiModelProperty("当前分组最终生成的绑定关系")
private List<IndexBindingResponse> bindings = new ArrayList<IndexBindingResponse>();
}

View File

@@ -2,6 +2,7 @@ package com.njcn.gather.icd.mapping.pojo.vo;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.njcn.gather.icd.mapping.pojo.bo.icd.IcdDocument; 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 com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
@@ -34,6 +35,10 @@ public class MappingTaskResponse {
@ApiModelProperty("正式生成成功后的完整映射 JSON") @ApiModelProperty("正式生成成功后的完整映射 JSON")
private String mappingJson; private String mappingJson;
/** 正式生成成功后的结构化映射文档。 */
@ApiModelProperty("正式生成成功后的结构化映射文档")
private MappingDocument mappingDocument;
/** 落盘后的绝对路径。 */ /** 落盘后的绝对路径。 */
@ApiModelProperty("落盘后的绝对路径") @ApiModelProperty("落盘后的绝对路径")
private String savedPath; private String savedPath;

View File

@@ -0,0 +1,29 @@
package com.njcn.gather.icd.mapping.pojo.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* XML 文件展示容器。
*/
@Data
@ApiModel("XML 文件展示容器")
public class XmlFileResponse {
/** XML 文件名。 */
@ApiModelProperty("XML 文件名")
private String fileName;
/** 文件内容类型。 */
@ApiModelProperty("文件内容类型")
private String contentType;
/** 文件编码。 */
@ApiModelProperty("文件编码")
private String encoding;
/** XML 文件内容。 */
@ApiModelProperty("XML 文件内容")
private String content;
}

View File

@@ -1,7 +1,14 @@
package com.njcn.gather.icd.mapping.service.impl; package com.njcn.gather.icd.mapping.service.impl;
import com.njcn.gather.icd.mapping.component.DefaultTemplateLoader;
import com.njcn.gather.icd.mapping.component.*; import com.njcn.gather.icd.mapping.component.IcdParserService;
import com.njcn.gather.icd.mapping.component.IcdToXmlMappingService;
import com.njcn.gather.icd.mapping.component.IndexAnalysisService;
import com.njcn.gather.icd.mapping.component.IndexValidationService;
import com.njcn.gather.icd.mapping.component.JsonToXmlConversionService;
import com.njcn.gather.icd.mapping.component.MappingDocumentSerializer;
import com.njcn.gather.icd.mapping.component.MappingGenerationService;
import com.njcn.gather.icd.mapping.component.RuleBasedXmlMappingService;
import com.njcn.gather.icd.mapping.pojo.bo.IcdToXmlGenerateResult; import com.njcn.gather.icd.mapping.pojo.bo.IcdToXmlGenerateResult;
import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexAnalysis; import com.njcn.gather.icd.mapping.pojo.bo.analysis.IndexAnalysis;
import com.njcn.gather.icd.mapping.pojo.bo.analysis.ValidationResult; import com.njcn.gather.icd.mapping.pojo.bo.analysis.ValidationResult;
@@ -9,173 +16,292 @@ 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.bo.mapping.MappingDocument;
import com.njcn.gather.icd.mapping.pojo.bo.template.DefaultTemplate; import com.njcn.gather.icd.mapping.pojo.bo.template.DefaultTemplate;
import com.njcn.gather.icd.mapping.pojo.dto.IcdToXmlGenerateCommand; import com.njcn.gather.icd.mapping.pojo.dto.IcdToXmlGenerateCommand;
import com.njcn.gather.icd.mapping.pojo.dto.IndexSelectionGroupCommand;
import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus; import com.njcn.gather.icd.mapping.pojo.enums.GenerateStatus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/**
* ICD/XML 生成任务编排服务。
*
* 负责组织 ICD 到 XML、MMS JSON 到 XML 两类转换链路,避免 Controller 关注模板、规则和异常处理细节。
*/
@Slf4j
@Service @Service
@RequiredArgsConstructor
public class IcdToXmlTaskAppService { public class IcdToXmlTaskAppService {
/** ICD 转 XML 阶段名称。 */
private static final String ICD_TO_XML_TASK_NAME = "ICD 转 XML";
/** JSON 转 XML 阶段名称。 */
private static final String JSON_TO_XML_TASK_NAME = "XML 生成";
/** 缺少索引绑定提示。 */
private static final String INDEX_SELECTION_MISSING_MESSAGE = "索引配置缺失或不合法,请根据候选信息完成标签与数字索引的绑定后重新提交";
/** 映射生成成功提示。 */
private static final String MAPPING_GENERATE_SUCCESS_MESSAGE = "映射生成成功";
/** XML 生成成功提示。 */
private static final String XML_GENERATE_SUCCESS_MESSAGE = "XML 生成成功";
/** 空 JSON 提示。 */
private static final String MAPPING_JSON_EMPTY_MESSAGE = "MMS映射JSON不能为空";
/** 缺少默认 XML 模板提示。 */
private static final String DEFAULT_XML_MISSING_MESSAGE = "缺少默认xml配置文件";
/** 缺少默认规则文件提示。 */
private static final String DEFAULT_RULE_MISSING_MESSAGE = "缺少默认规则配置文件";
/** ICD/SCL 文件解析服务。 */
private final IcdParserService icdParserService; private final IcdParserService icdParserService;
/** DefaultCfg.txt 默认模板加载器。 */
private final DefaultTemplateLoader defaultTemplateLoader; private final DefaultTemplateLoader defaultTemplateLoader;
/** 根据 ICD 和模板生成前端可选索引候选。 */
private final IndexAnalysisService indexAnalysisService; private final IndexAnalysisService indexAnalysisService;
/** 校验前端回传的标签与 lnInst 绑定关系。 */
private final IndexValidationService indexValidationService; private final IndexValidationService indexValidationService;
/** 根据有效绑定关系生成最终 MappingDocument。 */
private final MappingGenerationService mappingGenerationService; private final MappingGenerationService mappingGenerationService;
/** XML 模板和规则文件加载服务。 */
private final RuleBasedXmlMappingService ruleBasedXmlMappingService; private final RuleBasedXmlMappingService ruleBasedXmlMappingService;
/** 将 MappingDocument 序列化为 JSON。 */
private final MappingDocumentSerializer mappingDocumentSerializer; private final MappingDocumentSerializer mappingDocumentSerializer;
private final FileStorageService fileStorageService; /** 根据索引绑定关系重建 XML 规则中的实例索引。 */
private final IcdToXmlMappingService icdToXmlMappingService; private final IcdToXmlMappingService icdToXmlMappingService;
/** 将 MMS JSON 中间态转换为 XML 内容。 */
private final JsonToXmlConversionService jsonToXmlConversionService; private final JsonToXmlConversionService jsonToXmlConversionService;
public IcdToXmlTaskAppService(IcdParserService icdParserService, /**
DefaultTemplateLoader defaultTemplateLoader, * 解析 ICD 并按索引绑定关系直接生成 XML 内容。
IndexAnalysisService indexAnalysisService, */
IndexValidationService indexValidationService,
MappingGenerationService mappingGenerationService,
RuleBasedXmlMappingService ruleBasedXmlMappingService,
MappingDocumentSerializer mappingDocumentSerializer,
FileStorageService fileStorageService,
IcdToXmlMappingService icdToXmlMappingService,
JsonToXmlConversionService jsonToXmlConversionService) {
this.icdParserService = icdParserService;
this.defaultTemplateLoader = defaultTemplateLoader;
this.indexAnalysisService = indexAnalysisService;
this.indexValidationService = indexValidationService;
this.mappingGenerationService = mappingGenerationService;
this.ruleBasedXmlMappingService = ruleBasedXmlMappingService;
this.mappingDocumentSerializer = mappingDocumentSerializer;
this.fileStorageService = fileStorageService;
this.icdToXmlMappingService = icdToXmlMappingService;
this.jsonToXmlConversionService = jsonToXmlConversionService;
}
public IcdToXmlGenerateResult generateFromIcd(IcdToXmlGenerateCommand command) { public IcdToXmlGenerateResult generateFromIcd(IcdToXmlGenerateCommand command) {
IcdToXmlGenerateResult result = new IcdToXmlGenerateResult(); return executeTask(ICD_TO_XML_TASK_NAME, result -> {
try { IcdToXmlTaskContext context = buildTaskContext(command, result);
// ========== 第一步ICD → JSON ==========
// 1. 解析 ICD if (isIndexSelectionEmpty(command.getIndexSelection())) {
IcdDocument icdDocument = icdParserService.parse(command.getFileBytes(), command.getFileName()); markNeedIndexSelection(result);
result.setIedName(icdDocument.getIedName()); return;
result.setLdInst(icdDocument.getLdInst());
// 2. 加载 DefaultCfg.txt和默认配置
//DefaultTemplate template = defaultTemplateLoader.loadDefaultTemplateToXml();
DefaultTemplate template = defaultTemplateLoader.load();
result.getProblems().addAll(template.verify());
InputStream templateStream = ruleBasedXmlMappingService.loadDefaultXmlFile();
if(templateStream==null){result.getProblems().add("缺少默认xml配置文件");}
List<InputStream> ruleStreams = ruleBasedXmlMappingService.loadDefaultRuleFile();
if(ruleStreams==null){result.getProblems().add("缺少默认规则配置文件");}
// 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(context.indexAnalysis, command.getIndexSelection());
ValidationResult validationResult = indexValidationService.validate(indexAnalysis, command.getIndexSelection());
if (!validationResult.isValid()) { if (!validationResult.isValid()) {
result.setStatus(GenerateStatus.NEED_INDEX_SELECTION); markNeedIndexSelection(result);
result.setMessage("索引配置缺失或不合法,请根据候选信息完成标签与数字索引的绑定后重新提交");
result.getProblems().addAll(validationResult.getProblems()); result.getProblems().addAll(validationResult.getProblems());
return result; return;
} }
// 6. 生成正式映射结构JSON中间态
MappingDocument mappingDocument = mappingGenerationService.generate( MappingDocument mappingDocument = mappingGenerationService.generate(
icdDocument, context.icdDocument,
template, context.template,
indexAnalysis, context.indexAnalysis,
command.getIndexSelection(), command.getIndexSelection(),
command.getVersion(), command.getVersion(),
command.getAuthor() command.getAuthor()
); );
result.setMappingDocument(mappingDocument); result.setMappingDocument(mappingDocument);
// 7. 序列化为JSON字符串中间产物
String mappingJson = mappingDocumentSerializer.toPrettyJson(mappingDocument); String mappingJson = mappingDocumentSerializer.toPrettyJson(mappingDocument);
bindIndexMapping(command.getIndexSelection());
// ========== 第二步JSON → XML ========== fillXmlContent(result, mappingJson, loadXmlResources());
// 8. 重新绑定正确索引
IcdToXmlMappingService.IndexMappingConfig mappingConfig = icdToXmlMappingService.buildIndexMappingFromSelection(command.getIndexSelection());
icdToXmlMappingService.setIndexMapping(mappingConfig);
// 9. 从JSON转换为XML
String xmlPath = jsonToXmlConversionService.convertFromJson(
mappingJson,
templateStream,
ruleStreams,
icdToXmlMappingService.getIndexMapping()
);
result.setSavedPath(xmlPath);
result.setStatus(GenerateStatus.SUCCESS); result.setStatus(GenerateStatus.SUCCESS);
result.setMessage("映射生成成功"); result.setMessage(MAPPING_GENERATE_SUCCESS_MESSAGE);
return result; });
} catch (Exception ex) {
result.setStatus(GenerateStatus.FAILED);
result.setMessage("映射生成失败:" + ex.getMessage());
result.getProblems().add(ex.getMessage());
return result;
}
} }
/** /**
* 直接从 JSON 字符串生成 XML 文件 * 直接从 JSON 字符串生成 XML 内容
* *
* @param mappingJson MMS 映射 JSON 字符串(由 getIcdMmsJson 接口返回) * @param mappingJson MMS 映射 JSON 字符串(由 getIcdMmsJson 接口返回)
* @return XML 生成结果 * @return XML 生成结果
*/ */
public IcdToXmlGenerateResult generateXmlFromJson(String mappingJson){ public IcdToXmlGenerateResult generateXmlFromJson(String mappingJson) {
return executeTask(JSON_TO_XML_TASK_NAME, result -> {
if (isBlank(mappingJson)) {
markFailed(result, JSON_TO_XML_TASK_NAME, MAPPING_JSON_EMPTY_MESSAGE);
return;
}
fillXmlContent(result, mappingJson, loadXmlResources());
result.setStatus(GenerateStatus.SUCCESS);
result.setMessage(XML_GENERATE_SUCCESS_MESSAGE);
});
}
/**
* 统一包装任务执行结果,避免每个入口重复编写异常兜底逻辑。
*/
private IcdToXmlGenerateResult executeTask(String taskName, IcdToXmlTaskAction action) {
IcdToXmlGenerateResult result = new IcdToXmlGenerateResult(); IcdToXmlGenerateResult result = new IcdToXmlGenerateResult();
try { try {
// 1. 加载模板和规则文件 action.execute(result);
InputStream templateStream = ruleBasedXmlMappingService.loadDefaultXmlFile();
if (templateStream == null) {
result.setStatus(GenerateStatus.FAILED);
result.getProblems().add("缺少默认xml配置文件");
result.setMessage("XML 生成失败缺少默认xml配置文件");
return result;
}
List<InputStream> ruleStreams = ruleBasedXmlMappingService.loadDefaultRuleFile();
if (ruleStreams == null) {
result.setStatus(GenerateStatus.FAILED);
result.getProblems().add("缺少默认规则配置文件");
result.setMessage("XML 生成失败:缺少默认规则配置文件");
return result;
}
// 3. 从 JSON 转换为 XML
String xmlPath = jsonToXmlConversionService.convertFromJson(
mappingJson,
templateStream,
ruleStreams,
icdToXmlMappingService.getIndexMapping()
);
result.setSavedPath(xmlPath);
result.setStatus(GenerateStatus.SUCCESS);
result.setMessage("XML 生成成功");
return result;
} catch (Exception ex) { } catch (Exception ex) {
result.setStatus(GenerateStatus.FAILED); handleTaskFailure(result, taskName, ex);
result.setMessage("XML 生成失败:" + ex.getMessage()); }
result.getProblems().add(ex.getMessage()); return result;
return result; }
/**
* 解析 ICD 并补齐模板、索引候选上下文。
*/
private IcdToXmlTaskContext buildTaskContext(IcdToXmlGenerateCommand command, IcdToXmlGenerateResult result) {
IcdDocument icdDocument = icdParserService.parse(command.getFileBytes(), command.getFileName());
fillIcdSummary(result, icdDocument);
DefaultTemplate template = loadTemplate(result);
IndexAnalysis indexAnalysis = analyzeIndexCandidates(icdDocument, template, result);
result.setIndexAnalysis(indexAnalysis);
return new IcdToXmlTaskContext(icdDocument, template, indexAnalysis);
}
/**
* 加载并校验默认模板。
*/
private DefaultTemplate loadTemplate(IcdToXmlGenerateResult result) {
DefaultTemplate template = defaultTemplateLoader.load();
result.getProblems().addAll(template.verify());
return template;
}
/**
* 分析 ICD 对应的索引候选。
*/
private IndexAnalysis analyzeIndexCandidates(IcdDocument icdDocument,
DefaultTemplate template,
IcdToXmlGenerateResult result) {
IndexAnalysis indexAnalysis = indexAnalysisService.analyze(icdDocument, template);
result.getProblems().addAll(indexAnalysis.getProblems());
return indexAnalysis;
}
/**
* 加载 XML 模板和规则文件。
*/
private XmlResourceContext loadXmlResources() throws Exception {
InputStream templateStream = ruleBasedXmlMappingService.loadDefaultXmlFile();
if (templateStream == null) {
throw new IllegalArgumentException(DEFAULT_XML_MISSING_MESSAGE);
}
List<InputStream> ruleStreams = ruleBasedXmlMappingService.loadDefaultRuleFile();
if (ruleStreams == null) {
throw new IllegalArgumentException(DEFAULT_RULE_MISSING_MESSAGE);
}
return new XmlResourceContext(templateStream, ruleStreams);
}
/**
* 根据索引绑定关系刷新 XML 转换所需的实例索引。
*/
private void bindIndexMapping(List<IndexSelectionGroupCommand> indexSelection) {
IcdToXmlMappingService.IndexMappingConfig mappingConfig = icdToXmlMappingService.buildIndexMappingFromSelection(indexSelection);
icdToXmlMappingService.setIndexMapping(mappingConfig);
}
/**
* 将 MMS JSON 中间态转换为 XML 内容。
*/
private void fillXmlContent(IcdToXmlGenerateResult result,
String mappingJson,
XmlResourceContext xmlResourceContext) throws Exception {
List<String> methodDescribeList = new ArrayList<>();
String xmlContent = jsonToXmlConversionService.buildXmlContentFromJson(
mappingJson,
xmlResourceContext.templateStream,
xmlResourceContext.ruleStreams,
icdToXmlMappingService.getIndexMapping(),
methodDescribeList
);
result.setXmlContent(xmlContent);
result.setMethodDescribe(String.join("\n", methodDescribeList));
}
/**
* 回填响应公共信息。
*/
private void fillIcdSummary(IcdToXmlGenerateResult result, IcdDocument icdDocument) {
result.setIedName(icdDocument.getIedName());
result.setLdInst(icdDocument.getLdInst());
}
/**
* 标记当前仍需用户重新确认索引绑定。
*/
private void markNeedIndexSelection(IcdToXmlGenerateResult result) {
result.setStatus(GenerateStatus.NEED_INDEX_SELECTION);
result.setMessage(INDEX_SELECTION_MISSING_MESSAGE);
}
/**
* 标记可预期的业务失败。
*/
private void markFailed(IcdToXmlGenerateResult result, String taskName, String errorMessage) {
result.setStatus(GenerateStatus.FAILED);
result.setMessage(taskName + "失败:" + errorMessage);
result.getProblems().add(errorMessage);
}
/**
* 统一处理任务异常,避免控制层再补失败分支。
*/
private void handleTaskFailure(IcdToXmlGenerateResult result, String taskName, Exception ex) {
log.error("{}失败", taskName, ex);
String errorMessage = resolveErrorMessage(ex);
markFailed(result, taskName, errorMessage);
}
private boolean isIndexSelectionEmpty(List<IndexSelectionGroupCommand> indexSelection) {
return indexSelection == null || indexSelection.isEmpty();
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
private String resolveErrorMessage(Exception ex) {
if (ex.getMessage() == null || ex.getMessage().trim().isEmpty()) {
return ex.getClass().getSimpleName();
}
return ex.getMessage();
}
@FunctionalInterface
private interface IcdToXmlTaskAction {
void execute(IcdToXmlGenerateResult result) throws Exception;
}
/**
* 供 ICD 转 XML 流程复用的编排上下文。
*/
private static class IcdToXmlTaskContext {
private final IcdDocument icdDocument;
private final DefaultTemplate template;
private final IndexAnalysis indexAnalysis;
private IcdToXmlTaskContext(IcdDocument icdDocument, DefaultTemplate template, IndexAnalysis indexAnalysis) {
this.icdDocument = icdDocument;
this.template = template;
this.indexAnalysis = indexAnalysis;
}
}
/**
* XML 转换所需的模板和规则资源。
*/
private static class XmlResourceContext {
private final InputStream templateStream;
private final List<InputStream> ruleStreams;
private XmlResourceContext(InputStream templateStream, List<InputStream> ruleStreams) {
this.templateStream = templateStream;
this.ruleStreams = ruleStreams;
} }
} }