# 方案 B 详细设计:下游接口明细对齐 API 调试(ApiTest) > **决策**:采用方案 B(大改),传输层(Query / Body / Header)与映射层(Mapping Body)分离,页面与数据模型对齐 `/api/apiDebugger`。 > **范围**:`itfx-master` 后端 + `oms0414` 前端 + 数据迁移 + 映射/发布/导入导出/授权联动。 > **关联文档**:[下游接口明细改造方案-小改vs大改.md](./下游接口明细改造方案-小改vs大改.md) --- ## 目录 1. [目标与原则](#1-目标与原则) 2. [概念模型前后对比](#2-概念模型前后对比) 3. [表结构前后对比](#3-表结构前后对比) 4. [枚举与常量前后对比](#4-枚举与常量前后对比) 5. [接口契约前后对比](#5-接口契约前后对比) 6. [转发与映射逻辑前后对比](#6-转发与映射逻辑前后对比) 7. [前端页面与数据流前后对比](#7-前端页面与数据流前后对比) 8. [关联能力改动清单](#8-关联能力改动清单) 9. [数据迁移方案](#9-数据迁移方案) 10. [兼容策略与版本节奏](#10-兼容策略与版本节奏) 11. [实施阶段建议](#11-实施阶段建议) --- ## 1. 目标与原则 ### 1.1 目标 | # | 目标 | |---|------| | G1 | 下游接口明细 Request 区与 **ApiTest** 一致:`Query` / `Body` / `Header` 为 KV 表,`Mapping Body` 为参数树 | | G2 | 解决 **Form 字段值为 JSON** 时,上游映射应对 **JSON 内部结构**(Mapping Body),表单字段用 **`$FromMappingBody`** 引用 | | G3 | 运行时 **按 FieldPosition** 把表单写入 URL Query、Header、Form Body | | G4 | 接口映射、发布、导入导出与新区分方式一致 | ### 1.2 设计原则 - **传输层**(真实 HTTP):`t_parameter_form` + `FieldPosition`(Query / Body / Header) - **映射层**(上游 → 下游):`t_parameter` + `ParaKind = MappingBody` - **Json 下游接口**:Mapping Body 树 **同时** 作为映射目标与 `SerializationBody` 的数据源(无 Form Body KV 或 Body KV 为空) - **接口级 FixedParameter**:逐步收敛——**Header/Query 迁入 Form**;Fixed 保留 **服务商级**、**Url 路径占位**、**脚本动态写入** 场景 - **兼容一期**:`GetInterfaceById` 仍返回 `formParameterList` / `inParameterList`(聚合旧字段),`SaveInterface` 同时接受新旧字段,服务端以新字段为准 --- ## 2. 概念模型前后对比 ### 2.1 改造前(现状) ```text ┌─────────────────────────────────────────────────────────────┐ │ 下游接口明细 (InterfaceDetail) │ ├─────────────────────────────────────────────────────────────┤ │ Query Tab → formParameterList (t_parameter_form) │ │ ⚠️ 后端全部当作 FormUrlEncoded Body,非 URL Query │ ├─────────────────────────────────────────────────────────────┤ │ Body Tab → inParameterList (t_parameter, IsInPara) │ │ ⚠️ 同时承担:Json HTTP Body + 上游映射目标 │ ├─────────────────────────────────────────────────────────────┤ │ 固定配置 Tab → FixedParameter 独立 API (t_parameter_fixed) │ │ Header / Query / Body / Url │ ├─────────────────────────────────────────────────────────────┤ │ cURL Tab → inParams 字符串示例 │ ├─────────────────────────────────────────────────────────────┤ │ Response Tab → outParameterList │ └─────────────────────────────────────────────────────────────┘ ``` ### 2.2 改造后(方案 B) ```text ┌─────────────────────────────────────────────────────────────┐ │ 下游接口明细 (对齐 ApiTest) │ ├─────────────────────────────────────────────────────────────┤ │ Query Tab → queryFormParameterList │ │ FieldPosition = Query → 运行时 URL Query │ ├─────────────────────────────────────────────────────────────┤ │ Body Tab → bodyFormParameterList │ │ FieldPosition = Body → FormUrlEncoded 等 │ │ 值可为 $FromMappingBody / $TimeStamp / 留空 │ ├─────────────────────────────────────────────────────────────┤ │ Header Tab → headerFormParameterList │ │ FieldPosition = Header → 请求头 │ ├─────────────────────────────────────────────────────────────┤ │ Mapping Body Tab → mappingBodyParameterList │ │ ParaKind = MappingBody → 仅上游映射 + 序列化源 │ ├─────────────────────────────────────────────────────────────┤ │ cURL Tab → inParams(不变) │ ├─────────────────────────────────────────────────────────────┤ │ Response Tab → outParameterList(不变) │ └─────────────────────────────────────────────────────────────┘ (可选)服务商级 / Url 固定项 → 仍在 t_parameter_fixed,接口明细页「固定配置」可隐藏或仅高级入口 ``` ### 2.3 职责对照表 | 概念 | 改造前 | 改造后 | |------|--------|--------| | URL Query 键值 | 主要靠 `FixedFieldList`(Query)或没有 | **`bodyForm` 之外**:`queryFormParameterList` | | 请求头 | `FixedFieldList`(Header) | **`headerFormParameterList`** + 服务商级 Fixed | | Form 业务字段 | 全部 `formParameterList` → 进 Form Body | **`bodyFormParameterList`**,按 ContentType 组装 | | 上游映射目标(下游侧) | `inParameterList`(IsInPara) | **`mappingBodyParameterList`**(ParaKind=MappingBody) | | Json HTTP Body | `inParameterList` 序列化 | **`mappingBodyParameterList`** 序列化(Json 接口) | | 表单里塞整段 JSON | `$FromBody`(语义混) | **`$FromMappingBody`**(明确指向映射结果) | --- ## 3. 表结构前后对比 ### 3.1 `t_parameter`(参数树) #### 改造前 | 列 | 类型 | 说明 | |----|------|------| | Id | int PK | | | InterfaceId | int | | | PId | int | 父节点 | | Name / Alias / CnName | string | | | Type | int | ParameterType | | IsRequired | bool | | | **IsInPara** | bool | **true=入参,false=出参** | | Description / Sort | | | | 审计字段 | | | **语义**:`IsInPara=true` 的记录 = 入参树 = 映射目标 +(Json 时)HTTP Body 结构。 #### 改造后 | 列 | 类型 | 说明 | 变更 | |----|------|------|------| | (原有列) | | | 保留 | | **ParaKind** | int NOT NULL DEFAULT 0 | 见下表 | **新增** | **ParaKind 枚举(建议)** | 值 | 名称 | 说明 | |----|------|------| | 0 | Normal | 默认;**出参**仅 Normal;**上游接口入参**仍为 Normal + IsInPara | | 1 | MappingBody | **仅下游**:映射体树;`IsInPara` 仍为 true | | 2 | TransportBody | **可选**,若 Json 接口要把「HTTP Body 结构」与「映射体」拆成两棵树时使用;一期可不用 | **约束(逻辑)** ```text IF ParaKind = MappingBody → 必须 IsInPara = true,且仅用于下游接口 (IsUpStream=false) IF IsInPara = false → ParaKind 必须为 Normal(出参) ``` #### DDL 示例 ```sql ALTER TABLE t_parameter ADD ParaKind INT NOT NULL DEFAULT 0 COMMENT '0=Normal,1=MappingBody,2=TransportBody(预留)'; CREATE INDEX IX_t_parameter_InterfaceId_ParaKind ON t_parameter (InterfaceId, ParaKind, IsInPara) WHERE IsDeleted = 0; ``` --- ### 3.2 `t_parameter_form`(表单 KV) #### 改造前 | 列 | 说明 | |----|------| | Id, InterfaceId | | | Name, Value, Description | Value 为占位符或常量 | | 审计字段 | | | **无位置字段** | 全部视为 FormUrlEncoded 的 Body 字典 | #### 改造后 | 列 | 说明 | 变更 | |----|------|------| | (原有列) | | 保留 | | **FieldPosition** | int NOT NULL DEFAULT 2 | **新增**;对齐 `FieldPosition` 枚举 | **FieldPosition 使用范围(form 表)** | 值 | 枚举 | 用于 form 表 | |----|------|--------------| | 1 | Header | ✅ | | 2 | Body | ✅(历史数据默认值) | | 3 | Query | ✅ | | 4 | Url | ❌ 仍用 Fixed(路径替换) | | 5 | Nothing | ❌ | #### DDL 示例 ```sql ALTER TABLE t_parameter_form ADD FieldPosition INT NOT NULL DEFAULT 2 COMMENT '1=Header,2=Body,3=Query'; CREATE INDEX IX_t_parameter_form_InterfaceId_Position ON t_parameter_form (InterfaceId, FieldPosition) WHERE IsDeleted = 0; ``` --- ### 3.3 `t_parameter_fixed`(固定参数) #### 改造前 | 列 | 说明 | |----|------| | ServiceProviderId / InterfaceId | 二选一或兼有 | | FieldName, FieldValue | 可含 `$FromBody` 等 | | FieldPositions | 逗号分隔多位置:`Header,Body,Query,Url` | **接口明细页**通过独立 API `FixedParameter/MaintainParameter` 维护。 #### 改造后(策略) | 场景 | 改造后归属 | 说明 | |------|------------|------| | 接口级 Header / Query 固定 KV | **迁入 `t_parameter_form`** | 与 ApiTest 一致,随 `SaveInterface` 保存 | | 接口级 Body 固定路径绑定 | 可迁入 form(Body)或保留 Fixed(Body) | 若值为 `$FromMappingBody` 路径片段,优先 Fixed Body 逻辑保留 | | **服务商级** 通用 Header/签名 | **保留 `t_parameter_fixed`** | `ServiceProviderId` 有值 | | **Url** 路径 `{id}` 替换 | **保留 Fixed(Url)** | form 表不支持 Url | | 脚本运行时 `AddHeader` | **保留 Fixed 动态能力** | `ForwardCoreContext.AddHeader` | **一期可不删表、不删 API**,仅接口明细 UI 隐藏「固定配置」;数据迁移把 `InterfaceId IS NOT NULL` 且 `FieldPositions` 含 Header/Query 的行复制到 form 表。 --- ### 3.4 `t_interface` / 其它表 | 表 | 变更 | |----|------| | `t_interface` | 无结构变更(`ContentType`、`MappedParamType` 仍控制序列化) | | `t_interface_map` / `t_interface_map_detail` | 无结构变更;**JOIN 参数时增加 ParaKind 条件** | | `t_interface_return_config` | 无变更 | --- ### 3.5 ER 关系(改造后) ```mermaid erDiagram t_interface ||--o{ t_parameter : "出参 Normal" t_interface ||--o{ t_parameter : "MappingBody IsInPara" t_interface ||--o{ t_parameter_form : "Query Body Header" t_interface ||--o{ t_parameter_fixed : "服务商级 Url" t_service_provider ||--o{ t_parameter_fixed : "服务商级" t_parameter { int ParaKind "0 Normal 1 MappingBody" bool IsInPara } t_parameter_form { int FieldPosition "1 Header 2 Body 3 Query" } ``` --- ## 4. 枚举与常量前后对比 ### 4.1 新增 / 扩展 | 类型 | 改造前 | 改造后 | |------|--------|--------| | **ParameterPlaceholder** | FromAccount, FromBody, TimeStamp, DateTime, Url, Token | **+ FromMappingBody** → `"$FromMappingBody"` | | **GlobalConst** | `FromBody` 等 | **+ `FromMappingBody`** | | **ParaKind**(新枚举) | 无 | Normal=0, MappingBody=1, TransportBody=2(可选) | | **FormParameterItem** | id,name,value,description | **+ fieldPosition** | | **InterfaceFormFieldDto**(运行时) | Name, Value, Description | **+ FieldPosition** | ### 4.2 Dictionary API ```http GET api/Dictionary/GetList?type=ParameterPlaceholder ``` **改造后** 返回增加一项: ```json { "key": 7, "value": "$FromMappingBody" } ``` (具体 key 值以实现为准,前后端 `itfxEnums.ts` 同步。) ### 4.3 占位符语义(改造后定稿) | 占位符 | 作用域 | 替换时机 | 替换结果 | |--------|--------|----------|----------| | `$FromMappingBody` | Form(Query/Body/Header) | ReplacePlaceholder / SetContentType | `OriginalInterfaceMappedInput` 按 ContentType 序列化后的字符串(整段) | | `$FromBody` | Form / Fixed | **兼容保留** | Form:同现行(整段 TargetInterfaceInput 或映射体,与现网一致);Fixed:从 `OriginalInterfaceMappedInput` 按 FieldName 路径取值 | | `$FromAccount` | Form / Fixed | ReplacePlaceholder | 账户字段 | | `$TimeStamp` / `$DateTime` | Form / Fixed | ReplacePlaceholder | 时间 | | `$Url` | Form / Fixed | ReplacePlaceholder | 请求地址 | | `$token` | Fixed 为主 | ReplacePlaceholder | 授权 token | **推荐**:新配置 JSON 业务字段统一用 **`$FromMappingBody`**;`$FromBody` 仅兼容老数据。 --- ## 5. 接口契约前后对比 ### 5.1 `GET Interface/GetInterfaceById` #### 改造前响应(节选) ```json { "id": 1001, "name": "1688回调", "contentType": 1, "mappedParamType": 3, "inParameterList": [ { "id": 1, "pId": 0, "name": "order", "alias": "order", "type": 3 } ], "outParameterList": [ ], "formParameterList": [ { "id": 10, "name": "notifyBizEventDTO", "value": "$FromBody", "description": "..." } ] } ``` #### 改造后响应(完整约定) ```json { "id": 1001, "name": "1688回调", "contentType": 1, "mappedParamType": 3, "queryFormParameterList": [ { "id": 11, "name": "_aop_timestamp", "value": "$TimeStamp", "description": "ms", "fieldPosition": 3 } ], "bodyFormParameterList": [ { "id": 10, "name": "notifyBizEventDTO", "value": "$FromMappingBody", "description": "", "fieldPosition": 2 } ], "headerFormParameterList": [ { "id": 12, "name": "Content-Type", "value": "application/x-www-form-urlencoded", "fieldPosition": 1 } ], "mappingBodyParameterList": [ { "id": 1, "pId": 0, "name": "order", "alias": "order", "type": 3, "isRequired": true, "paraKind": 1 } ], "outParameterList": [ ], "formParameterList": [ ], "inParameterList": [ ] } ``` **兼容字段(只读聚合,便于老客户端)** | 字段 | 聚合规则 | |------|----------| | `formParameterList` | `query` + `body` + `header` 三个列表合并 | | `inParameterList` | 若 `mappingBodyParameterList` 非空 → 等于其副本;否则沿用 DB 中 `ParaKind=Normal & IsInPara`(迁移期) | --- ### 5.2 `POST Interface/SaveInterface` #### 改造前请求(节选) ```json { "optionType": 3, "id": 1001, "isUpStream": false, "serviceProviderId": 5, "name": "1688回调", "contentType": 1, "inParameterList": [ { "optionType": 3, "id": 1, "pId": 0, "name": "order", "alias": "order", "type": 3 } ], "outParameterList": [ ], "formParameterList": [ { "optionType": 0, "id": 10, "name": "notifyBizEventDTO", "value": "$FromBody" } ], "inParams": "curl ..." } ``` #### 改造后请求(标准) ```json { "optionType": 3, "id": 1001, "isUpStream": false, "serviceProviderId": 5, "name": "1688回调", "contentType": 1, "mappedParamType": 3, "queryFormParameterList": [ { "optionType": 0, "id": 11, "name": "_aop_timestamp", "value": "$TimeStamp", "description": "ms", "fieldPosition": 3 } ], "bodyFormParameterList": [ { "optionType": 0, "id": 10, "name": "notifyBizEventDTO", "value": "$FromMappingBody", "fieldPosition": 2 } ], "headerFormParameterList": [ { "optionType": 0, "id": 12, "name": "X-Custom", "value": "fixed", "fieldPosition": 1 } ], "mappingBodyParameterList": [ { "optionType": 3, "id": 1, "pId": 0, "name": "order", "alias": "order", "type": 3, "isRequired": true } ], "outParameterList": [ ], "inParams": "curl ..." } ``` **`FormParameterInput` 扩展** ```csharp public class FormParameterInput : FormParameterItem { public OptionType OptionType { get; set; } public FieldPosition FieldPosition { get; set; } // 新增;也可由所在列表推断 } ``` **`ParameterInput` 扩展** ```csharp // ParameterInput 已有字段基础上 public ParaKind ParaKind { get; set; } // 映射体列表传入 MappingBody;出参传 Normal ``` #### 保存时服务端合并规则(改造后) ```text 1. mappingBodyParameterList → 写入 t_parameter (IsInPara=true, ParaKind=MappingBody) 2. outParameterList → 写入 t_parameter (IsInPara=false, ParaKind=Normal) 3. query/body/header 三列表 → 合并写入 t_parameter_form(带 FieldPosition) 4. 若仍收到 formParameterList(旧前端)→ 全部 FieldPosition=Body,且不与 body 列表重复 id 5. 若仍收到 inParameterList(旧前端)→ 当作 mappingBodyParameterList(迁移期) 6. Json 接口且 mappingBody 为空但 in 有值 → 同 5,并打日志提示升级 ``` --- ### 5.3 `GET FormParameter/GetParameterList` #### 改造前 ```http GET FormParameter/GetParameterList?interfaceId=1001 → List // 无 position ``` #### 改造后(二选一) **选项 1(推荐)**:扩展原接口 ```http GET FormParameter/GetParameterList?interfaceId=1001&fieldPosition=3 → 按 position 过滤;不传则返回全部(带 fieldPosition 字段) ``` **选项 2**:废弃,统一走 `GetInterfaceById`。 --- ### 5.4 `POST FixedParameter/MaintainParameter` | 维度 | 改造前 | 改造后 | |------|--------|--------| | 接口明细页 | 主要维护入口 | **隐藏**;Header/Query 改走 SaveInterface | | 服务商管理 / 高级配置 | 使用 | **保留** | | 行为 | 写 `t_parameter_fixed` | 不变;转发时与 form **合并**(见 §6.3) | --- ### 5.5 映射相关接口 #### `GET InterfaceMap/EditMapDetails` / `GetMapParameterCascadedList` | 维度 | 改造前 | 改造后 | |------|--------|--------| | 下游入参映射目标 | `t_parameter` 且 `IsInPara=true`(全部入参) | **`ParaKind = MappingBody`**(无则回退全部 IsInPara,迁移期) | | 上游系统接口入参 | `IsInPara=true` | **不变**(上游无 Mapping Body 概念) | #### `POST InterfaceMap/SaveParameterMaps`(保存映射明细) - 映射明细仍引用 `t_parameter.Id`;Mapping Body 节点 id 为新 id 空间,**与旧 in 参 id 可能变化**,迁移需重导映射或跑 id 映射脚本。 --- ### 5.6 上游接口 `SaveInterface`(IsUpStream=true) | 字段 | 改造后 | |------|--------| | `mappingBodyParameterList` | **不使用**(或忽略) | | `inParameterList` | **仍为**上游业务入参树(Normal) | | `query/body/headerForm` | 可选;一般上游为空 | | UI | 上游 InterfaceDetail **不展示** Mapping Body Tab(或展示但禁用) | --- ### 5.7 TypeScript 类型(前端 `typings.d.ts` 目标) ```typescript type FormParameterItem = { id?: number; name?: string; value?: string; description?: string; fieldPosition?: 1 | 2 | 3; // Header Body Query }; type InterfaceParameterOutputValueObject = { // ...existing paraKind?: 0 | 1 | 2; }; type InterfaceOutput = { // ...existing queryFormParameterList?: FormParameterItem[]; bodyFormParameterList?: FormParameterItem[]; headerFormParameterList?: FormParameterItem[]; mappingBodyParameterList?: InterfaceParameterOutputValueObject[]; /** @deprecated 聚合字段,勿写入 Save */ formParameterList?: FormParameterItem[]; /** @deprecated 聚合字段 */ inParameterList?: InterfaceParameterOutputValueObject[]; }; type SaveInterfaceInput = { // ...existing queryFormParameterList?: FormParameterInput[]; bodyFormParameterList?: FormParameterInput[]; headerFormParameterList?: FormParameterInput[]; mappingBodyParameterList?: ParameterInput[]; }; ``` --- ## 6. 转发与映射逻辑前后对比 ### 6.1 总流程 #### 改造前 ```text OriginalInterfaceInput → InParamRestructure(InParamTreeList from 下游全部 IsInPara) → OriginalInterfaceMappedInput → ReplacePlaceholder(Fixed + Form) → SerializationBody (按 ContentType) → RequestAsync Fixed → Url / Query / Header Form → 仅 FormUrlEncoded 整包 Body ``` #### 改造后 ```text OriginalInterfaceInput → InParamRestructure(MappingBodyTreeList) ← 仅 ParaKind=MappingBody → OriginalInterfaceMappedInput → ReplacePlaceholder(Fixed + Form 分位置) → SerializationBody Json/Xml: 序列化 OriginalInterfaceMappedInput FormUrlEncoded: 见 6.2 → RequestAsync Fixed → Url(保留) Form(Query) → UriBuilder.Query Form(Header) → msg.Headers Form(Body) + Fixed(Body) → SetContentType ``` --- ### 6.2 `SetContentType`(FormUrlEncoded)前后对比 #### 改造前 ```csharp // 所有 FormFieldList 打成一个字典作为 Body if (context.FormFieldList.Count != 0) { var field = context.FormFieldList.FirstOrDefault(x => x.Value == GlobalConst.FromBody); if (field != null) field.Value = body; // body = TargetInterfaceInput nameValueCollection = context.FormFieldList.ToDictionary(x => x.Name, x => x.Value); } ``` #### 改造后 ```csharp var bodyForms = context.FormFieldList.Where(x => x.FieldPosition == FieldPosition.Body).ToList(); // 替换 $FromMappingBody / $FromBody foreach (var item in bodyForms) { if (item.Value == GlobalConst.FromMappingBody) item.Value = JsonConvert.SerializeObject(context.OriginalInterfaceMappedInput); // ... 其它占位符 } msg.Content = new FormUrlEncodedContent(bodyForms.ToDictionary(x => x.Name, x => x.Value)); // Query / Header 已在 RequestAsync 前文处理,不进入此字典 ``` --- ### 6.3 `RequestAsync` 前后对比 | 步骤 | 改造前 | 改造后 | |------|--------|--------| | Url 替换 | `FixedFieldList` Url | **不变** | | Query | `FixedFieldList` Query | **Form Query** + Fixed Query(合并,见下) | | Header | `FixedFieldList` Header | **Form Header** + Fixed Header | | Body | `SetContentType` | **仅 Form Body** + Fixed Body 注入映射路径 | **合并优先级(建议写死到代码与文档)** ```text 同一 FieldName + 同一位置: 接口级 Form > 接口级 Fixed > 服务商级 Fixed ``` --- ### 6.4 `ReplacePlaceholder` 前后对比 | 处理对象 | 改造前 | 改造后 | |----------|--------|--------| | `FixedFieldList` | `$FromBody` 路径取值 | **保留**;可增加 `$FromMappingBody` 整段序列化 | | `FormFieldList` | 仅 TimeStamp/DateTime/Url/FromAccount | **+ `$FromMappingBody`**;Query/Header 同样支持 TimeStamp 等 | | Form 中 `$FromBody` | 在 SetContentType 用 TargetInterfaceInput 替换 | **建议标记过时**,逻辑保留 | --- ### 6.5 `InterfaceForwardQuery` 构建上下文 #### 改造前 ```csharp FormFieldList = await GetFormFieldListAsync(interfaceId); // 全部 form,无 position InParamTreeList = JsonHelper.BuildTreeList(items); // 全部 IsInPara 映射项 ``` #### 改造后 ```csharp FormFieldList = await GetFormFieldListAsync(interfaceId); // 含 FieldPosition InParamTreeList = JsonHelper.BuildTreeList( items.Where(x => x.ParaKind == ParaKind.MappingBody)); // 映射配置来自 MappingBody ``` **授权接口** `GetFormFieldListAsync` / `InParamTreeList`:授权若依赖表单,按授权接口自身配置;`InParamTreeList = null` 保持不变。 --- ### 6.6 `InParamRestructure` 数据源 | 维度 | 改造前 | 改造后 | |------|--------|--------| | 树来源 | 下游映射 SQL 拉取的 **全部 IsInPara** | **仅 MappingBody** 节点 | | Json 接口 HTTP Body | 同一棵树序列化 | 同一棵 **MappingBody** 树序列化 | | 出参映射 | OutParamTreeList | **不变** | --- ### 6.7 ContentType 分支(产品规则) | ContentType | Query Tab | Body Tab | Mapping Body | HTTP Body 来源 | |-------------|-----------|----------|--------------|----------------| | **Json (3)** | 可选 | 通常空 | **必填**(映射+序列化) | `Serialize(OriginalInterfaceMappedInput)` | | **FormUrlEncoded (1)** | 常用 | 常用(含 `$FromMappingBody`) | **必填**(映射) | Form Body 字典 | | **Xml (4)** | 可选 | 可选 | 必填 | Xml 序列化映射体 | | **FormData (2)** | 二期 | 二期 | 必填 | 暂可抛不支持 | --- ## 7. 前端页面与数据流前后对比 ### 7.1 InterfaceDetail Request 区 Tab | Tab | 改造前 | 改造后 | |-----|--------|--------| | Query | form 表(误标) | KV 表 → `queryFormParameterList` | | Body | **参数树** inParameterList | **KV 表** → `bodyFormParameterList` | | Header | 无(在固定配置) | **KV 表** → `headerFormParameterList` | | Mapping Body | 无 | **参数树** → `mappingBodyParameterList` | | 固定配置 | FixedParameter API | **默认隐藏** / 高级入口 | | cURL | 有 | 有;智能填充拆到 Q/H/B/Mapping | ### 7.2 `ServiceProviderInterfaceModal` 加载/保存 #### 改造前 ```typescript queryParams.value = interfaceData.formParameterList || []; requestParams.value = interfaceData.inParameterList || []; // save: formParameterList from query table only // save: inParameterList from request tree ``` #### 改造后 ```typescript queryParams.value = interfaceData.queryFormParameterList || []; bodyFormParams.value = interfaceData.bodyFormParameterList || []; headerParams.value = interfaceData.headerFormParameterList || []; mappingBodyParams.value = interfaceData.mappingBodyParameterList || []; // save: 四个列表分别组装 optionType // 不再把 query 写入 inParameterList ``` ### 7.3 `curlToInterfaceFill` 智能填充 | cURL 部分 | 改造前 | 改造后 | |-----------|--------|--------| | `?a=1&b=2` | 进 queryParams(实为 form) | **queryFormParameterList** | | `-H` | 部分进 Fixed / 忽略 | **headerFormParameterList** | | `--data-raw` JSON | 解析进 **inParameterList** 树 | 结构进 **mappingBodyParameterList**;若 FormUrlEncoded 单字段则 **body** 表一项 `$FromMappingBody` | | JSON 形态 query 值 | `$FromBody` | **`$FromMappingBody`** | ### 7.4 与 ApiTest 组件复用(建议) ```text components/ RequestTabs/ QueryKeyValueTable.vue ← 从 ApiTest 抽 HeaderKeyValueTable.vue BodyKeyValueTable.vue MappingBodyTreeTable.vue ← 从 InterfaceDetail 原 Body 树抽 ``` --- ## 8. 关联能力改动清单 | 模块 | 文件/服务 | 改造要点 | |------|-----------|----------| | 接口保存/读取 | `InterfaceService` | 拆分列表、兼容聚合 | | 表单参数 | `FormParameterService` | FieldPosition CRUD、按位置查询 | | 树形参数 | `ParameterService` | ParaKind、MappingBody 维护 | | 转发 | `DefaultForwardFlow` | RequestAsync、SetContentType、ReplacePlaceholder | | 上下文加载 | `InterfaceForwardQuery` | Form 带 position;InParamTreeList 过滤 MappingBody | | 映射 SQL | `InterfaceMapRepository` | JOIN 参数加 ParaKind | | 映射编辑 API | `InterfaceMapService.Details` | 级联列表过滤 | | 导入导出 | `ServiceProviderImportExportService` | form position + paraKind | | 授权 | `ServiceProviderAuthService` | 确认入参列表来源(MappingBody 或仍为原 in) | | 发布/缓存 | `ForwardCoreContextCache` | DTO 增加 FieldPosition、ParaKind | | 字典 | `ParameterPlaceholder` 枚举 | +FromMappingBody | | 前端枚举 | `itfxEnums.ts` | 同步 | | 文档 | `itfx枚举说明.md` | 更新 | | 上游接口页 | `UpstreamInterfaceModal` | 不展示 Mapping Body | | 映射配置页 | `upstreamInterfaceConfig*` | 下游侧展示 MappingBody 别名 | | 历史页 | `newPort.vue` | 同步或废弃 | --- ## 9. 数据迁移方案 ### 9.1 迁移步骤(建议顺序) ```text Step 1 DDL:t_parameter.ParaKind、t_parameter_form.FieldPosition Step 2 备份相关表 Step 3 form:历史行 FieldPosition = 2 (Body) Step 4 parameter:下游接口 IsInPara=true 且 ContentType=FormUrlEncoded → ParaKind = MappingBody(原 in 树整体迁移) Step 5 parameter:下游接口 ContentType=Json → 原 in 树 ParaKind = MappingBody(同时作为 HTTP body 定义) Step 6 fixed:InterfaceId NOT NULL 且 FieldPositions 含 Header/Query → INSERT INTO t_parameter_form(去重策略按 FieldName+Position) Step 7 验证映射明细 MappedParaId 仍指向有效 parameter.Id Step 8 发布缓存刷新 / 全量发布后回归 ``` ### 9.2 迁移 SQL 示意 ```sql -- form 默认 Body UPDATE t_parameter_form SET FieldPosition = 2 WHERE FieldPosition IS NULL OR FieldPosition = 0; -- 下游入参 → MappingBody(需按接口表 IsUpStream=0 关联,示意) UPDATE p SET p.ParaKind = 1 FROM t_parameter p INNER JOIN t_interface i ON p.InterfaceId = i.Id WHERE p.IsInPara = 1 AND p.IsDeleted = 0 AND i.IsUpStream = 0; -- Fixed → Form(Header=1, Query=3)需脚本逐行解析 FieldPositions,不建议单条 SQL 粗暴合并 ``` ### 9.3 回滚 - 保留 `ParaKind`、`FieldPosition` 列,回滚代码读旧逻辑:忽略新列,按 `IsInPara` + 全量 form 聚合。 - 迁移前务必备份 `t_parameter`、`t_parameter_form`、`t_interface_map_detail`。 --- ## 10. 兼容策略与版本节奏 ### 10.1 API 版本 | 阶段 | 后端 | 前端 | |------|------|------| | **V1 并行** | Save 接受新旧字段;Get 返回新旧字段 | 新页面只发新字段 | | **V2** | Save 对新字段必填(下游);旧字段 deprecated 日志 | 移除旧 Tab | | **V3** | 可选移除 `formParameterList` 聚合 | — | ### 10.2 行为兼容矩阵 | 场景 | 未迁移数据 | 已迁移数据 | |------|------------|------------| | GetById | `inParameterList` 有值,mapping 空 → 前端用 in 填 Mapping Tab | 四列表 + mapping 正常 | | Save 旧前端 | 仅 form + in → 服务端按 §5.2 规则 4、5 拆分 | — | | 转发 | ParaKind 全 0 → 等同改造前 | 新逻辑 | --- ## 11. 实施阶段建议 | 阶段 | 内容 | 产出 | |------|------|------| | **P0** | DDL + 枚举 + Get/Save 契约 + 单元测试 | 可存可取 | | **P1** | DefaultForwardFlow + InterfaceForwardQuery + 占位符 | 可转发 | | **P2** | 映射 SQL + 发布缓存 + 导入导出 | 可发布 | | **P3** | 前端 InterfaceDetail 重写 + Modal 保存 + curl 填充 | 可配置 | | **P4** | 数据迁移 + 选生产接口回归 | 上线 | | **P5** | 隐藏固定配置、文档、培训 | 收尾 | --- ## 附录 A:改造前后单接口完整示例(1688 风格 FormUrlEncoded) ### 改造前 **库内** - `t_parameter`:order.{field} 树,`IsInPara=1` - `t_parameter_form`:`notifyBizEventDTO=$FromBody`,无 position **运行时** - 映射到 order 树 → 序列化 → 填入 notifyBizEventDTO → 与其它 form 字段一起 POST ### 改造后 **库内** - `t_parameter`:order 树,`ParaKind=1`(MappingBody) - `t_parameter_form`:`(notifyBizEventDTO, $FromMappingBody, Body=2)`,`(timestamp, $TimeStamp, Query=3)` **运行时** - 映射到 MappingBody 树 → `OriginalInterfaceMappedInput` → `$FromMappingBody` 填入 Body 表单字段 → Query 拼 URL → POST --- ## 附录 B:后端类改动速查 | 类 | 变更 | |----|------| | `FormParameterEntity` | +FieldPosition | | `ParameterEntity` | +ParaKind | | `FormParameterItem/Input` | +FieldPosition | | `ParameterInput` / `InterfaceParameterOutputValueObject` | +ParaKind | | `InterfaceOutput` | +3 form 列表 + mappingBodyParameterList | | `SaveInterfaceInput` | 同上 | | `InterfaceFormFieldDto` | +FieldPosition | | `GlobalConst` | +FromMappingBody | | `ParameterPlaceholder` | +FromMappingBody | --- *文档版本:2026-06-02 · 方案 B 定稿详细设计*