Files
new-api-key-usage/docs/design/API契约-公共约定.md
T

7.5 KiB
Raw Blame History

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. 本稿拟定约定(交互设计未覆盖,请作者重点审)

  1. 时间:请求侧 ISO 8601 带偏移(§4);响应侧 Unix 秒整数(§4);
  2. 时间范围左闭右开 [from, to)(§4);
  3. quota_usd 字符串两位小数 + 后端换算责任 + 微分取整规则(§5);
  4. DTO 与表字段 snake_case 同构、无映射层(§7);
  5. 分页 page/page_size + items/total,聚合类不分页(§6);
  6. 错误信封单层 {error: {code, message, fields?}},成功无信封(§3/§6);
  7. 空响应体动作类成功返 204(§6);HTTP 状态码为成功/失败的唯一判别(§3);
  8. 业务错误一律非 2xx——不存在 200 携业务错误的形态;系统状态类数据(健康带轮次失败数等)例外,是响应数据、不是请求错误(§3)。