Skip to content

HTTP-REST硬语义与资源建模

第一课时

REST与HTTP硬语义打底

  • 新建目录:project/api_design/(后面专门放接口设计稿)

学习目标

  • 能清晰解释并正确使用:HTTP 方法、幂等性、状态码语义

  • 能把一个需求翻译成“资源模型 + REST 路由设计”

  • 产出一份可复用的《API 设计备忘录》(后面每天都用得上)

概念学习

1.1 HTTP 方法与幂等(重点)
  • 首先了解一下三个核心概念:资源、状态、幂等

    • 资源(Resource):HTTP 世界里你操作的是“资源”,通常用 URL 表示:

      /users/123: 用户 123 这个资源

      /orders/2026-0001: 订单 2026-0001

      /cart/items: 购物车条目集合(集合也是资源)

    • 状态(State)

      资源“现在长什么样/处于什么状态”,比如订单是 PAID 还是 CREATED。

    • 幂等(Idempotent)

      同一个请求重复执行 N 次,最终系统状态与执行 1 次相同(结果可能一样,也可能不完全一样,但“资源状态”不能被越改越离谱)。

      幂等关心的是 服务端状态,不是你看到的返回体是否一模一样。

      幂等非常现实:网络超时、前端重试、网关重发、用户疯狂连点按钮……都会让“同一请求被重复发送”。

      一个很直观的判断句: “重复发一次,会不会多创建一个东西 / 多扣一次钱 / 多加一次库存 /多登陆一次?” 会 → 通常非幂等;不会 → 幂等。

  • GET:读资源(不改状态)

    • 语义:只读取,不应该改资源状态。
    • 幂等性:是(反复 GET 不会把资源越 GET 越变)。
    • 安全性(safe):是(safe 指“理论上不产生副作用”)。

    • 例子:

      GET /users/123 → 返回用户信息

      GET /orders/2026-0001 → 查订单详情

      GET /products?keyword=honor → 搜索商品列表

    • 注意:

有些系统会在 GET 时做“记录访问次数/埋点/更新 last_seen”,这就有副作用了。
这类副作用一般被认为是“可接受的副作用”,
但设计上尽量别让 GET 去做关键状态变更(比如扣费、下单)。
  • POST:创建/触发动作(通常非幂等)

    • 语义:提交一个“要处理的东西”到某个集合资源,或触发一个动作。
    • 幂等性:通常否(重复 POST 往往会多创建一次 / 多触发一次)。

    • 典型创建(非幂等) POST /orders body: {items:[...]} 第一次:创建订单 2026-0001 第二次(同样 body 又发一遍):可能创建 2026-0002 状态不同了 → 非幂等

    • 典型动作(更非幂等)

    POST /payments/charge body: {order_id:..., amount:...}

    重复一次可能多扣一次钱 这就是为什么支付系统对幂等特别敏感。

    那 POST 就不能幂等吗?能,但要你“设计幂等”

    最常见办法:幂等键 Idempotency-Key(或你自己提供客户端请求 ID)

    客户端发:POST /orders,带 header:Idempotency-Key: 8f3a...

    服务端:如果发现这个 key 已处理过,直接返回第一次的结果,不再创建新订单

    这招在 Stripe 之类支付/订单系统里非常常见(理念就是这样)。

  • PUT:整体替换(幂等)

    • 语义:对某个明确资源做“整体替换”。
    • 幂等性:是(你重复替换成同一个样子,最终还是那个样子)。

    • 例子(用户资料整体替换): PUT /users/123 body: {"name":"Jia", "email":"a@b.com", "age":25} 重复 N 次,最终 /users/123 都是这套字段值 → 幂等

    • 注意两个关键点:

      • PUT 通常是对“已知资源 ID”:/users/123

      • 整体替换意味着:没给的字段可能会被清空/重置(看实现,但语义上是替换)

      一个特别直观的类比: PUT 像“覆盖保存文件”——你把文件内容覆盖成你给的内容。 - PATCH:部分更新(通常按设计可幂等) - 语义:对资源做“局部修改”。 - 幂等性:取决于你怎么定义“修改”。

    • 幂等的 PATCH(推荐)

      在 PATCH 里表达“把字段设置成某个值”:

      PATCH /users/123

      body: {"nickname":"猫大人"}

      重复 N 次,nickname 都是“猫大人” → 幂等

    • 非幂等的 PATCH(常见坑)

      你在 PATCH 里表达“增量操作”:

      PATCH /cart/123 body: { "add_item": {"sku":"A", "qty":1} }

      发一次 qty +1

      再发一次 qty 又 +1

      状态变了 → 非幂等 所以: “set/replace”风格 → 更容易幂等 “increment/add/append”风格 → 往往非幂等(除非你设计去重逻辑)

  • DELETE:删除(幂等,重复删结果一致)

    • 语义:删除一个资源。
    • 幂等性:是(删一次资源没了;再删一次资源还是没了)。

    • 例子:

    DELETE /orders/2026-0001

    第一次:删除成功(可能返回 200 或 204)

    第二次:可能返回 404 Not Found(因为已经不存在了)

    但这不影响它“幂等”:最终状态都一样——资源不存在。

