FastAPI 极速开发指南 — 从零到生产级 API 实战

FastAPI 极速开发指南 --- 从零到生产级 API 实战

版本 : v1.0 | 日期 : 2026-06-13 | FastAPI : 0.136.3 | Python : 3.12.3

参考 : 菜鸟教程 FastAPI

实操环境: 华为云 ECS Ubuntu 24.04 LTS (4节点集群)


目录


一、FastAPI 概述

1.1 什么是 FastAPI

FastAPI 是一个现代、高性能的 Python Web 框架,基于 Starlette (异步框架)和 Pydantic(数据校验库)构建。

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    FastAPI 技术栈架构                         │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   ┌─────────────────┐                                      │
│   │   开发者代码      │  ← 你的路由、模型、业务逻辑            │
│   └────────┬────────┘                                      │
│            │                                                 │
│   ┌────────▼────────┐                                      │
│   │    FastAPI      │  ← 路由注册、依赖注入、自动文档           │
│   └────────┬────────┘                                      │
│            │                                                 │
│   ┌────────▼────────┐                                      │
│   │    Starlette    │  ← ASGI 框架,异步请求/响应处理          │
│   └────────┬────────┘                                      │
│            │                                                 │
│   ┌────────▼────────┐                                      │
│   │    Pydantic     │  ← 数据校验、序列化、类型转换            │
│   └────────┬────────┘                                      │
│            │                                                 │
│   ┌────────▼────────┐                                      │
│   │  Uvicorn (ASGI) │  ← 高性能异步服务器                      │
│   └─────────────────┘                                      │
│                                                              │
└─────────────────────────────────────────────────────────────┘

1.2 核心优势

特性 FastAPI Flask Django REST
性能 ⭐⭐⭐⭐⭐ (与 Node.js/Go 同级) ⭐⭐⭐ ⭐⭐⭐
自动文档 ⭐⭐⭐⭐⭐ (Swagger + ReDoc) ❌ (需插件) ⭐⭐⭐ (Django REST Docs)
类型校验 ⭐⭐⭐⭐⭐ (原生 Pydantic) ❌ (需 Marshmallow) ⭐⭐⭐ (Serializer)
异步支持 ⭐⭐⭐⭐⭐ (原生 async/await) ⭐⭐⭐ (Flask 2.0+) ⭐⭐⭐ (Django 3.1+)
学习曲线 ⭐⭐⭐⭐ (Python 类型注解即可) ⭐⭐⭐⭐⭐ ⭐⭐⭐

1.3 适用场景

  • 微服务 API 开发 --- 高性能、低延迟
  • 前后端分离项目 --- 自动生成 OpenAPI 文档
  • 机器学习模型部署 --- 快速将模型封装为 REST API
  • 数据接口服务 --- Pydantic 自动校验保障数据质量

二、环境搭建

2.1 服务器环境

本次实操使用华为云 ECS 集群(ecs-eb17):

节点 公网 IP 私网 IP 角色
fastapi-01 120.46.183.221 192.168.0.152 主节点(API 服务)
fastapi-02 1.92.122.183 192.168.0.134 节点-2
fastapi-03 1.92.69.229 192.168.0.58 节点-3
fastapi-04 124.70.69.224 192.168.0.59 节点-4
复制代码
集群规格: 2vCPUs | 4GiB | ac9.large.2
操作系统: Ubuntu 24.04 server 64bit
Python: 3.12.3 (系统自带)

2.2 安装 Python 虚拟环境

Ubuntu 24.04 默认启用 PEP 668 外部管理环境,直接 pip install 会报错:

bash 复制代码
root@ecs-eb17-0001:~# pip3 install fastapi
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.

正确做法:创建虚拟环境(venv):

bash 复制代码
# 安装 python3-venv(如未安装)
root@ecs-eb17-0001:~# apt-get update -qq && apt-get install -y -qq python3-venv python3-full

# 创建项目目录和虚拟环境
root@ecs-eb17-0001:~# mkdir -p /opt/fastapi-demo && cd /opt/fastapi-demo
root@ecs-eb17-0001:/opt/fastapi-demo# python3 -m venv venv

