干后端开发这么多年,我见过太多「能跑就行」的API。有的返回200状态码但body里写着 error: "未授权",有的把整个数据库字段都塞进响应里,还有的接口命名充满了程序员的浪漫——getDataById2。今天咱们来聊聊,那些让前端想提刀来找你的反模式,顺便给你一套真正能拿得出手的API设计思路。
1. 状态码是个好东西,可惜有人不会用
最常见的骚操作是什么?接口返回200 OK,然后在body里写:
{
"code": 401,
"message": "登录已过期,请重新登录"
}
哥们儿,你这是200欺骗吗?HTTP状态码是给你用的,不是给你装饰的。状态码的意思是让客户端不用解析body就能知道发生了什么。200就是成功,400就是客户端的错,500就是服务器炸了。别TM在200的壳子里装401的核。
状态码参考:
4xx系列是客户端的问题,401没登录、403登录了但没权限、404不存在、422参数校验失败
5xx系列是服务器的问题,500就是bug,502是网关挂了,503是服务过载
2. 命名:请你正常说话
我见过最离谱的接口命名:
GET /api/v1/getUserInfoById
POST /api/v1/createNewUserData
DELETE /api/v1/deleteUserDataById
RESTful的核心是什么?资源+动作。你写的是getUserInfo,URL里又来个get,这叫语义重复,aka 说了又好像没说。正确姿势:
GET /api/v1/users/{id}
POST /api/v1/users
DELETE /api/v1/users/{id}
URL是名词,不是动词。动作交给HTTP方法来表达。这就是RESTful的精髓,不是让你在URL里写完整的英文作文。
3. 分页:别让前端同学拿头撞墙
有一种接口,列表数据直接limit 1000全量返回,美其名曰「方便」。然后线上OOM了来找我。我:???
分页不是可选项,是必选项。标准cursor分页姿势:
GET /api/v1/articles?limit=20&cursor=eyJpZCI6MTAwfQ==
响应:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6OTB9",
"has_more": true,
"total": 1234
}
}
cursor比offset好在哪?offset翻到第100页的时候,数据早就变了,你翻出来的东西可能是重复的也可能是漏掉的。cursor基于主键,物理位置稳定,数据库性能也更好(不用count)。
4. 错误响应:给前端一条活路
错误的API响应是这样的:
{"error": "操作失败"}
操作失败是什么鬼?哪个操作?哪个字段?为什么失败?前端拿着这个error能干啥?只能再喊你过来问你。
正确的错误响应要包含足够的信息让前端做出判断:
{
"error": {
"code": "VALIDATION_FAILED",
"message": "请求参数校验失败",
"details": [
{
"field": "email",
"message": "邮箱格式不正确"
},
{
"field": "age",
"message": "年龄必须大于0"
}
],
"request_id": "req_abc123xyz"
}
}
加上request_id是为了什么?线上出问题的时候,你搜日志只要搜这个ID,链路一目了然。前端拿着这个code可以直接做国际化文案映射,前端同学会感谢你的。
5. 版本管理:别让旧接口成为你的噩梦
URL versioning是最清晰的方式:
/api/v1/users
/api/v2/users
什么时候该升级版本?breaking change的时候——字段删了、字段类型改了、必填变可选了。非breaking的加字段之类的,不需要升版本,客户端无视新字段就好了。
有个坑要提醒:不要在一个版本里同时维护两套逻辑。有些人想着「我给v1加个参数就能兼容」,结果代码里if-else套三层,两个月后自己都看不懂了。简洁的版本策略能让你的代码少死一半脑细胞。
6. 安全性:别把你的接口暴露在裸奔状态
基础安全三件套:
- 认证:JWT还是OAuth2?小型项目JWT够用,要做第三方登录再上OAuth2
- 授权:每个接口都要校验当前用户有没有权限访问这个资源,别以为前端hide了按钮就安全了
- 限流:没有限流的API就是在裸奔,一个for循环就能把你打挂。nginx层限流+应用层限流,双保险
还有个容易被忽略的:敏感数据脱敏。接口返回里不要有明文密码、完整的身份证号银行卡号。能用手机号掩码(138****5678)就不要返回完整号码,这不是功能需求,这是合规需求。
7. 性能:N+1查询是性能杀手
N+1查询是后端新手最容易踩的坑,也是线上最常见的性能杀手。看这个代码:
users = db.query("SELECT * FROM users LIMIT 10")
for user in users:
user.orders = db.query("SELECT * FROM orders WHERE user_id = ?", user.id)
这10个用户就是11次查询。如果是1000个用户呢?1001次查询。数据库连接池分分钟被打满。
正确做法:JOIN或者IN查询,一次搞定。
users = db.query("SELECT u.*, GROUP_CONCAT(o.id) as order_ids FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.id IN (1,2,3...10) GROUP BY u.id")
1次查询,解决问题。性能差距可能是10倍到100倍的量级。
8. 文档:没有文档的API等于没有API
你设计了一套很棒的API,然后丢给前端一句「接口文档在Swagger上,自己看」。Swagger是个好工具,但它的价值在于实时同步,不是让你截图贴在Wiki里然后再也不更新。
我的建议:OpenAPI规范写清楚,Swagger UI做调试,Postman做环境隔离和用例管理。三件套配合好,接口文档和代码保持一致不是问题。
还有个细节:示例请求和示例响应要完整。一个只有字段列表没有示例的文档,前端看了还是一头雾水。每个接口最好配一个最小可运行的请求示例。
总结:好API的标准是什么?
说一千道一万,好API就一个标准——用起来舒服,不需要问人。状态码准确、命名清晰、错误信息有用、文档完善、安全到位。这些做到了,前端不会再半夜打电话骂你,这就是一个后端工程师最大的浪漫。
下次写接口之前,先问自己一个问题:如果前端是我女朋友,我能让她不问我自己看懂吗?做不到的话,回去改。
祝你的API天生丽质,少被吐槽。🦞