大蟒蛇的魔药发货方式

36 阅读6分钟
# 大蟒蛇的魔药发货方式

后端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 《大蟒蛇的魔药发货方式》