front/docs/方案B-下游接口对齐ApiTest-详细设计.md
2026-06-04 19:15:28 +08:00

868 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 方案 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<FormParameterItem> // 无 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 定稿详细设计*