# 接口文档站 v2 —— 生成与维护说明

## 0. 约定与一句话

**维护约定（负责人 2026-09-10 指定）**：本站点以「产品/运营/业务/开发都能看懂」为验收标准，
后续改动按推荐方案直接执行，不再逐项确认；改动后必须跑 `tools/check.py` 并保持全绿。



**契约的权威来源是后端源码**（`jxxx-dscp` + `chaos`），`data/apis.json` 是唯一数据源，
`v2/` 是渲染产物。**不要手改 v2 里的 HTML**：改 `tools/` 或 `data/` 后重新生成。

## 1. 目录

```
api-all/
├─ index.html              # 入口（自动跳 v2）
├─ ui/                     # 设计系统（CSS/JS 独立文件；视觉与交互参照三省一卡通平台 Apipost 导出文档）
│  ├─ v2.css  v2.js
├─ v2/                     # 新版文档站（纯静态，可 file:// 打开）
│  ├─ index.html           # 总览（数字全部由 build.py 计算 + 我的常用/最近查看）
│  ├─ 三层目录树（照「三省卡接口文档.html」结构）：项目 → 业务域 → 子域 → 接口中文名
│  │   项目节点两个：12301-dscp 后端接口(1,664) + 三省一卡通平台(外部 70)
│  │   **树里的接口叶子（含三省卡）一律直达详情页**，不允许"先进清单页再点一次"
│  │   分组计数徽标才是"跳清单页锚点"的入口；文档节点：全局公共参数在最上，附录 11 个文档页在树底部
│  ├─ frontends.html       # 前端调用总览（1 页，数量口径；7 个单端页已合并，旧链接为跳转桩）
│  ├─ guide.html           # 使用导读：按角色（产品/运营/开发/测试/对接方）+ 图例 + 常见坑
│  ├─ glossary.html        # 业务术语与状态字典（枚举原值＝含义，来自源码）
│  ├─ errorcodes.html      # 错误码字典（ResCode/ChaosRes 常量 + 被多少接口抛出 + 码值复用告警）
│  ├─ external/sanka.html  # 三省卡接口清单（外部一卡通平台 70 条）
│  ├─ external/ep/<名称>.html # 三省卡接口详情页（与内部接口同一套模板与模块）
│  ├─ changes.html         # 接口变更清单（快照 diff：新增/删除/字段与状态变更）
│  ├─ todos.html           # 数据待办清单（缺摘要/口径不一致/免鉴权复核/错误码复用，带源码位置）
│  ├─ domain/<域>.html     # 每个业务域一页业务总览（对象/状态/表/风险）
│  ├─ handbook.html        # 调用手册：前缀 / 主机 / 鉴权 / 响应包装 / 错误码现状
│  ├─ catalog.html         # 全量目录
│  ├─ usage-diff.html      # 在用口径修正（旧版误判清单 + 证据）
│  ├─ health.html            # 规范体检 + 源码 vs 旧站文档差异
│  ├─ fields.html tables.html# 字段/DTO 字典、数据表 → 接口 反查
│  ├─ about.html             # 数据口径与诚实性边界
│  ├─ api/<域>[-N].html      # 接口页（契约视图，每页 ≤46 个）
│  ├─ frontends/<仓库目录名>.html
│  └─ assets/{v2.css,v2.js,search.js}
├─ data/                   # 单一数据源（可 diff、可提交）
│  ├─ apis.json            # 权威：契约 + 调用链 + 表 + 错误码，全部源码直出
│  ├─ apis_from_html.json   # 旧站反解析结果（补充字段→表列/注释/示例，并用于交叉校验）
   ├─ changes.py           # 快照 + 逐条契约 diff（带解析口径指纹，区分代码改动/工具升级）→ data/snapshots/、data/changes.json
│  ├─ changes.json         # 相对上一份不同快照的变更清单
│  ├─ frontends_scan.json  # 7 端源码请求字面量与匹配证据
│  ├─ taxonomy.json deprecated.json diff_java_html.json
│  └─ openapi.json         # OpenAPI 3.0 导出（导入 Apifox/Postman）
├─ legacy/                 # 旧版站点，只读归档（不再维护）
└─ tools/                  # 生成器
   ├─ build_all.sh         # 一键重跑（推荐入口）
   ├─ extract.py           # legacy/ HTML → apis_from_html.json
   ├─ parse_java.py        # 后端源码 → apis.json（权威）+ 差异报告
   ├─ chains.py            # Controller→Service(Impl)→Mapper→实体→表 调用链遍历 + 抛错码抓取
   ├─ scan_frontends.py    # 前端源码 → frontends_scan.json
   ├─ scan_deprecated.py   # @Deprecated 标记
   ├─ taxonomy.py          # 包名 → 业务域 / 子域 / 对接方（人工维护）
   ├─ fields_clean.py      # 校验注解 → 必填 + 干净说明
   ├─ to_openapi.py        # OpenAPI 导出
   ├─ glossary.py          # 枚举/业务对象/数据表/对接方 → data/glossary.json
   ├─ errorcodes.py        # ResCode/ChaosRes 常量 + 抛错点统计 → data/errorcodes.json
   ├─ sanka.py             # 三省卡（外部一卡通平台）Apipost 导出页 → data/sanka.json
   ├─ biz_status.py        # 业务状态/维护人/最后改动/建议摘要（代码 + git blame 推断）→ data/biz_status.json
   ├─ changes.py           # 快照 + 逐条契约 diff（带解析口径指纹，区分代码改动/工具升级）→ data/snapshots/、data/changes.json
   ├─ build.py             # 渲染 v2/
   ├─ check.py             # 一致性校验（断链/锚点/数字/坏标签/结构完整性/搜索索引/体积预算）
   ├─ test_ui.js           # 前端渲染自测（极简 DOM 桩）：树叶子数、高亮/展开、不得滚动整篇文档
   ├─ test_click.py        # 真实浏览器点击回归（无头 Chrome）：树/搜索点一次是否直达详情
   ├─ click_probe.py       # 生成点击探针页（复制到 /tmp/lay2，注入读取/点击/搜索脚本）
   ├─ test_layout.sh       # 可选：无头 Chrome 量 390/820/1440 下是否横向溢出
   ├─ layout_probe.py      # 生成布局探针页（注入 overflow/widest 探针）
   ├─ publish.sh           # 发布到 api.hn12301.net（本地执行，含发布前自检闸门）
   ├─ publish_remote.sh    # 服务器端解包 + 旧链接保活 + 5 站点回归
   └─ README.md
```

