news 2026/10/9 10:20:40

【Web全栈进阶】FastAPI工程化:APIRouter拆分 + 配置 + 依赖注入

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Web全栈进阶】FastAPI工程化:APIRouter拆分 + 配置 + 依赖注入

之前的FastAPI还活在单文件里:所有路由挤在一个main.py。
200行时没问题,500行时没人敢动——今天把它升级成“分模块的工程”,用早报站的API实战三件套:APIRouter拆路由、Settings管配置、Depends依赖注入。

🎯本篇产出:早报站FastAPI应用骨架——app/分层、两个路由模块、统一会话注入、配置文件外置。含代码约150行。


📌 太长不看版(给想快速上手的你)

项目信息一句话说明
本篇目标FastAPI从单文件升级为分模块工程
代码行数~150行(含注释)
依赖fastapi+pydantic-settings(新增)
核心功能APIRouter拆分 + Settings配置 + Depends依赖注入
跑起来的命令uvicorn app.main:app --reload
核心知识点分层结构、配置外置、依赖注入
做完你能得到一套能维护、可扩展的FastAPI工程骨架

⚠️工程声明:本篇先把FastAPI的“骨架与结构”立起来,响应模型(response_model)的完整规范留到后面联调篇统一设计——今天解决“结构”,明天解决“规范”。


一、为什么需要“工程化”:单文件的三宗罪

单文件API是这个画风:

# 所有路由、所有逻辑、所有配置挤在一个文件@app.get("/todos")...@app.post("/todos")...@app.put("/todos/{id}")...

三个问题会随着代码长大依次爆发:

#问题表现
①路由一多文件失控找接口靠Ctrl+F
②配置散落连接串、密钥写在文件各处
③重复代码每个接口都写一遍“开会话、关会话”

📌工程化就是把这三件事制度化——这正是早报站从“玩具”走向“产品”必须跨过的一步。


二、新结构:app/层的标准布局

python_daily/ ├── core/ # 数据层(第03、04篇已就位) │ ├── db.py # 引擎与会话 │ └── models.py # ORM模型 ├── app/ # API层(本篇新建) │ ├── __init__.py │ ├── main.py # 应用入口:创建app、挂载路由 │ ├── config.py # Settings:配置集中管理 │ ├── deps.py # 共享依赖(get_session等) │ └── routers/ # 路由按资源分文件 │ ├── __init__.py │ ├── articles.py │ └── sources.py └── .env # 本地配置(进.gitignore!)

🎯分层逻辑一句话:core是“数据怎么存”,app是“接口怎么暴露”,routers是“一个资源一个文件”。


三、第1步:Settings——配置只写一处

app/config.py:

"""app/config.py —— 全局配置"""frompydantic_settingsimportBaseSettings,SettingsConfigDictclassSettings(BaseSettings):"""配置项集中声明;环境变量与.env文件自动注入"""database_url:str="postgresql+psycopg://postgres:你的密码@localhost:5432/daily"secret_key:str="dev-only"debug:bool=Falsemodel_config=SettingsConfigDict(env_file=".env",extra="ignore")settings=Settings()# 全局唯一实例,其他地方import它

📌 三个细节

#细节说明
①env_file=".env"自动读项目根的.env文件——.env必须进.gitignore(密钥不在代码里、不在仓库里,只在环境里)
②环境变量自动匹配字段database_url自动匹配环境变量DATABASE_URL(大小写不敏感),.env里写DATABASE_URL=xxx即生效
③默认值=本地开发兜底生产环境用环境变量覆盖,代码零改动

💡 以后任何接口要用配置,一行from app.config import settings。

📄.env示例(记得进.gitignore)

DATABASE_URL=postgresql+psycopg://postgres:你的密码@localhost:5432/daily SECRET_KEY=你的随机密钥 DEBUG=false

四、第2步:共享依赖——session注入

每个接口都要数据库会话,但“开一个、用、关一个”不该重复写。FastAPI的依赖注入(Depends)把这件事制度化:

app/deps.py:

"""app/deps.py —— 共享依赖"""fromcore.dbimportSessiondefget_session():"""每个请求一个独立会话;请求结束自动关闭(with保证)"""withSession()assession:yieldsession

app/routers/articles.py:

