NEWS DETAIL

用 FastAPI 搭一个新闻项目,router / crud / model / schema 应该怎么分层

发布时间:2026/10/11 9:36:00 · 栏目:资讯中心

尧图网络原创建站知识分享正文
学完路由、参数、响应、依赖注入、ORM 之后很多人会遇到第二个问题。单个知识点都会了但一到项目里就不知道文件该怎么放。新闻接口写哪里SQL 查询写哪里Pydantic 模型写哪里数据库表结构写哪里统一响应、异常处理、认证工具又写哪里如果所有代码都塞进main.py项目很快就会变成一坨。这篇我们用课程里的 AI 掘金头条项目把 FastAPI 项目分层讲清楚。核心思路很简单main.py只做装配router管 HTTPcrud管数据库操作model管表结构schema管输入输出形状utils放横切工具。本篇准备这一篇不引入新的依赖沿用前面已经准备好的 FastAPI SQLAlchemy 环境pipinstallfastapi uvicorn sqlalchemy aiomysql本篇重点不是多装一个包而是看清楚文件边界入口、路由、数据访问、ORM 模型、请求响应模型分别应该放在哪里。1. 为什么不能把所有代码都写进 main.py刚开始学 FastAPI这样写没问题fromfastapiimportFastAPI appFastAPI()app.get(/news/list)asyncdefget_news_list():return[]但真实项目里一个新闻系统至少有这些功能新闻分类新闻列表新闻详情用户注册用户登录获取用户信息收藏新闻浏览历史Redis 缓存异常处理跨域配置如果全部堆在main.py你会得到一个几千行的入口文件。这类代码最可怕的地方不是长而是边界消失。HTTP 参数、数据库查询、业务规则、响应格式、异常处理全混在一起后面想改一个接口都不知道会影响哪里。所以项目必须分层。2. AI 掘金头条的后端结构课程最终项目的后端结构大概是这样toutiao_backend/ ├── main.py ├── config/ │ ├── db_conf.py │ └── cache_conf.py ├── models/ │ ├── users.py │ ├── news.py │ ├── favorite.py │ └── history.py ├── schemas/ │ ├── base.py │ ├── users.py │ ├── news.py │ ├── favorite.py │ └── history.py ├── crud/ │ ├── news.py │ ├── news_cache.py │ ├── users.py │ ├── favorite.py │ └── history.py ├── routers/ │ ├── news.py │ ├── users.py │ ├── favorite.py │ └── history.py ├── cache/ │ └── news_cache.py └── utils/ ├── auth.py ├── security.py ├── response.py ├── exception.py └── exception_handlers.py看起来文件不少但逻辑很清楚。main.py 应用入口routers 路由层crud 数据访问层models ORM 模型MySQLschemas 请求/响应模型utils 横切工具cache 缓存封装Redis这张图比目录本身更重要。读一个 FastAPI 项目时不要先问有多少文件。先问这些文件各自负责什么。3. main.py只做应用级装配最终项目里的main.py做的事情很少。大概是fromfastapiimportFastAPIfromfastapi.middleware.corsimportCORSMiddlewarefromroutersimportnews,users,favorite,historyfromutils.exception_handlersimportregister_exception_handlers appFastAPI()register_exception_handlers(app)app.add_middleware(CORSMiddleware,allow_origins[http://localhost:5173,http://127.0.0.1:5173,],allow_credentialsTrue,allow_methods[*],allow_headers[*],)app.include_router(news.router)app.include_router(users.router)app.include_router(favorite.router)app.include_router(history.router)你会发现main.py基本不写业务逻辑。它只负责创建 FastAPI 应用注册异常处理器注册 CORS 中间件挂载业务路由这就是一个干净入口文件应该有的样子。入口文件越干净项目越容易维护。4. routers负责 HTTP 接口层routers/news.py这类文件负责定义接口。它关心的是URL 是什么HTTP 方法是什么参数从哪里来需要哪些依赖调用哪个 CRUD 函数返回什么响应简化后的新闻列表接口可能长这样fromfastapiimportAPIRouter,Depends,Queryfromsqlalchemy.ext.asyncioimportAsyncSessionfromconfig.db_confimportget_dbfromcrudimportnews routerAPIRouter(prefix/api/news,tags[新闻])router.get(/list)asyncdefget_news_list(category_id:int|NoneQuery(None,aliascategoryId),page:intQuery(1,ge1),page_size:intQuery(10,ge1,le100,aliaspageSize),db:AsyncSessionDepends(get_db)):returnawaitnews.get_news_list(dbdb,category_idcategory_id,pagepage,page_sizepage_size)这里有个边界很重要。router 可以处理 HTTP 参数但不应该堆复杂 SQL。如果 router 里全是select(...)、join(...)、where(...)说明数据访问逻辑已经漏到 HTTP 层了。5. crud负责数据库操作crud/news.py才是写数据库查询的地方。示例fromsqlalchemyimportselect,funcfromsqlalchemy.ext.asyncioimportAsyncSessionfrommodels.newsimportNewsasyncdefget_news_list(db:AsyncSession,category_id:int|None,page:int,page_size:int):stmtselect(News)ifcategory_id:stmtstmt.where(News.category_idcategory_id)total_resultawaitdb.execute(select(func.count()).select_from(stmt.subquery()))totaltotal_result.scalar()offset(page-1)*page_size resultawaitdb.execute(stmt.offset(offset).limit(page_size))return{list:result.scalars().all(),total:total,hasMore:totalpage*page_size}crud 层关心的是数据库。它不应该关心请求头是什么HTTP 状态码怎么返回前端路由叫什么这能让数据访问逻辑更容易复用。比如同一个get_news_list未来可能被接口调用也可能被后台任务调用。6. models负责数据库表结构models/news.py表达的是表结构。简化版fromsqlalchemyimportString,Text,Integer,DateTime,ForeignKeyfromsqlalchemy.ormimportMapped,mapped_columnclassNews(Base):__tablename__newsid:Mapped[int]mapped_column(Integer,primary_keyTrue)title:Mapped[str]mapped_column(String(255))description:Mapped[str|None]mapped_column(String(500))content:Mapped[str]mapped_column(Text)category_id:Mapped[int]mapped_column(ForeignKey(news_category.id))views:Mapped[int]mapped_column(Integer,default0)model 层不写接口逻辑。它只回答一个问题数据库里这张表长什么样。7. schemas负责请求和响应形状数据库字段不等于 API 字段。这句话非常重要。数据库里可能叫publish_time前端可能希望拿到publishTime。数据库里可能有password接口绝不能返回。这时就需要 schema。fromdatetimeimportdatetimefrompydanticimportBaseModel,ConfigDictclassNewsItemResponse(BaseModel):model_configConfigDict(from_attributesTrue)id:inttitle:strdescription:str|Noneimage:str|Noneauthor:str|Noneviews:intpublish_time:datetimePydantic v2 里ConfigDict(from_attributesTrue)可以让模型从 ORM 对象属性里读取数据。这样你就能把 SQLAlchemy 对象转成响应模型。schema 层的价值是定义 API 契约。只要契约稳定数据库内部怎么调整就不会轻易影响前端。8. utils放横切工具项目里的utils包含文件作用auth.py获取当前用户Token 认证依赖security.py密码哈希和校验response.py统一成功响应exception.py异常处理函数exception_handlers.py注册异常处理器这些逻辑不属于某一个具体业务模块。它们横跨多个模块所以放在utils比较合适。但也要注意utils很容易变成杂物间。判断一个函数该不该进utils可以问一句它是不是多个模块都需要而且不依赖某个具体业务如果只是新闻模块内部用就别放utils放crud/news.py或新闻相关模块里更清楚。9. 一次新闻列表请求的完整链路现在把这些层串起来。用户访问GET /api/news/list?categoryId1page1pageSize10项目内部大概这样跑MySQLmodels/news.pycrud/news.pyrouters/news.pymain.pyVue 前端MySQLmodels/news.pycrud/news.pyrouters/news.pymain.pyVue 前端GET /api/news/list路由匹配解析 Query 参数调用 get_news_list使用 News 模型构造查询执行 SQL返回数据返回列表和 totalJSON 响应这条链路看懂了项目结构就不再是死记硬背。10. CORS 为什么放在 main.py前端 Vite 默认可能跑在http://localhost:5173后端 FastAPI 跑在http://127.0.0.1:8000协议、域名、端口只要有一个不同就是不同源。浏览器会触发 CORS 限制。FastAPI 通过CORSMiddleware解决fromfastapi.middleware.corsimportCORSMiddleware app.add_middleware(CORSMiddleware,allow_origins[http://localhost:5173,http://127.0.0.1:5173,],allow_credentialsTrue,allow_methods[*],allow_headers[*],)本地开发时把 Vite 前端常用的两个地址写进去就够了。正式上线时不要这么放。应该明确指定前端域名例如allow_origins[https://your-frontend-domain.com]这是安全边界不是格式问题。尤其要注意如果allow_credentialsTrue就不要再用allow_origins[*]。浏览器携带 Cookie、认证头这类凭证时跨域规则必须更明确。11. 分层不是为了好看是为了控制变化项目分层的核心不是“看起来专业”。而是控制变化范围。变化应该主要影响哪里URL 改了router数据库查询改了crud表字段改了model 和迁移脚本API 返回字段改了schemaToken 认证规则改了utils/auth响应格式改了utils/response这就是分层的意义。当变化来了你知道去哪改也知道不该碰哪里。12. 小结FastAPI 项目分层可以用这句话记main.py 装配应用 router 接 HTTP crud 查数据库 model 映射表 schema 定契约 utils 放通用工具AI 掘金头条项目从 day03 开始做新闻模块day04 加用户模块day05 加收藏和历史day06 加 Redis 缓存。功能越来越多但只要分层边界清楚项目不会失控。下一篇我们继续看用户模块。注册、登录、Token、密码哈希、统一响应、全局异常处理这些东西加上之后一个 FastAPI 项目才真正开始像一个项目。参考资料FastAPI Bigger ApplicationsFastAPI CORS MiddlewarePydantic v2 Models
✦

本文为尧图网络原创建站知识分享。想针对自身行业获取定制化建站方案?欢迎咨询,资深顾问免费为你出思路。

RELATED

相关 资讯推荐

MORE

更多 新鲜资讯

JMeter入门实战:从零构建HTTP接口性能测试脚本与结果分析
2026/10/10 15:43:34

JMeter入门实战:从零构建HTTP接口性能测试脚本与结果分析

1. 项目概述:从零开始,用JMeter搞定HTTP性能测试 刚接触性能测试的新手,或者是从功能测试转过来的朋友,一听到“压测”两个字,心里可能就有点发怵。工具那么多,概念那么杂,从哪儿下手呢&#xf…

Earendel: 一个纯TS写的POSIX兼容的微内核Web操作系统
2026/10/10 15:43:34

Earendel: 一个纯TS写的POSIX兼容的微内核Web操作系统

最近我用TypeScript写了一个运行在浏览器里的Web操作系统 Earendel(目前已知的距离地球最遥远的恒星)。开发它的初衷主要是为了Linux和操作系统课程的教学。市面上绝大多数Web OS项目,要么只是做了一个长得像Windows/macOS的网页UI拼盘&#…

5分钟掌握Windows防撤回神器:让微信QQ撤回消息无处遁形[特殊字符]
2026/10/10 10:52:13

5分钟掌握Windows防撤回神器:让微信QQ撤回消息无处遁形[特殊字符]

5分钟掌握Windows防撤回神器:让微信QQ撤回消息无处遁形🚀 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁(我已经看到了,撤回也没用了) 项目地址: http…

从零制作同人动画:技术路线、流程与实战避坑指南
2026/10/10 15:19:16

从零制作同人动画:技术路线、流程与实战避坑指南

1. 先搞清楚这个“粉丝动画”到底是什么,以及它能解决什么问题看到“五条悟VS宿傩/咒术回战粉丝动画”这个标题,很多人的第一反应可能是去找一个现成的视频。但如果你是一个想自己动手创作、或者想了解这类作品背后技术流程的创作者,这个标题…

闭源降价80%,开源却在涨价:AI定价的交叉路口
2026/10/10 15:20:24

闭源降价80%,开源却在涨价:AI定价的交叉路口

8月6日,两条价格曲线悄然交汇。 一条向下:OpenAI把GPT-5.6 Luna的输出价格从6美元砍到1.2美元/百万Token,降幅80%。另一条向上:同一天,DeepSeek公告全线涨价,措辞是"涨幅较大",非试探…

产线三防平板选哪个牌子?5个品牌实测告诉你别瞎买
2026/10/10 12:04:54

产线三防平板选哪个牌子?5个品牌实测告诉你别瞎买

产线三防平板选哪个牌子?5个品牌实测告诉你别瞎买经常有工厂朋友拿着网上的"三防平板十大品牌排行榜"来问我:到底选哪个?说实话排行榜看看就行,真正选型得看你产线什么场景。今天挑五个在产线数字化里用得比较多的品牌—…

看完这篇,还想了解更多?

预约尧图网络顾问一对一沟通,针对你的行业与现状给出建站建议,全程免费。

免费咨询