feat(web基础): 新增零基础Web开发课程
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
# 第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`中的练习。
|
||||
@@ -0,0 +1,49 @@
|
||||
# 第5-5课练习:RESTful API设计
|
||||
#
|
||||
# 本文件只提供题目、预期结果、自查清单和验收标准,不包含参考答案或代码骨架。
|
||||
|
||||
|
||||
# 练习一:设计作者资源接口
|
||||
# 1. 设计查询作者列表、查询一个作者、新增作者、修改作者和删除作者的接口。
|
||||
# 2. 每个接口写明HTTP方法、路径、成功状态码和用途。
|
||||
# 3. 列表接口增加name查询参数和分页参数。
|
||||
# 4. 详情接口使用author_id作为路径参数。
|
||||
|
||||
|
||||
# 练习二:设计订单业务接口
|
||||
# 1. 设计查询订单列表、查询订单详情和创建订单的接口。
|
||||
# 2. 为“取消订单”设计一个能清楚表达业务动作的接口。
|
||||
# 3. 分别考虑成功、订单不存在、订单状态不允许取消和请求字段错误。
|
||||
# 4. 为上述结果选择合适的状态码,并用注释解释选择原因。
|
||||
|
||||
|
||||
# 练习三:检查不推荐的接口
|
||||
# 分析以下路径的问题,并写出更清晰的设计:
|
||||
# 1. GET /getAllBooks
|
||||
# 2. POST /createBook
|
||||
# 3. POST /deleteBook?id=10
|
||||
# 4. GET /books/delete/10
|
||||
|
||||
|
||||
# 预期关键结果:
|
||||
# 1. 资源路径以名词为主,操作意图主要由HTTP方法表达;
|
||||
# 2. 列表和详情具有不同路径;
|
||||
# 3. 查询条件使用查询参数,资源编号使用路径参数;
|
||||
# 4. 成功与失败结果都有明确状态码。
|
||||
|
||||
|
||||
# 自查清单:
|
||||
# 1. 路径是否围绕资源设计,而不是堆叠动词?
|
||||
# 2. 同一类资源的路径命名是否一致?
|
||||
# 3. HTTP方法是否符合读取、创建、修改和删除的意图?
|
||||
# 4. 是否区分路径参数、查询参数和JSON请求体?
|
||||
# 5. 是否为创建操作选择201,为无响应体删除选择204?
|
||||
# 6. 是否考虑400、404、409和422等失败状态?
|
||||
|
||||
|
||||
# 最终验收标准:
|
||||
# 1. practice.py保留为题目文件,不写入参考答案;
|
||||
# 2. 作者资源至少包含五个接口;
|
||||
# 3. 订单接口能表达查询、创建和取消;
|
||||
# 4. 能解释四个不推荐接口的问题;
|
||||
# 5. 能用一句话说明RESTful是设计风格,不是某个Python框架。
|
||||
@@ -0,0 +1,63 @@
|
||||
"""第5-5课标准示例:用数据表格表达一组RESTful图书接口。"""
|
||||
|
||||
|
||||
def build_book_api_design() -> list[dict]:
|
||||
"""返回图书资源的接口设计,不发送真实网络请求。"""
|
||||
|
||||
return [
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/books",
|
||||
"success_status": 200,
|
||||
"purpose": "分页查询图书",
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/books/{book_id}",
|
||||
"success_status": 200,
|
||||
"purpose": "查询一本图书",
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"path": "/books",
|
||||
"success_status": 201,
|
||||
"purpose": "创建图书",
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/books/{book_id}",
|
||||
"success_status": 200,
|
||||
"purpose": "局部修改图书",
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/books/{book_id}",
|
||||
"success_status": 204,
|
||||
"purpose": "删除图书",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def print_api_design(api_design: list[dict]) -> None:
|
||||
"""按“方法、路径、状态码、用途”的顺序输出接口设计。"""
|
||||
|
||||
print("方法 路径 成功状态 用途")
|
||||
for api in api_design:
|
||||
print(
|
||||
f"{api['method']:<6} "
|
||||
f"{api['path']:<20} "
|
||||
f"{api['success_status']:<9} "
|
||||
f"{api['purpose']}"
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""创建并输出图书资源接口设计。"""
|
||||
|
||||
api_design = build_book_api_design()
|
||||
print_api_design(api_design)
|
||||
print("列表筛选示例:GET /books?keyword=Python&page=1&page_size=20")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user