868 lines
32 KiB
Markdown
868 lines
32 KiB
Markdown
# 方案 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 定稿详细设计*
|