我做后端开发这些年,看过的API比看过的电影还多。有的API设计得让人想给作者寄锦旗,有的API设计得让人想寄刀片。今天咱们就来聊聊,怎么把API写得像样,别让它成为下一个开发者的噩梦。
RESTful?不是你想的那种"休息"
很多人听到RESTful就头疼,觉得这玩意儿太理论了。我当年也是这么想的,直到有一次我接手了一个项目,那个API长这样:
/api/getUser
/api/getUserInfo
/api/queryUserById
/api/user/detail?id=1
/api/user_get?id=1
看完了吗?看完你是不是想打人了?同一个项目,五种写法,五种风格,五种精神状态。这就是没有统一RESTful规范的后果。
RESTful的核心说白了就八个字:资源、动作、状态、统一。名词代表资源,动词用HTTP方法表示,状态码告诉你结果怎么样。
GET /users # 获取用户列表
GET /users/1 # 获取ID为1的用户
POST /users # 创建新用户
PUT /users/1 # 完整更新用户
PATCH /users/1 # 部分更新用户
DELETE /users/1 # 删除用户
这就是标准的RESTful风格。简单、清晰、一致。看到这个路由表,你就知道每个接口是干什么的。
状态码:这玩意儿比验证码还重要
状态码是API的门面。你去餐厅吃饭,服务员跟你说"您的菜在做",然后就没下文了,你慌不慌?API也一样,你调了个接口,返回个200,然后呢?成功了?失败了?数据在哪?
常用状态码,我给大家捋一捋:
2xx - 成功系列
200 OK # 搞定了
201 Created # 创建成功
204 No Content # 成功但没返回内容(常用于DELETE)
4xx - 客户端错误
400 Bad Request # 你请求的参数有问题
401 Unauthorized # 没登录就想干活?
403 Forbidden # 登录了也没权限
404 Not Found # 你要的东西不存在
422 Unprocessable Entity # 参数格式对了但语义不对
429 Too Many Requests # 刷接口刷太狠了兄弟
5xx - 服务器错误
500 Internal Server Error # 我们的问题,不是你的
503 Service Unavailable # 服务暂时不在家
最烦的一种情况是什么?接口返回200,但里面是个错误。这种行为就跟"好的收到"然后已读不回一样让人窝火。
错误处理:别让你的API变成"薛定谔的返回"
好的错误处理应该是这样的:
{
"code": 40001,
"message": "用户名不能为空",
"data": null
}
清晰的错误码,明确的错误信息,可能的话再给点修复建议。用户在调用你接口的时候,可能比你还懵,你得让他知道发生了什么。
我见过最离谱的错误处理是这样的:
{
"error": true,
"msg": "出错了",
"info": "请联系管理员",
"errorCode": 999
}
兄弟,你这是错误处理还是在叠buff?四种方式表达同一个意思,生怕我猜不出来是哪种错?
我的建议是:统一错误格式,错误码要有分类,文档要写清楚每个错误码的含义。
{
"code": 40001,
"message": "用户名不能为空",
"request_id": "req_abc123",
"docs": "https://api.example.com/errors#40001"
}
加个request_id是为了方便排查问题。日志里搜一下request_id,完整链路一目了然。这个习惯救过我很多次。
分页:别一口气把数据全吐出来
有些新人写接口,恨不得把所有数据一次返回给前端。用户表有一百万条数据,SELECT * FROM users,直接返回。这不叫接口,这叫DoS攻击。
标准分页应该这样设计:
GET /users?page=1&page_size=20
Response:
{
"data": [...],
"pagination": {
"page": 1,
"page_size": 20,
"total": 1000,
"total_pages": 50
}
}
page_size别给太大,建议上限100。超过100的一律按100处理,这是对自己的保护,也是对数据库的尊重。
还有一种叫游标分页,适合数据量特别大且实时性要求高的场景:
GET /users?cursor=eyJpZCI6MTAwfQ&page_size=20
这种分页方式性能更稳,不会因为数据新增删除导致重复或遗漏。但实现起来稍微复杂点,看场景选用。
版本管理:给你的API留条后路
接口上线了,v1版本运行得好好的产品突然要加功能,改着改着发现要动原来的结构了,怎么办?直接改?万一有人还在用旧结构呢?
所以,版本管理是必须的:
/api/v1/users
/api/v2/users
新功能走v2,老接口v1继续维护,给个deprecated标记,给用户留足迁移时间。
我见过最骚的操作是:一个接口同时存在v1、v2、v3三个版本,每个版本的实现还略有不同。问为什么要这样?答曰:"历史遗留问题"。这种项目谁接谁倒霉。
安全性:别让你的API成为免费公共厕所
API安全是个老生常谈但总有人踩坑的话题。我见过有人在URL里传密码的:
GET /api/login?username=admin&password=123456
这种接口跟裸奔有什么区别?密码进了服务器日志、浏览器历史、代理服务器日志、CDN日志,四重曝光。密码这种东西,必须走POST body,HTTPS加密。
几个基本的安全措施:
- 所有接口走HTTPS,别省那点证书钱
- 敏感操作加身份验证,JWT或者OAuth2都行
- 请求频率做限制,别让接口被刷爆
- 输入参数做校验,别相信任何客户端数据
- 返回数据做脱敏,手机号、身份证号别明文返回
文档:没有文档的API跟没有说明书的产品一样
你设计了一套完美的API,路由清晰、状态码规范、错误处理友好。然后呢?写文档了吗?
没文档的API,等于没写。你自己知道怎么用,别人不知道。产品要对接,测开要测试,下一个后端要接你的班——他们都指着你的文档活呢。
文档要包含什么?
- 接口列表和功能说明
- 请求参数格式和示例
- 返回数据格式和示例
- 错误码对照表
- 认证方式说明
- 调用限制说明
工具推荐:Swagger/OpenAPI是目前最流行的方案,写一次文档,可以同时生成接口测试界面和客户端SDK。省时省力。
写在最后
API设计这事儿,说难不难,说简单也不简单。难的地方不在于你会不会用框架、会不会写代码,而在于你有没有站在调用者的角度想过问题。
好的API应该是这样的:看一眼文档,就能知道怎么用;出错了,能知道错在哪;接入了,能放心地依赖它。这才是API设计应该追求的目标。
下次写接口之前,先问自己三个问题:这个接口别人看得懂吗?出问题了别人能排查吗?万一我要改版了怎么平滑过渡?想清楚这三个问题,你的接口至少不会太差。
愿天下API都是好接口,愿天下开发者都不需要对接烂接口。共勉。