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 重构提交。
8.6 KiB
8.6 KiB
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)。
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。
失败(统一结构,禁止其他形状)
{ "code": 40302, "message": "无权访问该客户数据", "details": {}, "trace_id": "01JA..." }
API-12message面向用户、中文、不含内部实现细节;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。
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-22estimatedCost必填;为 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 为准;本节保留通用约束。
API-26路由常量集中apps/web/src/routes.ts,禁止散落字符串路径。API-27前端路由统一为/<模块>/<功能>:/system/users、/scripts/library;模块表、目录映射与旧路径重定向见 11 · 前端路由与目录规范。API-28统一布局路由:登录后进入AppLayout(侧边导航 + 面包屑),未登录访问自动跳/login并带回跳地址。API-29权限守卫组件<RequirePermission code="SCRIPT_APPROVE">;无权限渲染 403 页,不隐藏路由(避免"菜单有、点进去白屏")。API-30页面级懒加载(React.lazy+Suspense),首屏包体积纳入性能预算。API-31URL 是唯一状态源:筛选 / 分页 / 选中态可分享的必须进 query(useSearchParams),禁止只存 Zustand。API-32404 / 403 / 500 三个页面必须存在,且 500 页展示trace_id供排查。
9 契约与兼容
API-33OpenAPI 由 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通过