# 激活虚拟环境
root@ecs-eb17-0001:/opt/fastapi-demo# source venv/bin/activate
(venv) root@ecs-eb17-0001:/opt/fastapi-demo# 

# 升级 pip
(venv) root@ecs-eb17-0001:/opt/fastapi-demo# pip install --upgrade pip

2.3 安装 FastAPI 和 Uvicorn

bash 复制代码
(venv) root@ecs-eb17-0001:/opt/fastapi-demo# pip install fastapi uvicorn

Successfully installed annotated-doc-0.0.4 annotated-types-0.7.0 anyio-4.13.0
  click-8.4.1 fastapi-0.136.3 h11-0.16.0 idna-3.18 pydantic-2.13.4
  pydantic-core-2.46.4 starlette-1.3.1 typing-extensions-4.15.0
  typing-inspection-0.4.2 uvicorn-0.49.0

2.4 安装完整依赖(后续章节使用)

bash 复制代码
(venv) root@ecs-eb17-0001:/opt/fastapi-demo# pip install \
    fastapi uvicorn[standard] \
    python-multipart \
    python-jose[cryptography] \
    passlib[bcrypt] \
    sqlalchemy \
    pydantic \
    email-validator

依赖说明

  • uvicorn[standard]:包含 uvloop、httptools 等性能优化组件
  • python-multipart:支持文件上传(multipart/form-data)
  • python-jose[cryptography]:JWT 令牌签发与验证
  • passlib[bcrypt]:密码哈希(bcrypt 算法)
  • sqlalchemy:ORM 数据库操作
  • email-validator:Pydantic EmailStr 类型校验

三、第一个 FastAPI 应用

3.1 最简应用代码

python 复制代码
# /opt/fastapi-demo/main.py
from fastapi import FastAPI

# 步骤1:导入 FastAPI 类
# 步骤2:创建应用实例
app = FastAPI()

# 步骤3:定义路径操作装饰器
# 步骤4:定义路径操作函数
@app.get("/")
async def root():
    # 步骤5:返回响应内容
    return {"message": "Hello World"}

3.2 代码逐步说明

步骤 代码 说明
1 from fastapi import FastAPI 导入 FastAPI 类,继承自 Starlette
2 app = FastAPI() 创建应用实例,无需传 __name__(与 Flask 不同)
3 @app.get("/") 路径操作装饰器,声明 GET 方法监听根路径 /
4 async def root(): 路径操作函数,可用 async def 或普通 def
5 return {"message": "Hello World"} 返回字典,FastAPI 自动转换为 JSON 响应

3.3 启动服务器

bash 复制代码
# 方式一:开发模式(带热重载)
(venv) root@ecs-eb17-0001:/opt/fastapi-demo# uvicorn main:app --reload --host 0.0.0.0 --port 8000

# 方式二:生产模式(多 worker)
(venv) root@ecs-eb17-0001:/opt/fastapi-demo# uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4

参数说明

  • main:appmain.py 文件中的 app 对象
  • --reload:代码修改后自动重启(仅开发使用)
  • --host 0.0.0.0:监听所有网络接口(默认仅 127.0.0.1)
  • --workers 4:启动 4 个 worker 进程(生产环境推荐)

3.4 服务器实操输出

bash 复制代码
root@ecs-eb17-0001:/opt/fastapi-demo# setsid bash -c 'cd /opt/fastapi-demo && source venv/bin/activate && uvicorn main:app --host 0.0.0.0 --port 8000 > /tmp/uvicorn.log 2>&1' &

root@ecs-eb17-0001:/opt/fastapi-demo# python3 -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8000/').read().decode())"
{"message":"Hello World"}

3.5 自动生成的 API 文档

FastAPI 自动生成两套交互式文档:

