ci / 质量门禁 (push) Canceled after 0s
背景: - 前端路由缺少统一规范:导航模块 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 重构提交。
163 lines
8.6 KiB
Markdown
163 lines
8.6 KiB
Markdown
# 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` 通过
|