Files
lingxi/docs/工程规范/04-API与路由规范.md
Stev_Wang a1e28daec5
ci / 质量门禁 (push) Canceled after 0s
docs(routing): 新增前端路由与目录规范并收敛计划路由
背景:
- 前端路由缺少统一规范:导航模块 key 与 URL 首段不一致,页面目录未镜像
  路由,04 规范中的历史路由示例与实际实现不符;
- 用户确认「平台」更名为「管理中心」,路由统一为 /<模块>/<功能>。

改动:
- 新增 11-前端路由与目录规范:模块与基础路径表、路径语法(复数 kebab-case、
  /new、:id、层级与 query 状态)、pages/<模块>/<功能>/ 目录映射、路由注册、
  旧链重定向、新增页面检查清单;
- 04 §8 的 API-27 改为模块化路由并链接 11;工程规范索引与 AGENTS §9 登记 11;
- 开发计划:平台更名管理中心,附录 C 页面清单收敛为模块化路由,§7.1 / §8.3
  同步,版本记录补「路由规范回写」;
- A1 实施报告路由说明更新为 /system/users(旧 /users* 重定向)。

影响面:
- 仅文档与规范,不改变功能范围、权限点与后端接口;后续批次按 11 执行。

回滚方式:
- 回滚本提交即恢复原附录 C 与 04 §8 描述;代码不受影响。

验证证据:
- pnpm format:check 通过;路由与代码行为见相邻的 web 重构提交。
2026-11-03 10:30:55 +08:00

