12.RESTful API设计

1. 为什么需要RESTful API?

1.1 开发无非增删查改:CRUD的智慧

CRUD = Create(创建)、Read(读取)、Update(更新)、Delete(删除)

生活类比

想象你在管理一个图书馆

  • Create(创建):新书到了,登记入库
  • Read(读取):读者想找书,你去查找
  • Update(更新):书的信息有变化,更新记录
  • Delete(删除):旧书淘汰了,删除记录

实际例子

无论是博客系统、电商网站,还是待办事项应用,核心功能都是这4个操作:

博客系统:

  • Create:发布新文章
  • Read:查看文章列表、查看文章详情
  • Update:编辑文章
  • Delete:删除文章

用户管理:

  • Create:注册新用户
  • Read:查看用户信息
  • Update:修改用户信息
  • Delete:删除用户

💡 关键理解
99%的Web应用,核心功能就是CRUD!
学会了CRUD,你就掌握了Web开发的核心!


1.2 RESTful API:CRUD的标准表达

既然开发的核心是CRUD,那如何用API来表达这4个操作呢?

传统方式(不统一):

1
2
3
4
Create:/createUser
Read: /getUser?id=1
Update:/updateUser?id=1
Delete:/deleteUser?id=1

问题:每个接口命名都不一样,没有规律,难以记忆

RESTful方式(统一规范):

1
2
3
4
Create:POST   /users
Read: GET /users/1
Update:PUT /users/1
Delete:DELETE /users/1

优势

  • ✅ 统一:都用同一个资源(/users
  • ✅ 清晰:HTTP方法直接表达操作类型
  • ✅ 规范:所有接口都遵循相同模式

1.3 RESTful API的定义

RESTful API = 遵循REST规范的API设计方式

REST = Representational State Transfer(表述性状态转移)

核心思想:用资源HTTP方法来设计API

CRUD与HTTP方法的对应关系

CRUD操作 HTTP方法 RESTful API示例
Create(创建) POST POST /users
Read(读取) GET GET /usersGET /users/1
Update(更新) PUT/PATCH PUT /users/1
Delete(删除) DELETE DELETE /users/1

💡 记忆技巧
RESTful API = 用"资源"和"动作"来设计接口
CRUD的4个操作,对应HTTP的4个方法,简单明了!


2. RESTful规范

2.1 资源(Resource)

资源 = API操作的对象(如用户、文章、订单)

命名规范

  • ✅ 使用名词(复数形式)
  • ✅ 使用小写字母
  • ✅ 多个单词用连字符-)连接

实际例子

✅ 正确 ❌ 错误
/users /getUsers/user
/articles /articleList/article
/todo-items /todoItems/todos

2.2 HTTP方法

HTTP方法 = 对资源执行的操作

HTTP方法 作用 示例
GET 获取资源 GET /users - 获取用户列表
POST 创建资源 POST /users - 创建新用户
PUT 更新资源(完整更新) PUT /users/1 - 更新用户1的所有信息
PATCH 更新资源(部分更新) PATCH /users/1 - 只更新用户1的部分信息
DELETE 删除资源 DELETE /users/1 - 删除用户1

实际例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取用户列表
GET /users

// 获取单个用户
GET /users/1

// 创建新用户
POST /users
Body: { "name": "张三", "email": "zhangsan@example.com" }

// 更新用户
PUT /users/1
Body: { "name": "李四", "email": "lisi@example.com" }

// 删除用户
DELETE /users/1

2.3 URL设计规范

2.3.1 资源层级

原则:用URL路径表示资源的层级关系

实际例子

1
2
3
4
5
6
7
✅ 正确:
GET /users/1/articles # 获取用户1的所有文章
GET /users/1/articles/5 # 获取用户1的第5篇文章
GET /articles/5/comments # 获取文章5的所有评论

❌ 错误:
GET /getUserArticles?userId=1&articleId=5 # 不符合RESTful规范

2.3.2 查询参数

作用:用于过滤、排序、分页等

实际例子

1
2
3
4
5
6
7
8
9
10
11
# 过滤
GET /users?status=active # 获取状态为active的用户

# 排序
GET /articles?sort=createdAt&order=desc # 按创建时间倒序

# 分页
GET /articles?page=1&limit=10 # 第1页,每页10条

# 组合使用
GET /articles?status=published&page=1&limit=10&sort=createdAt

2.4 HTTP状态码

状态码 = 表示请求的结果

常用状态码

