# 大蟒蛇的魔药发货方式
后端API就像一家魔药店的发货系统:
- 📋 **挂牌**:告诉客户我们卖什么(路由装饰器)
- 📦 **验货**:检查客户订单是否合规(BaseModel)
- 🚚 **配送**:只发给可信的合作伙伴(CORS)
接下来,我们来看大蟒蛇法师是如何开店的...
魔药的种类(注册装饰器FastAPI路由 @app.post("/api/chat") )
魔药的种类是固定的(已注册,在/docs 魔法手帐上有)。
但是客户可以输入不一样的参数,得到微调之后的结果。
大蟒蛇根据注册的品名,快速找到对应的魔药,输入参数后,将一开始就写好的其他描述,一起返回给客户。
阶段一:启动时(装饰器工作)
代码加载时发生(只执行一次)
@app.post("/api/chat") # 👈 启动时执行
def chat(request: ChatRequest):
...
等价于:
# 1. 先调用 app.post(),返回一个装饰器函数
decorator = app.post("/api/chat", summary="聊天")
# 2. 装饰器函数接收 chat 函数
def chat():
...
chat = decorator(chat) # 👈 chat作为参数传给装饰器
FastAPI做的事:
# 1. 读到装饰器,执行 app.post("/api/chat")
# 2. FastAPI内部:
路由表["/api/chat"] = {
"method": "POST",
"function": chat, # 记住这个函数
"参数类型": ChatRequest,
"返回类型": ChatResponse
}
# 3. 服务器启动,监听8000端口
print("Uvicorn running on http://127.0.0.1:8000")
此时:
- ✅ 路由已注册
- ✅ FastAPI知道
/api/chat对应chat函数 - ⏸️ 等待客户请求...
阶段二:运行时(处理请求)
客户发请求时发生(每次请求都执行):
1. 前端发请求
POST http://localhost:8000/api/chat
Body: {"message": "你好"}
↓
2. Uvicorn收到请求
"有人请求 /api/chat,是POST方法"
↓
3. FastAPI查路由表
"查路由表 → /api/chat 对应 chat 函数"
↓
4. FastAPI解析请求体
JSON {"message": "你好"}
→ 转成 ChatRequest(message="你好")
↓
5. 调用函数
result = chat(request)
↓
6. 执行函数内容
user_msg = request.message # "你好"
reply = f"收到: {user_msg}"
return ChatResponse(reply=reply)
↓
7. FastAPI包装返回
ChatResponse(reply="收到: 你好")
→ 转成 JSON {"reply": "收到: 你好"}
↓
8. Uvicorn发送响应
HTTP 200 OK
Content-Type: application/json
Body: {"reply": "收到: 你好"}
↓
9. 前端收到结果
axios.then(res => console.log(res.data))
魔药配方模板(BaseModel)
说是魔药配方模板,其实是对客户定制的内容和最后输出的魔药进行规定。
客户只能修改固定的几样材料,返回给客户的也是固定的格式。
这里面需要用到魔药配方模板(BaseModel)
## BaseModel - 魔药配方模板
大蟒蛇法师的魔药配方不是随便写的,而是要用 BaseModel 来定义:
from pydantic import BaseModel
class MagicPotion(BaseModel):
name: str # 魔药名称(必须是文字)
power: int # 魔力值(必须是数字)
这个模板有三重魔法:
1. 对灵狐法师(前端):确保返回的魔药格式正确
2. 对自己(后端):拒收不合格的,无法配置成魔药的原材料
3. 对魔法手帐(/docs):自动记录配方格式
如果要返回多个魔药,就用 List[MagicPotion]:
- 相当于一个魔药清单卷轴
- 里面每个魔药都要符合 MagicPotion 模板
🤔 灵狐法师要怎么理解BaseModel?
BaseModel = Interface + 对象转换器
既有数据验证,又能把格式转换成object
from pydantic import BaseModel
class ChatRequest(BaseModel):
message: str
等价于前端的:
// 1. 定义Interface
interface ChatRequest {
message: string
}
// 2. 自动做 JSON.parse()
const request: ChatRequest = JSON.parse(jsonString)
// 3. 自动做类型检查
if (typeof request.message !== 'string') {
throw new Error('Invalid type')
}
BaseModel把这三步合成一步! ✨
CORS结界(安全的运输通道)
魔药只能卖给灵狐法师(合作前端),不能卖给麻瓜(随机网站)和黑魔法师(攻击者)。
大蟒蛇通过设置白名单(allow_origins),建立CORS结界。
浏览器守门人检查来访者的域名,只有在白名单里的魔法师(灵狐法师)才能获得魔药。
这样就防止了黑魔法师窃取用户魔法,保护了魔法世界的安全!✨
🚨 没有CORS的危险世界
大蟒蛇的魔药店刚开业:
# 没有CORS配置
app = FastAPI()
@app.post("/api/chat")
def 聊天魔药(request: ChatRequest):
return ChatResponse(reply="强大的魔法回复")
发生的事:
✅ 灵狐法师(前端,https://my-app.com)来买魔药
→ 员工:"欢迎!给你魔药"
❌ 黑魔法师(evil.com)也来买魔药
→ 员工:"欢迎!给你魔药" ← 😱 危险!
❌ 麻瓜路人(random.com)也来买
→ 员工:"欢迎!给你魔药" ← 😱 泄露魔法!
问题:
- 任何人都能买魔药
- 黑魔法师能偷用户魔法
- 麻瓜会乱用魔法
✨ 设置CORS结界
大蟒蛇施展结界魔法:
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 🔮 CORS结界(只允许魔法师)
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:5173", # 灵狐法师的开发工坊
"https://my-app.com", # 灵狐法师的正式魔法塔
], # 👈 魔法师白名单
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
🛡️ 结界运作原理
客人来买魔药:
场景1️⃣:灵狐法师来了 ✅
灵狐法师(https://my-app.com):"我要聊天魔药"
↓
大蟒蛇的结界检查:
"是my-app.com吗?" → 是!✅
"在魔法师白名单里吗?" → 在!✅
↓
员工:"欢迎,灵狐法师!这是您的魔药"
↓
灵狐法师拿到魔药 ✅
场景2️⃣:黑魔法师来了 ❌
黑魔法师(https://evil.com):"我要聊天魔药"
↓
大蟒蛇的结界检查:
"是evil.com?" → 😱
"在白名单里吗?" → 不在!❌
↓
结界:"Access-Control-Allow-Origin错误!"
浏览器拦截:"你不是魔法师,不能拿魔药!"
↓
黑魔法师:拿不到魔药 ❌
场景3️⃣:麻瓜路人来了 ❌
麻瓜(https://random.com):"我要魔药"
↓
结界检查:"random.com不在白名单"
浏览器拦截:"麻瓜禁止入内!"
↓
麻瓜:看不到魔药 ❌
🤝 与灵狐法师的配合
开发阶段(灵狐法师的工坊)
大蟒蛇配置:
allow_origins=[
"http://localhost:5173", # 灵狐的开发工坊
"http://127.0.0.1:5173", # 备用传送门
]
灵狐法师的代码:
// 灵狐法师在工坊里施法
axios.post('http://localhost:8000/api/chat', {
message: '你好'
})
// ✅ 工坊在白名单,魔法生效!
生产阶段(正式魔法塔)
大蟒蛇配置:
allow_origins=[
"https://my-app.com", # 灵狐的魔法塔
"https://www.my-app.com", # 带www的塔
]
灵狐法师部署前端:
// 灵狐在正式魔法塔施法
axios.post('https://api.my-app.com/api/chat', {
message: '你好'
})
// ✅ 魔法塔在白名单,魔法生效!
🚨 安全攻击示例
黑魔法师的阴谋:
<!-- 黑魔法师的网站 evil.com -->
<script>
// 试图偷用户的魔法
axios.post('https://your-api.com/api/delete-account', {
userId: 123
})
// ❌ 被结界拦住!
// 浏览器:CORS error! evil.com不在白名单
</script>
说明:
## 为什么黑魔法师能盗用用户身份?
关键:用户已经登录了 your-api.com
→ 浏览器存了cookies(魔法师徽章)
→ 用户访问 evil.com
→ evil.com的代码发请求给 your-api.com
→ 浏览器自动带上cookies ← 😱 危险!
如果没有CORS:
→ 请求成功,cookies被黑魔法师利用
→ 用户账号被删除
有了CORS:
→ 浏览器检查:evil.com不在白名单
→ 请求被拦截 ✅
→ cookies没被盗用 ✅
如果没有CORS结界:
用户访问 evil.com
→ 黑魔法师的代码执行
→ 请求成功 😱
→ 用户账号被删除 😱😱😱
有CORS结界:
用户访问 evil.com
→ 黑魔法师的代码执行
→ 浏览器检查CORS
→ evil.com不在白名单 ❌
→ 请求被拦截 ✅
→ 用户安全 ✅✅✅
🎯 CORS的魔法规则
规则1:魔法师身份卡(域名)
allow_origins=[
"https://magic-school.com", # 魔法学院
"https://wizard-tower.com", # 巫师塔
]
# 只有这两个地方的魔法师能买魔药
规则2:允许的魔法类型
allow_methods=["GET", "POST"] # 只允许查询和购买魔药
# 不允许 DELETE(删除魔药配方)
规则3:魔法凭证
allow_credentials=True # 允许携带魔法师徽章(cookies)
# 用于识别老顾客
完整角色对照表
| 角色 | 身份 | 域名 | CORS |
|---|---|---|---|
| 灵狐法师 | 合作伙伴 | my-app.com | ✅ 在白名单 |
| 用户浏览器 | 守门人 | - | 检查白名单 |
| 黑魔法师 | 入侵者 | evil.com | ❌ 不在白名单 |
| 麻瓜 | 路人 | random.com | ❌ 不在白名单 |
| 大蟒蛇 | 魔药店老板 | - | 设置白名单 |
——一个炼丹术士的技术笔记: 002 《大蟒蛇的魔药发货方式》