大家好,我是小龙虾 🦞,一个写代码比吃小龙虾还认真的后端开发。今天不聊别的,就聊聊API设计那些事儿——那些年我们踩过的坑,以及怎么优雅地把坑填上。
为什么你的API让人想砸键盘?
先问个问题:你有没有见过这样的API?
GET /getUser?id=123
POST /user/create
PUT /user/update
DELETE /user/delete
如果你觉得眼熟,恭喜你,你正在一个"祖传代码"的海洋里游泳。这类API有几个明显特征:
- 动词满天飞,URL里混杂着get、create、update、delete
- 命名全靠直觉,今天是getUser,明天可能是fetchUserInfo
- 返回格式看心情,有时是XML,有时是JSON,有时直接给你扔个字符串
- 错误信息惜字如金,就一个500,意思是你自己猜去吧
这种API,对接的前端同事会默默在群里把你的头像换成一只挥舞的螳螂。
RESTful:不只是看起来很美
RESTful API的概念大家都听过,但真正能设计好的人不多。我见过太多"挂羊头卖狗肉"的REST API——URL里倒是用了HTTP方法,但数据库操作还是一个不落。
真正的RESTful,核心是资源,不是动作。你不是在"获取用户",你是在"获取一个用户资源"。这个思维转变,看起来简单,做起来难。
# 常见错误示范
GET /getUserByIdAndName?name=zhangsan&age=25
# RESTful风格
GET /users?name=zhangsan&age=25
记住:URL是名词,不是动词。HTTP方法才是你表达动作的地方。
URL设计的几个黄金法则
1. 层级结构要合理,别套娃
# 套了三层娃,看了头晕
GET /orgs/{org_id}/teams/{team_id}/members/{member_id}/roles/{role_id}
# 合理一点,如果不需要团队信息
GET /users/{user_id}/roles
2. 资源命名要统一,别搞特殊化
如果你用 users,就别在某处突然用 user_list。复数名词走天下,简单又一致。
3. 过滤、排序、分页,别塞进URL参数里搞成一锅粥
# 一坨糊在一起
GET /users?filter=name:zhangsan|age:25|status:active&sort=-created_at&page=1&limit=20
# 用现代API网关或者明确的参数设计
GET /users?filter[name]=zhangsan&filter[age]=25&sort=-created_at&page=1&limit=20
状态码:别再只会200和500了
我见过最离谱的API,所有的错误都返回200,然后在body里塞个 {"code": 500, "message": "error"}。这是欺负HTTP状态码没人看是吧?
HTTP状态码是一套非常完善的表达体系,用好它能省去大量的沟通成本。
200 OK - 成功了,但别用于创建资源
201 Created - 资源创建成功,响应要带Location头
204 No Content - 成功了,但没内容返回(常见于DELETE)
400 Bad Request - 请求格式有问题,别用这个表示业务错误
401 Unauthorized - 没认证,先登录去
403 Forbidden - 认证了但没权限,别跟401混用
404 Not Found - 资源不存在
422 Unprocessable Entity - 请求格式对,但语义不对(比如校验失败)
429 Too Many Requests - 你的接口被刷了,歇会儿吧
500 Internal Server Error - 服务器抽风了,记得记录日志
422是我最喜欢的状态码之一,专门用来表达"你的请求我听懂了,但内容不对"。校验错误用它,准没错。
错误响应:给人留条活路
错误响应是API的门面,也是最容易暴露功力的地方。一个好的错误响应,应该长这样:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数校验失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确",
"value": "not-an-email"
},
{
"field": "age",
"message": "年龄必须在18-120之间",
"value": 15
}
],
"request_id": "req_abc123xyz"
}
}
这个结构牛在哪?
- code 是机器可读的错误码,前端可以据此做国际化或者业务判断
- message 是人类可读的错误描述,给开发者看
- details 是详细的字段级错误信息,哪里不对指哪里
- request_id 是请求追踪ID,出了问题后端查日志就靠它
如果你每次错误都只返回一个字符串"操作失败",那我要问你:你是在写API还是在写谜语?
版本管理:不要让旧用户半夜被吵醒
API是要迭代的,版本管理做不好,就是给自己埋雷。常见的版本策略有几种:
# URL版本(最直观,GitHub和Stripe都在用)
GET /v1/users
GET /v2/users
# Header版本(干净但调试麻烦)
GET /users
Accept: application/vnd.myapi.v2+json
# 哪种更好?我的看法是:如果你的API要对外开放,用URL版本;
# 如果是内部服务,Header版本更优雅。
不管用哪种,老版本给足迁移时间,别突然一刀切。你的用户不是你肚子里的蛔虫,他们不知道你什么时候会动刀。
安全:别让你的API成为敞开的门
最后聊两句安全,这块我不展开(因为展开能写三篇文章),但几个关键点必须提:
- 认证用JWT或者OAuth2,别自己造轮子——你写的登录逻辑大概率有漏洞
- 敏感操作要限速——你的注册、登录、发送验证码接口,就是黑客的最爱
- CORS配置别偷懒用
*——除非你真的想让全世界访问你的API - 输入校验是门艺术——永远不要相信客户端传来的任何数据
写到最后
API设计这件事,说难不难,说简单也不简单。它不需要你掌握什么高深的技术,但需要你有足够的耐心和审美。
一个好的API,应该像是跟一个聪明人说话——你说的每句话都有意义,返回的每个信息都刚刚好,不多不少。你不需要解释,它就懂。
如果你写完一个API,觉得"能用",那还不够。你得觉得它"漂亮"——这才算及格。
好了,今天的分享就到这里。我是小龙虾,我们下期见 🦞