状态码 含义 使用场景
200 成功 请求成功
201 已创建 创建资源成功
400 请求错误 参数错误、格式错误
401 未授权 未登录或token过期
403 禁止访问 没有权限
404 未找到 资源不存在
500 服务器错误 服务器内部错误

实际例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 成功
GET /users/1
Response: 200 OK
Body: { "id": 1, "name": "张三" }

// 资源不存在
GET /users/999
Response: 404 Not Found
Body: { "error": "用户不存在" }

// 参数错误
POST /users
Body: { "name": "" } // 名字为空
Response: 400 Bad Request
Body: { "error": "用户名不能为空" }

3. API设计原则

3.1 统一性原则

原则:所有接口遵循相同的设计规范

实际例子

1
2
3
4
5
6
7
8
9
10
11
12
✅ 统一的设计:
GET /users # 获取用户列表
GET /users/:id # 获取单个用户
POST /users # 创建用户
PUT /users/:id # 更新用户
DELETE /users/:id # 删除用户

GET /articles # 获取文章列表
GET /articles/:id # 获取单个文章
POST /articles # 创建文章
PUT /articles/:id # 更新文章
DELETE /articles/:id # 删除文章

3.2 简洁性原则

原则:URL要简洁明了,避免过长

实际例子

1
2
3
4
5
✅ 简洁:
GET /users/1

❌ 冗长:
GET /api/v1/users/getUserById?id=1&format=json

3.3 可扩展性原则

原则:设计时要考虑未来可能的功能扩展

实际例子

1
2
3
4
5
6
✅ 可扩展:
GET /users/:id/articles # 可以扩展为获取用户的其他资源
GET /users/:id/articles/:id # 可以扩展为获取用户的具体文章

❌ 不可扩展:
GET /getUserArticles # 难以扩展其他功能

3.4 安全性原则

原则:敏感操作需要认证和授权

3.4.1 什么是认证和授权?

认证(Authentication):你是谁?——验证用户身份(登录)

授权(Authorization):你能做什么?——验证用户权限(是否有权限执行操作)

生活类比

  • 认证:进学校要出示学生证(证明你是学生)
  • 授权:只有管理员才能进校长办公室(权限不同)

3.4.2 Bearer Token:身份凭证

Bearer Token = 一种身份认证方式,就像"通行证"

工作原理

  1. 登录获取Token:用户登录后,服务器返回一个Token
  2. 携带Token请求:后续请求在Header中携带这个Token
  3. 服务器验证:服务器验证Token,确认用户身份

实际例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 第一步:用户登录,获取Token
POST /api/login
Body: { "username": "zhangsan", "password": "123456" }
Response: {
"code": 200,
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}

