API 设计实战:把接口设计成「不会用错」的样子
好的接口,调用方不看文档都能猜对八成;坏的接口,得翻别人代码、反复试错才知道怎么调。这篇按命名、方法幂等、状态码与错误体、分页、重试安全、版本演进等九个维度,讲一套可落地的接口设计规矩。
一、先立观念:API 是一份长期契约
同一个业务需求,两个团队能设计出体验完全不同的接口。接口设计是少数「一次性投入、长期收息」的工作:发布前多花一小时,维护期能省一百小时。三条目标贯穿全文:可预测(命名、结构、行为符合直觉,调用方不用猜)、可演进(加功能不破坏已有调用方)、可调试(出错时双方都能快速定位)。
接口设计的验收标准不是「能用」,而是别人不看文档也能用对大半,出错时能自己看懂为什么。
二、资源与命名:名词复数,层级清晰
- 资源用名词复数:
GET /users/123/orders,而不是/getUserOrders - 层级表达从属:
/users/{id}/orders,最多两层,再深就考虑独立资源 - 业务动作可以破例:
POST /orders/{id}/cancel比/cancel-order清晰,别硬套 CRUD - 命名一致:全小写加连字符;全站别混用 user、customer、member 这类同义词
三、方法与幂等:一张表定规矩
| 方法 | 语义 | 安全(无副作用) | 幂等 | 典型用途 |
|---|---|---|---|---|
| GET | 读取 | 是 | 是 | 查询 |
| POST | 创建 / 动作 | 否 | 否 | 新建、执行动作 |
| PUT | 全量替换 | 否 | 是 | 覆盖式更新 |
| PATCH | 局部更新 | 否 | 可设计为幂等 | 字段级修改 |
| DELETE | 删除 | 否 | 是 | 删除 |
两个关键推论:GET 绝不产生副作用(浏览器预取、爬虫、重试都会触发它);能幂等的操作优先用 PUT 与 DELETE——天然支持安全重试。
四、状态码与错误体:别用 200 装失败
2xx 成功;4xx 调用方问题(参数、鉴权、限流);5xx 服务端问题。拒绝「HTTP 200 + body 里写 code 500」——网关、监控、客户端重试机制全部失灵,事故时最想骂人的就是这种设计。错误体要同时喂给机器和人:
{
"error": {
"code": "ORDER_ALREADY_PAID",
"message": "订单已支付,无法取消",
"request_id": "req_8f3c2a1e",
"details": { "order_id": "20260917001",
"paid_at": "2026-09-17T06:30:00+08:00" }
}
}
code 稳定、可枚举,给程序判断;message 给人看、允许改文案;details 给上下文;request_id 贯穿全链路日志——用户报障时报这一串就能定位。
五、分页:offset 与游标各有主场
| 方案 | 写法 | 优点 | 缺点 |
|---|---|---|---|
| offset 分页 | ?page=3&size=20 |
简单,可跳页 | 深页慢;期间有插入删除会漏项或重复 |
| 游标分页 | ?cursor=xxx&limit=20 |
稳定,深页快 | 不能跳页 |
选型:后台管理用 offset(需要跳页);信息流、大数据量、实时写入多的场景用游标。无论哪种,size 都要设上限(如最大 100),否则一个请求就能拖垮数据库。
六、重试安全:POST 也要能幂等
客户端超时重试是常态,非幂等的 POST 会重复下单。生产级做法是幂等键:客户端生成 Idempotency-Key(随机串)放请求头;服务端用去重表记录「键到首次处理结果」,重复请求直接返回原结果;有效期一般 24 小时。重试策略同样重要:只重试安全的(GET/PUT/DELETE、网络错误、5xx),避开 4xx 参数错误;用指数退避加随机抖动,避免重试风暴。
七、版本与演进:把兼容性写成规则
破坏性变更(必须升级版本):删字段、改类型、改语义、新增必填参数;安全变更(不用升版本):加可选字段、加新接口、加枚举值(需提醒客户端对未知值容错)。版本方式推荐 URL 版本(/v1/…)——最直观、最好排查。弃用流程:Deprecation 响应头加文档标注加迁移期(至少一个迭代周期),最后明确 Sunset 日期。一条纪律:破坏性变更不许「悄悄上线」——先发公告、再双跑、最后下线。
八、安全与限流:默认最小暴露
- 鉴权凭证放 Header(
Authorization),别放 URL 参数——URL 会进日志、进浏览器历史 - 权限校验做到资源粒度:不是「登录了就能访问」,而是「是你的订单才能看」
- 限流返回
429并带Retry-After与剩余额度头,让客户端知道该等多久 - 错误信息不泄露内部实现:堆栈、SQL、内部主机名都不该出现在响应里
九、可调试:文档即接口的一部分
用 OpenAPI 规范描述接口,文档由实现生成,避免「文档与代码两张皮」;提供 sandbox 环境与可一键运行的示例(curl 片段),把接入成本降到最低;每个响应带 request_id,日志、链路、工单三处可凭它互相跳转。
十、落地清单
- 资源命名统一:名词复数、层级不超过两层、同义词全站统一
- GET 无副作用;可幂等操作优先 PUT / DELETE
- 用真实语义的状态码;错误体含 code、message、request_id、details
- 列表接口必须分页且设 size 上限
- 写接口支持 Idempotency-Key;客户端遵守指数退避
- 破坏性变更走版本升级与弃用流程,提前公告
- 每个响应带 request_id;OpenAPI 文档自动生成
速查卡
| 场景 | 处方 |
|---|---|
| 命名纠结 | 名词复数 + 层级从属;动作接口用动词短语 |
| 更新接口怎么选 | 全量覆盖用 PUT,字段级修补用 PATCH |
| 错误怎么返回 | 语义化状态码 + code/message/request_id 错误体 |
| 列表接口 | 分页 + size 上限;深页或实时场景用游标 |
| 怕重复提交 | Idempotency-Key + 服务端去重表 |
| 避免破坏调用方 | 只做安全变更;破坏性变更升版本 + 弃用期 |
| 排障 | 全链路 request_id + OpenAPI 文档 + sandbox |
写在最后
接口设计的验收标准不是「能用」,而是两条更苛刻的要求:别人不看文档也能用对大半,出错时能自己看懂为什么。做到这两点,接口就是产品说明书;做不到,它就会变成团队之间永久的扯皮源头。
评论(0)