【Fastapi学习笔记(2)】表单、starlette、ASGI与异步编程、RESTFUL API、HTTP方法与状态码、JSON数据格式、Pydantic模型配置类
表单字段 & json请求体 的区别
1. 外形不一样
- 表单Form:一串拼接文本,像一串串串号
name=小明&age=20&sex=男 - JSON请求体:规整大括号包裹,像填好的档案表
{"name":"小明","age":20}
2. 装东西能力不同
- 表单:只能装零散简单文字、数字,不能嵌套多层数据、不能装数组
- 除此以外,表单还可以传文件,json请求体不行。
- JSON:随便装,多层嵌套、列表数组都没问题,只是能装复杂的数据而已,不能传二进制文件
3. 发送身份不一样
- 表单:浏览器原生页面点击提交按钮默认发的格式
- JSON:手机APP、Vue/React网页、接口调试工具常用格式
4. FastAPI接收写法
- 表单:单个字段挨个接收
def login(name:str=Form(), pwd:str=Form()):
- JSON:统一用模型整体接收
def login(user:UserModel):
5. 适用场景直白区分
- 老式网页登录、简单提交、传文件 → 用表单
- 手机端、前后端分离、复杂数据交互 → 用JSON
为什么表单字段和 JSON 请求体不能在同一个路由中同时使用?
表单数据以"字段"的形式发送,不是 JSON。因此不能将 Form 参数声明为 Pydantic 模型。表单字段和 JSON 请求体不能在同一个路由中同时使用。
第一句:
表单数据以"字段"形式发送,不是 JSON → 不能用 Pydantic 模型接收
为什么?
表单长这样:
username=张三&password=123&age=20
它是一整串扁平的字符串,没有层级、没有结构。
而 Pydantic 模型是给 JSON 结构化数据 用的:
{ "username": "张三", "password": "123" }
结论:
表单是扁平字符串 → 不能直接塞进 Pydantic 模型
只能一个字段一个字段接收:username: str = Form()
第二句:
表单字段 和 JSON 请求体 不能在同一个路由里同时使用
为什么?
因为一个 HTTP 请求,只能有一种请求体格式!
要么是:
application/json(JSON格式)
要么是:application/x-www-form-urlencoded(表单格式)
要么是:multipart/form-data(文件表单格式)
一个请求不能同时发两种格式!
就像:
你不能同时用中文和英文写同一句话,别人看不懂。
所以:
同一个接口,不能既接收 JSON,又接收表单!
用最通俗的比喻总结
JSON = 结构化档案(分层、嵌套)
表单 = 一串扁平字符串(无结构)
一个请求 = 一个信封
信封里只能装一种内容:
要么装档案(JSON)
要么装一串文字(表单)
不能同时装两种!
代码里的规则(必须记住)
✅ 可以:纯 JSON
def login(user: User): # JSON
✅ 可以:纯 Form
def login(username: str = Form(), password: str = Form()):
❌ 不可以:JSON + Form 同时写(报错!)
def login(user: User, username: str = Form()):
❌ 不可以:想用 Pydantic 接收表单(报错!)
def login(user: User = Form()):
最终超级大白话结论
- 表单是扁平字符串 → 只能一个字段一个字段收,不能用 Pydantic 模型收
- JSON 是结构化数据 → 必须用 Pydantic 模型收
- 一个接口只能选一种:要么 JSON,要么表单,不能两个都要
- 传文件 → 必须用表单
- 传复杂数据 → 必须用 JSON
一个请求,一种格式;JSON 用模型,表单用字段,不能混用!
什么是Starlette?
FastAPI 把 Starlette 当作自己的 “底层骨架”
就像:
Starlette = 房子的地基 + 框架
FastAPI = 精装修好的房子
Starlette 是一个轻量级 Python Web 框架FastAPI 完全使用了 Starlette,并在上面加了自动文档、数据校验等功能
ASGI 与异步编程
ASGI(Asynchronous Server Gateway Interface)异步服务器网关接口,Python Web 的新一代 “通信协议”,专门支持异步。
await asyncio.gather(任务1, 任务2, 任务3…)
asyncio.gather () = 让多个异步任务 同时、一起、并行跑!跑完之后,一次性把所有结果返回给你。
import asyncio
# 异步任务1
async def task1():
await asyncio.sleep(1) # 模拟耗时1秒
print("任务1完成")
return "结果1"
# 异步任务2
async def task2():
await asyncio.sleep(1)
print("任务2完成")
return "结果2"
# 主函数
async def main():
# ✅ 同时跑两个任务!不是一个跑完再跑一个
result1, result2 = await asyncio.gather(
task1(),
task2()
)
print("全部结果:", result1, result2)
# 运行
asyncio.run(main())
运行结果
任务1完成
任务2完成
全部结果:结果1 结果2
重点:
两个任务只花了 1 秒,不是 2 秒!
因为它们同时运行了。
Fastapi 中何时使用异步?
适合异步的场景
- 数据库操作
- 网络请求(API调用)
- 文件 I/O 操作
- 长时间等待的操作
不适合异步的场景
- CPU密集型计算
- 简单的数据处理
- 没有 I/O 等待的操作
REST API 设计原则
REST(Representational State Transfer,表述性状态转移)是一种 Web API 设计风格。用 HTTP 请求方式 + URL 地址,直观表达对数据的增删改查,风格统一、易懂规范。
RESTful URL设计原则
用名词,不用动词;
用复数,不用单数;
用 HTTP 方法表示操作,不用写在 URL 里;
层级清晰,参数放查询。
7条设计原则
1. 只用名词,不用动词
URL 只代表资源(数据),不代表动作。
✅ 正确/users/orders/products
❌ 错误/getUser/createOrder/deleteProduct
2. 资源用复数,更规范、更通用
企业 99% 都用复数!
✅ 正确/users/orders/articles
❌ 不推荐/user/order
3. 用 HTTP 方法表示操作,不写进 URL
这是 REST 最核心的设计!
| 方法 | 作用 | 示例 |
|---|---|---|
| GET | 查询 | GET /users |
| POST | 新建 | POST /users |
| PUT | 全量更新 | PUT /users/1 |
| PATCH | 局部更新 | PATCH /users/1 |
| DELETE | 删除 | DELETE /users/1 |
4. 层级关系用 / 表示,清晰直观
子资源放在父资源后面
✅ 正确
-
一个用户的所有订单
/users/1/orders -
一篇文章的评论
/articles/12/comments
5. 小写字母,用中横线 - 分隔,不用下划线
✅ 正确/user-orders/shopping-cart
❌ 不规范/userOrders/user_orders
6. 过滤、分页、排序参数 放 ? 查询参数
不放进路径里!
✅ 正确
-
分页
/users?page=1&size=10 -
筛选
/products?category=book&price_min=10 -
排序
/articles?sort=create_time,desc
✅ 正确写法(参数放?后面)GET /users?page=1&size=10&sort=age
❌ 不规范(参数放在路径里)
GET /users/page/1/size/10/sort/age
7. 版本号放前面,方便升级
✅ 正确/v1/users/v2/orders
最标准的 RESTful URL 示例大全(背下来)
用户
- 获取所有用户 →
GET /users - 获取单个用户 →
GET /users/1 - 新建用户 →
POST /users - 更新用户 →
PUT /users/1 - 删除用户 →
DELETE /users/1
用户的订单
- 获取用户1的所有订单 →
GET /users/1/orders - 获取用户1的某个订单 →
GET /users/1/orders/10
最容易犯的错误(一定要避开)
❌ /getUserInfo
❌ /api/user/delete
❌ /userInfo.action
❌ /UserList
这些都不是 RESTful!
超级总结(一句话记住)
RESTful URL = 复数名词 + 层级清晰 + HTTP方法表示操作 + 参数放查询
HTTP 方法与状态码
HTTP 方法
我用最简单、最通俗、最实用的方式,一次性把 HTTP 方法 + HTTP 状态码 讲透!
这是 RESTful API 最核心的基础,面试、工作必问必用。
一、什么是 HTTP 方法?
就是浏览器/前端 告诉后端:你想对数据做什么!
就像你对服务员说:
- 拿来 = GET
- 新增 = POST
- 替换 = PUT
- 修改 = PATCH
- 删掉 = DELETE
5 个最常用 HTTP 方法(必须背会)
| 方法 | 含义 | 作用 | 对应操作 |
|---|---|---|---|
| GET | 获取 | 查询数据 | 查 |
| POST | 提交 | 新建数据 | 增 |
| PUT | 更新 | 全量替换数据 | 改(全量) |
| PATCH | 修补 | 局部修改数据 | 改(局部) |
| DELETE | 删除 | 删除数据 | 删 |
最直观例子(用户接口)
GET /users→ 获取所有用户GET /users/1→ 获取 1 号用户POST /users→ 新建用户PUT /users/1→ 替换 1 号用户全部信息PATCH /users/1→ 只改 1 号用户的名字/年龄DELETE /users/1→ 删除 1 号用户
二、什么是 HTTP 状态码?
就是后端 告诉前端:这次请求结果怎么样!
就像服务员告诉你:
- 200 = 好了,没问题
- 400 = 你参数错了
- 404 = 找不到东西
- 500 = 服务器崩了
必背 10 个状态码(全世界通用)
1xx:消息(不用管)
2xx:成功(一切正常)
- 200 OK → 查询/修改成功
- 201 Created → 创建成功(POST)
- 204 No Content → 删除成功
3xx:重定向(很少用)
4xx:前端错了(客户端错误)
- 400 Bad Request → 参数错误
- 401 Unauthorized → 未登录
- 403 Forbidden → 没权限
- 404 Not Found → 资源不存在
- 405 Method Not Allowed → 方法不允许
- 409 Conflict # 资源冲突
- 422 Unprocessable Entity # 数据验证失败
- 429 Too Many Requests # 请求过于频繁
5xx:后端错了(服务器错误)
- 500 Internal Server Error → 服务器崩溃
- 502 Bad Gateway → 网关错误
- 503 Service Unavailable # 服务不可用
三、HTTP 方法 + 状态码 标准搭配(企业规范)
1. GET 查询
- 成功:200
2. POST 新建
- 成功:201
3. PUT / PATCH 修改
- 成功:200
4. DELETE 删除
- 成功:204
四、FastAPI 里如何使用?
from fastapi import FastAPI, status
app = FastAPI()
# 查询 → 200
@app.get("/users", status_code=status.HTTP_200_OK)
def get_users():
return {"msg": "查询成功"}
# 新建 → 201
@app.post("/users", status_code=status.HTTP_201_CREATED)
def create_user():
return {"msg": "创建成功"}
# 删除 → 204
@app.delete("/users/{id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user():
return
五、终极总结(一句话记住)
HTTP 方法 = 前端告诉后端要做什么
HTTP 状态码 = 后端告诉前端结果怎么样
最简记忆表
- GET → 查询 → 200
- POST → 新增 → 201
- PUT/PATCH → 修改 → 200
- DELETE → 删除 → 204
- 找不到 → 404
- 没权限 → 401
- 服务器崩 → 500
JSON数据格式
JSON = 一种前后端通用的数据格式,长得像 Python 字典,全平台都能读懂。
JSON 支持哪些数据类型?
JSON 只有 6 种基础类型:
- 字符串(“hello”)
- 数字(123 / 1.23)
- 布尔(true / false)
- 数组([1,2,3])
- 对象({…})【这里的对象指 带花括号的 键值对】
- null
JSON 对象的特点:
- 只有 字符串键
- 只能放 JSON 支持的 6 种基础类型
- 没有函数、没有类、没有复杂类型
- 前端、Java、Go 都能看懂
JSON 不支持什么?(非常重要)
这些 Python 类型 不能直接转 JSON:
- datetime 时间
- Decimal 高精度小数
- bytes 字节
- 对象 / 类实例 (比如:ORM模型、datetime、自定义对象)
所以必须序列化(转成字符串 / 数字)。
把 Python 对象 → 转成 JSON 格式 = 序列化
序列化 = 把内存里的对象 → 变成可传输、可存储的字符串 / 格式
反序列化 = 把 JSON 字符串 → 变回 Python 对象
Pydantic 模型的配置类
作用:告诉 Pydantic 怎么格式化、怎么序列化、怎么校验数据
下面的写法——Config配置类是Pydantic V1版本中的写法,V2版本写法是 使用模型配置
ConfigDict。V2 向下兼容 V1 的 Config 内部类,所以老代码能跑。
from datetime import datetime
from decimal import Decimal
from typing import Optional, List
from pydantic import BaseModel, Field
import json
# JSON 数据格式规范
class UserResponse(BaseModel):
id: int
username: str
email: str
full_name: Optional[str] = None
is_active: bool = True
created_at: datetime
updated_at: Optional[datetime] = None
# JSON 序列化配置
class Config: # 【Pydantic V1 版本写法】
# 允许使用字段别名
allow_population_by_field_name = True
# JSON 编码器
json_encoders = {
datetime: lambda v: v.isoformat(),
Decimal: lambda v: float(v)
}
(模型配置/Config配置类 里的) 序列化配置 json_encoders
json_encoders = {
datetime: lambda v: v.isoformat(),
Decimal: lambda v: float(v)
}
作用:
遇到哪些特殊类型,自动用指定方式转成 JSON
它是一个字典:{ 类型: 转换函数 }
示例:
datetime: lambda v: v.isoformat()
意思:
当遇到 datetime 时间对象时自动调用 isoformat () 转成标准时间字符串
例如:
# Python 对象(JSON不认识)
datetime(2025, 5, 20, 18, 30)
# 自动变成(JSON认识)
"2025-05-20T18:30:00"
Decimal: lambda v: float(v)
意思:
当遇到 Decimal 高精度小数时自动转成普通浮点数
例如:
# Python 对象(JSON不认识)
Decimal('19.99')
# 自动变成
19.99
最常用的3个编码器
json_encoders = {
datetime: lambda v: v.isoformat(), # 时间
Decimal: lambda v: float(v), # 高精度小数
bytes: lambda v: v.decode("utf-8") # 字节
}
什么时候用 utf-8,什么时候用 base64?(超级重要)
-
bytes 是普通文本
用:bytes: lambda v: v.decode("utf-8") -
bytes 是图片、文件、PDF、加密数据、二进制串(99% 场景)
用:bytes: lambda v: base64.b64encode(v).decode("utf-8")
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)