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/123DELETE /users/123,清爽多了。

4.4 资源命名:别让前端猜谜

资源命名这块,我踩过的坑最多。总结几条铁律:

  1. 用名词复数:/users 而不是 /user,/orders 而不是 /getOrder
  2. 用短横线分隔:/user-profiles 而不是 /user_profiles 或 /userProfiles
  3. 小写字母:全部小写,避免大小写敏感的问题
  4. 层级关系用斜杠:/users/123/orders 表示用户123的订单
  5. 查询参数用于过滤:/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,包括路由设计、中间件、异常处理这些实战内容。