4. HTTP协议与RESTful设计:HTTP方法、状态码、RESTful API设计原则与资源命名
说实话,我见过太多API接口设计得乱七八糟的项目了。有的把所有操作都塞进GET请求里,有的状态码永远返回200然后在body里塞个error字段。嗯,这些坑我都踩过。今天咱们就好好聊聊,怎么设计一套让人舒服的RESTful API。
4.1 HTTP方法:不只是GET和POST
很多人觉得HTTP方法就俩——GET和POST。其实不然。RESTful API里常用的有五个:
| 方法 | 用途 | 幂等性 | 安全性 |
|---|---|---|---|
| GET | 获取资源 | 是 | 是 |
| POST | 创建资源 | 否 | 否 |
| PUT | 完整更新资源 | 是 | 否 |
| PATCH | 部分更新资源 | 否 | 否 |
| DELETE | 删除资源 | 是 | 否 |
幂等性:同一个请求执行多次,结果都一样。比如DELETE /users/123,删一次和删十次,最终用户123都不存在了。
我在项目中遇到过有人用POST做更新操作,理由是「前端方便」。结果后来要做缓存和重试机制,整个团队都崩溃了。你想想看,如果用了幂等的PUT,重试根本不用考虑副作用。
4.2 HTTP状态码:别老用200了
我见过最离谱的API,所有请求都返回200,然后在响应体里写个code字段表示成功或失败。这完全是在浪费HTTP协议的能力。
常用的状态码其实就这些:
| 分类 | 状态码 | 含义 | 使用场景 |
|---|---|---|---|
| 2xx 成功 | 200 | OK | GET、PUT、PATCH成功 |
| 201 | Created | POST创建资源成功 | |
| 204 | No Content | DELETE成功,无返回体 | |
| 4xx 客户端错误 | 400 | Bad Request | 参数校验失败 |
| 401 | Unauthorized | 未认证或认证失败 | |
| 404 | Not Found | 资源不存在 | |
| 5xx | 500 | Internal Server Error | 服务器内部错误 |
个人习惯:我一般POST创建成功后返回201,同时在响应头里带上Location字段指向新资源的URL。这样客户端可以直接用这个URL去获取资源,省得自己拼地址。
4.3 RESTful设计原则:六个字搞定
RESTful设计说白了就六个字:资源、动作、表述。
- 资源:一切皆资源,用名词表示。比如/users、/orders
- 动作:用HTTP方法表示。GET获取、POST创建、PUT更新、DELETE删除
- 表述:资源的表现形式。JSON、XML、甚至HTML都行
为什么会这样设计?因为这样客户端和服务端的职责就分清楚了。客户端只管发请求,服务端只管处理资源。耦合度降到最低。
我曾经接手过一个项目,API路径长这样:/api/getUserInfo?id=123、/api/deleteUser?id=123。你发现问题了吗?动作写在了路径里,HTTP方法反而没用对。后来重构时改成GET /users/123和DELETE /users/123,清爽多了。
4.4 资源命名:别让前端猜谜
资源命名这块,我踩过的坑最多。总结几条铁律:
- 用名词复数:/users 而不是 /user,/orders 而不是 /getOrder
- 用短横线分隔:/user-profiles 而不是 /user_profiles 或 /userProfiles
- 小写字母:全部小写,避免大小写敏感的问题
- 层级关系用斜杠:/users/123/orders 表示用户123的订单
- 查询参数用于过滤:/users?status=active&page=1
避坑指南:我曾经在项目里用了动词+名词的命名方式,比如 /getUserOrders。结果后来需求变了,要支持批量查询,路径改成了 /getUserOrdersBatch。你看,这就是命名不规范导致的连锁反应。如果一开始就用 /users/123/orders,批量查询就是 /users/orders?ids=1,2,3,清晰多了。
4.5 实际案例:一个用户管理API
说了这么多理论,咱们看个实际例子。假设要设计用户管理相关的API:
// 获取用户列表
GET /users?page=1&per_page=20
→ 200 OK
{
"data": [...],
"meta": {
"total": 100,
"page": 1,
"per_page": 20
}
}
// 获取单个用户
GET /users/123
→ 200 OK
{
"id": 123,
"name": "张三",
"email": "zhangsan@example.com"
}
// 创建用户
POST /users
{
"name": "李四",
"email": "lisi@example.com"
}
→ 201 Created
Location: /users/124
// 更新用户(完整替换)
PUT /users/123
{
"name": "张三三",
"email": "zhangsansan@example.com"
}
→ 200 OK
// 部分更新
PATCH /users/123
{
"name": "张三三"
}
→ 200 OK
// 删除用户
DELETE /users/123
→ 204 No Content
我建议:分页信息一定要放在meta里,别跟数据混在一起。这样前端解析起来方便,后端改起来也灵活。另外,创建资源时返回201和Location头,这是RESTful的标准做法,别偷懒只返回200。
4.6 常见误区总结
最后,我把自己这些年见过的坑总结一下:
- 误区一:所有请求都用POST。结果就是没法做缓存,也没法利用浏览器的预加载能力。
- 误区二:状态码只用200和500。客户端根本不知道具体发生了什么错误,只能猜。
- 误区三:路径里带动词。比如 /getUser、/deleteUser。这完全违背了RESTful的初衷。
- 误区四:忽略HATEOAS。虽然不强制,但返回资源时带上相关链接,能让API更易用。
说白了,RESTful设计不是什么高深的理论,它就是一套约定。大家按这个约定来,前后端协作就顺畅了。你想想看,如果每个项目都自己发明一套命名规则,那前端换项目就得重新学一套API,多累啊。
嗯,今天就聊到这儿。下一章咱们讲讲实际开发中怎么用PHP实现这些RESTful API,包括路由设计、中间件、异常处理这些实战内容。