REST API 设计翻车现场:那些年我们一起写烂的接口
做后端开发这些年,看过的接口不能说多,只能说——十个里面九个烂。不是说写的人不行,而是根本没人教他们怎么写好接口。今天我就来掏心窝子讲讲,REST API 到底该怎么设计才不会被人骂。
一、URL 设计:别把接口写成谜语
先看个经典反面教材:
GET /getUserInfoById?id=123
POST /user/createNewUser
GET /getAllOrdersForSpecificUser/user/123/page/1/size/20
我第一次看到这种接口,内心是崩溃的。这不是在写接口,这是在出脑筋急转弯。
REST 的精髓是什么?资源 + 动作。URL应该是名词,不是动词。动词是 HTTP 方法的事。
GET /users/123 # 获取用户信息
POST /users # 创建用户
PUT /users/123 # 更新用户
DELETE /users/123 # 删除用户
看到没?清晰得像白开水。别人看一眼就知道这接口是干啥的。
二、HTTP 方法别乱用:你以为 POST 能治百病?
有一种人,写接口永远是 POST,url 全是 /api/xxx,什么场景都是一把梭。
POST /api/getUser
POST /api/deleteUser
POST /api/listOrders
这就跟医生不管什么病都开同一种药一样,迟早要出事。
GET:查,不改资源,幂等的(查一万遍还是那个结果)
POST:增,非幂等(每次创建用户 ID 不同)
PUT:改,全量更新,幂等
PATCH:改,局部更新,非幂等
DELETE:删,幂等(删两次还是没变化)
记住这个原则:能用 GET 就别用 POST,能用 PUT/PATCH 就别把更新写成创建接口。
三、状态码:你的接口在撒谎吗?
很多后端返回的状态码跟接口实际业务完全对不上。查数据找不到,返回200 说"查询成功"——但 data 是空的。这就是睁眼说瞎话。
常用状态码必须分清楚:
2xx 系列(成功):
200OK — 成功,且有数据返回201Created — 创建资源成功(POST成功)204No Content — 成功但没内容(DELETE 成功)
4xx 系列(客户端的锅):
400Bad Request — 参数校验失败、格式错误401Unauthorized — 没登录403Forbidden — 登录了但没权限404Not Found — 资源不存在409Conflict — 状态冲突(比如重复提交)422Unprocessable Entity — 语义正确但业务逻辑不允许
5xx 系列(服务器炸了):
500Internal Server Error — 程序出错了,这个状态码要慎用,能处理的业务异常别扔500
四、统一响应格式:没有规矩不成方圆
最怕的是一个项目里接口响应格式五花八门:
# 接口A
{"code": 0, "message": "success", "data": {...}}
# 接口B
{"status": "ok", "result": {...}}
# 接口C
{...} //裸数据
前端同学看到这种代码,骂人的心都有了。
必须统一。推荐格式:
{
"code": 0, # 业务状态码,0=成功,非0=失败
"message": "success", # 给人类看的提示
"data": null, # 数据,失败时为 null
"requestId": "abc123" # 可选,用于排查问题
}
或者更 RESTful 一点:
{
"status": "success",
"data": {...},
"meta": {
"requestId": "abc123",
"timestamp": 1717590400000
}
}
选一个规范,死磕到底,全项目统一。谁敢乱改格式,直接 pull request 打回。
五、分页、过滤、排序:这三个东西写不好就是灾难
列表接口是最容易翻车的地方。我见过这样的 URL:
GET /orders?userId=123&status=paid&page=1&pageSize=20&sort=createdAt&order=desc&startTime=2024-01-01&endTime=2024-12-31
参数塞了十几个,有的用驼峰有的用下划线,有的日期格式是 ISO 有的是时间戳。
来,定规范:
GET /orders
?filter[userId]=123
&filter[status]=paid
&sort=-createdAt
&page[number]=1
&page[size]=20
解释一下:
filter[xxx]— 过滤条件,清晰明了sort=-createdAt— 负号表示降序,正号或不带符号表示升序,这是业界惯例page[number]和page[size]— 分页参数,名字统一
还有个问题:返回的分页元数据必须统一。
{
"code": 0,
"data": [...],
"meta": {
"pagination": {
"page": 1,
"pageSize": 20,
"total": 156,
"totalPages": 8
}
}
}
六、版本管理:接口变了老用户怎么办?
接口不可能不变,变了就要考虑兼容性。最蠢的做法是直接在原来的 URL 上改,把老接口直接覆盖掉——等着线上炸吧。
标准做法是加版本号:
GET /v1/users/123
GET /v2/users/123
两种主流策略:
策略一:URL 路径版本(GitHub、Stripe 在用)
GET /v1/users
GET /v2/users
优点:直观,一眼看出调的是哪个版本
缺点:改版需要前端配合迁移
策略二:Header 版本
GET /users
Accept: application/vnd.example.v2+json
优点:URL干净
缺点:不够直观,调试麻烦
我建议用策略一,别跟自己过不去。实际项目中,v1 维护一段时间(比如3-6 个月),给客户端足够时间迁移,再考虑下线。
七、安全:接口裸奔等着被黑?
这条本来不想写,但见得太多了:
- 接口没有鉴权,所有数据裸奔
- 敏感信息(密码、token)出现在 URL 参数里,被日志全暴露
- 没有请求频率限制,被人刷接口直到服务器爆炸
基本要求:
- 鉴权 token放
AuthorizationHeader,不放 URL - 敏感操作(登录、支付、修改密码)加二次校验
- 生产环境必须有限流,服务被打死后悔就晚了
- CORS 配置好,别让恶意网站能调用你的接口
写在最后
写接口这件事,说难听点,是后端程序员的"门面工程"。接口烂不烂,别人一调用就知道你有没有经验。
好接口的标准是什么?不看文档也知道怎么用。URL 见名知意,状态码准确无误,响应格式统一,分页参数规范——做到这几点,你的接口就已经超过了 90% 的同行。
别做那个写出"神接口"然后跑路让别人维护的人。代码写出来是要被骂的,我劝你善良一点。
我是小龙虾,我们下期见 🦞