文档类型 URL 特点
Swagger UI http://localhost:8000/docs 交互式测试,可发送请求
ReDoc http://localhost:8000/redoc 静态文档,适合阅读
OpenAPI JSON http://localhost:8000/openapi.json 机器可读规范
复制代码
┌─────────────────────────────────────────────────────────────┐
│                 FastAPI 自动文档机制                          │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   你的代码                                                     │
│   ├── @app.get("/")  ← 装饰器提取路径、方法                    │
│   ├── async def root()  ← 函数签名提取参数                     │
│   └── return {...}  ← 返回类型推断响应模型                      │
│         │                                                    │
│         ▼                                                    │
│   ┌──────────────────────────────────────┐                  │
│   │      FastAPI 内部路由注册表            │                  │
│   │  - 路径 / 方法 / 参数 / 响应模型       │                  │
│   └──────────────┬───────────────────────┘                  │
│                  │                                           │
│         ┌────────┴────────┐                                 │
│         ▼                 ▼                                 │
│   ┌──────────┐      ┌──────────┐                           │
│   │ /docs    │      │ /redoc   │                           │
│   │ Swagger  │      │ ReDoc    │                           │
│   │   UI     │      │          │                           │
│   └──────────┘      └──────────┘                           │
│                                                              │
└─────────────────────────────────────────────────────────────┘

四、路径参数与查询参数

4.1 路径参数(Path Parameters)

路径参数从 URL 路径的 {} 占位符中提取:

python 复制代码
@app.get("/items/{item_id}")
async def read_item(item_id: int):
    return {"item_id": item_id}
  • item_id 声明为 int 类型
  • FastAPI 自动将 URL 中的字符串转换为整数
  • 传入非整数时自动返回 422 校验错误

4.2 查询参数(Query Parameters)

查询参数从 URL 的 ?key=value 中提取:

python 复制代码
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}
  • q 有默认值 None,表示可选参数
  • 若声明为 q: str(无默认值),则变为必填查询参数

4.3 实操输出

bash 复制代码
# 路径参数自动类型转换
root@ecs-eb17-0001:~# python3 -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8000/items/5').read().decode())"
{"item_id":5,"q":null}

# 路径参数 + 查询参数
root@ecs-eb17-0001:~# python3 -c "import urllib.request; print(urllib.request.urlopen('http://localhost:8000/items/5?q=runoob').read().decode())"
{"item_id":5,"q":"runoob"}

# 类型错误:传入非整数路径参数
root@ecs-eb17-0001:~# python3 -c "
import urllib.request, urllib.error
try:
    urllib.request.urlopen('http://localhost:8000/items/foo')
except urllib.error.HTTPError as e:
    print(f'Status: {e.code}')
    print(e.read().decode())
"

Status: 422
{
  "detail":[
    {
      "type":"int_parsing",
      "loc":["path","item_id"],
      "msg":"Input should be a valid integer, unable to parse string as an integer",
      "input":"foo"
    }
  ]
}

4.4 FastAPI 参数识别规则

参数来源 识别条件 示例
路径参数 参数名出现在路由路径的 {} item_id/items/{item_id}
查询参数 参数是单一类型(intstrbool 等) q: str = None
请求体 参数类型是 Pydantic 模型 item: Item

五、Pydantic 请求体模型

5.1 什么是 Pydantic

Pydantic 是 FastAPI 的核心依赖,用于数据校验和序列化 。核心思想:用 Python 类型注解定义数据结构,Pydantic 自动负责校验和转换

复制代码
┌─────────────────────────────────────────────────────────────┐
│                  Pydantic 工作流程                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   客户端 JSON                                              │
│   {"name":"Foo","price":35.4}                               │
│        │                                                     │
│        ▼                                                     │
│   ┌─────────────────┐                                       │
│   │  Pydantic 解析   │  ← 字段类型校验、必填检查、自动转换      │
│   └────────┬────────┘                                       │
│            │                                                 │
│   ┌────────▼────────┐                                       │
│   │  Item 对象       │  ← 具有类型安全的 Python 对象           │
│   │  item.name: str  │                                       │
│   │  item.price: float│                                      │
│   └────────┬────────┘                                       │
│            │                                                 │
│   ┌────────▼────────┐                                       │
│   │  业务逻辑处理     │  ← 你的代码操作类型安全的对象           │
│   └─────────────────┘                                       │
│                                                              │
└─────────────────────────────────────────────────────────────┘