1.2 状态码语义(今天先覆盖最常用的一套)
200 OK:成功(读/改都可用)

201 Created:创建成功(通常配 Location)

204 No Content:成功但无返回体(常用于 DELETE/PUT)

400 Bad Request:参数/格式错误(客户端错)

401 Unauthorized:没登录/没 token

403 Forbidden:登录了但没权限

404 Not Found:资源不存在(也常用于“隐藏存在性”)

409 Conflict:冲突(重复创建、版本冲突等)

422 Unprocessable Entity:语义校验失败(字段格式对但不合法)

429 Too Many Requests:限流

500/502/503:服务端问题(区分自身/网关/不可用)

实际操作

  • 先说一下这一节课的目标哈 上面我们先了解一下主要的操作和状态码 接下来我们需要的实践并不是直接就上手敲代码设计后端了 而是我希望养成一个好的工程思维。

    因此我们第一节可主要是写一份接口笔记,达到公司可以评审的api接口文档,这是一个公司团队需要做的事情。

1.1 场景:Todo(带用户)

2.1 资源模型(必须写在 api_design/todo_v1.md)

- User

- TodoList(清单)

- TodoItem(事项)

写清每个资源最少字段(例):

User: id, email, created_at

TodoList: id, user_id, name, created_at

TodoItem: id, list_id, title, done, due_date, created_at, updated_at

2.2 路由设计(REST 风格)

至少包含:

- POST /v1/auth/login(登录动作,不是资源也可以)

- GET /v1/lists

- POST /v1/lists

- GET /v1/lists/{list_id}

- PATCH /v1/lists/{list_id}

- DELETE /v1/lists/{list_id}

- GET /v1/lists/{list_id}/items

- POST /v1/lists/{list_id}/items

- PATCH /v1/items/{item_id}(跨层直接定位 item)

- DELETE /v1/items/{item_id}

2.3每个接口都要写(最少写 6 个接口的完整细节)

对每个接口写清:

方法 + 路径

请求头(是否需要 Authorization: Bearer <token>)

Query 参数(分页:page,size 或 cursor,limit 二选一)

Request body(JSON 示例)

Response body(JSON 示例)

成功状态码与失败状态码(列出原因)

要求:至少有一个接口体现 201 + Location,至少一个体现 409/422 的使用理由。

2.4幂等性设计(核心)

在文档里回答这两条(写成“规则”):

哪些接口幂等?为什么?

如果要让 POST /v1/lists 也支持幂等,你会怎么做?

提示:Idempotency-Key(请求头)+ 服务端记录请求结果

如上便是这次的实际操作任务,考虑到大多数人应该是第一次接触,那我这一次会带着大家一点点写一下这个

分步教学

在开始前,我们先理解一下接口文档的本质是什么,很多人感觉就是写一写路径就完事了,其实这个需要做的是三件事: - 资源模型:我们需要明确的写出系统里面有那些对象比如user,item等等,他们怎么关联,最少的字段有什么 - HTTP语义:来表达什么意图 - 可预测的契约:请求响应长啥样,成功失败的状态码,前端如何重试

ok接下来我们第一步先搭建骨架

# Todo API v1 设计文档
## 0. 总览
## 1. 通用约定(认证、时间、错误格式、分页)
## 2. 资源模型(User / TodoList / TodoItem)
## 3. 路由设计(REST)
## 4. 端点详细说明(至少 6 个端点写全)
## 5. 幂等性设计规则(重点)

第二步 通用约定

这一步是很多人懒得写的地方,但我认为接口文档是给团队看的,要做的是清晰明了,所以这一步我们要统一口径。写作格式主要是规则+理由+例子,大家一眼可以看懂

  • API基本信息:base url和版本策略

    • 应该写什么(规则):API 统一前缀:/v1 版本升级策略:破坏性变更才升大版本(v1→v2)
    • 理由:公司里多人协作时,版本策略不写清楚,客户端不知道什么时候要改
    • todo例子: Base path:/v1 示例:GET /v1/lists
  • 数据格式:Content-Type 与编码

    • 规则:

      • 请求/响应 body 默认 JSON

      • 请求必须带:Content-Type: application/json; charset=utf-8(有 body 时)

      • 响应带:Content-Type: application/json

    • 理由:避免有人用表单、有人用 text,导致后端解析不一致

    • 例子:请求头:

    http Content-Type: application/json; charset=utf-8 Accept: application/json

  • 认证和权限:Authorization+401/403口径

    • 规则:

      • 除 /v1/auth/login 外,其他接口都需要:Authorization: Bearer

      • 没 token / token 无效:401 Unauthorized

      • token 有,但访问别人的资源:403 Forbidden

      • (可选安全策略)也可以用 404 隐藏资源存在性,但必须写成全局规则并全站一致

    • 理由:防止前端不知道什么时候跳登陆,保证安全审计

    • 例子(俩失败案例):

      • 缺token

      http GET /v1/lists → 401

      • 访问他人list:

      http GET /v1/lists → 401

  • 统一错误返回格式

    • 规则:所有的非2xx都返回统一结构,例如:
    {
  "error": {
    "code": "validation_error",
    "message": "name is required",
    "details": { "field": "name" }
  },
  "request_id": "req_abc123"
}