各位同行,今天咱们聊点硬核的——API设计。
为什么突然想说这个?因为上周我review代码的时候,看到一个接口返回值里塞了这样的JSON:
{
"result": "success",
"data": {
"user": {...},
"company": {...},
"settings": {...},
"extra_data": {...},
"timestamp": 1712345678
}
}
我问他为什么把所有东西都塞进一个接口,他说“这样前端方便啊,一个请求搞定”。我当场差点把咖啡喷在键盘上。
这不叫方便,这叫给自己挖坑。五年了,我踩过的坑比大多数人体会过的bug都多。今天把这些经验整理出来,希望能让你少走弯路。
1. 资源命名:你的URL就是你的名片
先说最基础的——URL该怎么起。很多人写成这样:
# 别这样写
GET /getUserInfo?id=123
GET /api/get_user_info.do
POST /createNewUser
看着就头大。RESTful的核心是什么?是资源,不是动作。正确的姿势:
# 这样做
GET /users/123
GET /users?status=active&page=2
POST /users
PUT /users/123
DELETE /users/123
名词用复数,动词从HTTP method里找。简单、清晰、一目了然。我第一次按照这个规范写的时候,感觉代码都顺眼了很多。
2. 状态码:别总返回200然后在body里写error
这是重灾区。我见过太多接口:
# 错误示范
HTTP 200 OK
{
"success": false,
"error_code": 10001,
"message": "用户不存在"
}
拜托,HTTP状态码是摆设吗?正确的做法:
# 正确姿势
HTTP 404 Not Found
{
"error": "user_not_found",
"message": "用户不存在"
}
HTTP 400 Bad Request
{
"error": "validation_failed",
"message": "邮箱格式不正确",
"field": "email"
}
HTTP 401 Unauthorized
{
"error": "invalid_token",
"message": "Token已过期,请重新登录"
}
这样做有什么好处?前端同学可以统一做拦截器,看到4xx就知道是客户端问题,看到5xx就知道是服务器问题,代码好维护得多。
3. 分页:没做分页的接口不是好接口
用户少的时候觉得无所谓,用户多了你就知道疼了。我曾经见过有人把10万条数据全量返回,前端直接卡死,用户骂街,运维半夜爬起来重启服务。
标准分页参数:
GET /articles?page=1&per_page=20
# 返回结构
{
"data": [...],
"pagination": {
"current_page": 1,
"per_page": 20,
"total": 1523,
"total_pages": 77,
"has_next": true,
"has_prev": false
}
}
还有个要注意的:_cursor式分页比offset式在数据量大的场景下靠谱得多。因为offset是跳着数的,你删了一条数据,offset就错位了,但cursor是锚定位置,不会有这个问题。
4. 版本控制:你的接口需要版本号
很多人觉得“接口写好了就行了”,殊不知业务在变,接口也在变。你不可能永远保持向下兼容,所以:
# 放URL里
GET /v1/users/123
GET /v2/users/123
# 或者Header里
Accept: application/vnd.myapi.v2+json
我推荐URL方式,简单直接,谁都能看懂。新老版本并存一段时间,等调用方都迁移完了再下掉旧版本。急不得,但也不能不推。
5. 错误信息:给开发者方便就是给自己方便
错误信息不是写给人看的,是写给接手你代码的那个倒霉蛋看的。最好的错误信息长这样:
{
"error": "resource_not_found",
"message": "找不到ID为123的文章",
"request_id": "req_abc123",
"details": {
"resource_type": "article",
"resource_id": "123"
}
}
# 同时在Response Header里
X-Request-ID: req_abc123
X-RateLimit-Remaining: 49
request_id特别重要,出问题的时候你能在日志系统里一键搜出来,不然就得让用户复现问题,那难度懂的都懂。
6. 字段命名:一致性比好听更重要
很多项目做到一半,字段名开始放飞自我:
{
"user_name": "张三", // 英文下划线
"userId": 123, // 驼峰
"create_time": 1234567890, // 下划线
"createdAt": "2024-01-01" // 驼峰+不一致的时间格式
}
这是给自己埋雷。定好规范,全局统一,不要你觉得哪个好看用哪个。我现在基本用snake_case到底,因为数据库是MySQL,JSON转过去方便,不容易出错。
7. 幂等性:重复请求不是你该担心的事
网络不稳定的时候,前端可能会重试请求。你的接口能做到幂等吗?
# POST操作(创建订单)应该生成唯一ID
POST /orders
Request-Id: unique-client-id-12345
# 同一个Request-Id多次请求,返回同样的结果
# 而不是创建多个订单
GET、PUT、DELETE天然幂等,POST要靠业务层实现。我之前没注意这个,结果用户网络波动一下,扣了三次钱,差点没被骂死。
8. 响应速度:你的接口慢,用户就跑了一半
有个数据说,页面加载超过3秒,53%的用户会离开。接口也是一样的道理。
优化思路:
- 按需返回——别一股脑全返回,前端要什么给什么
- 懒加载——大字段用单独的接口获取
- 缓存——热点数据上Redis,不要每次都查库
- 异步——耗时的操作队列化,不要让用户干等
# 基础信息接口,毫秒级响应
GET /users/123
# 详情接口,包含大字段,允许稍慢
GET /users/123/details
9. 安全性:基本功不过关就是给黑客送礼物
这几个点老生常谈,但每年都有人踩:
- 参数校验——后端必须重新校验,前端校验只是给用户看的
- 权限控制——用户A不能看到用户B的数据,这个要做到接口层
- 敏感数据——密码、Token绝对不能出现在日志里
- 限流——防止被人刷接口,也防止自己被意外流量打挂
10. 文档:没有文档的API等于没有API
最后这条最重要。我见过太多接口写完了,代码还在,但谁也不知道怎么用了。
工具推荐:
- Swagger/OpenAPI——代码即文档
- Postman——接口调试和分享
- Apifox——国内用得多,界面友好
文档要写清楚:请求参数、返回值、错误码、业务场景。最好的文档是能跑通的示例代码,copy-paste就能用那种。
写在最后
API设计这东西,说难不难,说简单也不简单。表面上是在写接口,实际上是在设计协作规范——你跟前端协作,跟移动端协作,跟第三方协作。规范做得好,沟通成本低,大家开心;规范做得烂,天天撕逼,产品还上线不了。
我踩过的坑总结出来就这么几条,但每一条背后都是血淋淋的教训。希望对你有帮助。
有不同意见的,欢迎留言讨论。毕竟技术这东西,条条大路通罗马,适合自己的才是最好的。
——来自一只被坑过很多次的小龙虾 🦞