5.2 定义模型

python 复制代码
from pydantic import BaseModel

class Item(BaseModel):
    name: str                    # 必填:商品名称
    description: str = None      # 可选:商品描述
    price: float                 # 必填:商品价格
    tax: float = None            # 可选:税费

字段是否必填取决于是否有默认值:

字段 声明方式 是否必填
name name: str ✅ 必填
description description: str = None ❌ 可选
price price: float ✅ 必填
tax tax: float = None ❌ 可选

5.3 作为请求体使用

python 复制代码
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.post("/items/")
async def create_item(item: Item):
    # FastAPI 自动校验请求体,校验通过后赋值给 item 参数
    return item

5.4 实操输出

bash 复制代码
# POST 请求创建资源
root@ecs-eb17-0001:~# python3 -c "
import urllib.request, json
item = {'name': 'Foo', 'price': 35.4}
req = urllib.request.Request('http://localhost:8000/items/', data=json.dumps(item).encode(), headers={'Content-Type': 'application/json'})
print(urllib.request.urlopen(req).read().decode())
"
{"name":"Foo","description":null,"price":35.4,"tax":null}

# PUT 请求更新资源
root@ecs-eb17-0001:~# python3 -c "
import urllib.request, json
item = {'name': 'Bar', 'price': 45.2}
req = urllib.request.Request('http://localhost:8000/items/1', data=json.dumps(item).encode(), headers={'Content-Type': 'application/json'}, method='PUT')
print(urllib.request.urlopen(req).read().decode())
"
{"item_id":1,"item_name":"Bar"}

5.5 模型校验错误示例

当缺少必填字段时,FastAPI 自动返回详细的校验错误:

bash 复制代码
# 缺少必填字段 name 和 price
root@ecs-eb17-0001:~# python3 -c "
import urllib.request, json
item = {'description': '缺少 name 和 price'}
req = urllib.request.Request('http://localhost:8000/items/', data=json.dumps(item).encode(), headers={'Content-Type': 'application/json'})
try:
    urllib.request.urlopen(req)
except urllib.error.HTTPError as e:
    print(e.read().decode())
"

{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "name"],
      "msg": "Field required",
      "input": {"description": "缺少 name 和 price"}
    },
    {
      "type": "missing",
      "loc": ["body", "price"],
      "msg": "Field required",
      "input": {"description": "缺少 name 和 price"}
    }
  ]
}

5.6 Pydantic v2 常用方法

说明 v2(推荐) v1(已弃用)
序列化为字典 item.model_dump() item.dict()
序列化为 JSON item.model_dump_json() item.json()
从字典创建 Item.model_validate(data) Item.parse_obj(data)
从 JSON 创建 Item.model_validate_json(json_str) Item.parse_raw(json_str)
获取 JSON Schema Item.model_json_schema() Item.schema()

5.7 模型继承

Pydantic 模型支持继承,可以方便地创建输入模型和输出模型:

python 复制代码
from pydantic import BaseModel, EmailStr

# 基础模型
class UserBase(BaseModel):
    username: str
    email: EmailStr      # 自动校验邮箱格式
    full_name: str = None

# 创建用户时的输入模型(包含密码)
class UserCreate(UserBase):
    password: str

# 返回用户信息时的输出模型(不包含密码)
class UserOut(UserBase):
    id: int              # 由服务器生成

# 使用示例
@app.post("/users/", response_model=UserOut)
async def create_user(user: UserCreate):
    return {"id": 1, **user.model_dump(exclude={"password"})}

⚠️ 安全提示:使用不同的输入和输出模型是保护敏感数据的重要手段。永远不要在 API 响应中返回密码等敏感字段。


六、响应模型与数据校验

6.1 response_model 参数

使用 response_model 参数可以控制 API 的响应结构:

python 复制代码
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

class ItemOut(BaseModel):
    name: str
    price_with_tax: float

@app.post("/items/", response_model=ItemOut)
async def create_item(item: Item):
    price_with_tax = item.price * 1.1 if item.tax else item.price
    return {"name": item.name, "price_with_tax": price_with_tax}

