RESTful API 设计规范与版本演进

RESTful API 设计规范与版本演进

接口设计这件事,写的时候没人觉得难,维护的时候才知道痛。一个订单列表接口,第一版叫 getOrderList,第二版加了分页参数 page,第三版发现游标分页更好又加了 cursor,第四版有人搞了个 getOrderListV2 和前者并存。半年后没人说得清该调哪个。

这篇聊的就是怎么把接口设计得经得起时间折腾。以 REST 风格为主线,但原则本身和框架无关。

资源命名,先把词表定下来

REST 的核心是把接口看成资源而不是动作。URL 用名词、HTTP 动词表达意图:

bash 复制代码
GET    /orders          查订单列表
GET    /orders/123      查单个订单
POST   /orders          创建订单
PUT    /orders/123      全量更新
PATCH  /orders/123      部分更新
DELETE /orders/123      删除订单

几条容易跑偏的地方:

  • 名词用复数。/orders 而不是 /order,全 API 统一,别一半单数一半复数
  • 层级表达从属关系:/users/45/orders 表达"用户 45 的订单",但层级别超过两层,再深就用查询参数:/orders?user_id=45
  • URL 里不要出现动词。/orders/create、/getOrderList 这类 RPC 风格命名和资源风格混着用,是接口腐化的开始
  • 非 CRUD 的业务动作不好用名词表达时,可以退一步用子资源:POST /orders/123/cancellation 发起取消

字段命名也一并定死:JSON 用小驼峰还是下划线无所谓,关键是全 API 一致。字段名是 API 的词表,词表乱了,前端就得在每个接口里猜一遍。

状态码是协议的一部分,不是装饰

200 包一切、错误塞在 body 里的 {"code": 500, "msg": "..."} 写法很普遍,但它把 HTTP 协议自带的语义丢了。网关、监控、重试逻辑全都依赖状态码做判断,全 200 会让这些基础设施失明。

按语义用:

  • 200 成功,201 创建成功(配 Location 头指向新资源),204 删除成功无返回体
  • 400 参数错误(body 里给出哪个字段错了),401 未认证,403 无权限,404 资源不存在
  • 409 冲突(重复创建、并发修改),422 业务校验失败,429 限流
  • 500 服务端异常,502/503/504 网关和依赖服务问题

错误响应的结构要统一,且带足够的排错信息:

css 复制代码
{
  "error": {
    "code": "ORDER_ALREADY_SHIPPED",
    "message": "订单已发货,无法修改收货地址",
    "trace_id": "req-a1b2c3d4"
  }
}

错误码用字符串常量而不是数字,ORDER_ALREADY_SHIPPED 比 40012 可读得多,调用方判断也不用维护一张数字对照表。

分页、过滤、排序的统一约定

列表接口早晚要面对这三个需求,一开始就定好格式,省得每个接口各造一套:

ini 复制代码
GET /orders?page=1&page_size=20
GET /orders?cursor=eyJvIjoxMjM0fQ&limit=20
GET /orders?status=pending&created_after=2026-01-01
GET /orders?sort=-created_at,amount

两种分页各有位置。页码分页直观、支持跳页,适合后台管理界面;游标分页性能稳定(深翻页不退化)、数据实时性好,适合信息流和移动端无限滚动。判断标准看产品形态,别只用一种。

响应里除了数据列表,把分页元信息也带上:

yaml 复制代码
{
  "data": [...],
  "paging": {
    "total": 1024,
    "page": 1,
    "page_size": 20
  }
}

时间字段统一用 ISO 8601 带时区(2026-03-15T08:30:00+08:00),别用时间戳数字裸奔------调用方每次都要写一遍转换,时区出错还特别难查。

幂等,支付和创建类接口的保命设计

网络超时后调用方重试,一个创建订单的请求被执行两次,用户被扣了两次钱。这类事故的解法是幂等键:

bash 复制代码
POST /orders
Idempotency-Key: 6f1c8a2e-xxxx-xxxx

服务端记录这个键,同一个键的重复请求返回第一次的结果(或者直接告诉调用方"已处理过"),不再执行业务。创建、支付、退款这类有资金或副作用语义的接口,幂等设计是必选项,不是可选项。

PUT 和 DELETE 天然幂等(同样的请求执行多次结果不变),POST 不是,所以幂等键主要给 POST 用。

版本演进:不破坏比什么都重要

API 一旦有人用,每个改动都是公共事件。原则很简单:可以加,不能改,更不能删。

加字段、加可选参数、加新的枚举值,这些是安全的,调用方无感知。改字段名、改字段类型、改语义(原来传 0 表示不限,改成 0 表示精确匹配零),这些是破坏性变更。

真要做破坏性变更,走版本升级路线:

bash 复制代码
GET /v1/orders
GET /v2/orders

URL 版本化最直观,网关路由也容易做。老版本给一个明确的下线时间表,期间双版本并存、监控老版本流量归零后再下线。直接下线老版本的动作,相当于替你的调用方决定他们的排期,合作基本就到头了。

没有版本号的情况下也有补救手段:新能力用可选参数渐进灰度(feature flag 控制默认值),行为差异写进变更日志并在响应头里带版本提示(X-API-Deprecated: true),给调用方足够的迁移信号。

把规范固化成工程约束

口头规范活不过三个人。真正起作用的规范是写进工具里的:

  • OpenAPI(Swagger)描述文件作为接口的单一事实来源,评审先看文档再看代码
  • lint 工具扫 URL 命名、字段命名的一致性,不一致的 CI 直接挡下
  • mock server 从 OpenAPI 自动生成,前后端并行开发不互相等
  • 契约测试保证实现和文档不漂移

规范的价值不在文档写得漂亮,而在三个月后新同学照着做出来的接口,和团队里老人写的看不出区别。


领域:后端设计

关键词:RESTful、API设计、幂等性、版本管理、OpenAPI

相关推荐
补码缩写1 小时前
前端AVIF编码开多线程快两三倍,speed调到8就不快了
前端
cidy_981 小时前
企业级 AI Agent 工作台前端实战教程
前端
deli0071 小时前
换个 DNS 网速真的变快吗?解析 6 步逐跳实测,快慢差 45 倍
前端
葡萄城技术团队1 小时前
接一个前端库:以前要啃几天文档,现在三步——活字格 V12.1 AI辅助开发上手
前端
愿得一猪蹄1 小时前
浏览器开发者工具隐藏技巧,你可能只用了一成
前端
高晶2 小时前
ARM 嵌入式 Linux 内核编译与结构解析实战
前端·架构
今年下半年2 小时前
【前端】Vue2 项目 ECharts 统计图表组件封装与使用实践(附组件源码)
前端·javascript·echarts
西索斯coding2 小时前
在 中接入 教程:p配置、m填写、扩展思考参数一篇就够(2)
java·前端·javascript·ai
代码猎人2 小时前
前端怎么优化项目基础架构
前端