RESTful设计架构风格

RESTful 设计(架构风格)

1.1 REST 是什么?

REST(Representational State Transfer) 不是协议,是一种API 设计风格。核心思想:把一切都看作”资源”,用 HTTP 方法表示”对资源做什么操作”。

通俗比喻:RESTful API 就像一个自助餐厅的取餐系统

 
资源 = 菜品(用户、订单、文章...)
 
URL  = 菜品的位置(/冷菜区/凉拌黄瓜)
 
HTTP 方法 = 你要对这个菜做什么(看/拿/换/扔掉)
 
RESTful 设计 = 把所有操作统一成"找到资源位置 + 告诉我你要干嘛"
 

1.2 RESTful 的核心原则

原则说明例子
资源用名词,不用动词URL 表示”是什么”,不是”做什么”/users/getUsers
用 HTTP 方法表示操作GET 查、POST 建、PUT 改、DELETE 删GET /users 查所有用户
无状态每个请求自带全部信息,服务器不记你之前干了啥每次请求都带 token
统一接口所有资源都用同一套方式操作用户、订单、文章都用 GET/POST/PUT/DELETE
分层系统客户端不关心背后有几层(缓存、网关、负载均衡)你只管发请求,中间经过什么你不知道

RESTful 资源操作映射图


graph TD

    R["资源 /users"]

    R -->|"GET /users"| A["📋 查询列表 → 200"]

    R -->|"GET /users/1"| B["📋 查询单个 → 200 / 404"]

    R -->|"POST /users"| C["➕ 创建 → 201"]

    R -->|"PUT /users/1"| D["🔄 全量替换 → 200"]

    R -->|"PATCH /users/1"| E["✏️ 部分更新 → 200"]

    R -->|"DELETE /users/1"| F["🗑️ 删除 → 204"]

1.3 URL 设计规范

✅ 正确示范

 
GET    /users              → 获取用户列表
 
GET    /users/1            → 获取 id=1 的用户
 
POST   /users              → 创建新用户
 
PUT    /users/1            → 更新 id=1 的用户(全量)
 
PATCH  /users/1            → 更新 id=1 的用户(部分)
 
DELETE /users/1            → 删除 id=1 的用户
 
GET    /users/1/orders     → 获取用户 1 的订单列表(子资源)
 
GET    /users/1/orders/5   → 获取用户 1 的第 5 号订单
 

❌ 错误示范

 
GET    /getUsers           → ❌ URL 里有动词(GET 已经表示"获取"了)
 
POST   /createUser         → ❌ 同上,POST 已经表示"创建"
 
GET    /user/delete/1      → ❌ 用 GET 做删除操作,违反语义
 
POST   /users/1/delete     → ❌ URL 里有动词,应该用 DELETE /users/1
 
GET    /users/getAll       → ❌ 应该是 GET /users
 

1.4 状态码配合 RESTful

操作成功状态码说明
GET /users200返回用户列表
GET /users/1200返回单个用户
GET /users/999(不存在)404找不到
POST /users201创建成功(返回新资源)
PUT /users/1200更新成功(返回更新后的资源)
PATCH /users/1200部分更新成功
DELETE /users/1204删除成功(没有返回体)

1.5 完整 FastAPI 示例

 
from fastapi import FastAPI, HTTPException         # 导入 FastAPI 和异常类
 
from pydantic import BaseModel                      # 导入数据验证模型
 
app = FastAPI()                                     # 创建应用实例
 
# ---- 模拟数据库 ----
 
fake_db = {                                         # 假装这是数据库
 
    1: {"id": 1, "name": "张三", "age": 20},
 
    2: {"id": 2, "name": "李四", "age": 22},
 
}
 
class UserCreate(BaseModel):                        # 创建用户时的数据格式
 
    name: str                                       # 必填:用户名
 
    age: int                                        # 必填:年龄
 
class UserUpdate(BaseModel):                        # 更新用户时的数据格式
 
    name: str | None = None                         # 可选:用户名
 
    age: int | None = None                          # 可选:年龄
 
# ---- GET /users:获取用户列表 ----
 
@app.get("/users")                                  # GET 方法,路径 /users
 
def list_users():
 
    return list(fake_db.values())                   # 返回所有用户
 
# ---- GET /users/{id}:获取单个用户 ----
 
@app.get("/users/{user_id}")                        # GET 方法,路径带参数
 
def get_user(user_id: int):
 
    if user_id not in fake_db:                      # 用户不存在
 
        raise HTTPException(status_code=404, detail="用户不存在")  # 抛 404
 
    return fake_db[user_id]                         # 返回该用户
 
# ---- POST /users:创建用户 ----
 
@app.post("/users", status_code=201)                # POST 方法,成功返回 201
 
