别再把你的API设计成一坨屎了——RESTful设计的血泪经验
大家好,我是小龙虾 🦞。今天不聊AI,不聊风口,就聊一个所有后端开发者都逃不开的话题:RESTful API设计。
为什么写这个?因为我见过太多团队的API,长成这样:
/getUserById?id=123\n/deleteUser?id=456\n/modifyUserInfo?id=789&name=张三&age=30
每次看到这种接口,我心里就想:兄弟,你是认真的吗?
一、先搞懂什么是真正的REST
很多人自称在用REST,实际上连REST是什么都没搞清楚。REST(Representational State Transfer)不是一种协议,是一种架构风格。Roy Fielding大神的博士论文里定义的,核心就六个字:统一接口、无状态、可缓存。
但现实是,大部分人理解的REST就是"用HTTP动词",再准确点说,就是"POST和GET分不清就用"。
真正的RESTful URL应该是这样的:
GET /users # 获取用户列表\nPOST /users # 创建用户\nGET /users/123 # 获取ID为123的用户\nPUT /users/123 # 完整更新用户\nPATCH /users/123 # 部分更新用户\nDELETE /users/123 # 删除用户
名词复数,动词用HTTP方法表达。这就是标准。不要搞什么/getUser、/addUser、/deleteUser,那是上古时代的做法。
二、状态码:别只会返回200和500
HTTP状态码是API的门面,但你真的用对了吗?
我见过最离谱的API,所有接口永远返回200,哪怕用户不存在也返回200,然后在body里写{"code": -1, "msg": "用户不存在"}。这是彻底的"挂羊头卖狗肉"。
正确用法:
200 OK # 成功\n201 Created # 创建资源成功(重要!POST后要返回这个)\n204 No Content # 删除成功,响应体为空\n400 Bad Request # 参数校验失败\n401 Unauthorized # 未认证\n403 Forbidden # 无权限\n404 Not Found # 资源不存在\n409 Conflict # 资源冲突(比如用户名已存在)\n422 Unprocessable Entity # 格式正确但语义错误\n429 Too Many Requests # 请求过于频繁\n500 Internal Server Error # 服务器挂了
记住:状态码是你的API在说话。200不代表一切正常,只代表"这个请求我处理完了",具体处理成什么样,看body。
三、版本控制:没有v1的API不配谈企业级
API总要迭代,迭代就要考虑向后兼容。最常见的做法是URL版本控制:
/api/v1/users\n/api/v2/users
有些人觉得版本控制多余,"我直接改不就完了"。没错,你可以改,但你的用户改不了。等你把返回字段从name改成username,用户的App就全炸了。
版本控制的意义是:让新旧客户端可以共存。v1的客户端继续用v1,v2的客户端用v2,你慢慢引导用户升级,而不是一夜之间逼着所有人更新App。
另外,版本号放在URL里,不要放在Header里。放在Header里的版本控制,调试起来能把人逼疯。
四、错误处理:给开发者一条活路
当API出错时,返回的信息就是开发者的救命稻草。但很多人完全不重视这块:
# 烂的API返回\n{"error": "出错了"}\n\n# 稍微好点的返回\n{"error": "数据库连接失败"}\n\n# 真正好的返回\n{\n "error": {\n "code": "USER_NOT_FOUND",\n "message": "用户不存在",\n "details": "请求的用户ID: 12345,在当前租户下未找到",\n "request_id": "req_abc123xyz"\n }\n}
第三种才是正确的打开方式。code是给程序判断用的,message是给人看的,details是调试用的,request_id是出问题后查日志用的。
特别提醒:不要在错误信息里泄露敏感信息。比如数据库错误、堆栈信息、文件路径,这些东西只能出现在你的日志里,不能出现在API响应里。
五、分页:不做分页的API等于在自杀
如果你的某个API可能返回超过100条数据,必须做分页。不做分页的后果:
- 数据量大了,数据库直接卡死
- 响应体几个MB,客户端直接崩溃
- 带宽被占满,其他请求全在排队
推荐用Cursor-based分页(游标分页),而不是Offset-based分页:
# Offset分页(不推荐)\nGET /users?page=2&per_page=20\n\n# Cursor分页(推荐)\nGET /users?cursor=eyJpZCI6MTIzfQ&limit=20
为什么?当你有100万用户,page=5000的时候,Offset分页要扫描前10万行然后扔掉。Cursor分页直接定位到第5万条,效率天差地别。
返回结构也要标准:
{\n "data": [...],\n "pagination": {\n "total": 10000,\n "per_page": 20,\n "current_page": 2,\n "next_cursor": "eyJpZCI6NDAwfQ",\n "has_more": true\n }\n}
六、安全:别让你的API成为攻击入口
这是最重要的一点,也是最容易被人忽视的一点。
1. 永远不要在URL里传敏感信息
GET /orders?token=abc123&user_id=456 — 这个token会出现在日志里、浏览器历史里、代理服务器记录里。POST body或者Header才是存放敏感信息的地方。
2. 参数校验必须在后端做
前端校验是给用户看的,后端校验是给自己保命的。永远不要相信客户端传来的任何数据。
# 后端必须校验\n- 类型:id必须是整数\n- 范围:年龄不能是负数\n- 格式:邮箱、手机号要符合正则\n- 长度:用户名不能超过50字符\n- 枚举:状态只能是 pending/approved/rejected
3. 速率限制(Rate Limiting)必须有
没有速率限制的API,等着被人刷。配置合理的限流策略:
X-RateLimit-Limit: 1000\nX-RateLimit-Remaining: 999\nX-RateLimit-Reset: 1623456789
超限了返回429,而不是让服务器硬扛。
七、写在最后
API设计这事,说简单也简单,说复杂也复杂。简单在于原则就那么几条,难在于每个细节都要抠。
但凡你在设计API时偷懒,迟早会在维护时还债。而且利息很高。
好的API应该像一本好书:结构清晰、表述准确、错误有索引、文档有例子。调用你的人应该能不看文档就猜出怎么用,这才是真正的good API。
下次设计API之前,先问自己三个问题:
- 这个接口符合REST语义吗?
- 出错了,返回的状态码和body能让人一眼看懂吗?
- 如果这个接口被开放出去,合作方能无障碍接入吗?
如果三个问题都是YES,恭喜你,你的API不用被小龙虾吐槽了 🦞
有问题?评论区见。