7.5 KiB
7.5 KiB
API契约-公共约定
两个世界(管理面 / 成员视图)共用的 HTTP 层约定:路由隔离、鉴权两档、错误信封、时间 / 数值 / 分页 / DTO 命名。本篇是《API契约-管理面》《API契约-成员视图》的引用锚点——跨面共性在此定死,端点逐个定义归后两篇。
| 项目 | 内容 |
|---|---|
| 状态 | 定稿(2026-10-02;R16 修订 2026-10-10) |
| 关联 | 《交互设计-总览与共性》(§1 访问拓扑 · §3 呈现约定)·《交互设计-登录与会话》(鉴权档位与 401 分流)· 各篇表字段设计(DTO 字段权威)·《技术选型》§4(DTO 类型属 packages/core) |
| 产出 | 路由前缀与隔离机制 · 鉴权两档 · 错误信封 · 时间 / 数值 / 分页 / 命名约定 |
1. 总则
- JSON over HTTP,UTF-8;请求与响应体均为
application/json - 一套路由代码,两个世界靠路径前缀与凭证中间件物理隔离(共性 §1:令牌进不了管理面、会话进不了成员视图)
- DTO 类型定义于
packages/core,web 以纯类型依赖引用——前后端一份契约类型(技术选型 §4 依赖规则) - 端点面向交互设计,不与数据表对齐——每个端点对应一个功能面(页面 / 流程需要什么);表只是存储模型,按其事实性质获得恰好够用的操作面(引擎专写表纯只读、人工抄录表全 CRUD、账本实体软删除为主——空壳例外可硬删:无 key 归属的成员行不承载账本事实,见 R16)
2. 路由前缀与隔离机制
| 面 | 路由前缀 | 凭证 | 校验中间件 |
|---|---|---|---|
| 管理面 | /api/admin/* |
会话 cookie | sessions 校验 |
| 成员视图 | /api/member/* |
Bearer 令牌 | members 哈希比对 + status 检查 |
- 会话 cookie:
HttpOnly + Secure + SameSite=Lax,Path=/api/admin(「路径限管理面」裁决的落地);随请求自动携带、登出即删行 - 成员令牌:
Authorization: Bearer <token>;明文令牌只存在于登录壳输入与 sessionStorage(关标签即失效——《交互设计-登录与会话》§4) - 401 分流为前端职责:管理面任意 401 → 登录页「会话已过期」;成员视图任意 401 → 登录壳「令牌无效或已停用」——后端 401 响应不带面特化信息,前端按所在面分流(共性 §1)
- 命名空间约定:
/api/*专属接口——前端页面路由永不进入/api前缀;未知/api/*路径 = 接口404错误信封,非/api请求归前端托管(SPA history 回退「非/api→ index.html」,细则归骨架设计)
3. 错误信封
统一错误体(成功响应无信封包装,见 §6):
{ "error": { "code": "invalid_credentials", "message": "用户名或密码错误" } }
message:面向使用者的中文、说人话(共性 §3.6 文案基调);不携带堆栈与内部细节code:机器可读枚举(snake_case);表单校验失败可加fields(字段名 → 中文说明);判别已足时允许端点附机器可读附注字段(如重复登记的409附key_id定位既有行)
| HTTP 状态 | 语义 | 本项目对应 |
|---|---|---|
| 200 / 201 | 成功 / 创建成功 | 创建类写操作返 201 |
| 400 | 请求格式错误 | 参数缺漏、JSON 解析失败 |
| 401 | 凭证无效或过期 | 会话过期、令牌停用 / 重发 |
| 404 | 资源不存在 | 非法 id |
| 409 | 冲突 | 重复登记(sk-key 已存在等) |
| 422 | 校验失败 | 表单字段校验(带 fields) |
| 429 | 请求过频 | 登录节流(管理面,最小实现) |
| 5xx | 服务端故障 | message 统一「服务暂时不可用,请稍后重试」,不泄内部细节 |
- HTTP 状态码是成功/失败的唯一判别——客户端永远先看状态码、再解析响应体,不从响应体形状推断成败;错误信封的解析集中在前端唯一的统一请求封装内,各端点代码只碰成功类型;中间人返回的非 JSON 错误体(代理 502 页等)回退为通用文案
- 业务错误即请求失败——状态码表即业务错误分类表(409 重复登记、422 规则校验失败、404 资源不存在等),一律非 2xx + 错误信封;不存在「请求成功但携带业务错误」的响应形态。系统状态类数据例外:响应携带的「坏事」是事实数据而非请求错误——如总览健康带的「最近轮次失败 2」走 200,失败的轮次是数据、不是本次请求的错误
4. 时间口径
- 响应时刻字段:Unix 秒整数(UTC 时刻,如
1780000000)——无时区歧义;本地/北京时间由展示层转换 - 请求时间参数(
from/to、paid_at/period_*等):ISO 8601 带时区偏移(如2026-09-08T00:00:00.000+08:00,便于人读;亦接受同绝对时刻的…Z);范围 左闭右开[from, to) - 存储层维持 timestamptz / 上游 Unix 秒;DTO 输出统一
toUnixSec - 快捷项(最近 7 / 30 天、本订阅期间、自定义起止)是前端预置逻辑:前端算出
from/to再请求,后端无快捷参数
5. 数值口径(对齐共性 §3.2)
- quota 双单位:用量一律成对返回
quota(整数原值,口径内精确显示)+quota_usd(字符串,两位小数,如"25.00") - 换算责任在后端:按日志时刻取 QPU(
qpu_history时间线)逐行换算——单一事实源,前端只呈现不换算;占比 / 分段条比例由前端现算(纯呈现职责) - 取整规则:换算内部以微分(1/100 分)整数运算,仅输出时四舍五入到分;聚合桶先微分求和再一次取整——小行贡献不丢失、单一舍入点
- 支付金额:
paid_amount原样数字(币种无关,R9 裁决),无符号、无换算、无派生金额 - 次数:
count(日志行数)
6. 分页与响应形态
- 列表类端点(日志明细、审计日志等):
page(1 起)+page_size(默认 50,上限 200)→{ "items": [...], "total": 123 } - 聚合类端点(按成员 / key / 来源分组):不分页——分组基数有限(成员个位数、key 10–25),返回全量数组
- 成功响应无信封:对象直返、列表
items/total、创建返创建后实体——信封只用于错误(§3);空响应体的动作类成功(如登出)返204 No Content无响应体
7. DTO 命名
- snake_case、与表字段同名同构——各篇表字段设计即 DTO 字段权威,无映射层
- 前端不做 camelCase 转换:全栈 snake_case(组件 props 是 antd 的 API、与数据 DTO 互不干扰)
8. 本稿拟定约定(交互设计未覆盖,请作者重点审)
- 时间:请求侧 ISO 8601 带偏移(§4);响应侧 Unix 秒整数(§4);
- 时间范围左闭右开
[from, to)(§4); quota_usd字符串两位小数 + 后端换算责任 + 微分取整规则(§5);- DTO 与表字段 snake_case 同构、无映射层(§7);
- 分页
page/page_size+items/total,聚合类不分页(§6); - 错误信封单层
{error: {code, message, fields?}},成功无信封(§3/§6); - 空响应体动作类成功返
204(§6);HTTP 状态码为成功/失败的唯一判别(§3); - 业务错误一律非 2xx——不存在 200 携业务错误的形态;系统状态类数据(健康带轮次失败数等)例外,是响应数据、不是请求错误(§3)。