ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Flask的学生荣誉证书管理系统设计与实现

基于Flask的学生荣誉证书管理系统设计与实现 最近我基于Flask做了一套学生荣誉证书管理系统从需求沟通到部署上线前后花了两周。这类系统在学校场景里其实挺常见的但网上能找到的完整方案不多大部分是零散的代码片段或者只讲了某个模块。我做完之后最大的感受是真正难的不是Flask本身而是把证书申报、审核、生成、导出这一整套流程理顺并且让学校教务处的老师用得顺手。这篇文章我打算把整个项目的核心设计和实现过程完整拆开讲包括业务建模、数据库设计、权限控制、证书自动生成、PDF导出以及我在实际开发中踩过的坑和排查过的奇怪问题。不管你是准备做课程设计、毕业设计还是学校真要上一个类似的内部系统这篇内容应该能帮你省下不少调研和试错的时间。1. 项目整体设计与需求拆解1.1 为什么学生荣誉证书需要系统化管理没接触过这个场景的人可能会想荣誉证书不就是评完发一张纸吗有什么好管理的实际上一所几千人的学院每学期要处理的证书可能有几百上千张涉及优秀学生、奖学金、竞赛获奖、志愿服务证明等多种类型。传统的人工Excel登记方式问题非常多证书编号容易重号或断号学生姓名和奖项容易填错查历史记录要翻半天表格领导要统计各年级获奖情况还得手动汇总。所以这套系统的核心价值是把申报—审核—制证—发放—归档—统计这条链路全部数字化。每张证书从提交申请到最终生成PDF状态全程可追踪证书编号自动生成不重号统计报表一键导出。对教务处来说这省掉的不只是打印的功夫更是查证、溯源和汇总的效率。1.2 核心角色与业务流程梳理做系统设计之前我先把使用角色和流程画了一遍。角色很清晰就三种学生、辅导员/审核老师、系统管理员。学生负责提交申请教务处的审核老师负责确认资格管理员管理证书模板、奖项类型和系统参数。业务流程则是这样一个闭环学生填写申请选择奖项类型、上传证明材料提交后进入待审核状态审核老师看到申请后核验材料通过或驳回被驳回的要能填写原因审核通过后系统自动生成证书编号并根据选定的证书模板渲染出带姓名、奖项、编号的证书图片和PDF学生和老师在系统里都能随时查看和下载已生成的证书。这个流程看起来简单但有个很容易忽略的点证书一旦生成里面的信息就不能随便改了否则编号和实际内容对不上后面查证会有麻烦。所以我加了版本控制证书生成后如果要修改会新生成一个版本保留历史记录。1.3 方案选型为什么是Flask而不是Django或Spring Boot选框架的时候我其实先排除掉了几种方案。Django功能很全自带Admin后台和ORM但恰恰因为太全这个项目里一半用不上对没接触过Django的人来说学习曲线反而陡。Spring Boot更适合团队协作的大型Java项目但做这个规模的管理系统Java的开发和部署成本都偏高。Flask胜在轻量和灵活。它只提供路由、模板、请求处理这些最核心的东西其他能力比如ORM、表单验证、登录认证都用扩展来加。这样一来项目结构完全由我控制不会有一堆用不到的代码杵在那里。而且Flask应用本身就是一个Python进程部署到服务器上非常方便对学校机房那种资源有限的服务器来说尤其友好。我用的核心依赖不算多Flask负责Web框架Flask-SQLAlchemy负责数据库操作Flask-Login处理登录会话Flask-WTF用来做表单校验。就这四个加上前端用的Bootstrap和Jinja2模板整个项目清爽得很。2. 系统架构与关键技术选型2.1 技术栈全景Flask SQLAlchemy Jinja2 Bootstrap整套系统的顶层架构是经典的Flask MTV模式Model用SQLAlchemy定义Template用Jinja2渲染View就是Flask路由函数。为了不让所有代码挤在一个app.py里我按模块拆分了蓝图Blueprint每个功能域一个文件。项目结构大概长这样honors_system/ ├── app.py # 应用入口 ├── config.py # 配置项 ├── models/ │ ├── __init__.py │ ├── user.py # 用户模型 │ ├── honor.py # 荣誉申请模型 │ ├── certificate.py # 证书模型 │ └── template.py # 模板模型 ├── blueprints/ │ ├── auth/ # 登录认证 │ ├── student/ # 学生端功能 │ ├── admin/ # 管理端功能 │ └── api/ # 接口 ├── utils/ │ ├── certificate_gen.py # 证书生成工具 │ ├── pdf_exporter.py # PDF导出 │ └── decorators.py # 权限装饰器 └── templates/ # Jinja2模板目录拆蓝图的好处是后面加功能不用翻一大坨代码比如现在要加一个证书模板在线预览的功能我只动admin蓝图和模板目录就行了。2.2 数据库选型与理由数据库我选了SQLite做开发环境线上切MySQL。开发阶段用SQLite的最大优势是零配置跑起来就能玩不用装数据库服务。但是真正上线的时候SQLite扛不住并发写入好几个老师同时审核证书申请锁冲突会明显增多所以线上必须换成MySQL。SQLAlchemy的好处就是这时候体现出来的。ORM层把两种数据库的差异基本抹平了切换的时候只需要改连接字符串和少数几个字段类型定义。比如JSON字段在SQLite和MySQL里的支持就不太一样我统一用了Text字段加json序列化的方式彻底绕开这个差异。2.3 前端方案服务端渲染还是前后端分离当时纠结过要不要上Vue做前后端分离。热词里也有不少人搜vue后台管理系统说明这个方向确实火。但对于这个项目我最后选择的是Jinja2模板加Bootstrap的服务端渲染方案理由很实在学校内网系统对交互要求不高不需要复杂的前端状态管理服务端渲染天然省掉一套API开发的成本表单校验交给Flask-WTF处理浏览器里跑得就很流畅。当然有几个地方我用了轻量级的前端交互比如证书模板的在线预览用了iframe审核页面的批量通过按钮用了原生JavaScript加Ajax请求。这些场景如果全走页面刷新操作体验会很割裂。小步引入前端增强性价比最高。3. 功能模块详细设计与实现3.1 证书模板管理模块证书模板是整个系统里比较有技术含量的模块。校园里的证书风格各不相同有正式庄重的红头文件风格有活泼的扁平插画风格有些二级学院还要放自己学院的Logo和校训。如果每张证书都写死HTML代码改起来太痛苦了。我的解决方案是让证书模板变成一种HTML模板占位符的组合。管理员在后台维护一套模板里面用双花括号占位符标出姓名、奖项、编号、日期这些变量具体长这样div classcertificate div classcert-title{{ honors_name }}/div div classcert-content 兹证明 span classstudent-name{{ student_name }}/span 同学 在{{ award_date }}举办的{{ activity_name }}中荣获{{ award_level }}。 /div div classcert-no编号{{ cert_no }}/div /div学生申请通过后系统把这条申请的数据填进占位符再用渲染引擎生成最终证书。这样做的好处是模板和业务逻辑彻底解耦以后想换证书样式管理员在后台改模板就行代码一行都不用动。3.2 申报与审批流程模块审批流程我用了一张状态机来管理申请的状态迁移。一张申请从创建到归档依次经历待审核、已通过、已驳回、已领取这几个状态外加一个特殊的待补充材料状态。每个状态迁移都记录操作人和操作时间方便出问题的时候追溯。学生端提交申请时页面上会有一个材料清单每个材料支持上传图片或PDF。上传文件的管理上有个小坑文件名最好重命名不要直接存用户上传的原文件名。我之前吃过亏两个学生上传的扫描件叫1234.jpg后来的那个把前一个覆盖了。我现在的做法是用UUID重命名存储文件原文件名存到数据库字段里保留显示。审核端的列表页做了按奖项类型、按状态、按学院、按时间范围的多条件筛选老师不用在几千条记录里瞎翻。每个申请点进去能看到完整的证明材料缩略图和学生信息支持一键通过和一键驳回驳回时要填原因这个原因会直接显示在学生端。3.3 证书生成与PDF导出模块证书生成是这个项目最核心也最让我折腾的部分。两种方案对比下来我选了Pillow绘图生成图片再转PDF的方案而不是直接用Python库生成PDF。Pillow方案的好处是渲染可控性强字体、颜色、位置都能精确到像素生成的就是一张标准图片想发给学生看、想打印都方便。生成过程大概是先新建一张A4比例的画布背景铺上模板底图然后把文本逐行绘制上去最后输出JPEG或PNG。字体这块是个大坑Linux服务器上中文字体经常没有Pillow指定微软雅黑路径之前先得检查系统里装了没有。我在项目中放了一个fonts目录自己携带了思源黑体的授权版本这样部署到任何机器上都不会出现方块字。生成完图片后用img2pdf把图片转成PDF或者用reportlab把多张证书拼到一个PDF里方便批量打印。批量导出的场景在学期末评优的时候非常刚需老师点一次就下载整个年级的证书压缩包。3.4 统计看板与检索模块统计看板最开始不在需求里是教务处老师用了几天后主动提的。她说每到学年末都要写一个获奖情况汇总报表以前对着Excel填半天现在系统里都有数据了能不能自动出。我加了三个维度的统计按奖项类型统计、按学院统计、按时间趋势统计。展示用的简单柱状图和饼图没引ECharts那么重的库直接用Chart.js一个小JS文件搞定。导出功能支持CSV和Excel两种格式CSV用Python内置的csv模块就行Excel加了点格式控制用openpyxl生成。检索模块更偏实用支持按姓名、学号、证书编号、奖项名称模糊搜索。证书编号是唯一的所以单独做了一个编号直达查询输入编号就能看到这张证书的真伪和完整信息。这个功能以后可以拓展成对外验真系统贴在证书上的二维码一扫就能查。4. 数据库设计与核心逻辑实现4.1 表结构设计数据库表我设计得比较克制一共六张核心表用户表、角色表、荣誉申请表、证书表、证书模板表、操作日志表。用户表存账号密码和基础信息密码字段用Werkzeug提供的密码哈希绝不存明文。荣誉申请表是核心业务表记录学生提交的每一项荣誉申请包含奖项类型、活动名称、获得时间、证明材料文件路径等关键字段。证书表关联荣誉申请表证书生成后记录证书编号、PDF路径、生成时间、版本号。操作日志表最容易被人忽略但也是最实用的表之一。谁通过了谁的申请、谁修改了模板、谁导出了批量证书全部留痕。学校系统最怕出问题说不清日志表就是事后审计的依据。4.2 核心业务逻辑证书编号生成规则证书编号看起来简单其实藏着很多细节。直接用自增ID当编号暴露了系统里的申请总量老师们也反馈编号没有规律不好人工记忆和分类。最后定的规则是年份四位 奖项类别码两位 当年序列号四位。比如20250001Z表示2025年第一张类别为综合奖的证书。序列号从每年1月1日归零重新计数生成的时候用数据库事务锁住计数行防止并发情况下两个申请拿到同一个编号。序列号重置的逻辑我放在了一个单独的函数里每年第一次生成证书时检查年份变化如果跨年了就把计数器清零。这个逻辑当时让一个刚接手代码的同事看半天没看明白后来我加了一长段注释才讲清楚。4.3 权限控制实现权限控制基于角色但没引入扩展的RBAC库。我的做法是定义一个装饰器在路由函数上声明这个接口需要什么角色才能访问装饰器内部从session里取当前用户查他的角色没有权限就直接返回403页面。def role_required(*roles): def decorator(f): wraps(f) def decorated_function(*args, **kwargs): if not current_user.is_authenticated: flash(请先登录, warning) return redirect(url_for(auth.login)) if current_user.role not in roles: abort(403) return f(*args, **kwargs) return decorated_function return decorator学生只能看到自己的申请和自己的证书审核老师只能看到管理范围内学生的申请管理员拥有一切权限。这个模型简单直接对这个规模的项目完全够用。5. 实操演练从零搭建核心功能5.1 环境准备与项目初始化先交代一下环境Python 3.10Windows开发Ubuntu服务器部署。开发环境用venv建虚拟环境避免和系统Python打架。# 创建虚拟环境 python -m venv venv # 激活虚拟环境Windows venv\Scripts\activate # 安装依赖 pip install flask flask-sqlalchemy flask-login flask-wtf pip install pillow img2pdf openpyxl装完依赖后第一步先搭一个能跑起来的最小应用确认Flask路由、数据库连接、模板渲染都正常再往上加功能。我习惯先把用户的登录注册跑通再写业务功能这样后续调试接口都带着权限验证早点发现问题。5.2 证书自动生成的实现思路证书生成函数是整个系统的核心我单独开了一个工具模块。思路分成三步第一步从数据库查出申请和证书关联数据第二步把数据填充到模板中渲染出HTML第三步用Pillow把渲染结果绘制成图片。如果直接用第四章的模板字符串我需要先做一个模板解析功能。我用了Jinja2自带的Template类把模板内容当作模板字符串渲染from jinja2 import Template def render_certificate(cert_data, template_content): template Template(template_content) rendered_html template.render(**cert_data) return rendered_html渲染出HTML之后问题来了Pillow不能直接画HTML。我需要把HTML转成图片。这里我用的方案是先把HTML用浏览器引擎渲染成图片再让Pillow做后处理。具体实现是用Playwright打开一个本地HTML文件模拟浏览器打印成PDF或截图然后再做裁剪和拼接。这个方案折腾了我不少时间但效果确实好。CSS的排版能力比Pillow手动画坐标强太多了写起来也容易理解。最后生成的是高清PNG打印效果完全满足证书要求。5.3 一键导出与批量打印批量导出功能放在审核通过后的动作里。学生申请通过后系统自动生成单个证书。但老师更需要的是一键导出某某学院2025年优秀学生的所有证书打成压缩包。我的实现是前端点击按钮传一个筛选条件到后台后台用Flask的send_file返回zip压缩包。Excel明细表也一起打进去每张证书对应一行记录方便老师线下存档。压缩包的文件名我做了规范化处理学院名加奖项名加日期例如计算机学院-优秀学生-2025.zip。Windows上文件名不能包含斜杠和冒号这些字符我全部过滤掉了不然打包出来传到其他电脑会解压失败。5.4 部署到服务器需要注意的几个坑部署我用的是Gunicorn加Nginx的组合系统服务用Supervisor托管。Gunicorn负责跑Flask应用Nginx做反向代理和静态文件服务Supervisor保证进程挂了能自动重启。服务器上最容易出问题的几个点第一是静态文件的路径Flask生产环境读不到开发时的相对路径我统一用了绝对路径配置第二是上传文件的存储路径我把上传目录配置到项目外的独立数据目录避免部署更新时被清掉第三是MySQL连接池Flask-SQLAlchemy默认的配置在并发量上来之后容易断连我加了一个pool_pre_ping配置每次取连接前先探活。部署上线那天我光调Nginx的location匹配就花了一个小时最后发现是静态文件请求被路由到了Flask后端没有走Nginx直接返回。这个经验教训是部署之前先在服务器上curl测试静态文件能不能直接访问别等页面全打开一遍才发现。6. 开发中的问题排查与性能优化实录6.1 证书里的中文乱码问题第一个踩的坑就是中文乱码。开发环境Windows上跑得好好的部署到CentOS服务器后生成的证书姓名全变成了方框。查下来是服务器缺少中文字体Pillow和浏览器渲染引擎都找不到可用的中文字形。解决办法就是在服务器上安装中文字体包当时装的是wqy-microhei。但我后来想到学校老师们说不定会在自己的电脑上部署系统用所以干脆把思源黑体的OTF文件放进了项目目录代码里优先从本地fonts目录加载字体。6.2 证书样式在不同浏览器显示不一致证书预览和生成用的HTML模板在Chrome里显示正常但用360浏览器打开就会出现偏移。学校老师用的浏览器种类很杂有些还是老旧的IE内核。我的对策是模板里所有布局用绝对定位和固定宽度禁用流式布局和flex。虽然这样写代码不够优雅但换来的是老浏览器上也能稳定显示。另外生成证书用的是Playwright内置的Chromium不依赖系统浏览器所以最终导出的证书样式永远是统一的。6.3 多人同时申报时证书编号重复上线后遇到最严重的一个bug是有两个学生在同一秒提交申请系统生成了两个相同编号的证书。原因是没有做并发控制两个请求同时通过查当前最大序号拿到了同一个值。修复方案是在数据库层面加了唯一约束同时改成了事务内锁行。我给年份和类别加了一个复合唯一索引再配合一个编号计数器表每次取编号都先UPDATE那个计数器返回最新的值。这样并发的请求会被数据库锁串行化再也不会出现重复编号了。6.4 系统性能优化建议这个系统日常并发很低几百人在线已经很了不起了但有些优化经验还是值得分享。第一对证书列表和申请列表这两个高频查询加上分页每页最多显示20条第二上传的证书原图做了压缩缩略图单独生成一份列表页加载速度明显变快第三MySQL查询字段加了索引尤其对状态、学生ID、创建时间这三个过滤条件建了联合索引。如果以后并发量真的大到扛不住Flask层可以做水平扩展用Gunicorn开多个worker会话用Redis保存。不过对于校园管理系统这个阶段大概率用不上。我建议别过度设计先把基本功能做稳比什么都重要。性能优化一定是在真实瓶颈出现后再做的别在开始就给自己加戏。这个项目做完之后回想整个开发过程我最大的体会是Flask本身真的不难把每个模块拆开看无非就是增删改查加文件处理。真正的复杂度来自业务流程的完整性比如审批状态怎么流转、证书编号怎么保证唯一、生成之后怎么追溯版本这些才是让系统真正能用的关键。如果之后有同学要在这个项目基础上扩展我建议优先做两个方向一个是证书扫码验真把证书编号生成一个二维码印在证书上手机扫一下就能查真伪另一个是导入导出优化对接学校现有的教务系统数据这样学生信息就不用手工录入了。这两个功能加上去这个系统的完整度就非常像一个真正的商业产品了。
返回列表