# 下游接口明细改造方案:小改 vs 大改 > 场景:下游接口页(`/api/downstreamInterface`)保存接口明细时,需要更好支持 **FormUrlEncoded / 表单类接口** 中「某个字段的值是一整段 JSON」的情况。 > 目标:为上游映射提供独立的 **Mapping Body(映射体)** 结构树,表单字段用 **`$FromMappingBody`** 引用映射结果。 > 参考 UI:`/api/apiDebugger`(ApiTest:Query / Request(Body) / Header)。 --- ## 1. 现状速览(为何需要改) | 层级 | 现状 | 问题 | |------|------|------| | **表** | `t_parameter`(`IsInPara`)= 入参树;`t_parameter_form`(无位置)= 表单 KV;`t_parameter_fixed`(`FieldPositions`)= 固定 Header/Query/Body | 前端「Query」Tab 实际写的是 `formParameterList`,后端却只把 Form 当 **整段 x-www-form-urlencoded Body**,不是 URL Query | | **页面** | InterfaceDetail:Query(表单) + Body(参数树) + 固定配置 + cURL | 与 ApiTest 的 Query/Body/Header 不一致;**Body 树**同时承担「HTTP JSON 结构」和「上游映射主体」,表单里 JSON 字段只能塞 `$FromBody`,语义混 | | **转发** | `InParamRestructure` ← 下游 `IsInPara` 树;`FormFieldList` ← 全部 form;Query/Header ← `FixedFieldList` | 映射树与传输层表单未分离 | ```text 上游 JSON ──► InParamRestructure(下游 IsInPara 树) ──► OriginalInterfaceMappedInput │ ▼ ReplacePlaceholder / 序列化 │ ▼ FormUrlEncoded(form 全进 Body)+ Fixed(Query/Header) ``` --- ## 2. 方案总览 | 维度 | **方案 A:小改(推荐优先)** | **方案 B:大改(完全对齐 ApiTest)** | |------|---------------------------|-------------------------------------| | **核心思路** | 只新增 **Mapping Body** 参数树 + **`$FromMappingBody`**,其余结构基本不动 | Query/Body/Header 表单分表存储 + 转发按位置组装;Mapping Body 独立;逐步弱化「固定配置」与旧 form 语义 | | **表变更** | 1 张表加 1 个字段(或 1 个枚举列) | 至少 2 张表扩展 + 数据迁移 + 行为重构 | | **API 变更** | `SaveInterface` / `GetInterfaceById` 增字段;占位符枚举增 1 项 | 契约重组(多列表/统一带 position);Fixed 与 Form 职责重划 | | **转发逻辑** | 映射树数据源切换 + 占位符分支(约数十行级) | `RequestAsync` / `SetContentType` / 缓存 / 映射 SQL 全链路调整 | | **前端** | InterfaceDetail 增 1 个 Tab,保存多传一个列表 | 整页 Request 区重写,对齐 ApiTest;curl 填充、映射页联动 | | **风险** | 低,可灰度、可回滚 | 中高,影响所有下游接口与发布数据 | | **工期(粗估)** | 后端 2~4 人日 + 前端 3~5 人日 | 后端 8~15 人日 + 前端 10~18 人日 + 联调/迁移 | --- ## 3. 方案 A:小改(Mapping Body 增量) ### 3.1 要解决什么 **只解决**:Query/Body **表单**里某字段值为 JSON(或应填 `$FromBody`)时,上游映射应对的是 **JSON 内部结构**,而不是把整段 form 或错误树当映射目标。 **不解决**(保持现状): - Form 仍整体作为 `FormUrlEncoded` Body(不拆真正的 URL Query 表单 Tab 行为) - Header 仍主要走 **固定配置**(`FixedParameter`) - `ContentType=Json` 的下游接口仍用现有 `inParameterList` 作 Body + 映射(可不显示 Mapping Body Tab) ### 3.2 表结构 | 表 | 变更 | 说明 | |----|------|------| | `t_parameter` | 新增 `ParaKind`(int)或 `IsMappingBody`(bit) | `0=Normal`(现有入参/出参逻辑),`1=MappingBody`(仅映射,不参与 Json 序列化 Body 时可配置策略) | | `t_parameter_form` | **不改** | 仍 flat KV;JSON 字段 value 改为 `$FromMappingBody` | | `t_parameter_fixed` | **不改** | Header/Query 固定值照旧 | **迁移**:历史数据 `ParaKind=0`;原 `inParameterList` 若实为映射树,可脚本把「仅用于 FormUrlEncoded 映射」的接口标为 MappingBody,或 **读时兼容**(见下)。 ### 3.3 接口契约 #### `GET Interface/GetInterfaceById` ```json { "inParameterList": [], "mappingBodyParameterList": [], "formParameterList": [], "outParameterList": [] } ``` **兼容规则**: - 若 `mappingBodyParameterList` 为空 → 用 `inParameterList` 填充(老数据零感知) - 新保存的 FormUrlEncoded 接口:映射树写 `mappingBodyParameterList`,`inParameterList` 可留空或仅存「真实 HTTP JSON Body 结构」(Json 类型仍用 in) #### `POST Interface/SaveInterface` ```json { "mappingBodyParameterList": [{ "optionType": 1, "id": 1, "pId": 0, "name": "order", "alias": "order", "type": 3 }], "inParameterList": [], "formParameterList": [ { "name": "notifyBizEventDTO", "value": "$FromMappingBody", "optionType": 0 } ] } ``` `inParameterList` / `outParameterList` / `formParameterList` 字段 **保留**,老前端、导入导出可继续用。 #### 其它接口 | 接口 | 变更 | |------|------| | `Dictionary/GetList?type=ParameterPlaceholder` | 后端枚举加 `FromMappingBody` → 展示 `$FromMappingBody` | | `InterfaceMap/*`(映射编辑) | 下游入参映射 SQL/列表 **优先** `ParaKind=MappingBody`,无则回退 `IsInPara` | | `FixedParameter/*` | 无 | | `FormParameter/GetParameterList` | 无 | ### 3.4 后端逻辑改动点 | 模块 | 改动量 | 内容 | |------|--------|------| | `ParameterService` / `InterfaceService` | 小 | 保存/读取时按 `ParaKind` 过滤;维护 MappingBody 列表 | | `InterfaceForwardQuery.GetParameterMapListAsync` | 小 | JOIN `t_parameter` 增加 `ParaKind=MappingBody` 条件(入参映射) | | `InterfaceForwardQuery` 构建 `InParamTreeList` | 小 | 有 MappingBody 用其建树,否则用原 `IsInPara` | | `DefaultForwardFlow.ReplacePlaceholder` | 小 | `FixedFieldList` / `FormFieldList` 增加 `$FromMappingBody` → `Serialize(OriginalInterfaceMappedInput)` | | `DefaultForwardFlow.SetContentType` | 小 | `FormUrlEncoded` 里对 `$FromMappingBody` 与 `$FromBody` 同样替换为序列化结果(可共用实现) | | `DefaultForwardFlow.RequestAsync` | **无或极小** | 不拆 Query/Header 表单 | | `ServiceProviderImportExportService` | 小 | 导出/导入带上 `ParaKind` | | 发布缓存 `ForwardCoreContextCache` | 小 | 缓存参数列表带 `ParaKind` | **`$FromBody` vs `$FromMappingBody`(小改语义建议)** | 占位符 | 小改建议语义 | |--------|----------------| | `$FromBody` | **保持现有行为**(兼容老配置) | | `$FromMappingBody` | **明确**指向 `OriginalInterfaceMappedInput` 的序列化(映射完成后、写进 form 字段) | ### 3.5 前端改动点 | 文件/模块 | 改动量 | |-----------|--------| | `InterfaceDetail` | 中:Request 区增加 **Mapping Body** Tab(复用现有 Body 树表格);原 **Body** Tab 可改名为「传输 Body」或保留树仅给 Json 类型显示 | | `ServiceProviderInterfaceModal.saveInterface` | 小:多组装 `mappingBodyParameterList`;`formParameterList` 仍只来自 Query 表 | | `curlToInterfaceFill` | 小:JSON 类 form 字段推断 `$FromMappingBody` | | `upstreamInterfaceConfig` 映射页 | 小:下游参数源改读 mapping 列表(后端改即可) | | `itfxEnums` / 文档 | 小 | | `ApiTest` | 无 | ### 3.6 方案 A 优点 / 缺点 **优点** - 改动面可控,**只增加概念**,不推翻 Form/Fixed 分工 - 老接口、老映射、老占位符可继续跑 - 符合「多的映射 body 只解决表单里还有 json」的诉求 **缺点** - UI 仍与 ApiTest **不完全一致**(Query 名不副实、Header 仍在固定配置) - 技术债仍在:`formParameterList` ≠ URL Query --- ## 4. 方案 B:大改(完全对齐 API 调试) ### 4.1 目标结构(与 ApiTest 一致) ```text ┌─ 基本信息(地址、Method、ContentType…)────────────────┐ ├─ Query KV 表 → 真实 URL Query │ ├─ Body KV 表 → Form Body / 或 Raw(按 ContentType) │ ├─ Header KV 表 → 请求头 │ ├─ Mapping Body 参数树 → 仅上游映射主体($FromMappingBody) │ ├─ Response 参数树 + 返回配置(保持) │ └─ cURL 示例(可选) ┘ ``` 传输层(Query/Body/Header)与映射层(Mapping Body)**彻底分离**。 ### 4.2 表结构 | 表 | 变更 | 说明 | |----|------|------| | `t_parameter_form` | 新增 **`FieldPosition`**(int,enum:Header=1, Body=2, Query=3) | 一条 form 记录明确归属;历史数据默认 `Body=2` | | `t_parameter` | 新增 **`ParaKind`**(Normal / MappingBody / 可选 TransportBody) | MappingBody 只用于映射;Json 接口的 HTTP Body 树可用 Normal 或单独 Kind | | `t_parameter_fixed` | **可选废弃或降级** | Header/Query 迁入 form 后,Fixed 仅保留服务商级 / Url 路径 / 脚本写入项;需迁移工具 | | `t_interface` | 一般不改 | — | **数据迁移** - 旧 `formParameterList` → `FieldPosition=Body` - 旧 `inParameterList`(Form 场景)→ 拆到 `MappingBody` + 清空 in 或保留 Json 专用 - 旧 `FixedParameter`(Header/Query)→ 迁入 `t_parameter_form` 或双写过渡期 ### 4.3 接口契约(重组) #### `GetInterfaceById`(示例) ```json { "queryFormParameterList": [], "bodyFormParameterList": [], "headerFormParameterList": [], "mappingBodyParameterList": [], "inParameterList": [], "outParameterList": [], "formParameterList": [] } ``` `formParameterList` 兼容期 = 三表合并;**新前端只认分列表**。 #### `SaveInterface` - 必传:`queryFormParameterList` + `bodyFormParameterList` + `headerFormParameterList` + `mappingBodyParameterList` + `outParameterList` - `inParameterList`:仅 **ContentType=Json** 时作为 HTTP JSON Schema(或合并进 mapping,产品需二选一) #### 其它 | 能力 | 大改影响 | |------|----------| | `FixedParameter/MaintainParameter` | 接口明细页可能 **移除** 或仅服务商维度保留 | | 授权 `ServiceProviderAuth` | 入参列表来源可能改为 MappingBody | | 接口映射发布/对比 | 参数树 diff 逻辑调整 | | 导入导出 | 全量适配 position + ParaKind | | 前置脚本 Skill | `FormFieldList` 分 position;文档重写 | ### 4.4 后端逻辑改动点 | 模块 | 改动量 | 内容 | |------|--------|------| | `FormParameterService` | **大** | CRUD 带 `FieldPosition`;按 position 查询 | | `InterfaceService` Save/Get | **大** | 多列表合并/拆分 | | `DefaultForwardFlow.RequestAsync` | **大** | `FormFieldList` 按 Query → `UriBuilder.Query`;Header → `msg.Headers`;Body → `SetContentType` | | `DefaultForwardFlow` 与 Fixed 合并 | **大** | 同名冲突策略、服务商级覆盖规则 | | `InterfaceForwardQuery` | **大** | 映射、授权、缓存多处 | | `InterfaceMapRepository` SQL | **中** | 下游参数过滤 MappingBody | | `ServiceProviderImportExportService` | **大** | | | 单元/集成测试 | **大** | 需覆盖 Json / FormUrlEncoded / 混合 Header | ### 4.5 前端改动点 | 模块 | 改动量 | |------|--------| | `InterfaceDetail` | **很大**:Request 区 Tabs 与 ApiTest 同构;删除或隐藏「固定配置」;三份 KV 表 + Mapping Body 树 | | `ServiceProviderInterfaceModal` | **大**:保存/加载拆 4 类参数 | | `ApiTest` | **中**:可抽公共组件 `RequestTabs` 复用 | | `curlToInterfaceFill` | **大**:-H→header,?query→query,--data→body form 或 mapping | | `downstreamInterface/index` | **中** | | `upstreamInterface` | **小~中**:上游是否展示 Mapping Body | | `newPort.vue` 等历史页 | **中**:废弃或同步 | ### 4.6 方案 B 优点 / 缺点 **优点** - 产品、开发、运维认知与 **API 调试** 一致,培训成本低 - Query/Header 行为正确,不再名不副实 - 映射与传输分层清晰,后续扩展 FormData、Raw Body 更自然 **缺点** - **改动面大**:表 + 转发 + 映射 + 导入导出 + 多页面 - 必须做 **数据迁移与发布回归**,线上 Form 接口需逐个验证 - 与「只为表单 JSON 加一个映射体」相比,**投入不成比例**(若 80% 接口是 Json Body) --- ## 5. 改动量对照(文件级清单) ### 5.1 后端 `itfx-master`(约) | 路径/模块 | 方案 A | 方案 B | |-----------|--------|--------| | `ParameterEntity` + 迁移 SQL | ● | ●● | | `FormParameterEntity` + 迁移 | — | ●● | | `SaveInterfaceInput` / `InterfaceOutput` | ● | ●● | | `InterfaceService` | ● | ●● | | `ParameterService` | ● | ●● | | `FormParameterService` | — | ●● | | `DefaultForwardFlow` | ● | ●●● | | `InterfaceForwardQuery` | ● | ●● | | `InterfaceMapRepository` / `InterfaceMapService` | ● | ●● | | `ServiceProviderImportExportService` | ● | ●● | | `GlobalConst` / `ParameterPlaceholder` | ● | ● | | `ServiceProviderAuthService` | ○ | ● | | 测试项目 | ● | ●●● | 图例:— 无 ○ 可选 ● 必改 ●● 重度 ●●● 重构 ### 5.2 前端 `oms0414`(约) | 路径/模块 | 方案 A | 方案 B | |-----------|--------|--------| | `InterfaceDetail/index.vue` | ●● | ●●● | | `ServiceProviderInterfaceModal.vue` | ● | ●● | | `curlToInterfaceFill.ts` | ● | ●● | | `constants/itfxEnums.ts` + `docs/itfx枚举说明.md` | ● | ●● | | `api/InterfaceServices/typings.d.ts` | ● | ●● | | 上游映射配置页 | ● | ●● | | `ApiTest/index.vue`(抽组件) | — | ●● | | `downstreamInterface/newPort.vue` | ○ | ●● | --- ## 6. 行为对比(典型:1688 / FormUrlEncoded) | 步骤 | 现状 | 方案 A | 方案 B | |------|------|--------|--------| | 定义 JSON 订单结构 | 写在 Body 参数树 | 写在 **Mapping Body** 树 | 同 A | | 表单字段 `notifyBizEventDTO` | `$FromBody` | `$FromMappingBody` | `$FromMappingBody`(在 **Body 表单** Tab) | | URL `?a=1` | Fixed Query 或没有 | 仍 Fixed / 没有 | **Query 表单** Tab | | Header `sign` | 固定配置 + 脚本 | 同左 | **Header 表单** Tab | | 上游映射配置 | 对下游 in 树 alias | 对 **MappingBody** alias | 同 A | --- ## 7. 选型建议 ```text 表单 JSON 映射问题 │ ┌───────────────┴───────────────┐ │ │ 仅 FormUrlEncoded 等少数接口 希望全站 UI/行为 需要拆映射体 与 ApiTest 完全一致 │ │ ▼ ▼ 【方案 A 小改】 【方案 B 大改】 优先落地、可迭代 单独立项、版本窗口 ``` | 若你的情况是… | 建议 | |--------------|------| | 主要痛点是「form 里 JSON 字段 + 上游映射对不上」 | **方案 A** | | 同时要强改 Query/Header 配置方式、去掉固定配置双轨 | **方案 B**(或 A 上线后再做 B 的第二阶段) | | 资源紧、要先上线 | **A**;B 里「form 分 position」可单独立项 | **推荐路径**:**先做方案 A** → 验证 `$FromMappingBody` + 映射发布 → 若仍纠结 Query/Header 体验,再开 **方案 B 第二阶段**(只迁 `t_parameter_form.FieldPosition` + `RequestAsync`,Mapping Body 不再动)。 --- ## 8. 方案 A 实施 checklist(可直接拆任务) - [ ] DB:`t_parameter.ParaKind` + 默认值 - [ ] 实体/DTO/枚举:`ParameterPlaceholder.FromMappingBody` - [ ] `GetInterfaceById` / `SaveInterface` 增加 `mappingBodyParameterList` + 兼容 - [ ] `InParamTreeList` / 映射 SQL 使用 MappingBody - [ ] `DefaultForwardFlow` 占位符 - [ ] 导入导出 - [ ] 前端 Mapping Body Tab + 保存字段 - [ ] curl 智能填充推断 `$FromMappingBody` - [ ] 文档 `itfx枚举说明.md` 更新 - [ ] 选 1~2 个 FormUrlEncoded 生产接口回归 + 发布 --- ## 9. 方案 B 实施 checklist(摘要) - [ ] 产品定稿:Json 接口 in 树 vs MappingBody 分工;Fixed 是否保留 - [ ] DB 迁移脚本 + 回滚脚本 - [ ] Form 分 position 全链路 - [ ] `RequestAsync` 三分支组装 - [ ] 接口契约 v2(可 `/Interface/SaveInterfaceV2` 并行一期) - [ ] 前端 InterfaceDetail 重写 + 组件抽取 - [ ] 映射/授权/导出/发布全量回归 - [ ] 灰度:按服务商或接口标签切换 --- ## 10. 附录:与 API 调试(ApiTest)结构对齐度 | 能力 | ApiTest | 现状 | 方案 A | 方案 B | |------|---------|------|--------|--------| | Query KV | ✅ | ⚠️ 名是 Query 实为 form | ⚠️ | ✅ | | Body KV / Raw | ✅ | ⚠️ 树在 Body | ⚠️ form 仍在 Query 表 | ✅ | | Header KV | ✅ | ⚠️ 在固定配置 | ⚠️ | ✅ | | 映射 JSON 结构 | — | ⚠️ 混在 in 树 | ✅ Mapping Body | ✅ | | `$FromMappingBody` | — | ❌ | ✅ | ✅ | --- *文档版本:2026-06-02 · 基于 `itfx-master` + `oms0414` 下游接口/ApiTest 代码梳理*