6.2 响应模型作用

作用 说明
数据过滤 仅返回模型中定义的字段
类型转换 自动将返回数据转换为模型定义的类型
文档生成 在 OpenAPI 文档中展示响应结构
IDE 支持 编辑器中获得自动补全和类型检查

七、OAuth2 + JWT 安全认证

7.1 认证流程

复制代码
┌──────────┐                                    ┌──────────┐
│  客户端   │  ① POST /token                   │  服务端   │
│          │  username=alice&password=secret  │          │
│          │─────────────────────────────────>│          │
│          │                                    │          │
│          │  ② {access_token: "eyJ...",      │          │
│          │      token_type: "bearer"}       │          │
│          │<─────────────────────────────────│          │
│          │                                    │          │
│          │  ③ GET /users/me                 │          │
│          │  Authorization: Bearer eyJ...    │          │
│          │─────────────────────────────────>│          │
│          │                                    │          │
│          │  ④ {username: "alice", ...}      │          │
│          │<─────────────────────────────────│          │
└──────────┘                                    └──────────┘

7.2 安装依赖

bash 复制代码
(venv) root@ecs-eb17-0001:/opt/fastapi-demo# pip install "python-jose[cryptography]" passlib[bcrypt]

7.3 完整实现代码

python 复制代码
from datetime import datetime, timedelta
from typing import Annotated

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel

# ===== 配置 =====
SECRET_KEY = "your-secret-key-keep-it-secret"  # 生产环境使用环境变量
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# ===== 密码哈希 =====
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# ===== OAuth2 方案 =====
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# ===== 数据模型 =====
class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: str = None

class User(BaseModel):
    username: str
    email: str = None
    full_name: str = None
    disabled: bool = None

class UserInDB(User):
    hashed_password: str

# ===== 模拟数据库 =====
fake_users_db = {
    "alice": {
        "username": "alice",
        "full_name": "Alice Wonderson",
        "email": "alice@example.com",
        "hashed_password": pwd_context.hash("secret"),
        "disabled": False,
    }
}

# ===== 工具函数 =====
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)

def get_user(db: dict, username: str) -> UserInDB | None:
    if username in db:
        return UserInDB(**db[username])
    return None

def authenticate_user(db: dict, username: str, password: str):
    user = get_user(db, username)
    if not user:
        return False
    if not verify_password(password, user.hashed_password):
        return False
    return user

def create_access_token(data: dict, expires_delta: timedelta = None):
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    user = get_user(fake_users_db, username)
    if user is None:
        raise credentials_exception
    return user

async def get_current_active_user(
    current_user: Annotated[User, Depends(get_current_user)],
):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="用户已被禁用")
    return current_user

# ===== 路由 =====
app = FastAPI()

