版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
开发“后台管理”模块第5章章节任务与工作流从静态展示到动态管理:打造管理员专属的Web操作后台🧱SQLModel数据建模定义数据库结构与ORM映射🔄Alembic数据库迁移版本化管理数据库变更🎛️SQLAdmin后台集成可视化内容增删改查🎨Template模板渲染前端动态展示数据SQLModel核心特性现代化ORM选择:融合SQLAlchemy与Pydantic的最佳实践🔗强强联合底层基于SQLAlchemy,提供强大的数据库交互能力;同时继承Pydantic的数据验证机制,确保数据安全。⚡一次定义,多处使用同一个SQLModel类既是数据库表结构的定义(ORMModel),又是API请求/响应的数据模式(Schema)。🤝FastAPI完美契合专为FastAPI设计,天然支持依赖注入和自动文档生成,显著减少胶水代码,提升开发效率。数据库引擎配置集中管理连接配置,为SQLModel提供统一的通信入口fromsqlmodelimportcreate_engine
#定义SQLite数据库连接字符串
DATABASE_URL="sqlite:///company.db"
#创建数据库引擎
engine=create_engine(
DATABASE_URL,
connect_args={"check_same_thread":False}
)create_engine()SQLModel与数据库通信的入口点,负责管理连接池和SQL方言适配。SQLite特有参数check_same_thread=FalseFastAPI是多线程环境,默认情况下SQLite限制单线程访问。设置此参数允许不同线程共享连接,防止请求阻塞。CompanyInfo模型定义映射数据库表结构,与前端模板内容一一对应class
CompanyInfo(SQLModel,table=True):
__tablename__="company_info"
#主键ID
id:Optional[int]=Field(default=None,primary_key=True)
#公司简介部分
overview_title:str
overview_image_url:str
overview_lead_text:str
#发展历程部分(history_1~history_4)
history_1:str
...
#企业文化部分
culture_title:str
...models/company.py•table=True:声明该模型对应数据库中的一张物理表。•字段映射:-overview_*:对应前端公司简介区-history_*:对应发展历程时间轴-culture_*:对应企业文化展示块•设计原则:单行表设计,表中始终只存储一行公司信息记录。Alembic初始化与配置连接数据库与模型元数据,打通自动化迁移路径1.初始化alembicinitalembic创建版本控制目录结构:/alembic/versions(存放脚本)
alembic.ini(配置文件)2.修改alembic.ini设置数据库连接URL:sqlalchemy.url=
sqlite:///company.db⚠️注意:禁止中文注释3.配置env.py关键修改点:导入engine和modelstarget_metadata=
SQLModel.metadataconnectable=engine创建并应用迁移将SQLModel的模型变更同步到真实数据库中1.生成迁移脚本alembicrevision
--autogenerate
-m"Createtable"自动检测模型变更,在versions目录下生成新的.py脚本文件。2.修正脚本(关键!)打开生成的脚本文件,手动添加导入:importsqlmodel若缺少此步骤,迁移执行时可能会因为找不到sqlmodel类型定义而报错。3.应用升级alembicupgradehead将数据库结构升级到最新版本(head)。此时company.db中会创建出物理表。SQLAdmin基础视图定义通过简单的类定义,自动生成功能完备的CRUD界面fromsqladminimportModelView
frompanyimportCompanyInfo
class
CompanyInfoAdmin(ModelView,model=CompanyInfo):
#列表页显示的字段
column_list=[CompanyInfo.id,CompanyInfo.overview_title]
#菜单显示名称
name="公司信息"
name_plural="公司信息"
#FontAwesome图标
icon="fa-solidfa-house"核心配置解析•ModelView:SQLAdmin的基础视图类,提供对单个模型的CRUD操作。•model=...:绑定数据模型,自动读取字段元数据。•column_list:定制在数据列表页默认展示哪些列。•name/icon:定制左侧菜单栏的显示文本和图标,提升易用性。挂载Admin后台只需三行代码,即可将管理系统集成至FastAPI应用fromsqladminimportAdmin
fromadminimportCompanyInfoAdmin
#...FastAPIapp定义...
#1.创建Admin实例
#关联app和数据库引擎
admin=Admin(app,engine)
#2.注册视图
#将定义的ModelView加入管理后台
admin.add_view(CompanyInfoAdmin)集成效果•自动路由:自动创建/admin路径及相关子路由。•零前端代码:所有后台页面由SQLAdmin内置模板自动渲染,无需手动编写HTML。•即时生效:重启服务后即可访问/admin管理数据。后台新增记录流程无需编写SQL,通过可视化界面完成数据初始化🔗1.访问后台浏览器打开/admin进入仪表盘➕2.新建记录点击+New按钮进入创建表单页📝3.填充内容迁移静态文本将about.html内容复制到对应字段💾4.保存生效点击Save数据写入SQLite,列表页可见数据库会话依赖利用Python生成器模式,优雅管理数据库连接生命周期fromsqlmodelimportSession
fromdatabaseimportengine
#定义依赖项函数
def
get_session():
#上下文管理器自动处理关闭
withSession(engine)assession:
yieldsession机制解析•Yield模式:FastAPI的依赖注入系统支持生成器。yield之前的代码在请求处理前执行,yield之后的代码在响应发送后执行。•With语句:确保session在使用完毕后自动关闭,释放数据库连接资源,防止连接泄漏。路由查询与上下文注入会话依赖,执行查询逻辑,传递数据给前端asyncdefget_company_about_page(
request:Request,
#注入数据库会话
session:Session=Depends(get_session)
):
#查询第一条记录
statement=select(CompanyInfo)
company_info=session.exec(statement).first()
context={
"request":request,
"active_page":"company",
"company_info":company_info
}
returntemplates.TemplateResponse("company/about.html",context)关键步骤1.依赖注入
使用Depends(get_session)获取可用会话。2.ORM查询
使用session.exec()执行SQLModel查询,first()获取结果。3.传递上下文
将查询到的实体对象放入context字典,传递给Jinja2模板。模板动态渲染使用Jinja2语法,将数据库对象的数据注入到HTML结构中<!--健壮性检查-->
{%ifcompany_info%}
<!--简单变量替换-->
<h1>{{company_info.overview_title}}</h1>
<imgsrc="{{company_info.overview_image_url}}">
<!--数据处理技巧-->
<p>
<strong>{{company_info.history_1.split(':')[0]}}:</strong>
{{company_info.history_1.split(':')[1]}}
</p>
{%else%}
<div>暂无配置</div>
{%endif%}模板技巧•变量注入
使用双大括号{{}}输出后端传递的company_info对象属性。•逻辑控制
使用{%if%}块防止因数据缺失导致的页面崩溃。•动态分割
直接在模板中调用Python的split()方法处理字符串,实现更灵活的排版(如将年份和事件分开加粗)。前后台联动验证闭环测试:确认数据流在后台->数据库->前台之间畅通无阻👀1.确认初始状态访问前台页面显示数据库当前内容✏️2.后台修改访问Admin修改标题并Save🔄3.验证同步刷新前台页面内容即时更新后台标题定制从默认的"Admin"到专属品牌标识,提升系统专业度#main.py
#创建Admin实例时,传入title参数
admin=Admin(
app,
engine,
title="派森科技后台管理系统"
)这一简单的配置修改,会直接反映在后台管理界面的左上角和浏览器标签页标题上,是后台定制化的第一步。模型字段汉化将数据库的英文字段名转换为用户友好的中文列名class
CompanyInfoAdmin(ModelView,model=CompanyInfo):
#...其他配置...
#定义字段在列表页显示的中文名称
column_labels={
CompanyInfo.id:"编号",
CompanyInfo.overview_title:"简介标题"
}ColumnLabels•作用
建立模型属性与显示名称的映射关系。•效果
在后台数据列表页中,表头将不再显示生硬的代码字段名(如overview_title),而是显示直观的中文名称(如“简介标题”),极大降低了非技术人员的操作门槛。SQLAdmin界面汉化深入源码层面的本地化修改方案1.定位源码使用pip命令查找安装路径:pipshowsqladmin找到`Location`字段,进入`sqladmin/templates`目录。2.修改模板直接编辑HTML文件。例如将:
value="Save"修改为:
value="保存"权衡分析•优点
简单直接,无需复杂配置,完全掌控。•缺点
库升级时修改会被覆盖,需重新汉化。工程化与安全提示在快速开发的同时,不应忽视安全与规范🛡️安全警示当前后台处于“裸奔”状态,未配置任何用户认证。任何人知道URL均可访问。后续第9章将结合JWT实现完整的登录保护。🔄版本控制每次修改模型(如新增字段),都必须执行新的Alembic迁移。保持`models/`代码与数据库Schema的严格同步是工程化的基石。🛠️维护规范修改第三方库源码(汉化)后,应记录文档备忘。升级依赖库前,务必检查是否会覆盖自定义修改。感谢开发“新闻动态”模块教材第6章模块目标与路线💡需求动机为了让非技术人员能够轻松地进行图文并茂的内容编辑,我们需要集成“所见即所得”的富文本编辑器,替代传统的纯文本输入。🚀实现路线📄数据模型设计News模型🛠️编辑器集成TinyMCE本地化⚙️后台接入SQLAdmin集成💻前端呈现列表与详情页News模型定义models/news.py:定义新闻数据结构,与SQLAdmin后台联动class
News(SQLModel,table=True):
#主键ID
id:Optional[int]=Field(default=None,primary_key=True)
#新闻标题
title:str
#新闻内容(HTML)
content:str
#发布时间
created_at:datetime=Field(default_factory=datetime.utcnow,nullable=False)字段名说明id主键,自动增长的整数,唯一标识一条新闻。title新闻标题,必填项。content新闻正文,存储由编辑器生成的HTML代码字符串。created_at创建时间,默认使用UTC当前时间,确保时区统一。Alembic迁移News表1.更新配置(alembic/env.py)导入新模型以确保被识别:frommodelsimportcompany,news2.生成迁移脚本在项目根目录执行命令,生成版本文件:alembicrevision--autogenerate-m"Createnewstable"检查生成的脚本中是否包含`importsqlmodel`3.应用变更同步至数据库,创建news表:alembicupgradehead后台编辑器接入1.自定义模板templates/sqladmin/create.html继承layout.html2.字段渲染逻辑{%if=='content'%}class="tinymce-editor"{%else%}class="form-control"{%endif%}3.脚本注入(BlockTail)加载tinymce.min.js初始化:selector:'.tinymce-editor'提交时triggerSave()关键:覆盖create.html模板,拦截content字段并注入特定CSS类,再通过底部JS脚本完成富文本编辑器的初始化与数据同步。TinyMCE本地化集成集成步骤版本选择:
推荐使用5.0.0社区版,无需APIKey验证,适合本地化部署。资源部署:
下载并解压tinymce文件夹,将其完整放置于项目的static目录下。验证访问:
确保tinymce.min.js等核心文件可通过静态路径访问。📂目录结构static/├──bootstrap/├──css/├──image/├──js/└──tinymce/
←新增├──tinymce.min.js└──...(plugins等)本地图片上传流程🖥️TinyMCE前端选择图片->自动上传POST⚙️FastAPI接口接收文件->保存生成唯一文件名JSONURL🖼️插入图片生成<img>标签即时回显实现闭环:上传->处理->返回路径->嵌入显示新闻列表页面设计新闻列表页是面向公众的内容入口,核心在于高效的数据检索与展示。🔗路由设计routers/news.pyprefix="/news"🔍查询逻辑按发布时间倒序order_by(News.created_at.desc())📄分页策略分批加载避免过载limit/offset上传约束与存储结构🛡️安全校验
严格校验file.content_type,必须以image/开头,防止非图片文件上传。🆔命名规范
使用uuid.uuid4()+原始扩展名生成全局唯一文件名,避免同名文件覆盖。
例:550e8400-e29b-41d4-a716-446655440000.jpg📂存储路径
文件保存在static/uploads/目录下。
利用FastAPI的静态文件挂载机制,确保上传后即可通过URL直接访问。分页逻辑与模板Backend(SQLModel)1.接收参数page:int=Query(1,gt=0)2.计算偏移offset=(1)*page_size3.数据查询select(News).offset(offset).limit(10)4.计算总页数total_pages=ceil(total/page_size)Frontend(Jinja2)1.模板变量news_list,total_pages,current_page2.页码窗口只显示当前页前后2页(start_page,end_page)3.边界处理首页/末页显示逻辑,省略号(...)占位4.状态样式active(当前页),disabled(不可点击)列表卡片与交互公司发布2026年度战略规划2026-02-15InteractiveHover关键实现细节Bootstrap组件list-grouplist-group-item-action提供默认的悬停效果和点击区域。空状态处理{%else%}当列表为空时,显示“暂无新闻发布”的友好提示,提升用户体验。日期格式化strftime('%Y-%m-%d')将datetime对象转换为易读的字符串格式。新闻详情页面设计URL参数/detail/{news_id}后端查询session.get(News,news_id)IfNone:404Error模板渲染1.标题(Title)2.时间(Date)3.HTML正文(Content)后端通过动态路由获取ID,查询数据库。若ID不存在则抛出404异常,确保系统健壮性。详情导航设计列表页list.htmlurl_for()详情页detail.html返回/news/list{{url_for('get_news_detail_page',news_id=item.id)}}链接生成机制在列表页循环中,使用url_for动态生成链接。传入路由函数名get_news_detail_page和关键参数news_id,FastAPI会自动解析路由规则生成正确URL。内容管理工作流✍️内容创作SQLAdmin+TinyMCE图文混排编辑🔄数据处理UploadAPI图片转存&URL生成HTML存入数据库🌐即时发布FrontendPage安全渲染HTML用户即可见全流程贯通:从后台编辑到前台展示的无缝衔接详情内容渲染要点🔓安全渲染{{news_detail.content|safe}}关键点:使用safe过滤器。告诉Jinja2信任此变量中的HTML标签(由后台TinyMCE生成),不做转义处理,直接渲染富文本格式。📱响应式适配针对富文本中的图片元素:.article-contentimg{
/*宽度不溢出父容器*/
max-width:100%;
/*高度自动维持比例*/
height:auto;display:block;}确保无论在PC还是移动端,图片都能自适应屏幕宽度,避免撑破布局。注意:必须为详情页的正文容器添加class="article-content",以便CSS选择器生效。异常与健壮性🚫404处理场景:详情页ID不存在后端:ifnotnews_detail:raiseHTTPException(404)避免程序崩溃,返回标准错误响应。📭空数据状态场景:列表无新闻前端:{%for...%}{%else%}暂无新闻发布{%endfor%}提供友好提示而非空白页面。📏边界校验场景:分页参数异常校验:Query(1,gt=0)确保页码始终为正整数。展示:禁用首尾页的无效跳转按钮。性能与规范⚡性能优化数据库分页使用OFFSET/LIMIT替代全表查询,显著降低内存占用。静态资源缓存图片和JS库(如TinyMCE)通过static目录服务,利用浏览器缓存机制。📏开发规范时区标准化数据库统一存储UTC,前端通过Jinja2过滤器转为本地时间(如北京时间)。富文本规范图片强制max-width:100%,保证移动端布局不被破坏。模块完成与闭环🎉核心成果✅TinyMCE集成所见即所得的图文编辑体验✅图片本地上传摆脱图床依赖,实现闭环管理✅前台展示体系列表分页+详情页自适应渲染内容闭环创作发布展示管理感谢开发“产品中心”模块教材第7章模块目标与路线🔗数据关系建模实现“产品”与“类别”的一对多关联,构建稳固的数据基础📎富文本附件拓展TinyMCE编辑器,支持PDF、ZIP等通用文件的上传与嵌入📑多维筛选布局采用“侧边栏动态筛选+右侧网格展示”的经典两栏布局🔍搜索与高亮实现关键词模糊搜索,并自动对匹配结果进行红色高亮显示本章任务路线图:从底层模型到后台管理,再到前台交互,循序渐进构建完整功能模块产品类别模型定义models/product_category.py,作为“一对多”关系中的“一”端class
ProductCategory(SQLModel,table=True):
#映射到数据库表名__tablename__="product_category"
#主键IDid:Optional[int]=Field(default=None,primary_key=True)
#类别名称,建立索引优化查询name:str=Field(index=True)
#描述信息description:Optional[str]=None
#关系定义:反向关联Product模型products:List["Product"]=Relationship(back_populates="category")关键点解析索引优化(index=True)
为name字段建立索引,大幅提升类别查询与筛选的性能。Relationship
定义ORM层面的关系,允许通过ducts直接访问该类别下的所有产品列表。__tablename__
显式指定数据库表名为product_category,遵循命名规范。产品模型与外键定义models/product.py,作为“多”的一方,通过外键关联类别class
Product(SQLModel,table=True):id:Optional[int]=Field(default=None,primary_key=True)name:str=Field(index=True)content:str#存储富文本内容created_at:datetime=Field(default_factory=datetime.utcnow)
#外键定义:指向product_category表的id字段category_id:Optional[int]=Field(default=None,foreign_key="product_category.id")
#关系定义:ORM层面的双向关联category:Optional[ProductCategory]=Relationship(back_populates="products")核心机制解析CategoryID(外键)
foreign_key="product_category.id"在数据库层面建立了物理约束,确保产品必须归属于有效的类别。Relationship(ORM)
定义category属性后,代码中可以直接使用product.category访问关联对象,无需手动编写Join查询。数据库迁移与校验1更新Alembic配置修改alembic/env.py,导入新模型:frommodelsimportproduct
frommodelsimportproduct_category确保SQLModel元数据能识别这两个新类。2生成迁移脚本执行自动生成命令:alembicrevision
--autogenerate-m"..."检查生成的脚本文件,确认头部包含importsqlmodel,否则手动添加。3应用迁移执行升级命令:alembicupgradehead数据库中将创建两张新表,外键关系正式生效。后台视图定义ProductCategoryAdmincolumn_list=[id,name]
column_labels={...}基础管理视图:展示类别ID和名称支持类别的增删改查通过form_args配置字段标签ProductAdmincolumn_list=[id,name,Product.category]高级特性:智能关联展示:
直接在列表中显示category对象(实际上是显示__str__返回的名称),而非ID。自动下拉菜单:
在创建/编辑表单中,category字段会自动渲染为包含所有类别的下拉选择框。富文本集成:
复用create.html模板以加载TinyMCE。附件上传与链接插入1.后端接口(routers/utils.py)@router.post("/upload/file")asyncdefupload_file(file:UploadFile=File(...)):
#保存文件至static/uploads/filesfile_path=...
#返回TinyMCE要求的特定JSON格式returnJSONResponse(content={
"location":f"/{file_path}",
"title":file.filename})2.前端配置(tinymce_init.js)file_picker_callback:function(callback,value,meta){
//1.创建input[type=file]并触发点击
//2.监听文件选择,使用fetch上传到/upload/filefetch('/upload/file',{method:'POST',body:formData}).then(response=>response.json()).then(result=>{
//3.将返回的URL回填到对话框callback(result.location,{title:result.title});});}后台注册与验证1.注册视图(main.py)fromadminimport(
ProductAdmin,
ProductCategoryAdmin
)#添加管理视图
admin.add_view(ProductCategoryAdmin)
admin.add_view(ProductAdmin)只需简单注册,SQLAdmin即可自动生成完整的管理后台。2.验证流程✅类别管理
进入“产品类别”,创建“工业解决方案”、“娱乐消费”等测试数据。✅关联录入
进入“产品”创建页,验证“所属类别”下拉框是否包含刚才创建的选项。✅附件测试
在编辑器中点击插入链接,上传本地ZIP文件,验证是否自动生成下载链接。产品列表后端逻辑routers/product.py:实现复合查询(筛选+搜索+分页)@router.get("/list",response_class=HTMLResponse)asyncdefget_product_list_page(request:Request,page:int=1,
category_id:int=Query(None),
q:str=Query(None)):
#1.构建基础查询query=select(Product)
#2.动态添加筛选条件ifcategory_id:query=query.where(Product.category_id==category_id)ifq:
#使用or_实现名称与内容的模糊搜索query=query.where(or_(P.contains(q),Product.content.contains(q)))
#3.计算总数与分页total=session.exec(select(func.count()).select_from(query.alias())).one()final_query=query.offset((1)*9).limit(9)列表页面布局与分页containerrow侧边栏(col-md-3)类别筛选器{%forcatincategories%}主内容区(col-md-9)🔍搜索框(FormGET)产品卡片1产品卡片2产品卡片3«123...»分页组件搜索结果高亮1.自定义过滤器(后端)在templating.py中定义逻辑:frommarkupsafeimportMarkupdefhighlight_search_term(text,term):
ifnotterm:returntext
highlighted=text.replace(
term,
f'<spanclass="text-danger">{term}</span>'
)
#标记为安全HTML,防止转义
returnMarkup(highlighted)注册:templates.env.filters['highlight']=...2.模板调用(前端){{|highlight(search_query)}}搜索关键词:"智能"AI智能客服系统参数化链接与状态保持核心挑战:在筛选或分页时,如何不丢失已有的查询条件?当前状态示例:正在浏览“软件类”(category_id=1)且搜索“Office”(q=Office)❌错误做法href="?page=2"结果:丢失筛选与搜索条件,重置为所有产品。✅正确做法href="?category_id=1&q=Office&page=2"结果:保留上下文,精准跳转到目标结果的下一页。产品详情页面1.后端路由(Python)接收路径参数product_id并查询:@router.get("/detail/{product_id}")
asyncdefdetail(product_id:int):
p=session.get(Product,product_id)
ifnotp:
raiseHTTPException(404)
returntemplates.TemplateResponse(...)核心逻辑:主键查询+404异常处理。2.前端模板(Jinja2)渲染富文本内容的关键:<h1>{{}}</h1>
<divclass="article-content">
{{product.content|safe}}
</div>注意:必须使用|safe过滤器,否则HTML标签会被转义显示为纯文本代码。正文样式与自适应复用.article-content样式类,确保富文本内容在各种设备上完美呈现Desktop/WideScreen图片显示原始尺寸(如果<容器宽度)最大宽度限制为100%ImageMobileImage(Scaled).article-contentimg{max-width:100%;height:auto;}详情导航与回退列表页(ListPage)url_for('get_product_detail_page',product_id=item.id)详情页(DetailPage)URL:/products/detail/123异常处理与健壮性📭空状态处理当搜索或筛选无结果时,不应显示空白页面,而是渲染友好的Alert提示用户调整条件。🚫404NotFound后端路由中必须严谨判断查询结果。若ID不存在,应显式抛出HTTPException(404)。⚠️上传异常捕获前端fetch上传文件时,必须使用.catch()捕获网络错误,防止页面假死。性能与工程化1.数据库索引对`name`字段启用`index=True`,显著提升侧边栏筛选和模糊搜索的查询速度。2.关联查询优化注意N+1问题。在生产环境中,建议使用`joinedload`预加载关联数据,减少SQL查询次数。3.分页策略`Count(*)`配合`Limit/Offset`是标准做法,确保仅加载当前页数据,降低内存消耗。4.静态资源托管图片与附件上传后,应由Nginx进行托管并配置缓存策略,减轻应用服务器压力。综合验证清单上线前的最后一道关卡:全流程功能验收🗂️数据预置
创建至少3个产品类别,每个类别下录入3-5个产品,确保分页功能可测试。🔍搜索高亮
输入关键词(如“智能”),验证列表中标题和内容的匹配项是否已变红加粗。📎附件下载
进入产品详情页,点击附件链接,确认能成功触发浏览器下载行为。📱移动端适配
开启浏览器开发者工具(手机模式),检查大图是否自适应宽度,侧边栏是否正常堆叠。感谢开发“人才招聘”模块教材第8章模块全景与数据关系招聘岗位
JobPosition一对多关系应聘记录
ApplicationRecord开发链路全览:数据建模数据库迁移后台管理前端展示在线表单任务与报表招聘岗位模型(JobPosition)关键字段定义id:主键,自增IDtitle:职位名称(Index索引)department:所属部门content:岗位职责(富文本存储)is_active:激活状态(控制显示)created_at:发布时间class
JobPosition(SQLModel,table=True):__tablename__="job_position"id:Optional[int]=Field(...)title:str=Field(index=True)department:str=Field(...)content:str=Field(...)is_active:bool=Field(default=True)#关系定义applications:List["ApplicationRecord"]=\Relationship(back_populates="job_position")应聘记录模型(ApplicationRecord)关键属性定义applicant_name:应聘者姓名applicant_email:邮箱联系方式applicant_phone:电话联系方式resume_path:简历文件存储路径job_position_id:外键关联(FK)applied_at:投递时间(默认为当前时间)class
ApplicationRecord(SQLModel,table=True):__tablename__="application_record"id:Optional[int]=Field(...)applicant_name:str=Field(...)resume_path:str=Field(...)applied_at:datetime=Field(...)#外键与关系job_position_id:Optional[int]=\Field(foreign_key="job_position.id")job_position:Optional[JobPosition]=\Relationship(back_populates="applications")数据库迁移与校验1.注册模型#alembic/env.pyfrommodelsimport(company,news,product,job_position,application_record)2.生成迁移脚本$alembicrevision--autogenerate-m"Addrecruitment"3.校验与执行检查点:确保脚本头部包含:importsqlmodel执行命令:$alembicupgradeheadSQLAdmin后台集成1.招聘岗位视图(JobPosition)管理功能定制:富文本编辑器:复用sqladmin/create.html模板,支持格式化排版岗位职责。状态控制:使用自定义BooleanSelectField,提供“激活/禁用”下拉选项。列表字段:ID,职位名称,部门,是否激活。2.应聘记录视图只读模式:can_create=False,can_edit=False。关键信息:姓名,应聘岗位,投递时间。#main.py注册admin.add_view(JobPositionAdmin)admin.add_view(ApplicationRecordAdmin)招聘列表页面开发后端路由逻辑#routers/recruitment.py@router.get("/jobs")asyncdefget_job_list(...):statement=select(JobPosition).where(JobPosition.is_active==True).order_by(JobPosition.created_at.desc())returntemplates.TemplateResponse("recruitment/list.html",...)前端模板list.html{%forjobinjob_list%}<divclass="list-group-item"><h5><ahref="{{url_for('get_job_detail_page',job_id=job.id)}}">{{job.title}}</a></h5><small>{{job.department}}</small></div>{%endfor%}岗位详情页面开发1.路由校验逻辑job_detail=session.get(JobPosition,job_id)#关键安全校验ifnotjob_detailornotjob_detail.is_active:raiseHTTPException(status_code=404)2.模板渲染要点#富文本安全渲染{{job_detail.content|safe}}#申请入口<ahref="{{url_for('get_application_form_page',job_id=job_detail.id)}}"class="btnbtn-primary">立即申请</a>在线申请表单页(GET)表单设计(apply.html)<formmethod="POST"enctype="multipart/form-data">1.基础信息字段applicant_name(文本)applicant_email(Email)applicant_phone(Tel)2.简历文件上传resume(FileInput,accept=".pdf,.doc")注意:必须设置enctype属性,否则文件无法上传。成功反馈与链接跳转1.成功页路由@router.get("/apply/success")asyncdefsuccess_page(request):returntemplates.TemplateResponse("recruitment/success.html",...)2.更新详情页入口<!--detail.html--><ahref="{{url_for('get_application_form_page',job_id=job.id)}}"...>立即申请</a>✅投递成功!您的申请已成功提交,HR将会尽快审阅您的简历。[返回招聘列表][返回首页]简历投递流程验证📄1.发起申请详情页点击按钮📝2.填写表单上传简历文件✅3.提交成功跳转反馈页面后台核验Checkpoints:文件系统:static/resumes/目录下是否存在UUID开头的文件?数据库:Admin后台“应聘记录”列表是否新增了一条记录?表单提交与文件上传(POST)@router.post("/apply/{job_id}")asyncdefhandle_application_form(...,applicant_name:str=Form(...),resume:UploadFile=File(...)):#1.校验岗位有效性#...#2.保存文件(UUID重命名)unique_name=f"{uuid.uuid4()}_{resume.filename}"path=f"static/resumes/{unique_name}"withopen(path,"wb")asf:f.write(awaitresume.read())#3.数据库记录record=ApplicationRecord(...,resume_path=path)session.add(record)#4.PRG模式重定向returnRedirectResponse(url=request.url_for('success_page'),status_code=303)核心处理逻辑数据接收:使用FastAPI的Form和File组件接收multipart/form-data数据。文件存储:为防止文件名冲突,采用UUID+原始文件名的方式重命名,并保存至static/resumes目录。数据入库:创建ApplicationRecord实例,记录申请人信息及文件路径。防止重复提交:使用Post/Redirect/Get(PRG)模式,处理完成后重定向至成功页。后台任务与Excel归档#automation.pydefwrite_application_to_excel(app_id):withSession(engine)assession:data=session.get(ApplicationRecord,app_id,options=[selectinload(ApplicationRecord.job_position)])#Excel操作ifnotos.path.exists(path):wb=openpyxl.Workbook()sheet.append(["姓名","职位",...])else:wb=openpyxl.load_workbook(path)sheet.append([...])wb.save(path)核心技术点📦OpenPyXLPython标准Excel读写库,支持.xlsx格式。⚡selectinloadSQLAlchemy预加载策略,一次性拉取关联数据,避免N+1查询问题。🔄独立会话后台任务使用独立的数据库Session,与Web请求解耦。路由集成后台任务#routers/recruitment.pyfromfastapiimportBackgroundTasks@router.post("/apply/{job_id}")asyncdefhandle_application_form(...,background_tasks:BackgroundTasks,...):#...数据保存逻辑...mit()#注册后台任务:异步执行,不阻塞响应background_tasks.add_task(write_application_to_excel,new_application.id)returnRedirectResponse(...)Web请求流程:接收表单->存库->[立即响应]->[后台执行Excel写入]报表下载接口#routers/utils.pyfromfastapi.responsesimportFileResponse@router.get("/download/applications-report")asyncdefdownload_report():ifnotos.path.exists(EXCEL_FILE_PATH):raiseHTTPException(status_code=404,detail="暂无报表文件")returnFileResponse(path=EXCEL_FILE_PATH,filename="applications.xlsx",media_type='application/vnd.openxml...')FileResponse特性🌊流式传输高效传输大文件,不占用过多服务器内存。🏷️自动Headers自动设置Content-Disposition和Content-Type,触发浏览器下载行为。自动归档流程验证📤1.新增投递提交新的申请表单,触发后台任务。📊2.检查Excel确认applications.xlsx文件生成且数据行追加。📥3.报表下载访问下载接口,验证文件完整性。关键验证点:确保Excel中的“应聘岗位”列正确显示了关联JobPosition的title,验证selectinload预加载是否生效。安全隐患与权限提示⚠️当前系统的重大风险1.报表下载接口完全公开,任何人可访问。2.后台管理系统无登录验证,数据裸奔。🚀下一章目标:构建认证系统实现用户登录、密码哈希存储、JWT令牌认证,为敏感接口加上安全锁。工程实践要点汇总🔧文件与表单UUID命名:避免同名文件覆盖冲突。Multipart:正确配置enctype才能上传文件。PRG模式:Post-Redirect-Get防止表单重复提交。⚙️后端架构BackgroundTasks:异步处理耗时任务,优化响应速度。Selectinload:解决ORM关联查询性能问题。独立Session:后台任务中管理数据库连接生命周期。感谢开发“认证系统”模块教材第9章安全背景与方案总览🚨当前安全隐患后台管理系统与报表下载接口处于完全公开状态,敏感的应聘者信息与全站数据操作缺乏保护,存在严重的数据泄露与误操作风险。🛡️解决路径💾数据层安全设计User模型
bcrypt密码哈希🔐认证逻辑JWT令牌签发
登录/登出机制🚧接口保护依赖注入(Depends)
路由权限拦截🧩系统集成SQLAdmin
统一认证用户模型设计classUser(SQLModel,table=True):id:Optional[int]=Field(default=None,primary_key=True)#用户名唯一且加索引username:str=Field(unique=True,index=True)#存储哈希后的密码hashed_password:str#超级管理员标识is_superuser:bool=Field(default=False)字段解析🔑username唯一标识(unique=True)。添加索引(index=True)以优化登录时的查询性能。🔒hashed_password仅存储加密后的哈希值。严禁存储明文密码,确保数据库泄露时的安全性。🛡️is_superuser权限控制开关。默认为False,用于区分普通用户与拥有后台管理权限的超级管理员。密码哈希与安全性密码哈希是一种单向加密过程,即使数据库泄露,攻击者也无法逆向还原原始密码。用户注册输入:"secret123"bcrypt哈希函数存入数据库$2b$12$EixZaYVK1fsbw1ZfbX3OXePaWrn...用户登录输入:"secret123"bcrypt校验比对结果计算哈希并与数据库值比对✅匹配则允许登录哈希工具与依赖安装1.安装依赖pipinstallpasslibbcrypt==4.0.12.创建工具函数(auth_utils.py)frompasslib.contextimportCryptContext#配置哈希上下文,使用bcrypt方案pwd_context=CryptContext(schemes=["bcrypt"],deprecated="auto")defhash_password(password:str)->str:returnpwd_context.hash(password)defverify_password(plain:str,hashed:str)->bool:returnpwd_context.verify(plain,hashed)创建用户表迁移1.更新alembic/env.py#...(原有导入)frommodelsimportuser#新增导入,确保Alembic识别模型2.生成并应用迁移#生成迁移脚本alembicrevision--autogenerate-m"Createusertable"#应用迁移到数据库(company.db)alembicupgradehead⚠️生成后请检查versions目录下的脚本,确保头部包含importsqlmodelJWT认证机制依赖安装:pipinstallpython-josecryptography认证流程与结构🎫通行凭证登录成功后服务器颁发,后续请求凭借此令牌证明身份,无需重复查库。📦令牌结构包含三个核心字段:sub(Subject/用户ID)exp(Expiration/过期时间)signature(防篡改签名)🍪携带方式本项目采用HttpOnlyCookie存储令牌,能有效防止XSS攻击,提升安全性。创建首位超级管理员create_superuser.py逻辑交互式输入
使用getpass隐藏密码输入,防止窥视重复校验
检查用户名是否存在,确认两次密码一致安全存储
调用hash_password加密后存入数据库权限设置
设置is_superuser=True终端执行效果$pythoncreate_superuser.py创建超级管理员账户请输入管理员用户名:admin请输入管理员密码:请再次确认密码:成功创建超级管理员账户:admin令牌生成函数#JWT配置(auth_utils.py)SECRET_KEY="09d25e...8e8d3e7"#生产环境需保密ALGORITHM="HS256"ACCESS_TOKEN_EXPIRE_MINUTES=30create_access_token实现defcreate_access_token(data:dict,expires_delta=None):to_encode=data.copy()#1.计算过期时间(exp)expire=datetime.now(timezone.utc)+timedelta(minutes=30)to_encode.update({"exp":expire})#2.编码生成JWT字符串returnjwt.encode(to_encode,SECRET_KEY,algorithm=ALGORITHM)后台登录逻辑GET/admin/login渲染HTML模板@router.get(...)defget_login_page(request):returntemplates.TemplateResponse("admin/login.html",...)POST/admin/login1.验证用户user=db.exec(...).first()ifnotverify(pwd,user.hash):error2.生成令牌token=create_access_token(data={"sub":user.username})3.响应与Cookieresp=RedirectResponse("/admin",303)resp.set_cookie("access_token",token)表单提交后台登录页面模板templates/admin/login.html<formmethod="POST"><!--用户名--><divclass="form-floatingmb-3"><inputname="username"required...></div><!--密码--><divclass="form-floatingmb-4"><inputname="password"type="password"></div><buttontype="submit">登录</button></form>页面预览派森科技后台管理
用户名
密码[登录]路由注册与验证main.py注册顺序(关键)#1.优先注册自定义认证路由app.include_router(auth.router)#2.然后再挂载SQLAdminadmin=Admin(app,engine,title="派森科技后台")⚠️如果顺序颠倒,SQLAdmin的默认路由会覆盖我们的/admin/login验证步骤1.访问页面浏览器打开/admin/login,确认显示新页面。2.检查Cookie登录成功后,F12查看access_token是否存在且为HttpOnly。3.登出测试访问/admin/logout,确认Cookie被清除并重定向。依赖注入与路由保护有了“通行证”还不够,我们还需要“门禁”。
FastAPI的依赖注入允许我们将认证逻辑封装为可复用的组件,像“门卫”一样守护特定路由。🌍客户端请求/download/...🛡️依赖项(门禁)1.检查Cookie2.验证JWT3.查询用户💎受保护资源defdownload_report(...)❌401/403登出与Cookie安全登出路由(Logout)asyncdefhandle_admin_logout():#重定向回登录页response=RedirectResponse(url="/admin/login",status_code=303)#关键:删除Cookieresponse.delete_cookie(key="access_token")returnresponse安全关键点:HttpOnlyresponse.set_cookie(...,httponly=True)为何重要?禁止前端JS访问:浏览器禁止document.cookie读取此Cookie。防御XSS:即使攻击者注入了恶意脚本,也无法窃取用户的身份令牌。当前用户依赖defget_current_user(session:Session=Depends(get_session),access_token:str=Cookie(None)#自动从Cookie提取)->User:验证流程🍪检查Cookie存在access_token?否:401Error🔓解码JWT提取sub(username)失败:401Error🔍查询用户DB中是否存在?否:401Error✅返回User依赖注入成功超级管理员依赖defget_current_superuser(#依赖组合:先执行get_current_user确保登录current_user:User=Depends(get_current_user))->User:ifnotcurrent_user.is_superuser:raiseHTTPException(status_code=status.HTTP_403_FORBIDDEN,detail="需要超级管理员权限")returncurrent_user依赖组合原理get_current_user身份认证(401)get_current_superuser权限检查(403)注入路由保护报表下载接口fromauth_utilsimportget_current_superuser@router.get("/download/applications-report")asyncdefdownload_applications_report(#关键修改:注入超级管理员依赖项
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 盲文印刷员安全应急能力考核试卷含答案
- 电线电缆包制工班组考核模拟考核试卷含答案
- 尿素加工工班组评比考核试卷含答案
- 湖盐采掘工成果模拟考核试卷含答案
- 建筑五金制品制作工安全知识宣贯考核试卷含答案
- 成品矿运送工冲突管理水平考核试卷含答案
- 中式面点师安全技能模拟考核试卷含答案
- 聚偏氯乙烯装置操作工岗位异常处置考核试卷含答案
- 三氯氢硅还原工岗前操作技能考核试卷含答案
- 酶制剂制造工技术传承水平考核试卷含答案
- 2026四川省定向乡镇 公共基础知识试题及答案
- 2026年中国银行招聘考试试题真题解析
- 辽宁朝阳市2026-2027学年高三上学期开学英语试题(文字版含答案)
- 养牛疾病防治培训试题与答案
- 2026年常州市中考语文试卷(含答案)
- 新版2025-2026学年湘美版(2026秋新教材)小学美术六年级上册(全册)教学设计合集
- 深圳报业集团笔试题目答案大全解析
- 《房地产信托投融资实务及典型案例》目录
- 2026统考专升本政治:考前冲刺资料
- 中国面神经炎临床诊疗指南(2025版)
- 桂林漓江风景名胜区总体规划
评论
0/150
提交评论