Files

250 lines
7.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第5-5课:RESTful API设计
## 一、本课目标
完成本课后,你能够:
1. 理解资源、资源标识和资源表现形式;
2. 解释表述性状态转移(Representational State Transfer,REST)的基本思想;
3. 使用路径、HTTP方法和状态码共同描述一个接口;
4. 区分列表接口、详情接口和业务动作接口;
5. 为图书管理系统设计一组结构一致的RESTful API。
## 二、前置知识
- 已学过URL和HTTP请求方法;
- 已学过HTTP响应和常用状态码;
- 已学过JSON请求体和响应体;
- 不要求接触过FastAPI、Django或任何接口框架。
## 三、先理解“资源”
资源(Resource)是系统希望通过接口管理的业务对象,例如图书、作者、用户和订单。它不是只指数据库中的一行,也不是只指一个Python对象,而是从接口使用者角度看到的业务事物。
一本编号为10的图书可以用路径表示:
```text
/books/10
```
这里:
- `books`表示图书资源集合;
- `10`用于标识集合中的一本图书;
- 返回的JSON是这本图书在当前时刻的一种表现形式。
同一个资源可以有JSON、HTML等不同表现形式,但后端API通常返回JSON。
## 四、REST与RESTful是什么
REST是一种Web接口架构风格,不是协议、框架或Python库。RESTful表示一个接口的设计遵循REST的资源化思想。
入门阶段先掌握三条核心规则:
1. 路径主要描述资源,优先使用名词;
2. HTTP方法描述想对资源做什么;
3. HTTP状态码描述处理结果。
因此创建图书通常写成:
```text
POST /books → 201 Created
```
而不是:
```text
POST /createBook → 200 OK
```
后一种不一定不能运行,但它没有充分利用HTTP已有的语义。
## 五、从一个接口开始设计
设计接口时按以下顺序思考:
1. 管理的业务资源是什么?这里是图书;
2. 是操作集合还是单个资源?`/books`是集合,`/books/10`是单个资源;
3. 操作意图是什么?查询、创建、修改还是删除;
4. 输入放在哪里?路径参数、查询参数或JSON请求体;
5. 成功返回什么状态码和数据?
6. 可能失败的情况有哪些?
## 六、图书资源接口
| 方法 | 路径 | 输入位置 | 成功状态 | 作用 |
|---|---|---|---:|---|
| GET | `/books` | 查询参数 | 200 | 查询图书列表 |
| GET | `/books/{book_id}` | 路径参数 | 200 | 查询一本图书 |
| POST | `/books` | JSON请求体 | 201 | 创建图书 |
| PUT | `/books/{book_id}` | JSON请求体 | 200或204 | 整体替换图书 |
| PATCH | `/books/{book_id}` | JSON请求体 | 200或204 | 修改部分字段 |
| DELETE | `/books/{book_id}` | 路径参数 | 204 | 删除图书 |
表中的`{book_id}`是路径参数占位符。真实请求会写成`/books/10`,花括号不会原样发送。
## 七、列表、详情与分页
列表接口操作资源集合:
```text
GET /books?keyword=Python&page=1&page_size=20
```
- `keyword`负责筛选;
- `page`表示第几页;
- `page_size`表示每页多少条。
一种清晰的列表响应是:
```json
{
"items": [
{"id": 10, "title": "Python入门"}
],
"page": 1,
"page_size": 20,
"total": 1
}
```
详情接口操作单个资源:
```text
GET /books/10
```
存在时返回200和图书JSON;不存在时返回404,而不是返回200和空字符串。
## 八、新增接口的完整设计
请求:
```http
POST /books HTTP/1.1
Content-Type: application/json
{"title": "Web基础", "price": 59.00}
```
成功响应:
```http
HTTP/1.1 201 Created
Location: /books/10
Content-Type: application/json
{"id": 10, "title": "Web基础", "price": 59.00}
```
`Location`响应头告诉客户端新资源的地址。常见失败包括JSON格式错误、字段不合规则和图书编号冲突。
## 九、PUT与PATCH的区别
- PUT通常表示用请求内容整体替换资源的可修改状态;
- PATCH通常表示只修改请求中出现的字段。
例如只修改价格时,PATCH请求体可以是:
```json
{"price": 69.00}
```
项目必须明确PUT缺少字段时如何处理。不要仅凭方法名称猜测,接口文档需要写清楚。
## 十、业务动作如何设计
不是所有业务都能自然表示为基础增删改查。例如“取消订单”是一个带业务规则的动作。
可以把取消记录理解成订单下的子资源:
```text
POST /orders/10/cancellations
```
也有项目使用:
```text
POST /orders/10/cancel
```
RESTful不是机械规则。选择能够清楚表达业务、便于权限与审计、并且全项目一致的方案更重要。
## 十一、错误响应也需要设计
同一个详情接口可能产生不同结果:
| 情况 | 状态码 | 响应含义 |
|---|---:|---|
| 查询成功 | 200 | 返回资源 |
| 编号格式错误 | 400或422 | 客户端输入不符合要求 |
| 图书不存在 | 404 | 找不到指定资源 |
| 未登录 | 401 | 需要完成身份认证 |
| 没有权限 | 403 | 身份已知但禁止访问 |
| 后端未知异常 | 500 | 服务器未能完成处理 |
错误响应应保持固定结构,例如包含错误代码和面向调用者的消息,但不能泄露数据库密码或程序堆栈。
## 十二、完整示例
`rest_api_design_example.py`不建立网络连接,而是把一组接口设计保存为Python列表并输出。这样可以把注意力放在方法、路径和状态码的组合上。
## 十三、运行方法
```powershell
cd D:\Code\Python\05_web基础\5_5_RESTful_API设计
python rest_api_design_example.py
```
预期输出包含五条图书接口和一个带筛选、分页参数的列表请求示例。
## 十四、代码执行顺序
1. `main()`调用`build_book_api_design()`;
2. 函数返回由多个字典组成的列表;
3. `print_api_design()`逐条读取接口;
4. 每条接口按统一列顺序输出;
5. 最后输出列表筛选示例。
示例中的数据结构只是“接口设计表”,不是服务器,也不会真的新增或删除图书。
## 十五、常见错误
### 15.1 路径全部使用动词
`/getBooks`、`/createBook`和`/deleteBook`会把HTTP方法已有的意图重复写入路径,并使接口越来越不一致。
### 15.2 用GET修改数据
浏览器、缓存或爬虫可能自动访问GET链接。GET应当用于读取,不能因为输入方便就用它删除数据。
### 15.3 列表接口一次返回全部数据
数据增长后会拖慢数据库、服务器和客户端。列表接口应尽早明确分页规则和最大每页数量。
### 15.4 只设计成功响应
客户端真正需要处理的往往是校验失败、资源不存在、状态冲突和权限不足。接口设计必须包含失败边界。
### 15.5 认为RESTful只有唯一答案
REST提供设计思想,复杂业务动作可能有多种合理表达。项目内一致、语义清楚和文档完整比形式上的绝对统一更重要。
## 十六、课堂练习
打开`practice.py`,完成作者资源、订单业务动作和反例改造练习。练习文件不含参考答案。
## 十七、本课小结
RESTful API围绕资源组织路径,用HTTP方法表达操作,用状态码表达结果。设计一个接口时,不仅要考虑成功路径,还要明确输入位置、错误状态和响应结构。
## 十八、验收标准
- 能解释资源、集合路径和详情路径;
- 能组合路径、方法和状态码;
- 能区分查询参数、路径参数和JSON请求体;
- 能解释PUT与PATCH的基本差异;
- 能设计包含失败结果的图书接口;
- 示例可独立运行;
- 完成`practice.py`中的练习。