163 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
# 04 · API 与路由规范
| 项 | 内容 |
| --- | --- |
| 文档版本 | V1.0(2026-11-02) |
| 依据 | 技术架构 §6(接口设计)、§3.2(模块划分) |
| 适用范围 | `apps/api` 全部 HTTP / SSE 接口、`apps/web` 全部前端路由 |
| 强制级别 | 强制 |
---
## 1 URL 与版本
- `API-01` 服务端统一前缀 `/api/v1`;版本在 **URL** 中,不使用 header 版本。
- `API-02` 资源名用**复数 kebab-case**:`/brief-cards`、`/media-tasks`、`/industry-packs`。
- `API-03` 嵌套 ≤ 2 层:`/clients/:clientId/scripts`;更深的归属用 query 过滤。
- `API-04` 动作优先用子资源表达(`POST /scripts/:id/approve` → 可接受),**禁止**在 URL 用动词拼接业务流(`/doGenerate`)。
- `API-05` 破坏性变更(删字段 / 改语义 / 改状态码)必须升 `/api/v2`;新增可选字段不升版本。
- `API-06` 对外契约由 OpenAPI 生成,`pnpm openapi:diff` 检出破坏性变更即阻断(见 [06](06-测试与CI规范.md))。
---
## 2 请求约定
| 项 | 约定 |
| --- | --- |
| 认证 | `Authorization: Bearer <access_token>`;refresh 走独立端点 |
| 幂等 | 写接口支持 `Idempotency-Key`(24h 内同 key 返回首次结果);**任务提交必带** |
| 内容类型 | `application/json; charset=utf-8`;上传走预签名直传,不经 API 中转 |
| 时间 | ISO 8601 带时区(`2026-11-02T14:30:00+08:00`),**存储统一 UTC** |
| ID | ULID 字符串,26 字符;请求体里的 ID 一律字符串 |
| 字段命名 | JSON body 用 **camelCase**;query 用 **snake_case**(`cursor` / `limit` / `sort` / `next_cursor`,与架构文档示例一致) |
| 未知字段 | 严格模式拒绝(Zod `.strict()`),避免静默丢弃 |
- `API-07` 所有入参用 Zod schema 校验,Controller 不写手写校验;动态 schema(要素卡)用 Ajv + 白名单。
- `API-08` 分页:`?cursor=<opaque>&limit=50`,返回 `next_cursor`;`limit` 默认 50、上限 200。
- `API-09` 排序:`?sort=-created_at`(`-` 为降序);仅允许白名单字段,非法字段 400。
- `API-10` 过滤:显式白名单参数;禁止把任意 query 透传给 Prisma。
- `API-11` 列表**禁止返回大字段**(JSONB 富文本、base64、完整分镜);产物只返回签名 URL。
---
## 3 响应约定
**成功**
- 单个资源:直接返回对象(不套 `data` 信封)。
- 列表:`{ "items": [...], "next_cursor": "..." }`;无下一页时 `next_cursor` 为 `null`。
- 创建:`201` + 资源;异步任务提交:`202` + `{ taskId, estimatedItems, estimatedCost, estimatedSeconds }`。
- 删除:`204` 无 body。
**失败**(统一结构,禁止其他形状)
```json
{ "code": 40302, "message": "无权访问该客户数据", "details": {}, "trace_id": "01JA..." }
```
- `API-12` `message` 面向用户、中文、不含内部实现细节;`details` 放字段级错误;`trace_id` 必填。
- `API-13` 禁止把堆栈、SQL、密钥、供应商原始报文写进响应。
---
## 4 错误码表
| 段 | 含义 | 常用码 |
| --- | --- | --- |
| `400xx` | 参数校验 | `40001` 参数缺失 / 格式错误 |
| `401xx` | 认证 | `40101` 未登录、`40102` token 过期、`40103` 签名失败 |
| `403xx` | 授权 | `40301` 无权限点、`40302` 数据范围外、`40303` 缺特殊权限、`40304` 违反职责互斥 |
| `404xx` | 不存在 | `40401` 资源不存在 / 已删除 |
| `409xx` | 冲突 | `40901` 版本冲突、`40902` 重复提交、`40903` 状态不允许 |
| `422xx` | 业务规则 | `42201` 业务校验失败、`42202` 合规拦截(禁忌词 / 极限词)、`42203` 成本超预算 |
| `429xx` | 限流 | `42901` 请求过频、`42902` 并发任务超限 |
| `500xx` | 系统 | `50001` 内部错误、`50002` 依赖不可用、`50003` 超时 |
- `API-14` 新增错误码必须集中登记在 `packages/shared` 的错误码常量,禁止在业务代码里写裸数字。
- `API-15` 授权类错误**一律 403 且不区分「不存在」与「无权限」的原因**(避免探测他人数据;日志里保留真实原因)。
---
## 5 鉴权与权限校验链
顺序固定,不得跳步:
```
JwtGuard → PermissionGuard(权限点)→ SpecialPermissionGuard(特殊权限)
→ ScopeGuard(数据范围)→ 业务校验 → Repository(ScopeContext) → 审计埋点
```
- `API-16` 权限点由代码注册(常量),角色是权限点组合(数据化),见技术架构 D-07。
- `API-17` 特殊权限(导出 / AI 配置 / 审计查看 / 行业包发布 / 跨客户复用授权)必须单独授予,不得打包进普通角色。
- `API-18` 数据范围解析失败**一律 403**,禁止降级为「全量可见」。
- `API-19` `/public/review/*`、`/public/share/*` 是**唯一免 JWT** 路由组,必须:HMAC 签名 + 有效期 + 范围(只读 / 可改稿)+ 限流 + 访问留痕;不得在其中暴露任何其他资源。
- `API-20` 每个端点必须有 3 类权限测试用例(有权限 / 无权限 / 范围外),见 [06](06-测试与CI规范.md)。
---
## 6 异步任务协议(所有耗时操作统一)
| 步骤 | 约定 |
| --- | --- |
| 提交 | `POST /xxx-tasks`,带 `Idempotency-Key`,返回 `202` + `{ taskId, estimatedItems, estimatedCost, estimatedSeconds }` |
| 订阅 | `GET /xxx-tasks/{id}/events`(SSE),事件 `item` / `progress` / `done` / `error`,支持 `Last-Event-ID` 续推 |
| 兜底 | `GET /xxx-tasks/{id}` 轮询(前端 SSE 失败 2 次后自动降级) |
| 取消 | `POST /xxx-tasks/{id}/cancel`;仅未开始项取消,已开始项等待结束 |
| 重试 | `POST /xxx-tasks/{id}/retry`;**只重跑失败项**,成功项不得重算成本 |
| 产物 | 一律签名 URL(5–15 分钟),随 `item` / `done` 事件下发 |
- `API-21` 长耗时操作**禁止**放在同步请求里(> 2s 一律异步)。
- `API-22` `estimatedCost` 必填;为 0 也要返回(前端展示"预估成本"是 PRD R-06 硬要求)。
- `API-23` 任务终态写入后事件必须可重放(存事件表或 Redis Stream),断线可恢复。
---
## 7 上传与下载
| 场景 | 约定 |
| --- | --- |
| 上传 | 预签名 PUT 直传对象存储;前端先算 `sha256` 走秒传;> 100 MB 用分片(`listMultipart`) |
| 校验 | 上传完成后服务端 `headObject` 校验大小 / 类型;不一致则任务失败并清理 |
| 下载 | `302` 到签名 URL(5–15 分钟),支持 `Range`(视频预览断点续播) |
| 导出包 | 服务端打包 → `exports` 桶 → 签名 URL,产物保留 3 天 |
| 限制 | 单文件上限读环境变量 `MAX_UPLOAD_MB`,**禁止**代码里写死 |
- `API-24` 禁止 API 进程中转文件流(内存与带宽不可控)。
- `API-25` 签名 URL 不得记录到日志(含 query 签名参数)。
---
## 8 前端路由
> 前端路由的模块表、路径语法与目录映射以 [11](11-前端路由与目录规范.md) 为准;本节保留通用约束。
- `API-26` 路由常量集中 `apps/web/src/routes.ts`,禁止散落字符串路径。
- `API-27` 前端路由统一为 `/<模块>/<功能>`:`/system/users`、`/scripts/library`;模块表、目录映射与旧路径重定向见 [11 · 前端路由与目录规范](11-前端路由与目录规范.md)。
- `API-28` 统一布局路由:登录后进入 `AppLayout`(侧边导航 + 面包屑),未登录访问自动跳 `/login` 并带回跳地址。
- `API-29` 权限守卫组件 `<RequirePermission code="SCRIPT_APPROVE">`;无权限渲染 403 页,**不隐藏路由**(避免"菜单有、点进去白屏")。
- `API-30` 页面级懒加载(`React.lazy` + `Suspense`),首屏包体积纳入性能预算。
- `API-31` URL 是唯一状态源:筛选 / 分页 / 选中态可分享的必须进 query(`useSearchParams`),禁止只存 Zustand。
- `API-32` 404 / 403 / 500 三个页面必须存在,且 500 页展示 `trace_id` 供排查。
---
## 9 契约与兼容
- `API-33` OpenAPI 由 NestJS 装饰器生成,提交到仓库,CI 用 `oasdiff` 检测破坏性变更。
- `API-34` 新增字段必须可选且前端有默认值;删除字段先标记 `deprecated` 一个版本。
- `API-35` 前端不得依赖未在 OpenAPI 声明的字段。
---
## 10 检查清单
- [ ] URL 复数 kebab-case,版本 `/api/v1`,嵌套 ≤ 2 层
- [ ] 入参 Zod 校验,未知字段拒绝
- [ ] 列表走游标分页,不返回大字段
- [ ] 错误体 `{ code, message, details, trace_id }`,错误码取自常量
- [ ] 守卫链完整,特殊权限单独校验
- [ ] 任务提交带 `Idempotency-Key` 与预估成本
- [ ] 产物只给签名 URL,不打日志
- [ ] 前端路由常量集中,权限守卫 + 三张错误页齐全
- [ ] OpenAPI 已更新,`openapi:diff` 通过