## 1.5 发布到线上（api.hn12301.net · 站点 5）

```bash
cd api-all
python3 tools/build_all.sh     # 先重建 + check 全绿
sh tools/publish.sh            # 默认 root@121.43.239.220，会提示输入密码
```

`publish.sh` 只上传 `index.html`、`v2/`、`legacy/`、`tools/`、`data/openapi.json`
（不把 `data/` 的中间产物与快照推上服务器）。服务器端 `publish_remote.sh` 会：
先备份 webroot 到 `/root/api-html-backup-<时间戳>.tar.gz` → 解包覆盖 →
把旧站深链接（`modules/`、`frontends/`、`assets/`、`about.html`、`三省卡接口文档.html` 等）
做成 `legacy/` 的符号链接保活 → 清 AppleDouble/`.DS_Store` → `chmod a+rX` →
自动跑 **5 个站点回归 curl**。全程只写 `/home/api/html`，不改 nginx 配置、不碰其他站点目录。

运维细节与缓存策略见 `/Users/jacobxu/Documents/obsidian/工作资料/121.43.239.220(H5站点服务器)运维手册_2026-08-04.md` 站点 5 一节。

## 2. 重新生成

```bash
cd api-all
./tools/build_all.sh                 # 一条命令跑完整条流水线 + 校验
# 或分步（顺序即依赖顺序）
python3 tools/extract.py             # legacy HTML → data/apis_from_html.json
python3 tools/parse_java.py [路径]   # 后端源码 → data/apis.json（默认 ../jxxx-dscp，含 chaos）
python3 tools/scan_frontends.py      # 前端源码 → data/frontends_scan.json
python3 tools/scan_deprecated.py     # @Deprecated → data/deprecated.json
python3 tools/taxonomy.py            # 业务域映射
python3 tools/glossary.py            # 业务术语与状态字典 → data/glossary.json
python3 tools/errorcodes.py          # 错误码字典 → data/errorcodes.json
python3 tools/sanka.py               # 三省卡外部文档 → data/sanka.json（源文件在 legacy/ 下）
python3 tools/biz_status.py          # 业务状态/维护人（读 jxxx-dscp 的 git 历史，只读不改代码）
python3 tools/to_openapi.py          # data/openapi.json
python3 tools/build.py               # 渲染 v2/
   ├─ changes.py           # 快照 + 逐条契约 diff（带解析口径指纹，区分代码改动/工具升级）→ data/snapshots/、data/changes.json
python3 tools/check.py               # 校验，失败返回非 0
```

