RESTful API 设计路上踩过的那些坑,今天全部交代
做后端开发这么多年,我见过太多团队在 API 设计上翻车。有些是新手不懂,有些是老手惯性思维,还有一些是「大家都这么干所以我也这么干」的从众心理。今天就把这些年亲眼目睹、亲身经历、亲手填过的坑,全部抖落出来。
声明一下:这不是给小白的「什么是 REST」科普,是给已经在做但做得不够好的同行的实战复盘。准备好接受暴击了吗?
坑一:把所有东西都塞进 GET 和 POST
见过最离谱的 API 长这样:
POST /api/getUserInfo
POST /api/deleteUser
POST /api/updateUser
POST /api/listUsers
是的,你没看错,全是 POST。为啥?因为「POST 什么都能传,GET 有长度限制」。我竟无力反驳,只能默默在心里给他点了一根蜡烛。
HTTP 动词是有意义的,别糟蹋它们:
GET /users # 获取用户列表
GET /users/123 # 获取单个用户
POST /users # 创建用户
PUT /users/123 # 完整更新用户
PATCH /users/123 # 部分更新用户
DELETE /users/123 # 删除用户
REST 不是形式主义,它是让你的 API 自解释的根基。当你看到 DELETE /users/123,你不需要文档就知道这是在删用户。这就是意义。
坑二:URL 命名放飞自我
来看一个真实案例(已脱敏):
/api/v1/userinfo_get
/api/v1/get_order_list_by_user_id_and_date_range
/api/v1/user/123/order/get
/api/v1/userGetInfo
我当时的表情:😱
URL 应该是名词,不是动作描述。动作交给 HTTP 动词来表达。正确的姿势:
GET /users/123 # 获取用户
GET /users/123/orders # 获取用户的订单
GET /orders?user_id=123&from=2024-01-01&to=2024-12-31 # 条件查询
几个原则记一下:
- 用复数名词:
/users而不是/user - 用横杠分隔单词:
/user-profiles而不是/userProfiles - 不要用下划线,看起来像屎
- 层级结构表达从属:
/users/123/orders
坑三:HTTP 状态码乱用
有一种恐怖叫「所有接口都返回 200,然后看 body 里的 code 字段」:
{
"code": 404,
"message": "用户不存在",
"data": null
}
HTTP 状态码是干嘛用的?就是让你不用看 body 就知道请求结果。你返回 200 告诉浏览器「一切正常」,然后在 body 里说「其实用户没了」,这不是脱了裤子放屁吗?
状态码使用指南:
- 200 - 成功(OK)
- 201 - 创建成功(Created),用于 POST 新建资源
- 204 - 成功但无内容(No Content),用于 DELETE
- 400 - 请求参数有问题(Bad Request)
- 401 - 未认证(Unauthorized)
- 403 - 已认证但没权限(Forbidden)
- 404 - 资源不存在(Not Found)
- 422 - 请求格式对但语义错(Unprocessable Entity)
- 429 - 请求太频繁(Too Many Requests)
- 500 - 服务器炸了(Internal Server Error)
有人会说「前端根据 code 做判断也挺好的啊」。好什么好?你的接口被第三方调用,人家看到 200 就认为成功了,谁他妈还去解析你 body 里的 code?到时候线上出问题你就等着哭吧。
坑四:不考虑分页直接 all in
新手最喜欢写的接口:
GET /api/users
然后数据库里有五百万用户。你猜返回什么?
是的,你的服务器会在某个风和日丽的下午突然暴毙,然后 DBA 提着刀来找你。
正确的分页姿势:
GET /users?page=1&per_page=20
GET /users?cursor=eyJpZCI6MTIzfQ&per_page=20
响应结构也要说清楚:
{
"data": [...],
"pagination": {
"current_page": 1,
"per_page": 20,
"total": 500000,
"total_pages": 25000,
"has_next": true,
"has_prev": false
}
}
页码分页适合管理后台这类「需要跳页」的场景。游标分页适合 feed 流这类「持续滚动」的场景。选哪个看业务,别一刀切。
坑五:版本控制拍脑袋
见过最随性的版本控制:
/api/v1/users
/api/v2/users
/api/beta/users
/api/l版/users (是的,你没看错,有人用过中文)
版本控制是 API 演进的生命线,必须规范。一般用 URL 路径:
/api/v1/users
/api/v2/users
也可以用 Header,但 path 方式更直观,调试也方便。
版本升级的原则:
- 新增字段兼容旧格式
- 删除字段要三思,可以先标记 deprecated
- 修改字段名不如新增字段然后废弃旧的
- 不要在一个版本里同时做太多改动
坑六:安全措施形同虚设
最常见的问题:接口裸奔,没有任何鉴权。
有人会说「内网接口不需要鉴权」。内网不会被打穿吗?微服务之间不会串数据吗?等到数据泄露那天你再后悔就晚了。
基础安全配置:
// 1. HTTPS 必须
// 2. 认证信息用 Authorization Header
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// 3. 敏感操作要校验权限
// 4. 防止 SQL 注入/XSS,别信任何输入
// 5. 请求频率限制
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
认证方案选型:
- 短期 Token 用 JWT
- 长期授权用 OAuth2.0
- 微服务内部用 mTLS
- 简单场景用 API Key
坑七:错误信息等于没说
这种错误响应见过吗?
{
"error": "error",
"message": "failed"
}
我 failed 你个头啊!这种错误信息对调试没有任何帮助。
好的错误响应应该包含:
{
"error": {
"code": "USER_NOT_FOUND",
"message": "用户不存在",
"detail": "user_id: 12345 在当前租户下不存在",
"help": "请检查 user_id 是否正确,或联系管理员",
"trace_id": "a1b2c3d4e5f6"
}
}
trace_id 特别重要,配合日志系统可以快速定位问题。线上出问题,用户说「报错了」,你问他「什么错误」,他说「打不开」。有了 trace_id,搜日志一秒定位。
坑八:不做幂等性设计
网络不稳定的时候,前端可能会发起重试。如果你的 POST 接口每次调用都创建一个用户,那用户就喜提一千个小号了。
幂等设计原则:
- GET、PUT、DELETE 天然幂等,设计时保持这个特性
- POST 如果涉及创建,用唯一键约束防止重复
- 支付等关键操作要用幂等 ID(idempotency key)
// 带幂等 key 的请求
POST /orders
Headers: {
"Idempotency-Key": "unique-request-id-12345"
}
服务端缓存这个 key 一段时间内(建议 24 小时)的响应,重试时直接返回缓存结果。用户体验不受影响,服务器也不会炸。
API 设计这事,说难不难,说简单也不简单。难的地方不在于你知道规范,而在于你能顶住压力坚持规范。当产品说「这个接口特殊一点没事的」,你能说「不,有事的」。
规范不是为了装逼,是为了你的接口在三年后、五个客户端、三百个调用方的时候,还能正常运转,而不是变成一坨没人敢动的屎山。
好了,坑交代完毕。能全躲过去的,算你厉害。