资讯动态

基于Flask+Vue的高校助奖学金管理系统开发实践

发布时间:2026/10/21 19:00:24 来源:尧图企业网站定制
前一阵子帮朋友调试一个高校助奖学金管理系统的项目顺便自己也完整梳理了一遍从技术选型到前后端联调的全过程——后端用Flask前端用Vue开发环境是PyCharm标题里还混着Django。说实话看到这种项目标题的第一反应是又一个被毕设逼疯的可怜孩子。但真正上手之后发现这类管理系统的需求非常典型学生信息、奖学金申请、助学金评定、辅导员审核、院系汇总、校级公示……每一个环节都绕不开数据流转和权限控制。这篇就把整个项目的核心设计、开发要点、以及我个人踩过的坑记录下来适合正在做类似毕设、课设的同学或者想从零搭一个前后端分离项目的开发者参考。1. 项目整体设计与技术选型思路1.1 需求拆解高校助奖学金管理到底要管什么先拆需求。一个高校助奖学金管理系统表面上看是“申请审核”两个动作真做起来会发现角色多、流程长、状态杂。常见的角色至少有四类学生负责填写申请、上传证明材料、查看结果辅导员负责初审核实材料真实性并做出初评院系管理员负责汇总、分配名额、复核校级管理员负责最终审定、公示和归档。另外可能还有财务角色但一般毕设级别不需要接支付。从这个角度看系统不是简单CRUD而是多个角色围绕同一条申请记录做状态流转。所以我习惯先画一张“状态机”脑图学生提交申请 - 待辅导员初审 - 辅导员驳回或通过 - 院系复核 - 校级终审 - 公示。每个节点都需要记录操作人和操作时间否则后续追溯时会被老师追问得很难受。除了核心申请流程还要管学生基本信息、成绩排名、获奖记录、证明材料文件、公告发布、名额与金额配置。这些看起来都是“增删改查”但细节不少。比如成绩排名有的学校按加权平均分有的按综测字段必须预留名额配置每个学院不一样校级管理员要能按院系下发名额院系在限额内审核。这些需求和业务方导员、资助中心老师聊起来能扯半天。所以动手写代码前强烈建议先列一份功能清单至少包含学生端、审核端、管理端、公告端、个人中心。再根据角色权限去拆分API不然一上来就写models后期必然返工。1.2 Flask与Django怎么选为什么这个项目更适合Flask标题里同时出现了Flask和Django我理解很多人是因为纠结——到底用哪个我自己两个都用过说一点主观感受。Django是全家桶自带Admin后台、ORM、Auth模块、迁移工具甚至模板和表单系统。如果要做一个功能巨多的内容管理平台Django确实能省很多事特别是它那个自带Admin几乎不写代码就能把数据表管理起来。但缺点也明显框架重、概念多、中间件和配置项让新手迷糊而且前后端分离时Django的Admin基本派不上用场模板系统也浪费了。Flask则轻量得多整个核心就一个“路由分发”数据库、表单、认证这些都要自己选第三方库。听起来麻烦但在前后端分离的项目里反而是优势。因为前端已经有Vue了后端只需要把API写清楚不需要服务端渲染模板。Flask用蓝图(Blueprint)组织业务模块用SQLAlchemy管理模型用Flask-CORS解决跨域整体结构非常清晰。对学生来说Flask的代码量更少出问题时也更容易定位。选型逻辑其实就一句话项目是否需要“开箱即用”的Admin后台如果你有大量复杂后台管理页面Django的Admin能帮你挡住很多重复劳动但如果你要的是给Vue提供JSON接口Flask更轻更可控。我这个项目显然属于后者所以我选了Flask。另外补充一点PyCharm的专业版对Flask项目有直接支持可以创建Flask项目模板调试断点也很方便配合虚拟环境很顺手。标题里的“pycharm”其实不是技术栈而是开发工具后面第四部分我会讲具体怎么配置。1.3 前后端分离还是服务端渲染Vue到底负责什么选择了Flask做API前端自然走Vue。有些同学习惯后端用Jinja2模板渲染页面那样Flask也能做但页面交互稍微复杂一点就很痛苦加载数据要刷新页面、表单校验自己用JS手写、局部更新要拼HTML……而且后期想换手机端或小程序模板渲染的接口根本没法复用。Vue负责的是“页面长什么样 用户操作怎么响应”Flask负责的是“数据存哪、状态怎么变、谁能做什么”。两者通过HTTP接口通信前端用Axios发送GET/POST请求后端返回JSON。这样开发时前后端可以并行我只需要和后端约定好接口路径和字段名前端先用mock数据跑页面后端把真正的数据准备好后一对接就能通。当然前后端分离也有代价就是跨域问题。开发时Flask跑在5000端口Vue跑在8080端口或5173浏览器会拦截跨域请求。解决办法我放在第四章这里先不展开。简单说就是后端配置CORS或者在Vue devServer里配置代理二选一就行。2. 核心功能模块与数据库设计2.1 角色权限设计学生、辅导员、审核员如何各司其职权限是这类系统的命根子。设计角色时我不喜欢特别复杂的RBAC表——虽然理论上更灵活但对一个高校辅助管理系统来说角色固定、权限较稳定用简单的角色字段足够。我建议在用户表里加一个role字段取值为student、tutor、college_admin、school_admin。再利用装饰器写一个权限校验函数拦截一些API。比如学生只能提交自己的申请、查看自己的记录辅导员只能看自己管辖的学生申请校级管理员可以查看所有数据。如果不做权限控制任意学生把请求里的id改成别人的就能看到别人的申请这种事故一旦在答辩现场被老师试出来直接社死。除了角色字段每个用户最好关联学院和专业。学生表里存student_no、name、college、major、grade等辅导员表里存关联的学院。申请记录里除了申请人的user_id还要冗余存一份college字段方便按学院筛选。虽然从设计范式角度可能违反第三范式但实际写筛选SQL时能省不少事。这里我特别想说一个坑不要只靠前端隐藏按钮来防越权。Vue菜单是根据角色动态渲染的但接口必须做二次校验。否则一个学生打开浏览器控制台直接Fetch调用管理员接口结果就是权限形同虚设。所以在每个需要权限的接口里都要从当前登录状态里拿当前用户id和角色而不是完全信任请求参数。2.2 数据库模型从用户表到申请表的字段设计我用SQLAlchemy定义模型核心表大概这些用户表 userid, username, password_hash, role, college, major, student_no, name, email, phone, created_at申请表 applicationid, user_id, apply_typescholarship/assistanceapply_yeartitlereasonamountstatusfilessubmit_timereviewer_idreview_timereview_comment公告表 announcementid, title, content, publisher_id, publish_time, attachment名额表 quotaid, college, apply_type, year, total_quota, used_quota密码一定不能存明文使用Werkzeug的generate_password_hash和check_password_hash。申请表的status我用整数枚举0待初审、1初审通过、2初审驳回、3院系通过、4终审通过、5公示中、6结束。这样状态流清晰而且可以用一个状态字段加时间戳追溯。我写模型时喜欢把公共字段抽出来比如create_time、update_time放一个BaseModel类里然后用mixin继承。很多新人直接在每张表里重写一遍后期加字段要改一堆文件没必要。关于ORM还是原生SQL我建议用ORM。SQLAlchemy在Flask里非常成熟写关联查询也方便。比如统计每个学院申请数量db.session.query(Application.college, func.count(Application.id)).group_by(Application.college)。一眼能看懂。虽然原生SQL执行效率略高但这类项目根本到不了要优化的程度可维护性更重要。2.3 核心流程从学生提交申请到最终公示的完整链路把整个流程说清楚写代码就有底了。第一步学生登录在申请页面选择奖学金或助学金类型、年度填写申请理由上传证明材料。提交后系统创建一条application记录status0。第二步辅导员登录后看到“待初审”列表点击某条申请查看学生填写的资料和附件选择通过或驳回。通过时status1驳回时status2必须填写驳回理由学生端能收到。第三步院系管理员看到辅导员已通过的所有申请可以按名额进行筛选。如果超过院系名额可以驳回或退回辅导员通过后status3。第四步校级管理员看到院系通过的申请可以随机抽查也可以全部通过。终审通过后status4。第五步系统进入公示环节。通常可以生成一个公示列表展示获奖学生姓名、学院、项目。公示期结束后status5或6归档。公示期间管理员可以撤销撤销后状态回退到4。实际开发中状态回退逻辑最容易出bug。比如公示中驳回是回到终审还是直接驳回业务方要求是回到待初评还是直接标记无效所以在设计表时我建议加一个 return_reason 字段用来做状态回退备注。另外每个状态变更都应该记录下来所以我还建了一张申请表日志表 status_logid, application_id, from_status, to_status, operator_id, remark, created_at。别嫌表多后期调试时有大用处。3. 实操环节Flask后端与Vue前端的实现细节3.1 Flask路由与ORM实现要点项目结构我推荐按模块划分而不是把所有路由堆在app.py里。project/ app/ __init__.py models.py api/ __init__.py auth.py student.py auditor.py admin.py common.py utils/ decorators.py response.py config.py run.py这样每个蓝图负责一组接口。auth负责登录、获取用户信息、修改密码student负责学生端申请、我的申请、上传附件auditor负责辅导员的审核列表和审核操作admin负责名额管理、公告管理、统计报表。用Blueprint注册代码清爽很多。具体到一个接口的实现我习惯在路由里做三步参数校验、业务代码、返回统一格式。比如获取当前用户信息的接口from flask import Blueprint, request, g from app.models import User from app.utils.decorators import login_required, check_role auth_bp Blueprint(auth, __name__) auth_bp.route(/user/info, methods[GET]) login_required def user_info(): user g.current_user return { code: 0, data: { id: user.id, username: user.username, name: user.name, role: user.role, college: user.college } }代码中的login_required装饰器会解析前端传的Token从User表中取出当前用户并挂到g上。这样后续接口都能用g.current_user直接拿当前用户不需要每个视图都重复解析一次。Token我用的是JWT生成时把user_id和role放进payload过期时间24小时前端每次请求在header里带Authorization: Bearer 。ORM写查询时要注意能选懒加载的别用eager loading减少无谓联表。比如学生列表接口如果只需要展示姓名和学院就别去查申请列表。有些同学用Flask-SQLAlchemy的query.filter时喜欢把整个对象转字典然后手动写一堆字段我建议封装一个to_dict方法或者直接用序列化库。项目小的时候手动写其实也不麻烦但字段一旦超过10个每次都手动写容易漏。大家可以试试用marshmallow或者直接自己写一个简单的序列化函数。3.2 Vue组件与Axios请求封装前端我用Vue 3 Vite Element Plus当然也可以用Vue 2 Vue CLI差别不大。组件结构大概三层路由页面、业务组件、通用组件。把请求封装成一个request.js统一处理Token和错误提示这段代码我比较推荐import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use((config) { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) service.interceptors.response.use( (response) { return response.data }, (error) { const { response } error if (response response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(error) } ) export default service这样每个页面调用接口时只需要业务代码不用管认证和错误处理。页面开发上我建议先做一个layouts布局把侧边菜单、顶部导航、面包屑放在里面然后根据角色动态生成菜单。菜单数据可以从后端登录接口里拿前端用它渲染。注意不要在路由里写死所有权限不然前端“隐藏”菜单表面上看没问题但刷新后页面可能因为路由不存在而报错。申请表单是学生端最核心的组件。要处理文件上传上传进度校验文件格式和大小。Element Plus的Upload组件和Flask的接口对接时需要注意后端接收文件的字段名。Flask代码student_bp.route(/application/upload, methods[POST]) login_required def upload_file(): file request.files.get(file) if not file: return {code: 1, msg: 没有上传文件}, 400 filename secure_filename(file.filename) file.save(os.path.join(app.config[UPLOAD_FOLDER], filename)) return {code: 0, data: {url: /uploads/ filename}}upload组件里action写的是绝对路径需要配合Vite的代理。Vite配置文件这样写server: { proxy: { /api: { target: http://127.0.0.1:5000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } }这样前端请求 /api/xxx 会被代理到 Flask同时对后端跨域也是“无感的”因为前端与后端产生请求的域名在浏览器看来是同源的。这个方法比在后端开启CORS要好因为开发时Flask的run.py不需要额外配置。当然部署时如果前后端分属不同域名还是CORS更直接。3.3 开发环境配置PyCharm运行FlaskVue的完整步骤直接用PyCharm打开Flask项目需要正确配置Python解释器和启动方式。第一步在项目根目录创建虚拟环境python3 -m venv venv source venv/bin/activate pip install flask flask-sqlalchemy flask-cors pyjwt第二步在PyCharm里把解释器指向venv。File - Settings - Project - Python Interpreter - 选择刚才创建的虚拟环境路径。第三步配置运行入口。点击右上角运行配置选择Flask ServerTarget设为run.pyEnvironment variables里加上实际使用的数据库连接如DATABASE_URLsqlite:///data.db。注意项目里不要把数据库配置写死在代码里用环境变量读取方便部署时切换。第四步单独启动前端。如果电脑上装了Node进入前端目录npm install npm run devVite默认跑在5173端口后端跑在5000端口通过代理完美连接。PyCharm里可以同时打开两个终端窗口一个跑后端一个跑前端调试起来很顺手。这里插一句标题里写“pycharm”我理解是想强调用PyCharm作为IDE。其实后端代码可以直接用PyCharm调试断点打在Flask视图函数里前端请求一进来就会暂停特别方便排查参数或者数据查询问题。但是直接用PyCharm跑Vue不太专业PyCharm对Node的支持虽然有了但很多同学习惯还是用终端跑。如果要调试前端我推荐在Chrome里装Vue Devtools比在IDE里强行调舒服很多。4. 常见问题与排查技巧实录4.1 跨域问题前后端分离最经典的坑我前面提到可以用Vite代理解决但如果后端单独部署到服务器前端用Nginx还是有概率遇到跨域。最简单的兜底方案是Flask初始化时配置CORSfrom flask import Flask from flask_cors import CORS app Flask(__name__) CORS(app) # 允许所有域名仅开发时用这样前端直接请求http://服务器IP:5000/api/xxx不会报跨域错误。但生产环境不建议全开最好指定允许的域名CORS(app, resources{r/api/*: {origins: [http://your-frontend.com]}})如果你在用Flask-RESTful或者自定义蓝图注意flask-cors对每个接口的OPTIONS预检也要放行。写路由时如果遇到请求方法是OPTIONS却被拦截多半是CORS配置没有覆盖“预检请求”。通过装饰器方式也能解决但直接用flask-cors更省事。另外我还遇到过一种情况浏览器里没有跨域错误但请求发送后后端收不到参数。这往往是因为前端axios请求时设置了错误的Content-Type。比如FormData文件上传axios会自动设置multipart/form-data有时候我们手动写错了后端request.get_json()拿不到。建议上传文件时不要在请求头手动覆盖Content-Type让axios自动设定。4.2 状态流转与审核并发边界情况怎么处理审核类系统最常见的bug就是重复审核。比如辅导员点通过时另一个辅导员也在同一个申请列表两个人都点通过导致状态重复更新。解决思路是用状态条件过滤来更新UPDATE application SET status1 WHERE idxxx AND status0而不是先SELECT再UPDATE。SQLAlchemy可以这样写from sqlalchemy import update stmt update(Application).where(Application.id app_id, Application.status 0).values(status1) result db.session.execute(stmt) db.session.commit() if result.rowcount 0: # 说明已经被其他人改过了 return {code: 1, msg: 该申请已被审核请刷新页面}同样的方法也适用于名额扣减。分配名额时先查剩余额度再判断是否充足然后扣减。一定要在更新语句里加上“used_quota total_quota”的条件否则高并发下会把额度扣成负数。对毕设项目来说并发量不会很大但用手动方式处理一下面试或答辩时能展现出工程意识。状态回退也是一个要考虑的点。我用status_log日志表记录了每次变更如果业务需要“撤回”可以把当前状态改回前一个状态并插入一条新日志。注意撤回操作一定要带上操作人便于追踪。我在实际开发中遇到过一个问题学生提交申请后想修改但状态已经变成审核中了这时候要不要允许撤回业务方给的答案是允许但必须在辅导员“未处理”的窗口内。所以我的实现是status等于0或2即待初审或初审驳回时学生可以撤回撤回后删除或标记为已取消重新生成一条申请记录。这个逻辑虽然多一步但在高校里很常见因为很多学生材料准备不齐。4.3 性能与权限分页、文件上传、密码安全先看性能。如果学生数据有几千条申请列表不做分页会非常慢。Flask的分页很简单page request.args.get(page, 1, typeint) per_page request.args.get(per_page, 10, typeint) pagination Application.query.filter_by(collegeg.current_user.college).paginate(pagepage, per_pageper_page, error_outFalse) data { items: [a.to_dict() for a in pagination.items], total: pagination.total, pages: pagination.pages, current: pagination.page }前端Element Plus的el-pagination组件和后端返回结构能直接对接。前端列表页一般会显示总条数和页码翻页时重新请求一次接口速度很快。文件上传要限制大小。Flask里可以这样app.config[MAX_CONTENT_LENGTH] 20 * 1024 * 1024超过20MB会直接抛异常需要配合错误处理器返回友好提示不然浏览器看到的是500。我不是第一次遇到这个坑后来在全局错误处理里统一拦截app.errorhandler(413) def too_large(e): return {code: 1, msg: 文件大小超过限制}, 413密码安全方面用户表一定用密码哈希。Werkzeug的generate_password_hash默认使用pbkdf2:sha256已经足够。记得登录时用check_password_hash验证不要自己写MD5加盐方案。过期项目里见过直接把密码存在数据库的答辩时被老师问得下不了台。4.4 实际部署与环境配置中的疑难问题很多同学项目做完只会在本地跑但答辩时老师可能会问你“怎么部署”。这里说几个常见问题。如果后端用SQLite部署到服务器后会出现“database is locked”错误因为SQLite对并发写支持不好建议换MySQL或PostgreSQL。开发时用SQLite没问题部署前改一下环境变量里的DATABASE_URL即可。注意SQLAlchemy在连接MySQL时要在连接串里指定charsetutf8mb4否则中文可能乱码。例如DATABASE_URLmysqlpymysql://user:passwordlocalhost/dbname?charsetutf8mb4如果前端构建后想由后端统一托管静态文件需要把Vue构建出来的dist目录放到Flask的static下并配置一个路由把非API请求都重定向到index.html。这个方案适合小型部署省得单独配Nginx。Flask里写一个兜底路由app.route(/, defaults{path: }) app.route(/path:path) def catch_all(path): if path.startswith(api/): return {code: 404, msg: not found}, 404 return send_from_directory(dist, index.html)但要注意这种方式在刷新前端路由时可能返回404所以需要send_from_directory把请求导到index.html。当然正规部署还是建议Flask用gunicorn跑再用Nginx做反向代理同时托管Vue的dist目录。Gunicorn的命令很简单gunicorn -w 4 -b 127.0.0.1:8000 run:appNginx配置里把根路径指向dist目录把/api/开头的路径代理到8000端口。这样做避免了CORS问题因为浏览器访问的是同一个域。整个过程不复杂只要熟练一遍对项目整体理解会加深很多。最后再分享一个小技巧项目里统一使用环境变量管理配置不要硬编码密钥和数据库地址。我在本地开发时用一个.env文件里面写好FLASK_SECRET_KEY、DATABASE_URL等部署到服务器后直接在环境或启动脚本里设成实际值。这样代码仓库里不会泄漏敏感信息换环境也只需改文件或变量不用改代码。这个习惯养成后从课设走向正式项目会顺畅很多。

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

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

免费获取报价 →
↑