要求：Python 3.8+，无第三方依赖，不需要网络，不需要启动服务。
建议把 `python3 tools/check.py` 放进 CI 或提交前钩子。

## 3. 常见维护动作

| 想改什么 | 改哪里 |
|---|---|
| 后端换了目录 / 多仓库 | `tools/parse_java.py` 的 `roots`（默认 jxxx-dscp + chaos + chaos-component） |
| 新增一个前端仓库 | `tools/scan_frontends.py` 的 `PROJECTS`（目录名→展示名、源码子目录、渠道前缀） |
| 确认了生产域名 | `tools/build.py` 顶部 `HOSTS`，重跑 build+check（一处改全站生效） |
| 业务域 / 子域 / 对接方命名 | `tools/taxonomy.py`（`DOMAINS` / `MODULE_DOMAIN` / `WORD_CN` / `PARTNERS`） |
| 每页接口数、体积预算 | `build.py` 的 `S.PER`；`check.py` 的预算（清单页 ≤900KB/16000 标签，详情页 ≤90KB/4000 标签） |
| 详情页必备区块（防改版漏区块） | `check.py` 里 `id="info"/req/req-h/req-q/req-p/req-b/resp/sample/impl` 断言 |
| 目录树结构（域→子域→接口） | 数据 `build.py` 的 `build_tree_data()`；服务端直出 `tree_seed()`/`sanka_seed()`；前端渲染 `ui/v2.js` 的 `renderTree()` |
| 树/搜索的落点（详情 vs 清单） | 三个地方必须同旹：`v2.js` 的 `renderTree()` 叶子 href、`build.py` 的 `sanka_seed()`、`build_index_sanka()` 的第 11 项 href。改一处要跑 `python3 tools/test_click.py` |
| 三省卡文件名/树/锚点不一致 | 全部用 `build.py` 的 `sk_slug()`（Python 的 `\W` 会保留汉字，JS 的 `[^\w]` 不会 → 切勿在 JS 里重算 slug，用数据里给好的）；**内部子域锚点同理**：`build.py` 的 `sub_anchor()` 算好后随树数据一起给前端（曾有 198 个子域因前端重算导致点击无反应）；`check.py` 断言每个锚点在清单页确存在 |
   ├─ changes.py           # 快照 + 逐条契约 diff（带解析口径指纹，区分代码改动/工具升级）→ data/snapshots/、data/changes.json
| 详情页分区与右侧导航 | `build.py` 的 `render_ep_pages()` 内 `sec()`；新增分区要同步 `check.py` 的必备区块清单 |
| 配色/交互基调 | `tools/ui/v2.css`（强调橙 `#ea6a0c`、选中底 `#fff8f3`、白面板 + 灰底代码块） |
| 树的层级与叶子文案 | `build.py` 的 `nav_html()`（三层树）与 `leaf_label()`（叶子＝中文接口名，自动去掉源码里的 `xuchao:` 这类作者前缀） |
| 详情页分区顺序/表头 | `build.py` 的 `render_ep_pages()` 内 `sec(...)` 与 `THEAD`；参数值列由 `sample_value()` 生成 |
| 前端调用信息 | 已并入接口「基本信息 → 调用方」；总览在 `frontends.html`（`build_all` 后自动重生成，单端页为跳转桩） |
| 字段说明清洗规则 | `tools/fields_clean.py` |
| 文案/样式/交互（人话摘要、图例、收藏、Markdown 复制） | `tools/ui/v2.css`、`tools/ui/v2.js`、`build.py` 的 `render_*` |
| 首页业务域卡片（点击范围、两个入口） | `build.py` 的 `render_index()` 里 `cards` 循环 + `v2.css` 的 `.dcard`/`.dbtn` |
| 接口的「动作」判定（新增/查询/导出/回调…） | `build.py` 的 `ACTION_WORDS` / `ACTION_CN` |
| 调用链噪声过滤（不进链的方法名） | `tools/chains.py` 的 `SKIP_METHODS` / `SKIP_OPS` |
| 子域中文名 | `tools/taxonomy.py` 的 `WORD_CN` / `SUBDOMAIN_OVERRIDE` |

## 3.3 详情模板（内部接口与三省卡共用一套）

`build.py` 的 `render_detail(vm, ...)` 是唯一详情页模板，模块与顺序照「三省卡接口文档.html」：

```
基本信息（接口状态 / 接口URL / 请求方式+Content-Type / 暴露渠道 / 名称来源 / 调用方 /
          需求上下文 / 维护人+最后改动 / 读写数据表 / 控制器 / 源码位置 / 判定依据）
请求参数 → Header 请求参数 · Query 请求参数 · 路径参数及说明 · Body 请求参数
          （统一 5 列：参数名 参数值 是否必填 参数类型 描述说明；空参数也出表头+占位行）
认证方式 → 响应参数 → 响应示例（成功/失败） → 附录·实现细节（调用链/表/证据/注释）
```

- 内部接口：`v2/ep/<id>.html`（1,664 页），数据来自 `apis.json`
- 三省卡接口：`v2/external/ep/<slug>.html`（70 页，slug=`sk_slug(接口名)`），数据来自 `sanka.json` 的 Apipost 导出参数表
- **叶子直达详情**：目录树、全局搜索点一次就要进到详情页（历史上三省卡曾指向清单页锚点，导致「要点两次」）
- 三省卡详情页 `body[data-sk=<slug>]` 带当前接口，树据此高亮 + 自动展开所属分组；分组顺序与清单页、上一/下一完全一致
- 搜索索引 `assets/search.js` 含 1,664 内部 + 70 外部；内部 id 是数字（锚点 `#ep-<id>`），外部 id 是 `sk-<slug>`（锚点 `#sk-<slug>`）
- 两者走同一个 `vm`（视图模型）→ **模块内容、表头、UI 完全一致**；`check.py` 断言两套页面必须包含同一组区块 id 与 5 列表头

### 接口名称策略（`biz_status.py`）

优先级：**源码 javadoc → 操作日志注解 → 方法名推断（带子域前缀）**。
git 提交说明**不再用作名称**（一次提交常覆盖多个接口，如"新增…共9个接口"，当名称会含糊），
只作为详情页「需求上下文」一行展示。非源码注释的名称一律带「推断名·来源」标记，`todos.html` 可导出补注释清单。

## 3.4 布局约定（改样式前必读，都是踩过的坑）

- **禁止嵌套 `<a>`**（HTML 规范不允许）：浏览器遇到内层 `<a>` 会**提前闭合外层**，
  于是"整张卡片可点"和卡片里的小链接全郣乱掉——首页业务域卡片就踩过（用户反馈"像是一堆一样的链接"）。
  **整卡可点的正确做法**：卡片用 `<div>`，标题链接的 `::after` 用 `position:absolute;inset:0` 铺满全卡，
  卡内其它可点元素（如「业务总览」按钮）用 `position:relative;z-index:1` 盖回来。
  `check.py` 全站断言不得出现嵌套 `<a>`；`test_click.py` 用 `elementFromPoint` 做真实命中测试。
- **禁止用负边距做"通栏"**：`.top` 曾用 `margin:0 -30px` 配 `main{padding:var(--pad)}`，
  窄屏 `--pad` 变小时两者不匹配 → 顶栏比视口宽 → 整页可左右滑动。现在顶栏留在内容列内。
- **网格/弹性子项一律 `min-width:0`**（`.wrap>*{min-width:0}`），否则长 URL、长路径会把列撑破。
- **长文本换行兜底**：`code{overflow-wrap:anywhere}`、`img/table/pre{max-width:100%}`。
- **响应式三档**：`--pad` 随断点变化（30/20/16）；≤1180 隐藏右侧内容导航；≤980 隐藏清单表"调用端"列；
  **≤820 表格改为逐条堆叠**（`td::before{content:attr(data-h)}` 显示字段名，单元格需带 `data-h`，
  由 `build.py` 的 `fld_rows()/ep_row()` 输出）。
- **CSS 变量必须在 `:root` 里真的定义**：`.wrap{grid-template-columns:var(--navw) …}` 若变量缺失，
  整行声明失效 → 网格塌陷成单列、`main` 左内边距变 0。改完样式要 grep 确认变量存在。
- 菜单宽度可拖拽：`#grip`（`--navw` 200–560px，存 localStorage，双击复位）；详情区与菜单间距由 `--gap-x` 控制。
- 长参数值（如 JWT token）必须 `max-width + word-break`，否则表格撑破页面造成横向滚动。
- 检测：`sh tools/test_layout.sh`（可选，需本机 Chrome）；`node tools/test_ui.js`（必跑，含
  "渲染不得滚动整篇文档"断言）；`python3 tools/test_click.py`（真实浏览器点击：树与搜索点一次直达详情、
  落地后当前接口高亮、分组展开；无 Chrome 自动跳过）。
- **dump-dom 会把单引号属性序列化为双引号**：浏览器断言的正则要兼容两种引号（历史上 check.py 因只匹配双引号假通过）。