// 第二步:使用Token访问需要认证的接口
GET /api/users/me
Headers: {
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response: {
"code": 200,
"data": {
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
}
}

3.4.3 Bearer Token的使用方式

格式Authorization: Bearer <token>

前端代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 使用axios发送请求
import axios from 'axios';

// 登录后保存Token
const login = async (username, password) => {
const response = await axios.post('/api/login', {
username,
password
});

// 保存Token到本地存储
localStorage.setItem('token', response.data.data.token);
};

// 使用Token访问需要认证的接口
const getUserInfo = async () => {
const token = localStorage.getItem('token');

const response = await axios.get('/api/users/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});

return response.data;
};

后端验证示例(FastAPI):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from fastapi import FastAPI, Header, HTTPException
from typing import Optional

app = FastAPI()

# 模拟Token验证(实际应该用JWT库)
def verify_token(token: str) -> bool:
# 实际项目中,这里应该验证JWT Token
return token.startswith('eyJ')

@app.get("/api/users/me")
async def get_current_user(
authorization: Optional[str] = Header(None)
):
if not authorization:
raise HTTPException(status_code=401, detail="未登录")

# 提取Token(去掉"Bearer "前缀)
if not authorization.startswith('Bearer '):
raise HTTPException(status_code=401, detail="Token格式错误")

token = authorization.replace('Bearer ', '')

# 验证Token
if not verify_token(token):
raise HTTPException(status_code=401, detail="Token无效或已过期")

# 返回用户信息
return {
"code": 200,
"data": {
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
}
}

3.4.4 Token的优势

优势 说明
无状态 服务器不需要存储Session,减轻服务器压力
跨域友好 可以轻松实现跨域认证
安全性高 Token可以设置过期时间,防止长期有效
易于扩展 可以在Token中携带用户信息(JWT)

实际例子

1
2
3
4
5
6
7
// 需要登录(认证)
GET /users/me # 获取当前用户信息
Headers: { "Authorization": "Bearer token123" }

// 需要权限(授权)
DELETE /users/1 # 删除用户(需要管理员权限)
Headers: { "Authorization": "Bearer admin_token" }

4. 接口文档编写

4.1 为什么需要接口文档?

作用

作用 说明
统一理解 前后端对接口理解一致
提高效率 前端不需要等后端,可以先看文档开发
便于维护 接口变更时,文档同步更新

4.2 接口文档的基本结构

包含内容

  1. 接口基本信息:URL、方法、描述
  2. 请求参数:参数名、类型、是否必填、说明
  3. 响应数据:响应格式、字段说明
  4. 错误码:可能的错误情况

实际例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
## 获取用户列表

**接口地址**`GET /api/users`

**接口描述**:获取用户列表,支持分页和过滤

**请求参数**

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| page | number | 否 | 页码,默认1 |
| limit | number | 否 | 每页数量,默认10 |
| status | string | 否 | 用户状态(active/inactive) |

**请求示例**

GET /api/users?page=1&limit=10&status=active

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

**响应数据**:
```json
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
}
],
"total": 100,
"page": 1,
"limit": 10
}
}

错误响应

1
2
3
4
5
{
"code": 400,
"message": "参数错误",
"data": null
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

---

## 4.3 使用工具编写接口文档

### 4.3.1 Swagger/OpenAPI

**Swagger** = 自动生成接口文档的工具

**优势**:

- ✅ 代码即文档(写代码时自动生成文档)
- ✅ 可视化界面(可以直接测试接口)
- ✅ 支持多种语言

**实际例子**(FastAPI自动生成):

```python
from fastapi import FastAPI

app = FastAPI()

@app.get("/users/{user_id}")
async def get_user(user_id: int):
"""
获取用户信息

- **user_id**: 用户ID
"""
return {"id": user_id, "name": "张三"}

访问 http://localhost:8000/docs 即可看到自动生成的文档。


4.3.2 Markdown文档

适用场景:简单项目、快速文档

实际例子

1
2
3
4
5
6
7
8
9
10
11
12
13
# API文档

## 用户相关接口

### 1. 获取用户列表
- **URL**: `GET /api/users`
- **参数**: `page`, `limit`
- **响应**: 用户列表

### 2. 获取用户详情
- **URL**: `GET /api/users/:id`
- **参数**: `id` (路径参数)
- **响应**: 用户信息

5. AI实战:使用AI设计文件上传API

5.1 任务描述

目标:使用AI工具设计一个文件上传的RESTful API

功能要求

  • 支持上传图片文件
  • 支持文件大小限制(最大5MB)
  • 支持文件类型限制(只允许jpg、png、gif)
  • 返回文件URL
  • 遵循RESTful规范

5.2 第一步:设计Prompt

Prompt示例

1
2
3
4
5
6
7
8
9
10
11
12
设计一个文件上传的RESTful API,要求:
1. 遵循RESTful规范
2. 使用POST方法
3. 支持图片上传(jpg、png、gif)
4. 文件大小限制5MB
5. 返回上传后的文件URL
6. 包含完整的接口文档:
- 接口地址、方法、描述
- 请求参数说明
- 响应数据格式
- 错误处理
7. 使用FastAPI实现(可选)

5.3 AI生成的API设计(示例)

5.3.1 接口设计

接口信息

  • URLPOST /api/files/upload
  • 方法:POST
  • 描述:上传文件

请求参数

参数名 类型 位置 必填 说明
file File Body 要上传的文件
type string Query 文件类型(image/document)

请求示例

1
2
3
4
5
6
7
8
9
10
// 使用FormData上传
const formData = new FormData();
formData.append('file', fileInput.files[0]);

fetch('/api/files/upload', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => console.log(data));

成功响应(200):

1
2
3
4
5
6
7
8
9
10
11
{
"code": 200,
"message": "上传成功",
"data": {
"fileId": "123456",
"fileName": "image.jpg",
"fileUrl": "https://example.com/uploads/image.jpg",
"fileSize": 102400,
"uploadedAt": "2024-01-15T10:30:00Z"
}
}

错误响应

1
2
3
4
5
6
7
8
9
10
11
12
13
// 文件太大(413)
{
"code": 413,
"message": "文件大小超过限制(最大5MB)",
"data": null
}

// 文件类型不支持(400)
{
"code": 400,
"message": "不支持的文件类型,仅支持jpg、png、gif",
"data": null
}

5.3.2 FastAPI实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from fastapi import FastAPI, UploadFile, File, HTTPException
from fastapi.responses import JSONResponse

app = FastAPI()

@app.post("/api/files/upload")
async def upload_file(
file: UploadFile = File(...),
type: str = "image"
):
"""
上传文件

- **file**: 要上传的文件
- **type**: 文件类型(image/document)
"""
# 检查文件大小(5MB限制)
contents = await file.read()
if len(contents) > 5 * 1024 * 1024:
raise HTTPException(status_code=413, detail="文件大小超过限制(最大5MB)")

# 检查文件类型
allowed_types = ['image/jpeg', 'image/png', 'image/gif']
if file.content_type not in allowed_types:
raise HTTPException(
status_code=400,
detail="不支持的文件类型,仅支持jpg、png、gif"
)

# 保存文件(这里简化处理)
file_url = f"https://example.com/uploads/{file.filename}"

return {
"code": 200,
"message": "上传成功",
"data": {
"fileId": "123456",
"fileName": file.filename,
"fileUrl": file_url,
"fileSize": len(contents),
"uploadedAt": "2024-01-15T10:30:00Z"
}
}

5.4 代码解析

核心思路

  1. 使用POST方法:上传文件属于创建操作
  2. 使用FormData:文件上传的标准格式
  3. 参数验证:检查文件大小和类型
  4. 统一响应格式:成功和错误都返回统一格式

关键点

  • 遵循RESTful规范(POST方法、资源路径)
  • 完整的错误处理
  • 清晰的接口文档

5.5 API调试软件

5.5.1 为什么需要API调试工具?

问题:写好了API,如何测试?

传统方式(不方便):

  • 写前端代码调用API(太麻烦)
  • 用浏览器只能测试GET请求(功能有限)
  • 用命令行curl(不够直观)

解决方案:使用API调试工具(如APIFox)

优势

优势 说明
可视化操作 图形界面,操作简单
支持所有HTTP方法 GET、POST、PUT、DELETE等
自动生成文档 测试的同时生成接口文档
团队协作 可以分享接口给团队成员

5.5.2 APIFox简介

APIFox = 一款集API设计、开发、测试、文档于一体的工具

核心功能

  • API测试:发送请求,查看响应
  • 接口文档:自动生成接口文档
  • Mock数据:模拟后端数据,前端可以先开发
  • 团队协作:多人协作开发

5.5.3 APIFox基本使用

第一步:创建请求

  1. 打开APIFox,点击"新建请求"
  2. 选择HTTP方法(GET、POST等)
  3. 输入接口地址(如:http://localhost:8000/api/users

第二步:设置请求参数

GET请求示例(查询用户列表):

1
2
3
4
5
6
方法:GET
URL:http://localhost:8000/api/users
查询参数:
- page: 1
- limit: 10
- status: active

POST请求示例(创建用户):

1
2
3
4
5
6
7
8
9
10
方法:POST
URL:http://localhost:8000/api/users
请求头:
Content-Type: application/json
请求体(JSON):
{
"name": "张三",
"email": "zhangsan@example.com",
"password": "123456"
}

第三步:设置认证信息

如果需要Bearer Token认证:

1
2
请求头:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

第四步:发送请求并查看响应

点击"发送"按钮,查看响应结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"code": 200,
"message": "成功",
"data": {
"list": [
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com"
}
],
"total": 100
}
}

5.5.4 实际例子:测试文件上传API

场景:测试前面设计的文件上传接口

步骤

  1. 创建POST请求

    1
    2
    方法:POST
    URL:http://localhost:8000/api/files/upload

  2. 设置请求体

    • 选择"form-data"格式

    • 添加字段:

      • file: 选择文件(类型:File)
      • type: image(类型:Text)
  3. 发送请求

    • 点击"发送"
    • 查看响应结果

APIFox界面示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────┐
│ POST http://localhost:8000/api/... │
├─────────────────────────────────────┤
│ 请求参数 │
│ ┌─────────────────────────────────┐ │
│ │ file [选择文件] [image.jpg] │ │
│ │ type [image] │ │
│ └─────────────────────────────────┘ │
│ │
│ [发送] │
│ │
│ 响应结果 │
│ ┌─────────────────────────────────┐ │
│ │ { │ │
│ │ "code": 200, │ │
│ │ "message": "上传成功", │ │
│ │ "data": { ... } │ │
│ │ } │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘

5.5.5 APIFox高级功能

1. 环境变量

可以设置不同环境(开发、测试、生产):

1
2
3
4
5
开发环境:
base_url: http://localhost:8000

生产环境:
base_url: https://api.example.com

2. 前置脚本和后置脚本

  • 前置脚本:请求前执行(如:自动生成Token)
  • 后置脚本:请求后执行(如:提取响应数据)

3. 接口文档自动生成

测试接口后,可以一键生成接口文档,分享给前端团队。

4. Mock数据

后端还没开发完?可以用APIFox生成Mock数据,前端先开发。

5.5.6 其他API调试工具

工具 特点
Postman 老牌工具,功能强大
APIFox 国产工具,界面友好,功能全面
Insomnia 轻量级,界面简洁
Thunder Client VS Code插件,集成在编辑器中

💡 推荐
对于初学者,推荐使用APIFox,界面友好,功能全面,而且有中文支持。


6. 选讲:API版本控制

6.1 为什么需要版本控制?

问题场景

  • API需要更新,但旧版本还在使用
  • 直接修改API会破坏现有功能

解决方案:API版本控制


6.2 版本控制的方式

6.2.1 URL路径版本控制

方式:在URL中包含版本号

实际例子

1
2
3
/v1/users          # 版本1
/v2/users # 版本2
/api/v1/users # 带api前缀的版本1

优点

  • 直观,一眼看出版本
  • 易于管理

缺点

  • URL变长

6.2.2 请求头版本控制

方式:在HTTP请求头中指定版本

实际例子

1
2
3
4
GET /users
Headers: {
"Accept": "application/vnd.api+json;version=2"
}

优点

  • URL不变,更简洁

缺点

  • 不够直观

6.2.3 查询参数版本控制

方式:在查询参数中指定版本

实际例子

1
GET /users?version=2

优点

  • 简单易用

缺点

  • 容易忘记加版本参数

6.3 版本控制策略

6.3.1 何时创建新版本?

需要创建新版本的情况

  • ✅ 修改了响应格式(字段名、结构变化)
  • ✅ 删除了接口
  • ✅ 修改了必填参数

不需要创建新版本的情况

  • ✅ 添加了新字段(向后兼容)
  • ✅ 添加了新接口
  • ✅ 修复了bug

6.3.2 版本兼容策略

策略

策略 说明 示例
同时支持多版本 旧版本和新版本都可用 v1和v2同时提供服务
逐步废弃 先发布新版本,再废弃旧版本 v1标记为deprecated,6个月后删除
强制升级 直接废弃旧版本 直接删除v1,只保留v2

推荐策略:同时支持多版本 + 逐步废弃


6.4 REST架构风格理解

6.4.1 REST的核心原则

REST的6个约束

  1. 客户端-服务器:前后端分离
  2. 无状态:每次请求都包含完整信息
  3. 可缓存:响应可以被缓存
  4. 统一接口:使用标准的HTTP方法
  5. 分层系统:可以有多层(代理、网关等)
  6. 按需代码:可以返回可执行代码(可选)

6.4.2 RESTful vs 非RESTful

对比

特性 RESTful 非RESTful
URL设计 资源导向 动作导向
HTTP方法 使用标准方法 只用GET/POST
状态码 使用标准状态码 都用200
数据格式 JSON 各种格式

实际例子

1
2
3
4
5
6
7
8
9
10
11
RESTful:
GET /users/1
POST /users
PUT /users/1
DELETE /users/1

非RESTful:
GET /getUser?id=1
POST /createUser
POST /updateUser?id=1
POST /deleteUser?id=1

本章总结

通过本章的学习,你已经掌握了:

RESTful规范

  • 资源命名规范
  • HTTP方法的使用
  • URL设计规范
  • HTTP状态码

API设计原则

  • 统一性原则
  • 简洁性原则
  • 可扩展性原则
  • 安全性原则

接口文档编写

  • 接口文档的结构
  • 使用工具编写文档
  • 文档的重要性

实战技能

  • 设计RESTful接口
  • 使用AI设计API
  • 编写接口文档

进阶知识(选讲):

  • API版本控制
  • REST架构风格

🌟 恭喜你!
你已经掌握了RESTful API设计的核心技能!
这些知识将帮助你:

  • 设计规范的API接口

  • 编写清晰的接口文档

  • 提高前后端协作效率

记住:好的API设计是前后端协作的基础!
继续练习,继续探索,API设计的世界等着你! 🚀