在日常开发中,你可能遇到过这样的困惑:
为什么有的接口用 GET 请求,有的用 POST?
查询数据用 GET 没问题,那 新增、修改、删除 要用什么方法?
PUT、PATCH、DELETE 这些 HTTP 方法到底有什么区别?
为什么要搞这么复杂,直接用 POST 不行吗?
今天我们就来深入聊聊这个话题—— 接口设计,到底要不要区分"查询接口"和"动作接口"?
查询接口是指那些只获取数据、不改变服务端状态的接口。这些接口的共同特点是: 调用多次和调用一次的效果是一样的 ,不会对数据产生任何副作用。这些接口的共同特点是: 调用一次和调用多次的结果可能不同 ,会产生副作用。- 当我们看到 /api/users 是 GET 请求时,我们知道这是在 查看 用户列表。
- 当我们看到 /api/users 是 POST 请求时,我们知道这是在 创建 新用户。
- HTTP 方法本身就是一种语义表达 ,让调用者不需要去看文档就知道这个接口要做什么。
GET /api/users # 获取用户列表
POST /api/users # 创建新用户
GET /api/users/123 # 获取指定用户
PUT /api/users/123 # 更新指定用户(完整更新)
DELETE /api/users/123 # 删除指定用户
浏览器和 CDN 会对 GET 请求进行缓存,但 不会缓存 POST 请求 。# 第一次请求
GET /api/products?category=books
→ 返回 200 OK + 数据 + Cache-Control: max-age=3600
# 缓存有效期内再次请求
GET /api/products?category=books
→ 直接从缓存返回,不需要到达服务器
而动作接口不应该被缓存,因为每次调用都可能产生不同的结果。- GET 请求的参数暴露在 URL 中,适合 不敏感 的查询操作
- POST 请求的参数在请求体中,适合 敏感 的操作(如登录、支付)
- GET 请求可能会被浏览器history记录、server日志记录,而 POST 相对更隐蔽
幂等性是指:同一个请求执行一次和执行多次的结果是一样的。理解幂等性对于 重试机制 非常重要。如果一个接口调用失败,客户端可能会自动重试:REST(Representational State Transfer) 是一种架构风格,它强调:虽然 RESTful 不是银弹,但它是一种 被广泛接受和理解 的约定。遵循这个约定可以让你的 API 更容易被其他开发者接受。# 查询用户 - 用 POST
POST/api/getUser
POST/api/queryUserList
# 创建用户 - 用 POST
POST/api/createUser
# 更新用户 - 用 POST
POST/api/updateUser
# 删除用户 - 用 POST
POST/api/deleteUser
- 语义混乱
调用方无法从请求本身判断这个接口是要查询还是修改,必须依赖接口命名或者文档。 - 无法利用 HTTP 语义
缓存、重试、安全性等 HTTP 特性都无法发挥作用。 - 可发现性差
好的 RESTful API 可以通过 HATEOAS 让客户端发现相关操作,但如果都用,这个优势就荡然无存了。
开发者需要记住每个接口的具体名称,而不是用惯用的方式与 API 交互。// 查询接口 - GET
@GetMapping("/api/users")
public List<User> getUsers() { ... }
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable Long id) { ... }
// 动作接口 - POST
@PostMapping("/api/users")
public User createUser(@RequestBody User user) { ... }
// 但如果动作需要幂等性,用 PUT 更合适
@PutMapping("/api/users/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) { ... }
@DeleteMapping("/api/users/{id}")
public void deleteUser(@PathVariable Long id) { ... }
# GET 查询 - URL 长度有限制
GET/api/users?age=20&city=beijing&status=active&sort=name&order=asc
# POST 查询 - 请求体可以传递复杂结构
POST/api/users/search
{
"filters":[
{"field":"age","operator":">=","value":20},
{"field":"city","equals":"beijing"},
{"field":"status","equals":"active"}
],
"sort":{"field":"name","order":"asc"},
"page":1,
"size":20
}
这种场景下,用 POST 做 查询 是合理的,但要注意命名规范:@PostMapping("/api/users/search")
public List<User> searchUsers(@RequestBody UserQuery query) { ... }
// 批量删除 - 用 DELETE,幂等
@DeleteMapping("/api/users/batch")
public void deleteUsers(@RequestBody List<Long> ids) { ... }
// 批量更新 - 用 PUT,幂等
@PutMapping("/api/users/batch")
public List<User> updateUsers(@RequestBody List<User> users) { ... }
# 用动词(传统方式)
POST /api/createOrder
POST /api/deleteUser
# 用名词 + HTTP 方法(RESTful)
POST /api/orders # 创建订单
DELETE /api/users/123 # 删除用户
推荐使用名词 + HTTP 方法的方式 ,因为它更符合 RESTful 规范。# 发送验证码 - 很难找到合适的名词
POST /api/sendSmsCode
# 发起支付 - 也是动作
POST /api/initiatePayment
# 这种情况用动词是可以接受的
随着 API 的演进,可能需要对接口进行版本控制:# URL 版本控制
GET /api/v1/users
GET /api/v2/users
# Header 版本控制
GET /api/users
Accept: application/vnd.myapp.v2+json
技术上,HTTP 规范并没有禁止 GET 请求有请求体。但实际上:- 大多数 HTTP 客户端和服务器对 GET 请求体的支持不一致
建议 :查询条件复杂时用 POST 查询,而不是给 GET 加请求体。POST 是最"万能"的方法,但这不意味着它应该被滥用:- 更新 用 PUT(完整更新)或 PATCH(部分更新)
这些方法有明确的语义,不应该全部用 POST 代替- 是一个面向公众的 API,需要被大量第三方调用 → 尽量遵循 RESTful
关键是 团队统一、文档清晰 ,而不是教条地遵循某个规范。回到最初的问题: 接口设计要不要区分"查询接口"和"动作接口"?
当然,也不必教条地遵循规则。 关键是团队统一、约定明确、文档清晰 。记住: 好的 API 设计是为了让人更容易理解和使用 ,而不是为了彰显技术。
阅读原文:原文链接
该文章在 2026/3/30 10:34:59 编辑过