# 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` 为结构化示意，实际字段数量与内容以运行结果为准
