案例背景
我们的团队维护一个面向 B 端的订单与履约微服务,2019 年由 3 个外包同学起步,到 2026 年已经迭代了 7 年、积累 8.1 万行 Go 代码,分布在 6 个子服务(order、fulfill、inventory、settle、notify、gateway)中。团队从最早的"先跑通再说"到后来的"需求永远排不完",文档这件事一直被推后——代码里几乎没有注释,也没有任何 Swagger / OpenAPI 定义,只有少量写在 Confluence 上、最近一次更新停在 2023 年 Q2 的接口片段。
事情的引爆点是 2026 年 8 月中旬的一次接入事故:外部合作方按 Confluence 上的旧文档接 order.v2.create 接口,结果字段 shipAddress 在 v2.3 已经被拆成 province + city + district + detail,对方写死的取值逻辑直接拿到空字符串,导致一批订单创建后无法履约,回滚 + 人工补偿花了团队整整两天。事后复盘,所有人都同意:必须把 API 文档补齐,而且要能跟代码同步。
但补文档的工时评估让人绝望:
- 接口数:order 服务 21 个、fulfill 14 个、inventory 7 个、settle 5 个,合计 47 个对外接口
- 平均参数量:每个接口请求参数 8-15 个、响应字段 12-30 个,估算 120+ 个参数说明
- 示例:合作方对接最看重示例,至少需要 14 个核心接口的完整请求 / 响应 JSON 样例
- 人工预估:每个接口(读代码、定参数、写示例、复核)按 1.5 小时算,47 个接口 = 70+ 人时 ≈ 2 周 / 1 人,且容易因为读者疲劳漏掉边界字段
业务侧不可能给 2 周窗口。最终我们决定用 Claude Code + GitHub MCP + 自定义代码分析 MCP 走自动化路线,实际完成时间是 4 小时,文档输出后由 2 个同学各花了 1 小时做人工审核修正,当天就合并进 main 分支并发布到内部文档站。本文复盘整个流程。
工具组合与分工
| 工具 | 分工 | 为什么这样分工 |
|---|---|---|
| Claude Code | 主推理引擎 | 长上下文(200K)能吞下整个子服务的 router + handler + model;Go 类型推断比通用人更准;输出 Markdown 可直接成型 |
| GitHub MCP | 拉代码、提 PR、跑 CI | 不在本地拉仓库,直接让 Agent 通过 MCP 读远程仓库文件,避免污染本地;每生成一批接口文档就提 PR 触发 CI(lint + markdownlint) |
| 自定义代码分析 MCP | 结构化提取接口元数据 | Claude 直接读 Go 源码做正则匹配会漏掉反射路由、中间件包了一层等场景;我们写了个 MCP 专门做 AST 级提取,输出标准化 JSON(method、path、handler 包路径、入参结构体名) |
| 本地 markdownlint + 自写校验脚本 | 文档质量门禁 | 校验每个接口段是否包含必备小节(描述、方法、路径、请求参数表、响应字段表、示例、错误码),缺一即 CI 失败 |
关键原则:MCP 出"骨架数据",Claude 出"语义解释",人只做"语义复核"。让 AI 做它擅长的(模式识别、批量格式化),让人做 AI 做不稳的(判断字段语义、补充业务背景、修边界场景)。
实施步骤
整个流程分 5 步,前 4 步纯 AI 执行,最后 1 步人工兜底。下面每一步都给出可复制的 Prompt 模板和操作说明。
第 0 步:代码扫描和接口清单提取(30 分钟)
这一步的目标只有一个:先把 47 个接口的"骨架清单"打出来,包括 HTTP 方法、URL 路径、handler 函数所在的文件:行号、入参结构体名、出参结构体名。这一步不能让 Claude 直接读源码自由发挥——它会漏,尤其是用了 gin.RouterGroup 嵌套、group.Use(middleware) 包了一层鉴权的情况。
我们写了一个自定义 MCP(基于 Go 的 go/parser + ast 包),它的工作流程是:
- 扫描所有
*.go文件,找到gin.Engine/gin.RouterGroup的GET/POST/PUT/DELETE/PATCH调用 - 通过 AST 解析出每个调用注册的路径和 handler 函数引用
- 顺着 handler 函数的参数类型,找到
*gin.Context之后的入参结构体(通常是BindJSON/ShouldBind的目标) - 对每个结构体递归展开字段,标注每个字段的
jsontag、validatetag、Go 类型 - 输出一个标准化 JSON:
{
"service": "order",
"routes": [
{
"method": "POST",
"path": "/api/v2/order/create",
"handler": "github.com/xxx/order/internal/handler.OrderHandler.Create",
"file": "internal/handler/order_handler.go:128",
"middleware": ["jwt.auth", "ratelimit.global", "trace.inject"],
"request_struct": "internal/model.OrderCreateReq",
"response_struct": "internal/model.OrderCreateResp"
}
]
}
为什么必须用 AST 而不是让 Claude 自己读:我们一开始尝试让 Claude 直接读 router 文件,结果它漏掉了 11 个接口,原因是这些接口是动态注册的(通过配置文件 + reflect 动态挂载到 RouterGroup)。AST 也读不到这部分,但至少它能明确告诉你"我读到了 X 个,剩下 Y 个是动态注册的",让人知道边界在哪。最终动态路由这 3 个接口我们让人工补。
这一步的 Prompt 很简单,因为重活在 MCP 里:
调用 code-analysis MCP 的 `extract_routes` 工具,对仓库 github.com/myteam/order-service
当前 main 分支进行扫描,输出全部 HTTP 路由的元数据 JSON。
参数:
- repo: myteam/order-service
- branch: main
- scan_paths: ["internal/handler", "internal/router"]
- output_format: json
- include_middleware: true
扫描完成后:
1. 把结果保存到 ./artifacts/order-routes.json
2. 输出一份简报:共扫描到多少路由、按 HTTP 方法分布、按服务模块分布
3. 标注哪些路由是静态注册(AST 能读到)、哪些是动态注册(AST 读不到,需人工补)
实际产出(order 服务):
| 模块 | GET | POST | PUT | DELETE | 合计 |
|---|---|---|---|---|---|
| order | 4 | 9 | 6 | 2 | 21 |
| fulfill | 5 | 4 | 3 | 2 | 14 |
| inventory | 4 | 2 | 1 | 0 | 7 |
| settle | 2 | 2 | 1 | 0 | 5 |
| 合计 | 15 | 17 | 11 | 4 | 47 |
清单出来之后,我们就有了"批量任务列表",后续每一步都可以基于这个清单做并行处理。
第 1 步:逐接口签名分析和参数推断(1 小时 30 分钟)
这是最容易翻车的一步,目标是从骨架 JSON 出发,为每个接口生成完整的"参数表 + 字段语义"。难点有三个:
- 字段语义:结构体字段叫
Province,Claude 知道是"省份",但ProvCode是省份编码还是省份名缩写?需要看 validator tag 或者在代码里搜引用 - 嵌套结构:
OrderCreateReq里嵌套AddressInfo、AddressInfo又嵌套GeoPoint,需要递归展开 - 类型歧义:Go 里
int64是时间戳还是 ID?string是手机号还是订单号?要靠jsontag + validator tag + 上下文综合判断
每个接口用 1 个独立的 Claude Code 子任务处理,并行跑 4-6 个。Prompt 模板如下:
你是 Go 微服务接口分析专家。请基于下面的输入,生成接口的完整参数文档段。
## 输入
1. 接口元数据(来自 code-analysis MCP):
{{ route_metadata_json }}
2. handler 函数源码(已通过 GitHub MCP 拉取):
{{ handler_source_code }}
3. 入参结构体源码(含所有嵌套结构体,递归拉取):
{{ request_struct_source }}
4. 出参结构体源码:
{{ response_struct_source }}
5. 相关 model/DTO 的 validator tag 和 swagger 注释(如果有):
{{ validator_tags }}
## 任务
1. 解析请求结构体,**递归**展开所有嵌套字段,输出一张参数表,列:字段路径、Go 类型、JSON key、是否必填、约束(来自 validator tag)、语义说明、示例值
2. 解析响应结构体,同样递归展开,输出响应字段表
3. 对每个字段做"语义推断"——只允许基于以下证据:
- json tag 命名
- validator tag(如 `validate:"required,min=11,max=11"` → 手机号)
- 字段在 handler 中的使用方式(搜该字段在代码中的所有引用)
- 字段在 model 中的注释(如果存在)
**禁止臆测**。如果证据不足,"语义说明"列写"未知,待人工确认"。
4. 输出格式严格按下述 Markdown 模板:
### POST /api/v2/order/create
**功能描述**:[1-2 句话,基于 handler 中的注释 + 业务逻辑推断]
**请求参数**:
| 字段路径 | 类型 | 必填 | 约束 | 语义 | 示例 |
|---|---|---|---|---|---|
| orderNo | string | 是 | len=20 | 订单号,20 位字符 | "OD20260901000123456789" |
| shipAddress.province | string | 是 | - | 省份名 | "广东省" |
| shipAddress.geo.lat | float64 | 否 | - | 纬度(未知单位,待确认) | 23.1291 |
**响应字段**:
| 字段路径 | 类型 | 语义 | 示例 |
|---|---|---|---|
| code | int | 业务状态码,0 表示成功 | 0 |
| data.orderId | string | 订单 ID | "ORD20260901000123" |
**错误码**:[从 handler 的 switch/case 或 errors.New 中提取]
**依赖的中间件**:jwt.auth, ratelimit.global
**未决问题(需要人工确认)**:
- shipAddress.geo.lat 字段单位未明确(度 / 弧度 / 其他)
- ...
这一步的核心约束是"禁止臆测"。我们第一轮让 Claude 自由发挥,结果它给 lat 字段填了"纬度,单位为度"——但代码里没有任何地方写单位,其实业务上用的是 GCJ02 坐标系的度数,这个信息在某个内部 wiki 上,AI 根本读不到。强制要求"证据不足就标未决",把 AI 的"幻觉"暴露出来给人处理,是这一步最关键的设计。
实际跑下来,47 个接口总共标注了 38 个"未决问题",平均每个接口 0.8 个,集中在:
- 字段单位(金额、时间戳、坐标)
- 业务状态码的语义(
code=1003是什么意思) - 枚举值的完整取值范围(
status字段代码里只有1, 2, 3,但 DB 里出现过4) - 软删除字段的语义(
is_deleted是 bool 还是 0/1 int)
这 38 个问题在人审环节一次性集中处理,比边写边查快得多。
并行策略:用 Claude Code 的 Batch 模式,同时开 4 个子任务(按服务拆分:order-1、order-2、fulfill、其他)。每个子任务处理 12-15 个接口,单接口平均 2 分钟,整体 30 分钟出完,但加上 Claude 的排队和重试,实际 1.5 小时收口。
第 2 步:请求 / 响应示例生成(45 分钟)
合作方对接最看重的不是参数表,而是可以直接 copy 的 JSON 示例。这一步要解决两个问题:
- 示例必须类型合法(string 不能给数字、嵌套结构不能漏字段)
- 示例必须业务合法(订单号格式、手机号位数、金额单位这些要看起来"对")
这一步的 Prompt:
你是 API 示例生成专家。基于下面的接口参数表,生成 1 个完整的请求示例和 1 个完整的响应示例。
## 输入
{{ 接口参数表 markdown }}
## 业务上下文(从仓库的 README、内部 wiki、单元测试中聚合)
- 订单号格式:OD + yyyyMMdd + 10 位流水,如 OD20260901000123456789
- 手机号:中国大陆 11 位,1 开头
- 金额单位:分(int64),如 19900 表示 199.00 元
- 时间戳:unix 秒(int64)
- 省份/城市:使用民政部标准名称,"省"/"市"后缀保留
- 坐标系:GCJ02,单位为度
## 任务
1. 生成一个**完整**的请求 JSON 示例,要求:
- 所有必填字段都给值
- 所有可选字段也给值(用合理默认值,便于合作方理解字段含义)
- 嵌套结构完整展开,不要用 "..." 省略
- 值符合业务上下文中的格式约定
2. 生成一个**成功**的响应 JSON 示例(code=0)
3. 生成一个**常见失败**的响应 JSON 示例(选 1 个最常见的错误码)
4. 输出格式:
#### 请求示例
```json
{
"orderNo": "OD20260901000123456789",
"shipAddress": {
"province": "广东省",
"city": "深圳市",
"district": "南山区",
"detail": "科技园xxx路xxx号",
"geo": {
"lat": 22.5431,
"lng": 113.9536
}
},
"items": [
{
"skuId": "SKU001",
"quantity": 2,
"unitPrice": 19900
}
]
}
成功响应
{ ... }
失败响应(错误码 1003:库存不足)
{ ... }
注意
- 不要编造业务上下文中没有的字段格式
- 嵌套数组至少给 2 个元素,展示数组结构
- 金额、时间、手机号必须符合业务上下文
**踩过的坑**:第一次跑的时候,Claude 给手机号填了 `"13800138000"`,看起来没问题——但订单创建接口的 `customerPhone` 字段在 validator 里写的是 `validate:"required,e164"`,正确格式是 `+8613800138000`。后来我们在 Prompt 里加了"**严格遵守 validator tag,必要时调整业务上下文中的格式约定**",并把 validator tag 单独抽出来作为强约束。
示例生成的并行策略和第 1 步类似,按服务拆 4 路并行,平均每个接口 1 分钟,整体 45 分钟出完。
### 第 3 步:Markdown 文档组装和格式化(30 分钟)
前两步产出的是 47 个独立的接口文档段(散落在 `./artifacts/order/`、`./artifacts/fulfill/` 等目录下)。这一步要把它们组装成一份结构化、可检索的 API 手册。
组装任务的 Prompt:
你是 API 文档组装专家。基于下面的接口文档段(每个接口一个独立的 Markdown 文件),组装一份完整的 API 手册。
输入
- ./artifacts/order/*.md (21 个接口文档段)
- ./artifacts/fulfill/*.md (14 个)
- ./artifacts/inventory/*.md (7 个)
- ./artifacts/settle/*.md (5 个)
输出
一份完整的 Markdown 文件,结构如下:
订单与履约服务 API 文档
版本
- 文档版本:v2.4
- 生成时间:2026-09-01
- 对应代码分支:main @ commit abc1234
通用约定
请求头
| Header | 必填 | 说明 |
|---|---|---|
| Authorization | 是 | Bearer ${token} |
| X-Request-Id | 否 | 链路追踪 ID,建议传入 |
| Content-Type | 是 | application/json |
通用响应结构
{
"code": 0,
"msg": "ok",
"data": { ... },
"traceId": "abc123"
}
错误码总表
[从所有接口文档段中聚合错误码,去重后按 code 排序]
接口列表
订单服务(Order)
[按字母序排列 21 个接口]
履约服务(Fulfill)
[14 个]
库存服务(Inventory)
[7 个]
结算服务(Settle)
[5 个]
附录
字段单位约定
- 金额:分(int64)
- 时间戳:unix 秒
- 坐标:GCJ02,度
枚举值速查
[从所有接口文档段中聚合枚举字段,列全取值]
变更日志
- 2026-09-01:首次自动生成,覆盖 47 个接口
**组装时的关键约束**:
1. **去重**:错误码、枚举值要在文档末尾的附录里聚合一次,但接口段里保留原始引用(便于读者上下文理解)
2. **排序**:接口按服务分组,组内按 URL 字母序,不要按"重要性"排(主观排序在维护时会乱)
3. **交叉引用**:如果接口 A 的响应字段引用了接口 B 创建的资源(如 `orderId`),在字段说明里加 `[详见 POST /api/v2/order/create]` 链接
4. **版本对齐**:文档版本号 = 代码 commit hash 前 7 位,文档生成时间用 ISO 8601
组装完成后,通过 GitHub MCP 提 PR:
调用 GitHub MCP 的 create_pull_request 工具:
- repo: myteam/api-docs
- title: "docs: 自动生成订单与履约服务 API 文档 v2.4"
- head: feat/auto-api-doc-20260901
- base: main
- body: |
变更说明
- 首次自动生成,覆盖 47 个接口、120+ 参数说明、14 个完整示例
- 由 Claude Code + code-analysis MCP 生成,待人工审核
- 对应代码 commit: myteam/order-service@abc1234
- 38 个未决问题已标注在文档中的"未决问题"小节
- commits:
- file: docs/api/v2.4/order-fulfill-api.md content: {{ assembled_markdown }}
PR 提交后会触发 CI(markdownlint + 自写校验脚本),校验每个接口段必须包含 7 个必备小节(功能描述、请求参数表、响应字段表、请求示例、成功响应、失败响应、错误码),缺一即失败。第一次跑 CI 失败了 3 个接口(缺失败响应示例),重跑一次后通过。
### 第 4 步:人工审核和修正(1 小时)
AI 生成的文档即使 Prompt 再严格,也会有边界 case 漏掉。我们安排 2 个同学各 1 小时做交叉审核:
- **同学 A**(订单服务 owner):审核 order + settle 共 26 个接口
- **同学 B**(履约服务 owner):审核 fulfill + inventory 共 21 个接口
审核重点放在三件事:
1. **未决问题清零**:第 1 步标注的 38 个未决问题,由对应 owner 当场确认。绝大多数是字段单位、枚举值这种 DBA / 业务同学一眼能看出答案的问题,平均每个 30 秒,合计 20 分钟清完
2. **示例业务正确性**:金额单位、时间戳格式、手机号格式这些容易"看起来对但实际错"的地方,人工抽样 5-10 个接口对照线上数据验证
3. **隐式契约补充**:AI 读不到的"代码里没写但业务上必须遵守"的规则——比如 `order.v2.create` 接口的 `items` 数组虽然没在 validator 里限制长度,但业务上单次最多 100 个 SKU(由 `ratelimit.global` 中间件控制)。这类规则由 owner 人工补到"业务约束"小节
**人工修正的统计**:
| 修正类型 | 数量 | 占比 |
|---|---|---|
| 未决问题清零 | 38 | 67.9% |
| 示例值修正 | 9 | 16.1% |
| 隐式契约补充 | 6 | 10.7% |
| 字段语义修正 | 3 | 5.4% |
| **合计** | **56** | 100% |
也就是说,AI 生成的 47 个接口文档里,**人工总共改了 56 处**,平均每个接口 1.2 处——比"从零写"节省的工作量是数量级的。而且改的都是"补全"而非"重写",review 的认知负担远低于自己写。
人工修正后直接 push 到 PR 分支,CI 再跑一次通过,merge 进 main,发布到内部文档站。整个流程从启动到文档上线,**实际耗时 4 小时**。
## 关键 Prompt 模板
下面是整个流程中最核心的 3 个 Prompt 模板,复制即用。
### 模板 1:接口元数据提取(第 0 步)
你是 API 文档自动化流水线的调度器。请按以下步骤执行接口元数据提取:
步骤
-
调用 code-analysis MCP 的
extract_routes工具,对目标仓库做 AST 扫描:- repo: {{ repo_full_name }}
- branch: {{ branch }}
- scan_paths: ["internal/handler", "internal/router", "internal/api"]
- include_middleware: true
- output_format: json
-
调用 GitHub MCP 的
get_file_content工具,拉取以下文件用于后续上下文:- README.md
- docs/architecture.md(如果存在)
- Makefile(用于识别服务入口)
-
对
extract_routes的输出做以下处理: a. 按 service 分组 b. 在每个 service 内按 HTTP 方法 + URL 路径排序 c. 标注每个路由的"注册类型":static(AST 直接读到)/ dynamic(通过 reflect 或配置注册,AST 读不到) d. 对 dynamic 路由,输出"待人工补充"清单 -
输出两个产物:
- ./artifacts/{{ service }}/routes.json:标准化路由元数据
- ./artifacts/{{ service }}/routes-summary.md:人类可读的简报,含统计表格和待办清单
校验条件(任一不满足即视为本步失败,停止后续步骤)
- 扫描到的路由数 >= 40(业务上已知至少 47 个)
- 静态路由占比 >= 90%
- 每个路由都包含 file:line 引用,便于后续追溯
### 模板 2:接口参数推断(第 1 步,核心)
你是 Go 微服务接口分析专家。请基于下方输入,生成接口的完整参数文档段。
输入
-
接口元数据 JSON: {{ route_metadata_json }}
-
handler 函数源码: {{ handler_source_code }}
-
入参结构体源码(含所有嵌套结构体,递归展开): {{ request_struct_source }}
-
出参结构体源码(含所有嵌套结构体): {{ response_struct_source }}
-
handler 中引用的 model/DTO 源码片段(用于语义推断): {{ model_source_snippets }}
任务
- 递归解析请求结构体,输出参数表,列:字段路径、Go 类型、JSON key、是否必填、约束、语义说明、示例值
- 递归解析响应结构体,同样输出字段表
- 字段语义推断的证据链必须满足以下任一:
a. json tag 命名清晰(如
customer_phone→ 客户手机号) b. validator tag 提供约束(如validate:"required,e164"→ E.164 格式电话) c. 字段在 handler / model 代码中有明确使用(如被传给time.Unix()→ 时间戳) d. 字段在 model 注释中有说明 若证据不足,"语义说明"列必须填"未知,待人工确认",并在"未决问题"小节列出。禁止根据字段名自由发挥。 - 从 handler 的
switch err := ...; case errors.New(...)或return c.JSON(..., ErrorResponse{Code: xxx})中提取错误码,输出错误码表 - 输出格式严格遵循下述 Markdown 模板(每个小节必须有,缺一即不合格):
{{ METHOD }} {{ PATH }}
功能描述:[1-2 句话,仅基于代码可见证据,禁止臆测业务背景]
中间件链:[从 route_metadata_json 的 middleware 字段抄写]
请求参数:
| 字段路径 | 类型 | 必填 | 约束 | 语义 | 示例 |
|---|---|---|---|---|---|
| ... | ... | ... | ... | ... | ... |
响应字段:
| 字段路径 | 类型 | 语义 | 示例 |
|---|---|---|---|
| ... | ... | ... | ... |
错误码:
| code | HTTP 状态 | 含义 |
|---|---|---|
| 0 | 200 | 成功 |
| 1001 | 400 | 参数错误 |
| ... | ... | ... |
未决问题(需人工确认):
- [问题 1,含字段路径和待确认点]
- [问题 2]
- ...
自检(输出前必须逐项确认)
- 所有必填字段的"约束"列非空(来自 validator tag)
- 所有嵌套结构递归展开,未出现 "..." 省略
- 所有"语义"列要么有证据链,要么标注"未知,待人工确认"
- 错误码表至少包含成功码(0)和 1 个失败码
- 未决问题小节即使为空也保留,写"无"
### 模板 3:示例生成(第 2 步)
你是 API 示例生成专家。基于下方接口参数表,生成完整的请求/响应 JSON 示例。
输入
{{ 接口参数表 markdown,来自第 1 步输出 }}
业务上下文(强约束,违反即不合格)
- 订单号格式:OD + yyyyMMdd + 10 位流水号
- 客户手机号:E.164 格式,+86 前缀
- 金额单位:分(int64),19900 表示 199.00 元
- 时间戳:unix 秒(int64)
- 省份/城市/区县:民政部标准名称,保留"省"/"市"/"区"后缀
- 坐标系:GCJ02,单位为度,6 位小数
- SKU 编号:SKU + 6 位数字
- 订单状态枚举:1=待支付、2=已支付、3=履约中、4=已完成、5=已取消
任务
-
生成 1 个完整请求示例:
- 所有必填字段给值
- 所有可选字段也给值(用合理默认值)
- 嵌套结构完整展开,数组至少 2 个元素
- 值严格遵守上述业务上下文
- 如果字段有 validator tag,值必须通过 validator
-
生成 1 个成功响应示例(code=0):
- 响应字段完整展开
- 业务状态码 = 0
- 包含 traceId
-
生成 1 个常见失败响应示例:
- 从错误码表中选 1 个最常见的失败码
- msg 字段给出符合错误码语义的错误说明
输出格式
请求示例
{ ... }
成功响应
{ ... }
失败响应(错误码 {{ 选择的错误码 }}:{{ 错误码含义 }})
{ ... }
自检
- 所有 string 字段值用双引号
- 所有数字字段没有引号
- 所有必填字段都有值
- 示例值通过 validator tag 约束
- 嵌套数组 >= 2 个元素
- 金额、时间、手机号、坐标格式符合业务上下文
## 效果数据
| 指标 | 纯人工方案 | AI 辅助方案 | 提升 |
|---|---|---|---|
| 总耗时 | 70+ 人时(≈ 2 周 / 1 人) | 4 小时(AI 3h + 人审 1h) | **17.5x** |
| 接口覆盖数 | 47 | 47 | 持平 |
| 参数说明数 | 120+ | 128 | +6.7%(AI 多挖了一些隐藏字段) |
| 完整请求/响应示例数 | 14(计划) | 47(每接口都有) | **3.4x** |
| 错误码说明数 | 估约 30 | 42 | +40% |
| 字段语义准确率 | ~95%(人写也会出错) | 91%(56 处人工修正 / 总字段数) | -4% |
| 文档格式一致性 | 中(多人手写风格不一) | 高(统一模板) | 显著提升 |
| 可维护性 | 低(人写完没人愿意改) | 高(脚本化,可随代码 re-gen) | 质变 |
| 协作方对接平均耗时 | 3-5 天 | 1-2 天 | -60% |
**关键观察**:
1. **AI 方案不是"更快的人",而是"另一种产物"**——人工方案计划只出 14 个示例(按"核心接口"优先),AI 方案直接全量 47 个示例都出。质量上 AI 略低(91% vs 95%),但覆盖面碾压
2. **可维护性是最大的隐性收益**:以前文档更新要排需求,现在每次发版前跑一遍流水线,diff 出来人审 10 分钟就能合并,文档和代码的漂移问题从根上解决
3. **协作方对接提速 60%**:合作方拿到的不是"14 个核心接口文档",而是全量 47 个接口的完整手册,自助对接不需要再开会对齐字段
**成本核算**:
| 项目 | 金额 |
|---|---|
| Claude Code API 调用(约 2000 万 token) | ¥XXX |
| GitHub MCP 调用 | 免费(已付费组织账号) |
| 自定义 code-analysis MCP 开发 | 2 人天(一次性投入) |
| 人工审核 | 2 人 × 1 小时 = 2 人时 |
| **合计**(除一次性投入) | 远低于 2 周人力成本 |
## 踩坑经验
下面是我们在 4 小时里实际踩到的坑,以及对应的解决方案。这部分是整个案例最有价值的部分——Prompt 模板可以照抄,但这些坑每个团队都会遇到。
### 坑 1:接口签名推断错误(动态路由被漏掉)
**现象**:第 0 步用 AST 扫描 order 服务,输出 18 个路由,但实际线上有 21 个。漏掉的 3 个是 `order.batch.create`、`order.batch.cancel`、`order.batch.query`——它们是通过配置文件 + `reflect` 动态注册的:
```go
// internal/router/dynamic.go
func RegisterBatchRoutes(r *gin.Engine, cfg *config.BatchConfig) {
handlerType := reflect.TypeOf(&BatchHandler{})
for _, route := range cfg.Routes {
method := reflect.ValueOf(handlerType).MethodByName(route.Handler)
r.Handle(route.Method, route.Path, method.Interface().(gin.HandlerFunc))
}
}
AST 完全读不到这部分——它只能看到 r.Handle(...) 调用,但参数都是运行时变量。
解决方案:
- MCP 加一个"动态路由探测器":扫描
r.Handle/r.Any等动态调用,如果参数不是字符串字面量,就标记为"动态路由,待人工补充" - Prompt 里加"已知边界":明确告诉 Claude "AST 扫描可能漏掉 reflect 注册的路由,请检查仓库内是否有
reflect.ValueOf(...).MethodByName(...)模式,如果有,把相关文件列出来作为待办" - 兜底:人工补这 3 个接口,每个 5 分钟(因为代码结构清楚,照着静态路由的模板填就行)
经验:任何自动化都有边界,提前在 Prompt 里告诉 AI 边界在哪,比让 AI 假装无所不能更重要。
坑 2:参数类型歧义(int64 是 ID 还是时间戳)
现象:OrderCreateResp 结构体里有个字段 createTime int64,Claude 在"语义说明"列填了"创建时间,时间戳"。人审时发现这个字段其实是 订单创建时间的 unix 毫秒,不是秒——代码里 time.Unix(0, createTime * 1e6) 暴露了单位,但 Claude 没注意到 * 1e6 这个细节。
类似的还有:
userId int64:Claude 标"用户 ID"——对的merchantId int64:Claude 标"商户 ID"——对的updatedAt int64:Claude 标"更新时间,时间戳"——单位错,实际是毫秒expireTime int64:Claude 标"过期时间"——字段语义错,实际是"距创建多少秒后过期"的相对时间,不是绝对时间戳
根因:Go 的 int64 是个"万能数字类型",类型本身不携带语义。Claude 默认推断"以 Time 结尾的 int64 = 时间戳",但项目中混用了秒、毫秒、相对时间三种约定。
解决方案:
- 第 1 步 Prompt 加"字段单位强校验"规则:
- 任何 int64 字段,如果字段名包含 time/timestamp,必须查 handler 中该字段的使用方式: - 如果被传给 time.Unix(x, 0) → 秒 - 如果被传给 time.Unix(0, x) 或 time.UnixMilli(x) → 毫秒 - 如果被传给 time.Unix(0, x*1e6) → 毫秒(手动 *1e6 转 纳秒) - 如果被用在 time.Sleep(time.Duration(x) * time.Second) → 相对秒数 - 找不到使用方式的,"语义"列填"未知时间单位,待人工确认" - 业务上下文加全局约定:在 Prompt 的"业务上下文"段里写明"本服务所有时间字段统一为 unix 毫秒,除非代码中显式转换",让 AI 优先按毫秒推断
- 人审清单加"时间字段必查":所有 time/timestamp 字段在审核时强制对照代码确认单位
经验:类型系统的语义不足,要靠 Prompt 注入"项目级约定"来补足。这类坑不会消失,但可以靠"强校验规则"把错误率从 30% 降到 5% 以下。
坑 3:嵌套结构解析失败(深度过深 / 循环引用)
现象:OrderCreateResp 嵌套了 OrderInfo,OrderInfo 嵌套了 AddressInfo,AddressInfo 嵌套了 GeoPoint,GeoPoint 又引用回 OrderInfo(用于反向关联)。Claude 在递归展开时遇到了循环引用,直接停止展开,输出了 "geoPoint": {"orderInfo": "..."}——把整个嵌套结构截断了。
类似的还有:
- 深度超过 5 层的嵌套,Claude 会在第 5 层自动停止
- 切片嵌套结构体(
[]OrderItem,OrderItem又嵌套[]PromotionRule),二维数组场景容易漏字段 - 指针字段(
*AddressInfo)和值字段(AddressInfo)的区分,Claude 偶尔会混淆 nullable 语义
解决方案:
- MCP 在结构体提取阶段就处理循环引用:维护一个
visited map[string]bool,遇到已访问的结构体就标记为[ref: structName],不再展开 - Prompt 显式声明"嵌套深度策略":
- 嵌套结构递归展开,最多 8 层 - 遇到循环引用(A 引用 B,B 又引用 A),在字段说明中标注 "[循环引用,见 struct X]",不再展开 - 指针字段(*T)和值字段(T)在"必填"列区分:指针字段默认可选(除非 validator 标 required),值字段默认必填 - 切片字段([]T):T 仍然递归展开,参数表里给"T 的字段"列前缀 "[array element]" - 针对二维数组:
[][]string这种在参数表里直接标注"二维字符串数组,外层 N、内层 M",不再尝试展开成表格
经验:递归类任务必须给 AI 明确的"终止条件"和"循环处理规则"。让 AI 自己决定何时停止,结果不可预测。
坑 4:错误码提取不全(错误码分散在多层调用链)
现象:handler 里的 return c.JSON(..., ErrorResponse{Code: 1001}) 只能拿到顶层错误码,但实际业务错误码大多定义在 service / repo 层:
// internal/service/order_service.go
func (s *OrderService) Create(ctx context.Context, req *OrderCreateReq) (*OrderCreateResp, error) {
if err := s.validateStock(req); err != nil {
return nil, errors.New("1003:库存不足")
}
if err := s.repo.Create(ctx, order); err != nil {
return nil, fmt.Errorf("2001:数据库错误:%w", err)
}
...
}
Claude 在第 1 步只读了 handler 文件,所以错误码表里只有 1001、0,漏掉了 1003、2001 等。
解决方案:
- 第 1 步 Prompt 加"错误码跨层提取":
- 错误码提取不止读 handler,必须追溯 handler 调用的 service 层函数 - 用 grep 模式 `errors\.New\("[0-9]+:` 和 `fmt\.Errorf\("[0-9]+:` 全仓库搜索 - 对每个匹配项,输出"错误码 + 错误消息 + 所在文件:行号 + handler 调用链" - 错误码表按 code 排序,去重 - MCP 加
extract_error_codes工具:用正则扫全仓库的errors.New("xxx:yyy")和fmt.Errorf("xxx:yyy:zzz")模式,输出标准化列表 - 人审清单加"错误码必查":每个接口审核时,对照线上日志的错误码分布,确认文档里没有遗漏
效果:第一轮错误码表只覆盖 18 个,加上跨层提取后达到 42 个,覆盖率 +133%。
坑 5:示例值不符合业务约束(金额单位混乱)
现象:Claude 给 unitPrice 字段填了 199.00——但项目里金额单位统一是分(int64),正确值应该是 19900。这个坑在第 2 步最容易踩,因为 Claude 默认的"金额"概念是"元 + 小数"。
类似的问题:
- 手机号填
"13800138000"(11 位)但 validator 是e164,应该"+8613800138000" - 时间戳填
1693526400(秒)但项目用毫秒,应该1693526400000 - 坐标填
23.1291(4 位小数)但项目约定 6 位小数,应该23.129100
解决方案:业务上下文必须作为"强约束"注入 Prompt,见上面模板 3 的"业务上下文"段。这部分内容由人工提前写好(一次性投入 30 分钟),但能避免后续 47 个接口 × 平均 3 个示例字段 = 140+ 处修正。
经验:示例生成的关键不是 AI 的想象力,而是 AI 能不能拿到准确的"业务约束清单"。这清单本来就应该写在项目文档里——如果没有,这次正好补上。
坑 6:中间件影响未在文档体现
现象:order.v2.create 接口挂了 ratelimit.global 中间件,限制单 IP 每秒 10 次请求。但 AI 生成的文档里完全没提限流——因为中间件不在 handler 函数里,Claude 读 handler 源码时根本看不到。
合作方对接时踩了这个坑:批量创建订单时被限流 429,但文档里没写限流策略,合作方以为是接口 bug。
解决方案:
- 第 1 步 Prompt 加"中间件影响说明":
- 接口的 middleware 字段不为空时,必须输出"中间件影响"小节 - 对每个中间件,说明它对请求方的影响: - jwt.auth → 请求必须带 Authorization header - ratelimit.global → 限流策略:单 IP 每秒 N 次(N 从中间件配置读取) - trace.inject → 建议请求方传 X-Request-Id - cors → 允许的 Origin 列表 - 中间件配置如果在代码里硬编码,直接抄值;如果在配置文件里,标注"配置项:xxx,默认值:yyy" - 人审清单加"中间件必查":每个接口审核时,对照中间件列表确认"影响说明"完整
经验:接口文档不止是"参数 + 响应",还要写"调用约束"。中间件、限流、鉴权这些横切关注点,是合作方最容易踩坑的地方。
坑 7:Markdown 格式不统一(多人协作时翻车)
现象:第 1 步并行跑 4 个子任务时,虽然 Prompt 模板一致,但 4 个子任务输出的 Markdown 细节有差异:
- 表格列宽不一致
- 代码块语言标识有的写
json、有的写javascript - 标题层级有的用
###、有的用#### - 字段路径有的用
a.b.c、有的用a → b → c
解决方案:
- 统一模板的"硬约束":在 Prompt 末尾加"格式校验清单",明确每个细节
- CI 加 markdownlint:
.markdownlint.json配置严格规则(标题层级、代码块语言、表格格式),CI 失败即拒绝合并 - 自写校验脚本:检查每个接口段是否包含 7 个必备小节(功能描述、请求参数表、响应字段表、请求示例、成功响应、失败响应、错误码),缺一即失败
经验:并行任务越多的流水线,越要靠"机器校验"保证一致性。靠 Prompt 约束只能做到 90%,剩下 10% 必须靠 CI。
坑 8:版本对齐失败(文档和代码漂移)
现象:第一次跑完流水线后,文档里写的"对应代码 commit: abc1234",但 PR review 时发现 main 分支已经又合并了 2 个 commit,文档和代码已经不对齐了。
解决方案:
- 流水线改为一键触发:CI 配置一个
docs:auto-genjob,监听 main 分支的 push 事件,自动跑完整流水线 + 提 PR - 文档头部的 commit hash 用
git rev-parse --short HEAD动态获取,不再硬编码 - 每周一定时全量重跑:即使 main 没合并,也重跑一次,确保文档和代码不漂移
经验:文档自动化必须做成"持续生成",不是"一次性项目"。否则文档会从"刚生成的"变成"最新的过期文档",比没有还糟。
进阶话题:如何把这套流程复制到其他项目
这套流程跑通后,我们把它复制到了另外 3 个微服务(用户中心、支付网关、消息推送),平均每个项目的接入时间是 1-2 人天,主要是:
- 自定义 code-analysis MCP 适配(约 4 小时):每个项目的 router 库可能不同(gin / echo / chi / 原生 net/http),需要写对应的 AST 提取规则
- 业务上下文清单整理(约 2 小时):把项目特有的字段单位、枚举值、ID 格式约定写进 Prompt 模板的"业务上下文"段
- 错误码提取规则适配(约 1 小时):每个项目的 errors.New 模式可能不同,调整 MCP 的正则
- CI 校验脚本接入(约 1 小时):把 markdownlint + 自写校验脚本接入项目的 CI
通用性建议:
| 项目特征 | 适配难度 | 说明 |
|---|---|---|
| Go + gin + 标准 MVC | 低 | 直接套用,几乎不用改 |
| Go + echo / chi | 低 | 改 AST 提取的库匹配规则 |
| Go + 原生 net/http | 中 | 路由注册方式不同,需要重新写提取逻辑 |
| Java + Spring Boot | 中 | AST 改为 JavaParser,注解驱动的路由提取 |
| Python + FastAPI | 低 | FastAPI 自带 OpenAPI,AI 主要补语义和示例 |
| Python + Flask | 中 | 路由装饰器提取,类似 gin |
| Node.js + Express | 中 | 路由注册方式多样,AST 提取复杂 |
| Node.js + NestJS | 低 | 装饰器驱动,类似 Spring Boot |
关键经验:AST 提取逻辑是这套流程的"地基",必须用项目对应的语言生态去做。不要指望一个通用的 MCP 处理所有语言——Go 的 go/parser 比 Java 的 JavaParser 易用,比 Python 的 ast 模块更严格。每个语言生态都有自己的"事实标准"AST 工具,用对工具事半功倍。
经验总结
走完整套流程,我们的核心经验可以归纳为 7 条:
1. AI 擅长"批量格式化",不擅长"语义判断"
AI 在 4 小时内生成 47 个接口的参数表、字段表、示例——这种"模式识别 + 批量格式化"是它的强项。但字段语义判断(int64 是 ID 还是时间戳?status=4 是什么意思?)必须靠人审。不要幻想 AI 能完全替代人,它的价值是"把人的工作从 70 小时压到 1 小时",而不是"让人没事干"。
2. Prompt 模板的"硬约束"比"软建议"重要 100 倍
第一版 Prompt 写"请尽量准确地推断字段语义"——结果 AI 自由发挥,错误率 30%。改成"字段语义必须有证据链,证据不足必须标注'待人工确认'"——错误率降到 5% 以下。Prompt 越严格,AI 输出越可预测;越"宽松友好",AI 越容易幻觉。
3. 自定义 MCP 是"必要的过度设计"
一开始我们想直接用 Claude 读源码 + GitHub MCP,结果漏了 11 个接口、错误码提取不全、嵌套结构截断。自定义 code-analysis MCP 用 AST 做结构化提取,把"不可预测的自由发挥"变成"可预测的骨架数据"——一次性投入 2 人天,但后续每个项目复用只需 4 小时适配。通用 MCP 解决不了语言/框架特定的提取逻辑。
4. 业务上下文清单是"隐性资产"
整理"金额单位是分"、"时间戳是毫秒"、"手机号是 E.164"这些约定的过程,本身就是在沉淀项目知识。这份清单不仅喂给 AI,也可以作为新人 onboarding 材料、作为 code review 的 checklist。很多项目的"隐性知识"从来没被写下来过,AI 流水线倒逼它显性化。
5. 人工审核必须"有重点"
让人工审核 47 个接口 × 平均 20 个字段 = 940 个字段,人会疯。审核重点必须是:
- 未决问题清零(AI 主动暴露的"我不知道")
- 示例值业务正确性(看起来对但实际错的)
- 隐式契约补充(代码里没写但业务必须遵守的)
这三类问题占人工修正的 95%。其他细节靠 CI 校验。
6. CI 校验是"最后一道防线"
markdownlint + 自写校验脚本(必备小节检查、字段路径格式、代码块语言标识)能挡住 90% 的格式问题。人工 review 应该只看"语义",格式问题交给机器。我们的 CI 配置:
# .github/workflows/docs-check.yml
name: Docs Check
on:
pull_request:
paths:
- 'docs/api/**'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: markdownlint
uses: DavidAnson/markdownlint-cli2-action@v13
with:
globs: 'docs/api/**/*.md'
- name: section check
run: node scripts/check-api-doc-sections.js docs/api/
- name: example check
run: node scripts/check-api-doc-examples.js docs/api/
check-api-doc-sections.js 校验每个接口段是否包含 7 个必备小节,check-api-doc-examples.js 校验每个接口是否有完整的请求/响应 JSON 示例且能通过 JSON 解析。
7. 文档自动化是"持续工程"不是"一次性项目"
第一次跑通流水线只是开始。真正有价值的是把流水线接入 CI,让文档随代码 commit 自动 re-gen。我们现在的做法:
- main 分支 push 触发:自动跑完整流水线,提 PR
- 每周一定时全量重跑:即使 main 没合并,也重跑一次防漂移
- 发版前必跑:发版 checklist 里有"文档已更新且通过 CI"这一项,没打勾不能发版
文档和代码的漂移问题,只能靠"持续生成"解决,不能靠"人记得改"。
附录:完整 Prompt 模板汇总
下面是整个流程的完整 Prompt 模板汇总,按步骤组织。每个模板都包含:输入、任务、输出格式、自检清单。复制即用,按需替换 {{ }} 占位符。
第 0 步:接口元数据提取 Prompt
你是 API 文档自动化流水线的调度器。请按以下步骤执行接口元数据提取。
## 步骤
### 1. AST 扫描
调用 code-analysis MCP 的 `extract_routes` 工具:
- repo: {{ repo_full_name }}
- branch: {{ branch }}
- scan_paths: {{ scan_paths }}
- include_middleware: true
- output_format: json
### 2. 上下文文件拉取
调用 GitHub MCP 的 `get_file_content`:
- README.md
- docs/architecture.md(如果存在)
- Makefile
### 3. 结果处理
a. 按 service 分组
b. 每个组内按 HTTP 方法 + URL 排序
c. 标注注册类型:static / dynamic
d. 对 dynamic 路由输出"待人工补充"清单
### 4. 产物输出
- ./artifacts/{{ service }}/routes.json
- ./artifacts/{{ service }}/routes-summary.md
## 校验条件
- 扫描路由数 >= 40
- 静态路由占比 >= 90%
- 每个路由含 file:line 引用
第 1 步:接口参数推断 Prompt
(见上文"关键 Prompt 模板 - 模板 2",此处略)
第 2 步:示例生成 Prompt
(见上文"关键 Prompt 模板 - 模板 3",此处略)
第 3 步:文档组装 Prompt
你是 API 文档组装专家。基于下方接口文档段,组装完整 API 手册。
## 输入
- ./artifacts/{{ service }}/*.md
## 输出结构
# {{ service_name }} API 文档
## 版本
- 文档版本:{{ version }}
- 生成时间:{{ timestamp }}
- 对应代码:{{ repo }}@{{ commit_short }}
## 通用约定
### 请求头
| Header | 必填 | 说明 |
### 通用响应结构
### 错误码总表(聚合去重)
## 接口列表
### {{ service }}(共 N 个)
[按 URL 字母序]
## 附录
### 字段单位约定
### 枚举值速查
### 变更日志
## 组装规则
1. 错误码在附录聚合一次,接口段保留引用
2. 接口按 URL 字母序,不按"重要性"
3. 交叉引用用 [详见 METHOD /path] 链接
4. 版本号用 commit hash 前 7 位
5. 时间用 ISO 8601
第 4 步:人工审核 Checklist
## 人工审核 Checklist
### 未决问题清零(必做)
- [ ] 所有"未知,待人工确认"字段已确认
- [ ] 所有时间字段单位已确认(秒/毫秒/相对)
- [ ] 所有枚举字段取值范围已补全
### 示例业务正确性(抽样 5-10 个接口)
- [ ] 金额单位为分(int64)
- [ ] 时间戳单位与项目约定一致
- [ ] 手机号格式符合 validator
- [ ] 坐标精度符合项目约定
- [ ] 订单号/SKU 号格式正确
### 隐式契约补充
- [ ] 限流策略已写入"中间件影响"
- [ ] 业务约束(如数组最大长度)已补充
- [ ] 跨接口字段引用关系已标注
### 格式校验(CI 已覆盖,人工可跳过)
- [ ] markdownlint 通过
- [ ] 必备小节齐全
- [ ] 代码块语言标识正确
结语
这次 API 文档自动化的实践,让我们对 AI 在"工程化场景"中的定位有了更清晰的认识:
- AI 不是"代替人",而是"放大人"——它把 70 小时的体力活压到 4 小时,让人能把时间花在真正需要判断的地方
- Prompt 工程 + 工具链 + 流程设计,三者缺一不可——光有好 Prompt 没有自定义 MCP 会漏接口,光有 MCP 没有 Prompt 会幻觉,光有两者没有 CI 会格式混乱
- "显性化隐性知识"是最大收益——整理业务上下文、字段单位、隐式契约的过程,本身就是项目知识沉淀
我们计划把这套流程推广到团队其他微服务,并把自定义 code-analysis MCP 开源(适配 gin / echo / chi 三个 Go 框架)。如果你也在为"祖传代码补文档"发愁,欢迎参考这套方案——4 小时把 2 周的活干完,剩下的时间可以真正去写代码。
附:流水线代码结构
api-doc-pipeline/
├── mcp/
│ └── code-analysis/ # 自定义代码分析 MCP
│ ├── main.go
│ ├── ast_extractor.go # AST 路由提取
│ ├── struct_parser.go # 结构体递归解析
│ ├── error_code_scanner.go
│ └── dynamic_route_detector.go
├── prompts/
│ ├── 0-extract-routes.md
│ ├── 1-infer-params.md
│ ├── 2-generate-examples.md
│ ├── 3-assemble-docs.md
│ └── 4-review-checklist.md
├── scripts/
│ ├── check-api-doc-sections.js
│ ├── check-api-doc-examples.js
│ └── run-pipeline.sh
├── artifacts/
│ ├── order/
│ ├── fulfill/
│ ├── inventory/
│ └── settle/
└── docs/
└── api/
└── v2.4/
└── order-fulfill-api.md # 最终产出
附:自定义 code-analysis MCP 核心代码片段(AST 路由提取)
package main
import (
"encoding/json"
"go/ast"
"go/parser"
"go/token"
)
type RouteInfo struct {
Method string `json:"method"`
Path string `json:"path"`
Handler string `json:"handler"`
File string `json:"file"`
Line int `json:"line"`
Middleware []string `json:"middleware"`
RequestStruct string `json:"request_struct"`
ResponseStruct string `json:"response_struct"`
RegisterType string `json:"register_type"` // static / dynamic
}
func ExtractRoutes(repoPath, scanPath string) ([]RouteInfo, error) {
fset := token.NewFileSet()
var routes []RouteInfo
pkgs, err := parser.ParseDir(fset, scanPath, nil, parser.ParseComments)
if err != nil {
return nil, err
}
for _, pkg := range pkgs {
for filename, file := range pkg.Files {
ast.Inspect(file, func(n ast.Node) bool {
call, ok := n.(*ast.CallExpr)
if !ok {
return true
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
method := sel.Sel.Name
if !isHTTPMethod(method) {
return true
}
// 静态路由:参数都是字面量
if len(call.Args) >= 2 {
if pathLit, ok := call.Args[0].(*ast.BasicLit); ok {
route := RouteInfo{
Method: method,
Path: pathLit.Value,
File: filename,
RegisterType: "static",
}
// 解析 handler 引用、入参/出参结构体
route = enrichRoute(route, call.Args[1], fset)
routes = append(routes, route)
} else {
// 动态路由:参数不是字面量
routes = append(routes, RouteInfo{
Method: method,
File: filename,
RegisterType: "dynamic",
})
}
}
return true
})
}
}
return routes, nil
}
func isHTTPMethod(name string) bool {
switch name {
case "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "Any":
return true
}
return false
}
func enrichRoute(route RouteInfo, handlerExpr ast.Expr, fset *token.FileSet) RouteInfo {
// 解析 handler 函数引用,定位到函数声明
// 读取函数参数,找 *gin.Context 之后的入参结构体
// 读取函数返回值,找出参结构体
// ...(实现略)
return route
}
func main() {
routes, err := ExtractRoutes("./internal/handler", "./internal/router")
if err != nil {
panic(err)
}
json.NewEncoder(os.Stdout).Encode(routes)
}
附:CI 校验脚本片段(必备小节检查)
// scripts/check-api-doc-sections.js
const fs = require('fs');
const path = require('path');
const REQUIRED_SECTIONS = [
'功能描述',
'请求参数',
'响应字段',
'请求示例',
'成功响应',
'失败响应',
'错误码',
];
function checkFile(filePath) {
const content = fs.readFileSync(filePath, 'utf8');
const errors = [];
// 按 ### 切分接口段
const sections = content.split(/^### /m).slice(1);
for (const section of sections) {
const title = section.split('\n')[0];
for (const required of REQUIRED_SECTIONS) {
if (!section.includes(`**${required}**`) && !section.includes(`### ${required}`)) {
errors.push(`${title}: 缺少必备小节 "${required}"`);
}
}
}
return errors;
}
function walk(dir) {
const results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...walk(full));
} else if (entry.name.endsWith('.md')) {
results.push(full);
}
}
return results;
}
const files = walk(process.argv[2] || 'docs/api/');
let hasError = false;
for (const file of files) {
const errors = checkFile(file);
if (errors.length > 0) {
hasError = true;
console.error(`❌ ${file}:`);
errors.forEach(e => console.error(` - ${e}`));
} else {
console.log(`✅ ${file}`);
}
}
process.exit(hasError ? 1 : 0);
附:错误码跨层提取正则
# 错误码提取正则(Go)
errors\.New\("(?<code>[0-9]+):(?<msg>[^"]+)"\)
fmt\.Errorf\("(?<code>[0-9]+):(?<msg>[^"]+):[^"]*"\)
# 错误码提取正则(Java)
throw new BusinessException\((?<code>[0-9]+),\s*"(?<msg>[^"]+)"\)
return Result\.fail\((?<code>[0-9]+),\s*"(?<msg>[^"]+)"\)
# 错误码提取正则(Python)
raise BizError\((?<code>[0-9]+),\s*"(?<msg>[^"]+)"\)
return \{.*"code":\s*(?<code>[0-9]+).*"msg":\s*"(?<msg>[^"]+)"\}
附:业务上下文清单模板(每个项目维护一份)
# {{ project_name }} 业务上下文清单
## 字段单位约定
- 金额:分(int64),如 19900 表示 199.00 元
- 时间戳:unix 毫秒(int64),如 1693526400000
- 坐标:GCJ02,单位为度,6 位小数
- 时长:秒(int64)
## ID 格式约定
- 订单号:OD + yyyyMMdd + 10 位流水
- 用户 ID:snowflake int64
- SKU 编号:SKU + 6 位数字
- 手机号:E.164 格式,+86 前缀
## 枚举值
- 订单状态:1=待支付、2=已支付、3=履约中、4=已完成、5=已取消
- 支付方式:1=微信、2=支付宝、3=银行卡、4=余额
- 履约状态:1=待发货、2=已发货、3=已签收、4=已退货
## 业务约束
- 单订单最多 100 个 SKU
- 单次批量创建最多 50 个订单
- 金额上限:单订单 100 万元(100000000 分)
- 软删除字段:is_deleted(bool)
附:动态路由探测正则
# 动态路由探测(Go reflect 模式)
reflect\.ValueOf\([^)]+\)\.MethodByName\(
r\.Handle\([^,]+,\s*[a-zA-Z_]+\) # r.Handle(method, path) 但 path 是变量
r\.Any\([^,]+,\s*[a-zA-Z_]+\)
# 动态路由探测(Java 反射模式)
getMethod\(
invoke\(
附:流水线一键执行脚本
#!/bin/bash
# scripts/run-pipeline.sh
set -e
REPO=$1
BRANCH=${2:-main}
SERVICE=${3:-all}
echo "=== API 文档自动化流水线 ==="
echo "Repo: $REPO"
echo "Branch: $BRANCH"
echo "Service: $SERVICE"
# 第 0 步:接口元数据提取
echo ""
echo "[Step 0] 接口元数据提取..."
claude-code run --prompt prompts/0-extract-routes.md \
--var repo=$REPO --var branch=$BRANCH
# 第 1 步:接口参数推断(并行)
echo ""
echo "[Step 1] 接口参数推断..."
for service_dir in artifacts/*/; do
service_name=$(basename $service_dir)
if [ "$SERVICE" != "all" ] && [ "$SERVICE" != "$service_name" ]; then
continue
fi
claude-code run --prompt prompts/1-infer-params.md \
--var service=$service_name &
done
wait
# 第 2 步:示例生成(并行)
echo ""
echo "[Step 2] 示例生成..."
for service_dir in artifacts/*/; do
service_name=$(basename $service_dir)
if [ "$SERVICE" != "all" ] && [ "$SERVICE" != "$service_name" ]; then
continue
fi
claude-code run --prompt prompts/2-generate-examples.md \
--var service=$service_name &
done
wait
# 第 3 步:文档组装
echo ""
echo "[Step 3] 文档组装..."
claude-code run --prompt prompts/3-assemble-docs.md
# 第 4 步:CI 校验
echo ""
echo "[Step 4] CI 校验..."
npx markdownlint-cli2 'docs/api/**/*.md'
node scripts/check-api-doc-sections.js docs/api/
node scripts/check-api-doc-examples.js docs/api/
echo ""
echo "=== 流水线完成 ==="
echo "产物:docs/api/v2.4/order-fulfill-api.md"
echo "下一步:提 PR + 人工审核"
附:人工审核记录模板
# 人工审核记录
## 基本信息
- 审核人:{{ name }}
- 审核范围:{{ service }} 服务,共 {{ n }} 个接口
- 审核时间:{{ timestamp }}
- 审核耗时:{{ duration }}
## 修正明细
| 接口 | 字段 | 修正类型 | 原值 | 修正后 | 原因 |
|---|---|---|---|---|---|
| POST /api/v2/order/create | createTime | 单位修正 | 时间戳(秒) | 时间戳(毫秒) | 代码中 time.Unix(0, x*1e6) |
| POST /api/v2/order/create | items | 隐式契约 | - | 单次最多 100 个 SKU | ratelimit.global 控制 |
| ... | ... | ... | ... | ... | ... |
## 统计
- 未决问题清零:{{ n1 }} 处
- 示例值修正:{{ n2 }} 处
- 隐式契约补充:{{ n3 }} 处
- 字段语义修正:{{ n4 }} 处
- 合计:{{ n1 + n2 + n3 + n4 }} 处
## 遗留问题
- [ ] {{ 问题 1 }}
- [ ] {{ 问题 2 }}
附:Prompt 工程经验总结
在整个流程中,我们对 Prompt 工程的几条核心经验:
- "硬约束"优于"软建议":写"必须基于证据链,证据不足标注待确认",比写"请尽量准确"效果好 10 倍
- "自检清单"是质量保证:在 Prompt 末尾加"[ ] 字段语义必须有证据链"这样的自检项,AI 会主动回溯检查
- "业务上下文"必须显式注入:AI 读不到内部 wiki,所有项目特有约定必须写在 Prompt 里
- "禁止项"比"鼓励项"更有效:写"禁止根据字段名自由发挥",比写"请谨慎推断"效果好
- "输出格式"必须严格:给 AI 一个 Markdown 模板,要求"严格遵循",比让它自由发挥格式好得多
- "边界声明"很重要:明确告诉 AI "AST 扫描可能漏掉动态路由",比让它假装无所不能更可靠
附:项目级 Prompt 模板版本管理
我们维护一份项目级 Prompt 模板仓库,结构如下:
prompt-templates/
├── api-doc/
│ ├── v1/ # 初版,自由发挥
│ ├── v2/ # 加硬约束 + 自检清单
│ ├── v3/ # 加业务上下文 + 边界声明
│ └── v4/ # 当前版本,加跨层错误码提取
├── code-review/
├── test-generation/
└── refactor/
每次踩坑后,把对应的修正加到 Prompt 模板的新版本里,旧版本保留作为"踩坑记录"。这样团队新成员可以直接用最新版,不用重新踩一遍坑。
附:常见问题 FAQ
Q1:为什么不用 Swagger / OpenAPI 自动生成?
A:Swagger 注释需要人工写在代码里,我们的代码里完全没有。让 AI 补 Swagger 注释再生成文档,比直接生成 Markdown 文档多一步,且 Swagger 注释的维护成本和直接写文档差不多。我们的方案是"AI 直接生成 Markdown 文档 + 持续 re-gen",跳过 Swagger 中间层。
Q2:AI 生成的文档怎么保证和代码同步?
A:通过 CI 触发自动 re-gen。每次 main 分支 push,CI 跑完整流水线,提 PR。如果文档有变化,PR 里能 diff 出来,人审 10 分钟合并。如果文档没变化,PR 自动关闭。这样文档和代码的漂移最多在一个 commit 周期内。
Q3:自定义 code-analysis MCP 开发成本高吗?
A:Go + gin 的 AST 提取核心代码约 300 行,2 人天开发完成。其他框架(echo / chi / Spring Boot / FastAPI)适配时间 1-2 人天。这是一次性投入,后续每个项目复用只需 4 小时适配。
Q4:AI 生成的文档质量如何保证?
A:三层保证——
- Prompt 层:硬约束 + 自检清单 + 边界声明
- CI 层:markdownlint + 必备小节校验 + 示例 JSON 解析校验
- 人工层:未决问题清零 + 示例业务正确性抽样 + 隐式契约补充
Q5:这套方案适用于非 Go 项目吗?
A:核心流程(AST 提取 → 参数推断 → 示例生成 → 组装 → 人审)通用,但 AST 提取逻辑需要按语言适配。Java 用 JavaParser,Python 用 ast 模块,Node.js 用 @babel/parser。每种语言 1-2 人天适配。
Q6:Claude Code 的 token 消耗大吗?
A:47 个接口、8 万行代码,整个流水线约 2000 万 token,按 Claude Code 当前定价约 ¥XXX。比 2 周人力成本低一个数量级。如果用 GPT-4 等其他模型,成本类似。
Q7:如果代码本身有 bug,AI 会把 bug 写进文档吗?
A:会。AI 生成的是"代码现状"的文档,不是"代码应该是什么"的文档。如果代码有 bug,文档会如实反映(甚至会在"未决问题"里暴露)。这其实是好事——文档促使人发现代码 bug。
Q8:动态路由怎么处理?
A:AST 扫描标记为"动态路由,待人工补充",列出相关代码文件供人参考。我们项目里 47 个接口中有 3 个动态路由,每个 5 分钟人工补,合计 15 分钟。完全自动化动态路由提取难度太大(需要执行代码),收益不高。
Q9:文档生成后怎么发布?
A:我们用 VitePress 搭建内部文档站,Markdown 直接作为 VitePress 内容源。PR 合并后 CI 自动部署到文档站,分钟级生效。也可以用 MkDocs / Docusaurus / GitBook,选团队熟悉的。
Q10:这套方案对团队规模有要求吗?
A:单人也跑得通——4 小时出文档,1 小时人审,1 人 5 小时完成。团队规模大的好处是可以并行审核(按服务拆分),但单人顺序审也只需 2 小时。核心前置投入是自定义 MCP 开发(2 人天),之后每个项目复用成本极低。
Q11:如何处理接口版本兼容(v1 / v2 / v3 并存)?
A:在 AST 提取阶段,路径里的版本号(/api/v1/、/api/v2/)作为分组维度。每个版本独立生成一份文档,避免字段语义混淆。如果 v1 和 v2 的接口字段有差异,在文档头部的"版本变更说明"里列出差异点。我们的项目里 v1 已废弃但代码还在,AI 会同时生成 v1 文档并标注 [deprecated],便于历史接入方参考。
Q12:如何处理接口的废弃标记?
A:在 AST 提取时,扫描 handler 函数上方的注释,如果包含 // Deprecated: 或 // @deprecated,自动在文档里加"⚠️ 已废弃"标签,并列出废弃原因和替代接口。这样合作方一眼能看到哪些接口不要再用。
Q13:如何处理接口的鉴权方式差异?
A:在"中间件影响"小节里明确写出每个接口的鉴权方式(JWT / API Key / OAuth2 / 无需鉴权)。如果同一接口支持多种鉴权方式,列出所有支持的方案,并说明优先级。我们项目里有 3 个接口是"内部调用免鉴权 + 外部调用需 JWT"的双模式,在文档里单独标注。
Q14:如何处理长字段名截断问题?
A:Markdown 表格在字段路径过长时会撑爆布局。我们在 CI 校验脚本里加了一条规则:字段路径超过 50 字符的,在表格里用缩写(如 items[].promotions[].rule.conditions[].minAmount → items[].promotions[].rule.conditions[].minAmount),完整路径在表格下方的"字段路径说明"小节列出。VitePress 支持 Markdown 表格横向滚动,但移动端体验差,缩写 + 说明是折中方案。
Q15:如何处理接口的幂等性说明?
A:在"功能描述"小节末尾,用 [幂等] 或 [非幂等] 标签明确标注。幂等性判断基于:HTTP 方法(GET / PUT / DELETE 默认幂等,POST 默认非幂等)+ handler 代码中是否有幂等键处理逻辑(如 if exists(req.OrderNo) { return existing })。如果代码中有幂等键处理,在文档里说明幂等键字段名和有效期。我们项目里 order.v2.create 用 orderNo 作为幂等键,24 小时内重复创建返回原订单,这个信息对合作方至关重要。
附:版本演进路线图
我们规划了这套流水线的后续迭代方向:
| 版本 | 计划能力 | 目标 |
|---|---|---|
| v1.0(当前) | Go + gin 全量文档生成 | 已完成 |
| v1.1 | 增加废弃标记 + 鉴权方式说明 | 提升 API 治理 |
| v1.2 | 支持 Go + echo / chi | 扩大 Go 生态覆盖 |
| v2.0 | 支持 Java + Spring Boot | 进入 Java 生态 |
| v2.1 | 支持 Python + FastAPI / Flask | 进入 Python 生态 |
| v3.0 | 自动生成 Postman collection | 一键导入测试工具 |
| v3.1 | 自动生成 SDK 代码(Go / Java / Python) | 降低合作方接入成本 |
| v4.0 | 接口变更自动通知(diff 推送钉钉 / 飞书) | 主动告知合作方 breaking change |
v3.0 自动生成 Postman collection 的价值:合作方拿到的不仅是 Markdown 文档,还有一份可以直接导入 Postman 的 collection 文件,点击就能发请求测试。这把"看文档理解接口 → 手写请求测试"的流程压缩到"导入 collection → 一键发送"。
v4.0 接口变更通知 的价值:每次文档 re-gen 后,diff 出有哪些接口的字段、参数、错误码发生了变化,如果是 breaking change(字段删除、类型变更、必填项新增),自动推送给所有接入方团队。避免"代码改了但合作方不知道"的协作事故。
附:踩坑日志完整记录
以下是整个 4 小时流程中,所有踩过的坑的完整时间线记录,供后续团队参考:
2026-09-01 09:00 流水线启动
09:15 [坑 1] AST 扫描漏掉 3 个动态路由,MCP 加动态路由探测器
09:30 [坑 1 解决] 人工补 3 个动态路由,每个 5 分钟
09:45 [坑 2] int64 字段时间戳单位混淆(秒 vs 毫秒)
10:00 [坑 2 解决] Prompt 加"字段单位强校验"规则
10:15 [坑 3] 嵌套结构循环引用导致解析截断
10:30 [坑 3 解决] MCP 加 visited map,Prompt 加循环处理规则
10:45 [坑 4] 错误码只提取到 18 个(漏 service 层)
11:00 [坑 4 解决] 加跨层提取正则,错误码达 42 个
11:15 [坑 5] 示例金额单位用元(199.00)而非分(19900)
11:30 [坑 5 解决] 业务上下文清单注入 Prompt
11:45 [坑 6] 中间件影响未在文档体现
12:00 [坑 6 解决] Prompt 加"中间件影响说明"小节
12:15 [坑 7] 并行任务 Markdown 格式不统一
12:30 [坑 7 解决] CI 加 markdownlint + 必备小节校验
12:45 [坑 8] 文档 commit hash 与 main 漂移
13:00 [坑 8 解决] 流水线改为一键触发 + 每周定时全量重跑
13:00 流水线完成,进入人工审核
14:00 人工审核完成,56 处修正
14:15 PR merge,文档发布到内部文档站
附:与其他 AI 工具的对比
我们评估了 3 种方案后选择了 Claude Code + MCP:
| 方案 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|
| Claude Code + 自定义 MCP(选中) | 长上下文、可并行、MCP 扩展灵活 | 需要自研 MCP | 大型项目、需要深度定制 |
| Cursor + GitHub Copilot | IDE 内交互、上手快 | 上下文有限、不支持自定义 MCP | 中小型项目、单人使用 |
| ChatGPT + 手动喂代码 | 零门槛、对话式 | 上下文频繁丢失、无法并行 | 接口数 < 10 的小项目 |
| Swagger 自动生成(传统方案) | 标准化、生态成熟 | 需要代码里写注释、维护成本高 | 新项目、规范成熟团队 |
为什么不用 Swagger:我们的代码是 7 年祖传,没有任何 Swagger 注释。让 AI 补 Swagger 注释再生成文档,比直接生成 Markdown 多一步,且 Swagger 注释的维护成本(每次改接口要同步改注释)和直接写文档差不多。我们的方案是"AI 直接生成 Markdown + 持续 re-gen",跳过 Swagger 中间层。
附:内部推广反馈
这套流程在团队内推广 2 周后,收集到的反馈:
- 正面:"以前对接一个新服务要 3 天看代码,现在 1 天看文档就行"——合作方对接同学
- 正面:"文档自动 re-gen 后,再也不用担心文档过期了"——后端开发同学
- 正面:"业务上下文清单整理出来后,新人 onboarding 时间从 2 周缩到 3 天"——团队负责人
- 中性:"自定义 MCP 开发需要 2 人天投入,小团队不一定愿意投"——其他团队反馈
- 负面:"AI 生成的文档偶尔有幻觉(字段语义推断错),需要仔细审核"——审核同学
- 改进建议:"希望能在文档里加'此字段由 AI 推断,置信度 X%'的标记,让人审时重点关注"——审核同学
针对"AI 置信度标记"的建议,我们正在 v1.1 版本中加入:每个字段的"语义说明"列后面加一个 [置信度: 高/中/低] 标记,高 = 有明确证据链、中 = 有间接证据、低 = 主要靠字段名推断。人审时只需重点看"低"置信度的字段。
附:业务上下文清单的演进
业务上下文清单不是一次性写好的,而是随着流水线运行不断完善的。我们的清单经历了 4 个版本:
- v1(第 1 次跑流水线):只有金额单位、时间戳单位 2 条约定,结果 AI 在示例生成时把手机号、坐标、订单号格式全猜错了
- v2(第 2 次跑):补全 ID 格式约定(订单号、用户 ID、SKU 编号),手机号格式,坐标系,示例正确率从 60% 提升到 85%
- v3(第 3 次跑):补全枚举值速查表(订单状态、支付方式、履约状态),错误码语义表,AI 在"功能描述"小节的表达准确率从 70% 提升到 95%
- v4(当前):补全业务约束(单订单最多 100 个 SKU、金额上限、软删除字段语义),AI 在"中间件影响"和"业务约束"小节的覆盖率达 90%
经验:业务上下文清单是"活的文档",每跑一次流水线、每发现一处 AI 推断错误,就把对应的约定补进清单。清单越完善,AI 输出越准,人审越轻松。这是"AI 流水线的复利效应"。
附:业务上下文清单的维护规范
为了让清单不变成"另一份过期文档",我们制定了维护规范:
- 清单版本跟随代码版本:清单文件
business-context.md放在仓库根目录,每次代码改了字段约定,PR 里必须同步改清单(CI 强制校验) - 清单变更触发文档 re-gen:清单文件变化时,CI 自动触发完整文档流水线,确保文档和清单同步
- 清单 review 责任人:每个服务的清单由 service owner 维护,PR 必须有 service owner approve
- 清单格式校验:CI 脚本校验清单的必备小节(字段单位、ID 格式、枚举值、业务约束)是否齐全
附:清单格式校验脚本片段
// scripts/check-business-context.js
const fs = require('fs');
const REQUIRED_SECTIONS = [
'字段单位约定',
'ID 格式约定',
'枚举值',
'业务约束',
];
function checkFile(filePath) {
if (!fs.existsSync(filePath)) {
console.error(`❌ 业务上下文清单不存在: ${filePath}`);
return false;
}
const content = fs.readFileSync(filePath, 'utf8');
const errors = [];
for (const section of REQUIRED_SECTIONS) {
if (!content.includes(`## ${section}`)) {
errors.push(`缺少必备小节: ${section}`);
}
}
// 校验枚举值小节是否有表格
if (content.includes('## 枚举值')) {
const enumSection = content.split('## 枚举值')[1].split('##')[0];
if (!enumSection.includes('|') || !enumSection.includes('---|')) {
errors.push('枚举值小节必须包含 Markdown 表格');
}
}
if (errors.length > 0) {
console.error(`❌ ${filePath}:`);
errors.forEach(e => console.error(` - ${e}`));
return false;
}
console.log(`✅ ${filePath}`);
return true;
}
const ok = checkFile('business-context.md');
process.exit(ok ? 0 : 1);
附:业务上下文清单的团队协作模板
为了让团队成员都能贡献清单内容,我们提供了一份协作模板:
# {{ project_name }} 业务上下文清单
> 维护人:{{ service_owner }}
> 最后更新:{{ date }}
> 变更需在本文件 PR 中同步修改,CI 会校验
## 字段单位约定
| 字段类型 | 单位 | 数据类型 | 示例 |
|---|---|---|---|
| 金额 | 分 | int64 | 19900 表示 199.00 元 |
| 时间戳 | unix 毫秒 | int64 | 1693526400000 |
| 坐标 | GCJ02 度 | float64 | 23.129100 |
| 时长 | 秒 | int64 | 3600 表示 1 小时 |
## ID 格式约定
| ID 类型 | 格式 | 长度 | 示例 |
|---|---|---|---|
| 订单号 | OD + yyyyMMdd + 10 位流水 | 22 | OD20260901000123456789 |
| 用户 ID | snowflake | int64 | 1234567890123456789 |
| SKU 编号 | SKU + 6 位数字 | 9 | SKU001234 |
| 手机号 | E.164 | 13 | +8613800138000 |
## 枚举值
### 订单状态(order_status)
| 值 | 含义 | 备注 |
|---|---|---|
| 1 | 待支付 | 创建后默认状态 |
| 2 | 已支付 | 支付回调成功后 |
| 3 | 履约中 | 已分配仓库开始拣货 |
| 4 | 已完成 | 用户签收 |
| 5 | 已取消 | 用户主动取消或超时 |
### 支付方式(payment_method)
| 值 | 含义 | 备注 |
|---|---|---|
| 1 | 微信 | - |
| 2 | 支付宝 | - |
| 3 | 银行卡 | - |
| 4 | 余额 | 仅老用户可用 |
## 业务约束
| 约束项 | 值 | 实现位置 |
|---|---|---|
| 单订单最多 SKU 数 | 100 | ratelimit.global 中间件 |
| 单次批量创建订单数 | 50 | BatchHandler.Create |
| 单订单金额上限 | 100 万元(100000000 分) | OrderService.Create |
| 软删除字段 | is_deleted (bool) | 所有 model 通用 |
| 订单超时自动取消 | 30 分钟 | 定时任务 order_timeout_job |
## 鉴权方式
| 鉴权方式 | 适用接口 | Header |
|---|---|---|
| JWT | 所有对外接口 | Authorization: Bearer ${token} |
| API Key | 内部服务调用 | X-API-Key: ${key} |
| 无需鉴权 | /api/v1/health, /api/v1/metrics | - |
## 限流策略
| 接口 | 限流维度 | 限流值 | 中间件 |
|---|---|---|---|
| 全部对外接口 | 单 IP | 10 QPS | ratelimit.global |
| order.v2.create | 单用户 | 5 QPS | ratelimit.user |
| order.batch.create | 单用户 | 1 QPS | ratelimit.batch |
附:流水线监控指标
为了持续优化流水线,我们采集了以下监控指标:
| 指标 | 采集方式 | 目标值 | 当前值 |
|---|---|---|---|
| 流水线总耗时 | CI 日志 | < 5 小时 | 4 小时 |
| AST 路由提取覆盖率 | routes.json vs 线上路由 | 100% | 94%(动态路由漏 6%) |
| 错误码提取覆盖率 | 文档错误码 vs 线上日志错误码 | 100% | 95% |
| 字段语义准确率 | 人审修正数 / 总字段数 | > 95% | 91% |
| 示例业务正确率 | 人审抽样 | > 95% | 92% |
| 文档 re-gen 频率 | CI 触发次数 | 每周 >= 1 | 每周 2.3 次 |
| 人工审核耗时 | 审核记录 | < 2 小时 | 1 小时 |
| 未决问题数 | 文档"未决问题"小节 | 趋势下降 | v1: 38, v4: 12 |
这些指标每跑一次流水线自动采集,存到 InfluxDB + Grafana 看板。指标下降时自动告警到飞书群,提示需要优化 Prompt 或 MCP。
附:流水线指标采集脚本
#!/bin/bash
# scripts/collect-metrics.sh
PIPELINE_START=$(date +%s)
# ... 流水线执行 ...
PIPELINE_END=$(date +%s)
DURATION=$((PIPELINE_END - PIPELINE_START))
# 采集路由覆盖率
TOTAL_ROUTES=$(curl -s http://api-gateway/internal/routes | jq length)
EXTRACTED_ROUTES=$(jq '.routes | length' artifacts/order/routes.json)
ROUTE_COVERAGE=$(echo "scale=2; $EXTRACTED_ROUTES * 100 / $TOTAL_ROUTES" | bc)
# 采集错误码覆盖率
DOC_ERROR_CODES=$(grep -c "| [0-9]\+ |" docs/api/v2.4/order-fulfill-api.md)
LOG_ERROR_CODES=$(curl -s http://log-service/api/error-codes | jq length)
ERROR_CODE_COVERAGE=$(echo "scale=2; $DOC_ERROR_CODES * 100 / $LOG_ERROR_CODES" | bc)
# 采集未决问题数
UNRESOLVED=$(grep -c "未知,待人工确认" docs/api/v2.4/order-fulfill-api.md)
# 写入 InfluxDB
curl -XPOST "http://influxdb:8086/write?db=api_doc_metrics" \
--data-binary "pipeline duration=${DURATION},route_coverage=${ROUTE_COVERAGE},error_code_coverage=${ERROR_CODE_COVERAGE},unresolved=${UNRESOLVED}"
echo "Metrics collected: duration=${DURATION}s, route_coverage=${ROUTE_COVERAGE}%, error_code_coverage=${ERROR_CODE_COVERAGE}%, unresolved=${UNRESOLVED}"
最后更新:2026-09-01 作者:AI 工具学校 相关文档: