jwb-api-review · diff
git:20260915.c01fcc9 to v26.9.15
3 added, 0 removed. Audit A to A.
---
name: jwb-api-review
description: Use when 审查或新增求职工作台后端 API 时,需要按本仓库的错误码契约、状态码语义、两段式写入与安全边界逐条核对。English triggers:API review, REST design, error codes, AIP-193, Zalando API guidelines, two-phase write.
compatibility: 面向「开发 / 扩展本仓库后端」的场景;仓库在侧(jobws 指仓库内的 python tools/jobws.py);Python 3.12+。
+ license: MIT
+ metadata:
+ version: 26.9.15
---
# 后端 API 审查清单
外部参考:Google AIP-193(错误)、AIP-158(分页)、Zalando RESTful API Guidelines。
本文件是本仓库的**落地口径**——与既有代码 / 测试冲突时以测试为准。
## 错误:加法而非替换
- 每个错误必须同时带 `detail`(人读中文原文)与 `error_code`(稳定语义标识),
用户可见的动态值进 `error_params`;
- 命名 `<域>.<语义>`,域与路由对齐(ws / app / status / approval / sys / …);
**同一语义跨路由必须复用同一个 code**;
- 前端语言包按 code 出一份文案——改 code 等于破坏前端文案,要当契约对待;
- 新增 code 必须同步:两个语言包 + 自动校验(判定脚本会扫全部 ApiError 调用,
校验 code ↔ 文案 ↔ 参数三者的对齐)。
## 状态码语义(本仓库口径)
| 码 | 用于 |
|---|---|
| 400 | 值层面拒绝:未知键、越界的目标、非法路径 |
| 404 | 记录 / 工作区不存在 |
| 409 | 冲突:预览后数据变了、乐观并发失败(重试前必须重新读取) |
| 422 | 语义校验失败(格式、枚举、终态不回退);令牌不可用 |
| 200 + ok:false | MCP 工具层的「可读拒绝」形态(宿主不当异常处理) |
## 写入:两段式与安全边界
- 写端点先预览(不落盘)→ 一次性令牌 → apply;「要写什么、写到哪里」在令牌里,
**不在请求里**——apply 不接收路径 / 载荷参数;
- apply 的落盘段必须持资源锁(「读最新 → 重校验 → 写」整段互斥;锁在领域层的
落盘函数里,不在 web 层单点加——CLI / MCP 走同一条路);
- 工作区参数只收相对名,`realpath` 后校验在允许根之内;越界拒绝而非警告;
- 路径经安全拼接;错误响应不得泄露未授权位置的信息。
## 形状与分页
- 资源名用复数、动作由 HTTP 方法承载;响应字段 camelCase;与 CSV 列名对齐的
数据字段保持原样(中文列名不翻,展示层再映射);
- 列表响应:`items` + `total`;大集合设上限并提示「另有 N 条」(返回体不随
数据量膨胀);
- 时间用 ISO 8601 字符串;「未提供」用 null,不用空字符串表达。
## 审查动作(逐条走)
1. 列出本 PR 的全部写端点:每个都有预览 + 令牌吗?落盘锁在哪一层?
2. 扫 ApiError 调用:code 是否与既有同义 code 重复定义?参数是否齐全?
3. 扫前端 `err.<code>`:新增 code 两侧语言包都补齐了吗?
4. 路径与工作区边界:有没有绕过既有校验的新入口?
5. 兼容面:默认行为是否不变?破坏性变更是否走了版本流程?
## 常见错误
| 错误 | 后果 | 正确做法 |
|---|---|---|
| 新错误只给 detail 不给 code | 英文界面露出中文原文 | 同步双语言包 |
| 同义错误用了两个 code | 前端文案漂移 | 复用同一个 code |
| 写端点直接落盘 | 用户失去确认权 | 两段式 + 令牌 |
| 锁只加在 web 层 | CLI / MCP 路径并发丢写 | 落盘函数自持锁 |
| 列表接口不设上限 | 大数据撑爆响应 | items 上限 + total |