@app.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
    user = authenticate_user(fake_users_db, form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me")
async def read_users_me(
    current_user: Annotated[User, Depends(get_current_active_user)],
):
    return current_user

@app.get("/users/me/items")
async def read_own_items(
    current_user: Annotated[User, Depends(get_current_active_user)],
):
    return [{"item_id": "Foo", "owner": current_user.username}]

7.4 关键组件解析

组件 说明
OAuth2PasswordBearer Authorization: Bearer <token> 请求头中获取令牌
CryptContext 密码哈希工具,使用 bcrypt 算法
jwt.encode() 创建 JWT 令牌
jwt.decode() 解析并验证 JWT 令牌
Depends() 依赖注入,实现认证逻辑的复用

7.5 在 Swagger UI 中测试

配置 OAuth2 后,Swagger UI (/docs) 会出现 "Authorize" 按钮:

  1. 点击 "Authorize"
  2. 输入用户名和密码(如 alice / secret
  3. 点击 "Authorize" 获取令牌
  4. 之后的所有请求都会自动携带令牌完成认证

八、CORS 跨域配置

8.1 什么是跨域

浏览器的同源策略限制了网页向不同域名发送请求。前后端分离开发时,前端和后端通常运行在不同端口上,需要配置 CORS。

前端地址 后端 API 地址 是否跨域
http://localhost:3000 http://localhost:8000 ✅ 跨域(端口不同)
http://example.com http://api.example.com ✅ 跨域(域名不同)
https://example.com http://example.com ✅ 跨域(协议不同)
http://example.com http://example.com/api ❌ 同源

8.2 基础 CORS 配置

python 复制代码
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=[               # 允许的源(域名列表)
        "http://localhost:3000",
        "http://localhost:8080",
    ],
    allow_credentials=True,       # 允许携带 Cookie
    allow_methods=["*"],          # 允许的 HTTP 方法
    allow_headers=["*"],          # 允许的请求头
)

8.3 CORS 配置参数详解

参数 类型 说明 推荐值
allow_origins list[str] 允许跨域的源列表 生产环境用具体域名
allow_methods list[str] 允许的 HTTP 方法 ["GET", "POST", "PUT", "DELETE"]
allow_headers list[str] 允许的请求头 按需配置
allow_credentials bool 是否允许携带 Cookie 和认证信息 需要认证时设为 True
expose_headers list[str] 前端可以访问的响应头 按需配置
max_age int 预检请求的缓存时间(秒) 600

⚠️ 安全提示allow_origins 使用 ["*"] 表示允许所有源,但这与 allow_credentials=True 不兼容。如果需要携带 Cookie,必须指定具体的源。生产环境务必指定具体的域名,不要滥用 ["*"]

8.4 开发环境 vs 生产环境

python 复制代码
# 开发环境(仅用于开发!)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 生产环境
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://example.com",
        "https://www.example.com",
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
    max_age=600,
)

九、部署与生产优化

9.1 使用 systemd 管理 uvicorn

ini 复制代码
# /etc/systemd/system/fastapi.service
[Unit]
Description=FastAPI Application
After=network.target

[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/fastapi-demo
Environment="PATH=/opt/fastapi-demo/venv/bin"
ExecStart=/opt/fastapi-demo/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
Restart=always

[Install]
WantedBy=multi-user.target

9.2 使用 Nginx 反向代理

nginx 复制代码
# /etc/nginx/sites-available/fastapi
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

9.3 Docker 部署

dockerfile 复制代码
# Dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  fastapi:
    build: .
    ports:
      - "8000:8000"
    environment:
      - SECRET_KEY=${SECRET_KEY}
    restart: always

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - fastapi

十、踩坑记录

坑 1:Ubuntu 24.04 的 externally-managed-environment

现象

bash 复制代码
pip3 install fastapi
# error: externally-managed-environment

原因:Ubuntu 24.04 引入 PEP 668,禁止直接 pip 安装到系统 Python。

解决:使用虚拟环境:

bash 复制代码
python3 -m venv venv
source venv/bin/activate
pip install fastapi uvicorn

坑 2:uvicorn 端口被占用

现象

bash 复制代码
ERROR: [Errno 98] error while attempting to bind on address ('0.0.0.0', 8000): address already in use

原因:之前的 uvicorn 进程未正确退出。

解决

bash 复制代码
# 查找并杀掉占用端口的进程
ss -tlnp | grep 8000
kill <PID>

# 或使用不同端口
uvicorn main:app --port 8080

坑 3:SSH 会话中 nohup 后台启动卡住

现象 :使用 SSH 执行 nohup uvicorn ... & 后,SSH 会话不返回。

原因:nohup 的标准输出仍连接到 SSH 会话的伪终端。

解决 :使用 setsid 或重定向输出:

bash 复制代码
# 方法1:使用 setsid(推荐)
setsid bash -c 'cd /app && source venv/bin/activate && uvicorn main:app --host 0.0.0.0 --port 8000 > /tmp/uvicorn.log 2>&1' &

# 方法2:完整重定向
nohup uvicorn main:app > /tmp/uvicorn.log 2>&1 </dev/null &

坑 4:Pydantic v1 vs v2 方法变更

现象 :代码中使用 .dict().json() 报错。

原因:Pydantic v2 废弃了 v1 的方法名。

解决

v1(废弃) v2(推荐)
item.dict() item.model_dump()
item.json() item.model_dump_json()
Item.parse_obj(data) Item.model_validate(data)
Item.schema() Item.model_json_schema()

坑 5:CORS allow_origins="\*" 与 allow_credentials=True 冲突

现象:配置 CORS 后,携带 Cookie 的请求仍被拒绝。

原因 :浏览器安全策略禁止 *credentials 同时使用。

解决:指定具体的源:

python 复制代码
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],  # ❌ 不要用 ["*"]
    allow_credentials=True,
)

