资讯动态

FastAPI 从入门到实战:类型提示、依赖注入与 Ollama 集成

发布时间:2026/10/8 15:34:35 来源:尧图企业网站定制
新人刚接触 FastAPI 的时候最容易被各种概念绕晕异步、类型提示、依赖注入、Pydantic 模型……再加上网上教程水平参差不齐有的教你搭个 Hello World 就跑有的上来就甩一堆生产级代码结果就是学完还是不会自己写项目。我做了这么多年 Python 后端FastAPI 基本已经成了我搭建 API 服务的首选框架今天这篇就把我从零上手、到能独立搭出完整项目的路径和心得完整分享出来。先说清楚 FastAPI 到底是什么、能做什么、适合谁FastAPI 是一个基于 Python 类型提示的现代 Web 框架目标是帮你快速构建高性能 RESTful API。它自带请求参数校验、自动生成交互式接口文档Swagger UI 和 ReDoc、原生支持异步性能上能对标 NodeJS 和 Go 写出来的服务。适合三类人一是刚入门后端、想找一个不太痛苦的学习曲线的 Python 开发者二是用 Flask/Django 写了几年、受够了手动校验参数和手写文档的实干派三是需要在短时间内把 AI 模型封装成接口的算法工程师比如本地跑 Ollama 模型要对外开放 API用 FastAPI 就是顺手的事。1. 为什么是 FastAPI 而不是 Flask一次彻底的技术选型复盘1.1 Flask 和 FastAPI 的本质差异在哪里很多新人纠结的第一个问题就是我学过 Flask为什么要换 FastAPI这两者表面上看都是轻量级 Web 框架但骨子里的设计哲学完全不同。Flask包括它的后继者 Quart走的是我给你最核心的东西剩下的你自己装的极简路线路由、请求对象、响应对象就这么多。FastAPI 则一上来就把 Starlette高性能 ASGI 框架和 Pydantic数据校验库揉进了核心自带数据校验、序列化、依赖注入、OpenAPI 文档这些在 Flask 里要么自己手写、要么到处找第三方库拼装的能力FastAPI 出厂自带。举个最现实的例子一个注册接口前端传过来 JSON你要校验用户名不能为空、邮箱格式对不对、年龄必须是 18 到 100 之间的整数。用 Flask 写你得手动去 request.json 里取值然后一个个 if 判断抛异常还要自己包装成 JSON 响应。用 FastAPI你只要定义一个 Pydantic 模型from pydantic import BaseModel, EmailStr, Field class UserCreate(BaseModel): username: str Field(min_length3, max_length20) email: EmailStr age: int Field(ge18, le100)然后路由函数里直接把这个模型当参数声明app.post(/users/) async def create_user(user: UserCreate): # 走到这里user 已经是一个校验过的合法对象 return {username: user.username}写到这里校验失败时 FastAPI 自动返回 422 和具体的错误信息前端拿到就能定位问题你一行校验代码都没写。这省下来的时间我实测下来同样一个带 30 个字段的订单接口用 Flask 写校验逻辑大概需要 200 行FastAPI 只需要定义模型加注释工作量差距肉眼可见。1.2 性能差异从 WSGI 到 ASGI 的跨越性能是一个绕不开的话题。Flask 基于 WSGI 协议是同步模型一个请求占用一个线程池里的 worker处理过程中如果遇到 IO 等待比如查数据库、调外部 API这个线程就闲着但没法去处理别的请求。FastAPI 基于 ASGI 协议天生支持异步并发一个事件循环可以同时挂起成千上万个 IO 等待中的协程CPU 不用空转同样的资源下能扛住的并发量完全不同。我给一个最简单的压测数据参考在我的 MacBook Pro 上用 wrk 压测一个只返回 JSON 的接口Flask Gunicorn 单进程大概能跑 3000 左右 QPSFastAPI Uvicorn 单进程能跑到 9000 左右翻了三倍。当然实际业务里瓶颈都在数据库和外部服务调用上QPS 数字看看就好但同样的机器能撑住更大的并发压力这是实打实的优势。注意不要因为 FastAPI 支持 async 就处处 async。如果你的业务代码里全是同步阻塞的 CPU 计算老老实实定义成普通 def 函数FastAPI 会自动把同步函数丢到线程池里执行效果反而比强行 async 更好。我用time.sleep模拟阻塞逻辑时吃过亏刚开始全部写成 async def结果并发一高响应时间直线上升改成普通 def 之后反而稳了。1.3 社区生态和公司选型的现实考量选框架不能只看技术指标还得看生态和团队接受度。Flask 比 FastAPI 老得多插件数量多到数不清老项目里你什么奇怪需求都能找到现成的扩展。但这也带来一个隐性成本插件质量参差不齐很多维护已经停滞装一个插件等于给自己埋一个定时炸弹。FastAPI 虽然年轻但背靠 Starlette 和 Pydantic 这两个军火库自带的能力覆盖了大部分日常需求。社区活跃度极高GitHub 上 star 数增长非常快周边生态比如 FastAPI Users、FastAPI CRUD、sqlmodel 这些库的质量都很高而且持续在更新。我给一个小建议你如果是想做个东西学习、或者从零起一个没有历史包袱的新项目直接选 FastAPI如果你要接手或者维护的是存量 Flask 项目那就别盲目重构除非业务确实遇到了性能瓶颈否则迁移成本大于收益。2. 五分钟跑通FastAPI 安装与第一个接口2.1 安装环节的两个常见坑安装这东西看起来简单pip install fastapi uvicorn一条命令的事但我见过太多人在这一步卡住。第一个坑是 Python 版本不够老FastAPI 需要 Python 3.8 以上Pydantic V2 更是要求 3.8 的同时强烈建议 3.9 以上才能完整发挥类型提示能力。我建议直接用 Python 3.11 或者 3.12别在旧版本上折腾。第二个坑是环境隔离。我看到过无数当场翻车案例明明装好了 fastapiimport 的时候却报 ModuleNotFoundError原因就是 pip 装到了系统环境而 IDE 解释器用的是虚拟环境或者反过来。所以一上来就老老实实建虚拟环境python -m venv .venr source .venv/bin/activate # Windows 上是 .venv\Scripts\activate pip install fastapi uvicorn[standard]这里我特意写的是uvicorn[standard]而不是裸uvicorn这个细节很多人会忽略。standard 这个 extra 会额外装一些生产环境需要的东西比如 uvloop一个比 asyncio 默认事件循环更快的实现、httptools高性能 HTTP 解析器、websocketsWebSocket 支持。虽然这只影响 Uvicorn 的底层性能但对生产部署来说差距还是实打实存在的。开发环境用裸 uvicorn 没问题部署的时候建议一定加 standard。2.2 Hello World 背后的执行流程装好之后我们写一个最基础的入口文件main.pyfrom fastapi import FastAPI app FastAPI(title我的第一个 FastAPI 项目) app.get(/) async def read_root(): return {message: Hello World} app.get(/items/{item_id}) async def read_item(item_id: int, q: str | None None): return {item_id: item_id, q: q}启动命令是uvicorn main:app --reload --port 8000main:app的意思是从main.py文件里导入app这个 FastAPI 实例--reload是开发模式下的热重载改完代码保存服务自动重启。启动后访问http://127.0.0.1:8000/docs你就能看到 Swagger UI 自动生成的交互式文档每个接口的参数、响应模型、错误码都给你列得清清楚楚甚至可以直接在页面上点击 Try it out 来测试接口。这里稍微停下来分析一下这个看起来简单的接口背后到底发生了什么你声明item_id: intFastAPI 就会自动把 URL 路径里的字符串参数转成 int如果前端传了个非数字比如/items/abcFastAPI 直接返回 422连进业务逻辑的机会都不给。你声明q: str | None NoneFastAPI 就知道这是一个可选的查询参数不传就默认 None传了就自动解析成字符串。这就是类型提示的威力——它把原本要写 inif 判断的参数解析和类型转换全部浓缩到了函数签名里。2.3 自动文档系统的多层用法FastAPI 的自动文档很多人只用来看其实它是个非常好用的开发工具。/docs是 Swagger UI适合人工点击测试/redoc是 ReDoc排版更清晰适合展示给非技术同事看/openapi.json是原始 OpenAPI 规范文件可以导入到 Postman、Apifox 这些工具里直接生成可调试的接口集合。我现在的常规工作流是这样的本地起服务开着--reload写代码的时候偶尔瞄一眼 docs确认参数定义和响应模型的 schema 是否符合预期。写完接口之后先不用 Postman直接在 Swagger UI 上把边界测试做了比如传个超长字符串、传个负数确认校验逻辑没问题再把openapi.json导入 Apifox 作为团队协作基准。遇到前端同事对接时甩一句你去看 docs 页面省了不知道多少口舌。3. 项目目录结构从单文件到可扩展架构的关键一跃3.1 新手最常见的单文件巨兽病初学者爱把所有的代码堆在一个main.py里路由、模型、数据库连接、业务逻辑全塞进去等到文件超过 1500 行的时候改一个路由都要翻半天。这不是 FastAPI 的问题而是 Python Web 项目普遍的演进痛苦。FastAPI 的灵活性给了你充分的自由但这种自由如果一开始不加以约束后续重构成本会非常高。我自己的项目组织习惯是模块化路由加重型服务层目录结构大概是这样的fastapi-project/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 实例、注册路由、CORS、启动事件 │ ├── config.py # 配置项基于 Pydantic Settings │ ├── database.py # 数据库连接、会话管理 │ ├── models/ # SQLAlchemy ORM 模型数据库表结构 │ │ ├── __init__.py │ │ └── user.py │ ├── schemas/ # Pydantic 模型请求/响应数据结构 │ │ ├── __init__.py │ │ └── user.py │ ├── routers/ # API 路由模块按业务域拆分 │ │ ├── __init__.py │ │ ├── auth.py │ │ └── users.py │ ├── services/ # 业务逻辑层router 只管拿参数、调 service │ │ ├── __init__.py │ │ └── user_service.py │ └── utils/ # 杂项工具日志、加密、格式化 ├── tests/ ├── .env # 环境变量不提交到 Git ├── requirements.txt ├── Dockerfile └── README.md注意我强调的两个原则第一router 层只做三件事——接收参数、调用 service、返回结果不要在 router 里写业务规则第二schemas 里的 Pydantic 模型和 models 里的 ORM 模型是两个东西前者管数据进出的形状后者管数据库映射别混用。3.2 路由模块化APIRouter 的正确打开方式路由模块化的核心是APIRouter。在app/routers/users.py里from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.database import get_db from app.schemas.user import UserCreate, UserOut from app.services import user_service router APIRouter(prefix/users, tags[用户管理]) router.post(/, response_modelUserOut) def create_user(user: UserCreate, db: Session Depends(get_db)): return user_service.create_user(db, user) router.get(/{user_id}, response_modelUserOut) def get_user(user_id: int, db: Session Depends(get_db)): db_user user_service.get_user(db, user_id) if not db_user: raise HTTPException(status_code404, detail用户不存在) return db_user然后main.py里把这些 router 全部注册进来from fastapi import FastAPI from app.routers import users, auth app FastAPI(title我的项目, version0.1.0) app.include_router(users.router) app.include_router(auth.router)prefix参数是给这组路由统一加前缀的/users下面定义/{user_id}最终生成的路径就是/users/{user_id}方便统一规划路径段。tags参数用于把接口归类在 Swagger UI 里这组接口会整整齐齐地分在一个栏目里对多人协作时飞速定位接口非常有帮助。3.3 配置管理一份 .env 走天下配置管理的正规做法是使用pydantic-settings它是 Pydantic 官方出的配置管理库读取.env文件并自动做类型转换、字段校验。建一个app/config.pyfrom pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): app_name: str FastAPI Project debug: bool False database_url: str sqlite:///./dev.db jwt_secret: str change-me jwt_expire_minutes: int 1440 model_config SettingsConfigDict(env_file.env, env_file_encodingutf-8) settings Settings()这样你在任何模块里from app.config import settingssettings.database_url就能拿到配置值。环境变量、.env文件、代码里的默认值形成了一个优先级链环境变量最高.env其次代码默认值兜底。我踩过的坑是把生产数据库密码直接硬编码在代码里后来不得不全面排查 Git 历史——密文倒是没泄露到公网但这件事实实在在给我提了个醒。所以只要你准备让别人看到你的代码就要用.env.gitignore的组合把所有敏感字段挡在仓库之外。4. Pydantic 模型与依赖注入FastAPI 最强生产力的核心4.1 请求校验的进阶用法嵌套模型与 Field 约束前面已经展示了最基础的校验实际项目中请求体往往是嵌套的。比如一个创建订单的接口订单里有商品列表、配送地址、优惠券信息用 Pydantic 可以直观地表达这种层级结构from pydantic import BaseModel, Field from typing import List class OrderItem(BaseModel): sku: str quantity: int Field(ge1, le99, description购买数量 1~99) unit_price: float Field(gt0, description单价必须大于 0) class ShippingAddress(BaseModel): receiver: str Field(min_length1, max_length50) phone: str Field(patternr^1\d{10}$) address: str class OrderCreate(BaseModel): items: List[OrderItem] address: ShippingAddress coupon_code: str | None None这个模型一旦定义好FastAPI 会自动完成全部嵌套校验items 里的每个元素都会检查 sku 是否存在这个要你在业务逻辑里查数据库、 quantity 是否在范围内、单价是否为正数address 里的 phone 会按正则匹配校验手机号格式coupon_code 是可选的传不传都行。校验失败时错误信息会精确到嵌套的哪一层哪个字段比如body.items[2].quantity这也方便前端定位问题了。4.2 依赖注入把重复代码变干净依赖注入是 FastAPI 让人一旦习惯就回不去的特性。开发中你经常要做的两件事接口要验证用户是否登录接口要拿到数据库会话。每个接口里都写一遍认证逻辑和建连逻辑直接疯掉。FastAPI 的Depends就是解决这个问题的。拿用户认证举例from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from app.config import settings oauth2_scheme OAuth2PasswordBearer(tokenUrl/auth/login) def get_current_user(token: str Depends(oauth2_scheme)): credentials_exception HTTPException( status_codestatus.HTTP_401_UNAUTHORIZED, detail无效的认证凭证, headers{WWW-Authenticate: Bearer}, ) try: payload jwt.decode(token, settings.jwt_secret, algorithms[HS256]) user_id: int payload.get(sub) if user_id is None: raise credentials_exception except JWTError: raise credentials_exception return user_id # 在需要登录的接口里 app.get(/profile/) def get_profile(current_user: int Depends(get_current_user)): return {user_id: current_user}这个get_current_user就是依赖它依赖了oauth2_scheme来拿 token自己做 JWT 解码和异常处理。接口函数声明current_user: int Depends(get_current_user)后FastAPI 会在访问这个接口时自动解析这个依赖链先执行oauth2_scheme拿 token再执行get_current_user校验 token返回 user_id 注入到函数参数里。这样每个受保护接口的代码都干净得像没有安全逻辑一样。依赖注入另外一个重要的用法是数据库会话管理from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, Session from app.config import settings engine create_engine(settings.database_url) SessionLocal sessionmaker(bindengine, autoflushFalse) def get_db(): db SessionLocal() try: yield db finally: db.close()get_db用yield关键字声明了一整段生命周期FastAPI 拿到依赖后接口执行完会自动执行finally里的关闭逻辑。不需要你手动关连接、不需要 context manager以前 Flask 项目里大量 existing 的db.close()全被框架接管了。4.3 依赖的依赖多层依赖树与受保护路由组实际项目中依赖往往不是一层比如你要写一个只有管理员能删除用户的接口就需要先认证用户身份再检查角色权限。这种场景可以组装出多层依赖def get_current_admin(current_user: int Depends(get_current_user)): user user_service.get_user_with_roles(db..., user_idcurrent_user) if user.role ! admin: raise HTTPException(status_code403, detail需要管理员权限) return user然后在路由里router.delete(/{user_id}, dependencies[Depends(get_current_admin)])dependencies参数不需要声明在函数签名里FastAPI 也会先执行这些依赖再进入函数体适合做动词型的权限拦截函数签名保持干净。用多层依赖可以优雅地构建出认证在上、鉴权在下、业务参数最后进入的调用链理解之后你会发现一个复杂的权限系统其实也就是几个依赖函数的事。5. 链路实操FastAPI 调用 Ollama 本地模型的完整闭环5.1 场景与环境的匹配FastAPI 社区近期最热的话题之一就是调用本地大模型对应到热门搜索里的FastAPI 调用 Ollama。这里拆解一个实际的场景本地跑着一个 Ollama 服务端比如加载了llama3.1:8b或qwen2.5:7b模型现在要做一个 Web 服务让其他系统通过 HTTP 访问这个模型能力而不是每个人都直接去拉 Ollama 的 11434 端口。为什么中间要加一个 FastAPI 服务至少三个理由第一权限控制和鉴权模型调用方不应该直接接触 Ollama 原始端口敏感操作需要 token 验证第二请求过滤与审计比如对 Prompt 做敏感词过滤、记录每次调用的用户和消耗的 token 数第三业务层面的后处理比如把模型输出整理成 JSON 直接对接前端而不是让前端自己去解析 Ollama 的非结构化流式输出。5.2 异步 HTTP 调用 Ollama 的代码实现Ollama 本身提供了 HTTP API我们只需要在 FastAPI 里做一个透传 包装层。第一种是同步方式用httpx库import httpx from fastapi import APIRouter, HTTPException from pydantic import BaseModel router APIRouter(prefix/ai, tags[AI 能力]) OLLAMA_BASE_URL http://localhost:11434 DEFAULT_MODEL qwen2.5:7b class ChatRequest(BaseModel): prompt: str system_prompt: str 你是一个乐于助人的助手 model: str DEFAULT_MODEL temperature: float 0.7 class ChatResponse(BaseModel): reply: str model: str total_duration_ms: int router.post(/chat, response_modelChatResponse) async def chat_with_ollama(req: ChatRequest): payload { model: req.model, prompt: req.prompt, system: req.system_prompt, stream: False, options: {temperature: req.temperature}, } try: async with httpx.AsyncClient(timeout120) as client: resp await client.post(f{OLLAMA_BASE_URL}/api/generate, jsonpayload) resp.raise_for_status() data resp.json() except httpx.TimeoutException: raise HTTPException(status_code504, detail模型生成超时) except httpx.ConnectError: raise HTTPException(status_code503, detail无法连接到 Ollama 服务请检查模型是否已启动) return ChatResponse( replydata.get(response, ), modeldata.get(model, req.model), total_duration_msdata.get(total_duration, 0) // 1_000_000, )这段代码有几个细节值得展开。第一timeout120因为大模型生成比较慢尤其是 7B 以上模型单次推理动辄几十秒默认的 5 秒超时只会让你疯狂收到 timeout 异常第二stream: False是让 Ollama 一次性返回完整结果如果做流式对话可以改成True再用 SSE 推给前端但那是另一个复杂场景本篇不展开第三异常处理单独给了 504 和 503 两种状态码登录日志时能直接看出是模型超时还是服务没起来少走弯路。5.3 在 FastAPI 里管理外部模型调用的实践经验用 FastAPI 调用 Ollama几个经验值得记录。一是连接池复用每次请求都新建httpx.AsyncClient性能极差建议把 client 声明在模块级别或依赖里client httpx.AsyncClient(base_urlOLLAMA_BASE_URL, timeout120) router.post(/chat) async def chat_with_ollama(req: ChatRequest): resp await client.post(/api/generate, jsonpayload)二是并发限制大模型推理很吃显存如果同一个模型并行来几十个请求Ollama 会因为 GPU 内存不足而排队甚至 OOM。最简单的方案是加一个 asyncio 信号量限制同时最多 4 个调用import asyncio semaphore asyncio.Semaphore(4) router.post(/chat) async def chat_with_ollama(req: ChatRequest): async with semaphore: resp await client.post(/api/generate, jsonpayload)三是模型热切换不要在代码里硬编码模型名把DEFAULT_MODEL放到 settings 里通过环境变量控制这样你在本地试小模型、在 GPU 服务器上切大模型代码完全不用改。6. 部署与打包从开发环境到生产环境的最后一公里6.1 Uvicorn 的正确姿势不要裸启动开发环境里uvicorn main:app --reload是用来调试的生产环境直接裸启动等于给自己埋雷。--reload会开启文件监听生产环境完全不需要且浪费资源。正确的生产启动方式是用多 workeruvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4--workers 4会启动 4 个独立进程每个进程都有自己的事件循环能充分利用多核 CPU。但是注意一个前提使用--workers时必须保证你的应用进程状态是独立的比如不要在主进程里初始化全局连接池、不要让依赖注入依赖单例对象。你的代码需要做到线程安全 进程安全兼容。更常见的生产部署方案是把 Uvicorn 放在反向代理后面。Nginx 负责 TLS 终结、HTTP 头处理、静态文件服务Uvicorn 只关心应用逻辑server { listen 443 ssl; 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; } }6.2 Windows 打包 FastAPI 的实战方案热门搜索里有FastAPI Windows 打包这块确实是个隐蔽的坑。因为 FastAPI 项目本质上是一个 Python 环境 依赖包 源代码的组合在 Windows 上要打包成可执行文件最常见的是用 PyInstaller。但我直接给你结论PyInstaller 打包 FastAPI 项目非常容易翻车问题多半出在 Uvicorn 的 worker 进程和动态导入上。简单说两个最实用的路线。第一种用于内部分发用 PyInstaller 打包命令行工具但是把main.py直接作为入口脚本打包不要用--onefile单文件模式启动太慢且杀毒软件容易误报而用--onedir目录模式启动速度更快且更稳pyinstaller --name myapi --onedir --add-data app;app main.py打包完会在dist/myapi/下生成myapi.exe。双击运行后你会发现没有 Uvicorn 的启动日志因为 PyInstaller 的 exe 默认没有控制台窗口如果你用了--noconsole调试时务必加上--console参数让错误信息可见这是我在 Windows 打包时踩得最深的一个坑。第二种更推荐的路线使用 Docker。先在 Windows 上装 Docker Desktop写一个简单 DockerfileFROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD [uvicorn, app.main:app, --host, 0.0.0.0, --port, 8000]构建运行docker build -t my-fastapi-app . docker run -d -p 8000:8000 my-fastapi-app就不用考虑 PyInstaller 各种玄学问题了环境一致性也有了保证这是一条经过验证的平稳路径。6.3 Uvicorn 日志丢失问题的真正解法热搜词里有个Uvicorn 日志丢失的问题这个我遇到过先说结论多半是日志配置和 logging.basicConfig 冲突导致的。Uvicorn 用的是 Python 标准的 logging 模块但它默认会配置自己的 handler。你在项目里如果调用了logging.basicConfig(levellogging.INFO)有时会把 Uvicorn 的日志配置覆盖掉导致启动时看不到访问日志。还有一种情况是使用 PyInstaller 启动 exe 时Uvicorn 日志不会输出因为 Windows 的控制台日志需要一个特殊的--log-config参数来指定日志配置文件。我自己是这么处理的写一个log_config.py定义合适的 formatter在启动时传给 Uvicorn# run.py import logging import uvicorn logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, ) if __name__ __main__: uvicorn.run( app.main:app, host0.0.0.0, port8000, log_levelinfo, access_logTrue, )把启动逻辑全放在run.py里既保留了日志配置又统一了开发和生产环境的启动方式。从这之后我再也没有花时间排查过日志丢失的问题。7. 常见问题与排查实录7.1 同步接口拖垮整个服务的问题很多新手不理解为什么一个看起来毫不起眼的同步接口会导致整个 API 服务变慢。FastAPI 对async def和普通def的处理方式不同async def在事件循环中运行def则被放进线程池执行。如果业务代码里存在 CPU 密集或长时间阻塞的同步逻辑比如调一个很慢的同步 MySQL 驱动每次调用都会占用一个线程池 worker线程池默认只有 40 个线程一旦被打满所有同步接口都会排队。最优解是IO 密集型操作查数据库、调外部 API使用异步库配合async defCPU 密集型操作单独放到ThreadPoolExecutor或直接使用def让 FastAPI 帮你管理线程池。7.2 跨域问题CORS 配置失败的检查步骤开发前端的时候最常见的报错是浏览器控制台里的 CORS 错误。FastAPI 解决这个问题的方式非常标准from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[http://localhost:5173], # 前端地址 allow_credentialsTrue, allow_methods[*], allow_headers[*], )注意allow_origins不要无脑填*。如果你的前端请求带了 cookie 或者 Authorization 头allow_origins[*]配合allow_credentialsTrue会导致浏览器直接拒绝响应。要指定完整来源列表部署时从环境变量读。7.3 数据库连接断开MySQL lost connection 处理这是一个生产环境经典问题一个 FastAPI 服务跑了一晚上第二天早上第一个请求突然报Lost connection to MySQL server during query。原因是 MySQL 服务器wait_timeout默认 8 小时超过时间连接就断了而 SQLAlchemy 的连接池并不知道还在继续复用旧连接。解决办法是在创建引擎时指定连接池的回收时间和预检参数engine create_engine( settings.database_url, pool_size10, max_overflow20, pool_recycle3600, # 1小时回收一次连接 pool_pre_pingTrue, # 每次取连接之前先ping一下 )pool_pre_pingTrue是解决这个问题的银弹它会在从连接池取出连接前执行一次轻量的 SELECT连接已经断了就直接丢弃重建代价小到可以忽略。7.4 大文件上传与超时的兼容方案FastAPI 处理文件上传用UploadFilefrom fastapi import UploadFile, File app.post(/upload/) async def upload_file(file: UploadFile File(...)): content await file.read() # 处理 content return {filename: file.filename}但当上传大文件时这种一次读入内存的方式会炸内存。正确姿势是分块读取app.post(/upload/) async def upload_large_file(file: UploadFile File(...)): total 0 with open(f/tmp/{file.filename}, wb) as f: while chunk : await file.read(1024 * 1024): # 每次读 1MB f.write(chunk) total len(chunk) return {saved: total}Uvicorn 默认的请求体大小上限是无限的吗并不是。对于流式上传你要注意 Nginx如果用了反代的client_max_body_size设置不调大这个值再优雅的代码都会收 413 错误。7.5 异步任务没有执行完的问题FastAPI 的async def函数里面如果调用了同步的第三方库比如 OpenAI SDK 的老版本整个事件循环会被阻塞。如果你在接口里起了后台任务要使用BackgroundTasksfrom fastapi import BackgroundTasks def write_log(user_id: int): with open(access.log, a) as f: f.write(fuser {user_id} accessed\n) app.post(/login/) async def login(user: ..., background_tasks: BackgroundTasks): background_tasks.add_task(write_log, user.id) return {msg: 登录成功}BackgroundTasks 会在返回响应之后执行不会给当前请求添加延迟。但对耗时几十秒甚至几分钟的复杂任务比如数据报表、模型批量推理建议引入 Celery 或者使用asyncio.Task配合任务队列而不是依赖 BackgroundTasks。8. 进阶路径从会用到用好的几个关键方向如果你已经能把一个业务的后端独立搭出来下一步值得投入的方向按优先级和性价比排是这几个第一把 Uvicorn 换成hypercorn或者学习 Uvicorn 的 worker 生命周期机制理解 FastAPI 服务的多进程调度第二系统地学习 SQLAlchemy 2.0 的 async 特性async_sessionmaker、select语句配合 FastAPI 的异步链路数据库访问的性能瓶颈才真正打通第三搞懂 FastAPI 的 OpenAPI schema 定制如何隐藏内部字段response_model_exclude_unset、response_model_exclude、如何给不同环境生成不同的文档第四学会写单元测试和集成测试FastAPI 官方提供的TestClient基于httpx可以非常自然地测试异步函数from fastapi.testclient import TestClient from app.main import app def test_read_main(): with TestClient(app) as client: resp client.get(/) assert resp.status_code 200 assert resp.json() {message: Hello World}这套测试代码比 Flask 项目里要 mock context 的测试舒服太多。以我个人的实际经验来说学到这个程度一个 Python 开发者从打开 FastAPI 手册到能独立负责一个中小规模项目的后端开发周期大概在两到三周前提是沉得下心一个个接口去敲、踩坑、看文档、读源码。FastAPI 这个框架最大的优点从来不是快而是每一步的选择都让你觉得合理且自然——你越用它就越能体会到类型系统在 Web 开发里的力量。最后分享一个我反复强调的心得写 FastAPI 项目时始终把接口的定义当成一种契约来对待。Pydantic 模型不仅仅是校验工具它是你的 API 面向外部世界的第一层语言依赖注入也不只是消灭重复代码的手段它是你应用架构的表达机制。多花二十分钟设计好一个 schema、梳理清一条依赖链后期消除的隐患往往比多写几百行代码更有价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