"""app/routers/articles.py —— 文章接口"""fromfastapiimportAPIRouter,Dependsfromsqlalchemyimportselectfromsqlalchemy.ormimportSessionfromapp.depsimportget_sessionfromcore.modelsimportArticle router=APIRouter(prefix="/articles",tags=["articles"])@router.get("")deflist_articles(session:Session=Depends(get_session),# 依赖注入:FastAPI自动调用get_sessionlimit:int=10,):"""文章列表:最新在前"""stmt=select(Article).order_by(Article.id.desc()).limit(limit)returnsession.scalars(stmt).all()

📖 拆开看发生了什么

#关键点说明
①Depends(get_session)声明“这个接口需要get_session提供的东西”——FastAPI收到请求时自动调用get_session,把yield出的session作为参数传进来,请求结束自动执行收尾
②好处一:接口代码干净函数体里只有业务逻辑,没有样板
③好处二:全局替换将来测试时(第26篇),把get_session换成“测试库会话”,所有接口自动切换,一行接口代码不用改

📌依赖注入的第一个字面收益是整洁,深层收益是可替换。

🎯 再加一个业务型依赖练手——分页参数

app/routers/articles.py补充:

defget_page(page:int=1,size:int=10):"""分页依赖:page从1开始,返回(offset, limit)"""return(page-1)*size,size@router.get("/top")deftop_articles(session:Session=Depends(get_session),page:tuple[int,int]=Depends(get_page),# 依赖还能组合依赖):offset,limit=page stmt=select(Article).order_by(Article.id.desc()).offset(offset).limit(limit)returnsession.scalars(stmt).all()

💡 注意page的类型注解是tuple[int, int],和get_page返回值对齐——类型注解要和实际返回一致(第七节④会讲这个坑)。


五、第3步:应用入口——把路由挂上去

app/main.py:

"""app/main.py —— FastAPI应用入口"""fromfastapiimportFastAPIfromapp.routersimportarticles,sources app=FastAPI(title="早报站 API",version="0.2.0")app.include_router(articles.router)app.include_router(sources.router)

app/routers/sources.py(订阅源接口,模式同articles):

fromfastapiimportAPIRouter,Dependsfromsqlalchemyimportselectfromsqlalchemy.ormimportSessionfromapp.depsimportget_sessionfromcore.modelsimportSource router=APIRouter(prefix="/sources",tags=["sources"])@router.get("")deflist_sources(session:Session=Depends(get_session)):returnsession.scalars(select(Source)).all()

✅ 运行(在项目根目录执行)

uvicorn app.main:app--reload

打开/docs——见证结构化的第一个红利:

🎉Swagger里接口按articles / sources分组显示,tags自动归类,接口多了也不乱。


六、验收清单

1. uvicorn启动无报错,/docs里articles / sources两组接口2. 浏览器访问/articles?limit=3→ 返回3条文章JSON3. 访问/articles/top?page=2→ 翻页生效(offset正确)4. 改.env的DATABASE_URL指向不存在的库 → 接口报可读错误 → 改回5.gitstatus确认.env不在仓库里6. 全程无报错后提交Git
gitadd.gitcommit-m"FastAPI工程化:路由拆分 + Settings + 依赖注入"

七、常见报错:这6个,工程化改造的标配(重点!)

①ModuleNotFoundError: No module named 'pydantic_settings'

🔍 原因:pydantic-settings需要单独安装(FastAPI自带pydantic,不带settings)。

✅ 解法:

pipinstallpydantic-settings pip freeze>requirements.txt

②ModuleNotFoundError: No module named 'app'

🔍 原因:启动目录不对——不在项目根目录执行uvicorn。

✅ 解法:cd python_daily再跑;cwd在sys.path里才有app包(一季第5篇的老规矩)。


③/docs里接口地址变成/articles//或访问报307

🔍 原因:prefix="/articles"加@router.get("/")拼出了双斜杠。

✅ 解法:

  • 带prefix时路由写空串@router.get("");
  • 不带prefix时写"/"。

📌prefix与路径的拼接规则要记牢。


④ 访问接口报missing required argument: 'session'

🔍 原因:函数签名写了session: Session但忘了= Depends(get_session)——FastAPI把它当成普通必填参数。

✅ 解法:依赖注入的声明是Depends(...),不是类型注解本身。

📌类型注解只是文档,Depends才是接线。


⑤ 返回的JSON里datetime报cannot encode datetime

🔍 原因:直接返回ORM对象时,FastAPI默认序列化不了datetime等复杂类型。

✅ 解法:现在表里全是基础类型所以没炸;加了时间列就会遇到——响应模型(response_model)与序列化规范在后续统一处理。

📌先记住:裸ORM对象返回是过渡态。


⑥ 改了.env配置,程序没反应

🔍 原因:Settings在import时读一次.env——改完要重启uvicorn(--reload会自动重启,但改了.env有时不触发)。

✅ 解法:手动重启;或确认.env在项目根、名字正确(无后缀,就叫.env)。


八、课后练习

#练习难度提示
1补齐sources接口:给订阅源加“按url模糊搜索”⭐⭐Source.url.contains(keyword)
2分页依赖升级:给get_page加max_size=50上限,size传1000时自动截断⭐⭐依赖里的防御逻辑一次生效全接口受益
3密钥搬家:把遗留的明文配置全部迁入Settings + .env⭐⭐密钥军规正式落地
4(选做)新旧对照:用新结构重写待办API⭐⭐⭐感受“单文件”与“工程”的差距

📦 配套代码

完整app/结构已上传Git(python_daily/):【gitee仓库地址】

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 10:17:49

OpenClaw与Claude Code实战对比:TaoToken统一Key接入下的选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/9 10:13:07

Socket网络编程全解析:从原理、实战到高并发进阶

开门见山,想搞懂网络编程,socket 是绕不过去的第一道门槛。我刚入行那会,看着这两个英文单词一头雾水,查了一堆资料,概念背得滚瓜烂熟,一写代码还是不知道怎么让两台电脑“说话”。后来在项目里被数据收发、…

作者头像 李华
网站建设 2026/10/9 10:12:28

Linux系统篇(五)工具篇·一:软件工具与动静态库

◆博主名称:少司府 欢迎来到少司府的博客☆*: .。. o(≧▽≦)o .。.:*☆ ⭐数据结构系列个人专栏:初阶数据结构 高阶数据结构 ⭐C基础个人专栏:C初阶 C进阶 ⭐Linux个人专栏:Linux系统编程 ⭐琢玉成器终…

作者头像 李华
网站建设 2026/10/9 10:12:27

java八股,redis篇(缓存三兄弟,双写一致)

使用场景 缓存穿透: 原因(查询一个空数据,mydql查询不到数据,也不会直接写入缓存,导致每次都访问数据库,可能会宕机) 1.缓存空数据,但是内存消耗高2.布隆过滤器,使用哈希…

作者头像 李华