def create_user(user: UserCreate):                  # 请求体自动解析为 UserCreate
 
    new_id = max(fake_db.keys()) + 1                # 生成新 id
 
    new_user = {"id": new_id, **user.model_dump()}  # 组装用户数据
 
    fake_db[new_id] = new_user                      # 存入"数据库"
 
    return new_user                                 # 返回创建的用户(201 Created)
 
# ---- PUT /users/{id}:全量更新 ----
 
@app.put("/users/{user_id}")                        # PUT 方法
 
def replace_user(user_id: int, user: UserCreate):   # 全量更新需要所有字段
 
    if user_id not in fake_db:                      # 用户不存在
 
        raise HTTPException(status_code=404, detail="用户不存在")  # 抛 404
 
    updated = {"id": user_id, **user.model_dump()}  # 用新数据完全替换
 
    fake_db[user_id] = updated                      # 更新"数据库"
 
    return updated                                  # 返回更新后的用户
 
# ---- PATCH /users/{id}:部分更新 ----
 
@app.patch("/users/{user_id}")                      # PATCH 方法
 
def update_user(user_id: int, user: UserUpdate):    # 部分更新,字段可选
 
    if user_id not in fake_db:                      # 用户不存在
 
        raise HTTPException(status_code=404, detail="用户不存在")  # 抛 404
 
    stored = fake_db[user_id]                       # 取出原有数据
 
    updates = user.model_dump(exclude_unset=True)   # 只取客户端实际传了的字段
 
    stored.update(updates)                          # 合并:只改传了的字段
 
    return stored                                   # 返回更新后的用户
 
# ---- DELETE /users/{id}:删除用户 ----
 
@app.delete("/users/{user_id}", status_code=204)   # DELETE 方法,成功返回 204
 
def delete_user(user_id: int):
 
    if user_id not in fake_db:                      # 用户不存在
 
        raise HTTPException(status_code=404, detail="用户不存在")  # 抛 404
 
    del fake_db[user_id]                            # 删除用户
 
    # 204 不返回任何内容
 

1.6 非 CRUD 操作怎么设计?

有些操作不好用名词表示,比如”登录""发送验证码""导出报表”。

方案一:用动词作子资源(推荐)

 
POST   /users/1/activate     → 激活用户 1
 
POST   /auth/login            → 登录
 
POST   /auth/logout           → 登出
 
POST   /orders/5/cancel       → 取消订单 5
 
GET    /reports/export        → 导出报表
 

规则:动词放在资源后面,用 POST 触发。不是所有操作都能完美映射到 CRUD,别死板。

方案二:用状态转换表示

 
PATCH /orders/5
 
{"status": "cancelled"}       → 通过修改状态来"取消"订单
 

这种更 RESTful,但不如方案一直观。团队选一种统一就行。


速记卡(面试闪卡)

Q1:一句话讲清「RESTful设计架构风格」到底是什么?

A:RESTful 是一种把一切当资源、用 HTTP 方法表达增删改查的 API 设计风格。

Q2:一、REST 是什么 —— 怎么理解?

A:REST(表现层状态转移)不是协议,是风格:把系统里的”用户、订单”都看成资源(名词 URL),用 GET/POST/PUT/DELETE 说清要干什么。就像自助餐厅——菜品是资源、位置是 URL、拿/换/扔是 HTTP 方法。看懂差异,换个工具习惯也不废。

Q3:二、核心设计原则 —— 怎么理解?

A:四件套:① URL 用名词不用动词(✅ /users,❌ /getUsers);② 用 HTTP 方法表操作;③ 无状态——每个请求自带全部信息(带 token,服务器不记你之前干啥);④ 统一接口+分层系统(客户端不关心中间过了几层缓存、网关)。

Q4:三、状态码怎么配 —— 怎么理解?

A:GET 成功 200、POST 创建 201、PUT/PATCH 更新 200、DELETE 删除 204(无返回体)、找不到 404。状态码是 RESTful 的”礼貌用语”,把成功失败说清楚,别老拿 200 包一切,前端不好判断。

Q5:四、非 CRUD 操作怎么设计 —— 怎么理解?

A:登录、导出这类不好套名词的操作:用动词子资源 POST /auth/login、POST /orders/5/cancel,或改状态 PATCH /orders/5 {“status”:“cancelled”}。不是所有操作都能完美映射 CRUD,别死板,团队统一一种就行。

Q6:核心速记主线有哪些?

  • REST 是风格不是协议,核心是”资源+HTTP方法”

  • URL 用名词,动作交给 GET/POST/PUT/DELETE

  • 无状态:请求自带全部上下文

  • 状态码是 RESTful 的礼貌(200/201/204/404)

  • 非 CRUD 用动词子资源或状态转换

口诀

A:REST 不是协议是风格,

资源当名词方法定动作;

无状态自带全部信息,

状态码礼貌别总回二百。

相关链接