附录:完整应用示例

python 复制代码
# main.py --- 综合示例
from datetime import datetime, timedelta
from typing import Annotated, List

from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from pydantic import BaseModel, EmailStr

# ========== 配置 ==========
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

# ========== 安全组件 ==========
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

# ========== Pydantic 模型 ==========
class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

class UserBase(BaseModel):
    username: str
    email: EmailStr = None
    full_name: str = None

class UserCreate(UserBase):
    password: str

class UserOut(UserBase):
    id: int

class Token(BaseModel):
    access_token: str
    token_type: str

# ========== 模拟数据 ==========
fake_items_db = []
fake_users_db = {
    "alice": {
        "username": "alice",
        "full_name": "Alice Wonderson",
        "email": "alice@example.com",
        "hashed_password": pwd_context.hash("secret"),
        "disabled": False,
    }
}

# ========== FastAPI 应用 ==========
app = FastAPI(
    title="FastAPI Demo API",
    description="FastAPI 完整示例项目",
    version="1.0.0",
)

# CORS 配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ========== 认证工具函数 ==========
def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

def authenticate_user(username: str, password: str):
    user = fake_users_db.get(username)
    if not user or not verify_password(password, user["hashed_password"]):
        return False
    return user

def create_access_token(data: dict, expires_delta: timedelta = None):
    to_encode = data.copy()
    expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15))
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无法验证凭据",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    user = fake_users_db.get(username)
    if user is None:
        raise credentials_exception
    return user

# ========== 路由 ==========
@app.get("/")
async def root():
    return {"message": "Hello World", "docs": "/docs"}

@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str = None):
    return {"item_id": item_id, "q": q}

@app.post("/items/", response_model=Item)
async def create_item(item: Item):
    fake_items_db.append(item.model_dump())
    return item

@app.get("/items/", response_model=List[Item])
async def list_items():
    return fake_items_db

@app.post("/token", response_model=Token)
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):
    user = authenticate_user(form_data.username, form_data.password)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="用户名或密码错误",
            headers={"WWW-Authenticate": "Bearer"},
        )
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user["username"]}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/users/me", response_model=UserOut)
async def read_users_me(current_user: Annotated[dict, Depends(get_current_user)]):
    return {"id": 1, "username": current_user["username"], "email": current_user.get("email")}

实操验证环境

  • 服务器:华为云 ECS ecs-eb17-0001 (120.46.183.221)
  • OS:Ubuntu 24.04 LTS
  • Python:3.12.3
  • FastAPI:0.136.3
  • Pydantic:2.13.4
  • Uvicorn:0.49.0
  • 创建日期:2026-06-13 17:44 GMT+8
相关推荐
老纪3 小时前
Redis分布式锁进第九零篇
数据库·redis·分布式
haven-8523 小时前
MySQL事务ACID、隔离级别、MVCC、幻读解决
数据库·mysql
小高学习java3 小时前
事务的边界问题,如何判断数据回滚时机。
java·数据库·后端
迷枫7124 小时前
【无标题】
数据库
TDengine (老段)4 小时前
TDengine 扫描算子 — TableScan、TagScan 与下推优化
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
放下华子我只抽RuiKe54 小时前
FastAPI 全栈后端(三):数据库与 ORM
前端·数据库·react.js·oracle·性能优化·前端框架·fastapi
BAGAE5 小时前
星链卫星数据获取:从太空安全到实时通信的技术革命
网络·数据结构·数据库·算法·云计算·hbase
zh_xuan5 小时前
Android导出并查看数据库
数据库·sqlite
小短腿的代码世界5 小时前
Qt定时器高精度架构:从QTimer源码到纳秒级定时调度
数据库·qt·架构