## 3.5 业务状态是怎么来的（biz_status.py）

没有人提供接口台账，所以状态全部从代码与 git 推断，低置信度一律写「待确认」：

| 状态 | 依据 | 当前数量 |
|---|---|---|
| 前端在用 | 7 个前端仓库源码里能精确匹配到调用点 | 676 |
| 开放/回调用（服务端） | 渠道为开放/回调且有实现链路（第三方或支付回调触发） | 250 |
| 脚手架生成·未接入前端 | 控制器 @author Chaos-Toolkits 且方法是 add/update/delete/get/query 全套 CRUD，但前端无调用 | 586 |
| 未见调用·长期未改动（待确认） | 无调用证据且最后改动 >180 天 | 110 |
| 待确认（可能被服务端内部调用） | 无调用证据但近期有改动 / 非脚手架 | 17 |
| 近期改动·未见调用 | ≤45 天内改动且无调用 | 10 |
| 测试/临时接口 | 路径段含 test/demo/mock 或注释含「测试环境/临时/假数据」 | 9 |
| 已下线 | 源码里映射被注释掉（另附 7 条被注释的映射清单） | 6 |

- 维护人 = git blame 到接口映射行的作者；「最近提交」即需求上下文。
- 缺摘要的接口给出建议摘要（来自 @SysOperateLog 名称、commit 说明、逻辑摘要、方法名），标注需人工确认。
- 该脚本只读 jxxx-dscp，不写任何业务仓库。

## 4. 已验证的解析质量（源码 vs 旧站）

| 项 | 结果 |
|---|---|
| 接口总数 | 源码 1,660 条映射（旧站 1,646 条记录，其中 241 条只有路径没有契约） |
| 调用链源码化 | 1,660 个接口的调用链 / 涉及数据表 / 抛错码 / **字段→表列** 全部由 `chains.py` 从方法体与实体 `@TableName` 遍历得出（表集合与旧站相似度 0.94，166 个接口拿到旧站没有的表；1,394 个接口的列映射来自源码） |
| 错误码 | 46 个（业务 26 + 框架 20），26 个被接口显式抛出；发现码值 `20010` 被 3 种不同含义复用 |
| 变更清单 | 相对旧站基线：259 新增 / 0 删除 / 805 契约变更（含 pageNum→pageNo、免鉴权标记补全） |
| 旧站漏记 | **18 个**（`/statistics/statHome*` 7 个、`/zhyIcbc/*` 4 个、`/aliyun/face/*` 2 个等） |
| 旧站幽灵接口 | **4 个** `/tickets/order/appointment*`（源码里是 `//` 注释掉的映射） |
| 精简条目补全契约 | **238 个** |
| 分页入参字段名 | 旧站写 `pageNum`，真实字段是 `pageNo`（`AbstractPageRequest`）——已按源码修正 |
| 缺中文摘要 | 187 → **66**（源码 javadoc 补回 121 个） |
| 免鉴权接口 | `@Route(auth = false)` **430 个**，旧站从未体现，v2 打「免鉴权」标记 |
| Body 无法展开 | 16 个（类型在依赖 jar 内 / 为 Map、基础类型） |

## 5. 仍未解决（需要人决策，不是我没做）

1. **错误码字典**：后端 `new ErrorCode(` 命中 0 次，错误码在 `system_error_code` 表维护
   → 导出该表进 `data/`，或在 chaos 落 ErrorCode 常量。
2. **生产主机未确认** → v2 不写假 pro 地址；确认后改 `build.py` 的 `HOSTS`。
3. **66 个接口缺摘要**：源码无 javadoc，清单在 `v2/health.html`，补完重跑即消失。
4. **对接方代号未确认**：cqy / ssk / szh / tstc / pft / nydm / tgy / xjhlc / ysf / ybx 等，标签写「待业务确认」。
5. **「未发现调用」≠ 可下线**：只扫 7 个前端仓库源码字面量；服务端互调、定时任务、
   未纳入的端、`uni.uploadFile` 类动态拼接不在范围内（`pingxiangcard` 的 `/oss/upload*` 就漏了）。
6. **仅两项仍来自旧站解析**：方法内联注释、逻辑摘要（卡片折叠区已标注来源）。
   契约、调用链、数据表、字段→表列、错误码、示例均已源码直出。
   `tools/extract.py` 现在的作用＝补充这两项 + 作为源码解析结果的交叉校验基线，可独立移除而不影响契约正确性。
7. **全仓 `@Deprecated` = 0**：目前没有表达「接口已废弃」的手段，建议对不再推荐的接口补注解。
