ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

基于Python的贫困生资助管理系统:前后端分离架构与状态流转设计

基于Python的贫困生资助管理系统:前后端分离架构与状态流转设计 简介本资源为基于Python的贫困生资助管理系统毕业设计项目源码与数据库采用前后端分离架构面向计算机相关专业正在做毕业设计的学生及需要项目实战练习的学习者。项目经导师指导并认可通过评审分98分源码均经本地编译运行与严格调试难度适中可满足学习与使用需求。压缩包共755个文件约12.96MB涵盖40个py后端源码、38个vue前端组件、27个html页面、51个css与162个js样式脚本以及2个sql数据库脚本、若干图片与字体资源并附安装、运行、构建等bat脚本便于快速部署。目前已有108人学习下载。读者可获得完整可运行的前后端分离项目代码、数据库脚本与目录结构参考适合作为毕业设计选题模板或课程实战案例帮助理解资助申请、审核、信息管理等模块的实现思路与排错方法。1. 贫困生资助管理系统从一张 Excel 台账到前后端分离的完整落地每年秋季学期某高校学工部门的老师都要面对同一件头疼事几千份家庭经济困难认定申请表堆在桌上Excel 台账改了又改版本从最终版一路排到最终版不再改可一到公示环节还是有人发现数据对不上。这不是谁不认真而是用表格管流程本身就撑不住申请—审核—公示—发放—归档这条链路。基于 Python 的贫困生资助管理系统要解决的正是这个问题把认定流程、资助等级、发放记录搬进一个带权限、带状态流转、带审计痕迹的 Web 系统里。它适合正在做毕业设计、需要一套能跑通、能讲清架构、又能真实演示业务闭环的同学也适合想拿它当练手项目、补齐前后端分离经验的开发者。下面我按自己搭这类系统的顺序把选型、建表、接口、联调和踩坑一次讲透。2. 技术选型与数据库设计为什么这套组合最适合毕业设计2.1 后端选 Django 还是 Flask先看你要不要自带后台做资助管理系统绕不开一个现实你需要一个能管理用户、角色、权限的后台。Django 自带 admin、ORM、认证体系开箱就能拿到用户管理和权限控制Flask 更轻但用户、角色、权限这套东西你得自己搭工作量会翻倍。我的建议是如果这是毕业设计选 Django Django REST FrameworkDRF把精力留给业务逻辑而不是造轮子。前端这边Vue 3 Element Plus 是最稳的组合。Element Plus 的表格、表单、分页、弹窗组件齐全学工类系统大量是列表 表单 审批的界面用它开发效率高答辩演示也好看。前后端通过 JSON 交互前端跑在 5173 端口后端跑在 8000 端口用 CORS 打通。数据库选 MySQL 8.0。资助系统涉及金额、等级、状态需要事务保证一致性MySQL 的 InnoDB 引擎够用而且部署资料多答辩时老师问起来也好解释。2.2 核心表结构五张表撑起整条资助链路资助系统的数据模型其实不复杂关键是别把学生申请审核发放揉在一张表里。我一般拆成五张核心表表名作用关键字段student学生基础信息id、学号、姓名、院系、年级、班级application困难认定申请id、学生id、申请学年、困难等级、状态、提交时间review_record审核记录id、申请id、审核人id、审核结果、意见、审核时间grant_record资助发放记录id、申请id、金额、发放时间、发放状态sys_user系统用户id、账号、密码、角色、关联学生id这里有个设计要点application表里的状态字段是整个系统的灵魂。它决定了这条申请现在处于哪一步常见取值是draft草稿、submitted已提交、reviewing审核中、approved已通过、rejected已驳回、granted已发放。状态流转必须由后端控制前端只能发起请求不能直接改状态否则数据就乱了。-- 困难认定申请主表状态字段是流程控制的核心 CREATE TABLE application ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id BIGINT NOT NULL COMMENT 关联学生id, academic_year VARCHAR(20) NOT NULL COMMENT 申请学年如2024-2025, difficulty_level TINYINT DEFAULT NULL COMMENT 困难等级1一般 2困难 3特别困难, status VARCHAR(20) NOT NULL DEFAULT draft COMMENT 流程状态, submit_time DATETIME DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_student (student_id), INDEX idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时把status和student_id都加上索引因为列表页几乎永远在按状态筛选、按学生查询。difficulty_level允许为空是因为草稿阶段学生还没填完强制非空会导致保存草稿失败——这个坑我后面还会提。2.3 用 Django ORM 把表映射成模型表建好后用 Django 的模型类对应上后续所有查询都走 ORM避免手写 SQL 拼接带来的注入风险。# models.py 核心模型定义 from django.db import models class Student(models.Model): student_no models.CharField(max_length20, uniqueTrue, verbose_name学号) name models.CharField(max_length30, verbose_name姓名) department models.CharField(max_length50, verbose_name院系) grade models.CharField(max_length10, verbose_name年级) class Meta: db_table student class Application(models.Model): STATUS_CHOICES [ (draft, 草稿), (submitted, 已提交), (reviewing, 审核中), (approved, 已通过), (rejected, 已驳回), (granted, 已发放), ] student models.ForeignKey(Student, on_deletemodels.CASCADE) academic_year models.CharField(max_length20) difficulty_level models.SmallIntegerField(nullTrue, blankTrue) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultdraft) submit_time models.DateTimeField(nullTrue, blankTrue) class Meta: db_table applicationon_deletemodels.CASCADE表示学生被删除时其申请一并删除这在测试阶段方便清理数据但生产环境更稳妥的做法是用PROTECT防止误删学生导致历史资助记录丢失。毕业设计里用 CASCADE 没问题但答辩时如果被问到数据安全能说出这个区别是加分项。3. 后端接口开发把状态流转写成不可绕过的规则3.1 用 DRF 写第一个申请列表接口前后端分离的核心是接口。先写一个查询当前学生申请列表的接口把认证、序列化、分页三件事串起来。# views.py 申请列表与创建接口 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from .models import Application from .serializers import ApplicationSerializer class ApplicationListView(APIView): permission_classes [IsAuthenticated] def get(self, request): # 普通学生只能看自己的申请管理员可看全部 if request.user.role student: qs Application.objects.filter(student__userrequest.user) else: qs Application.objects.all() status_filter request.query_params.get(status) if status_filter: qs qs.filter(statusstatus_filter) serializer ApplicationSerializer(qs.order_by(-id), manyTrue) return Response({code: 0, data: serializer.data})这段代码有两个关键点。第一权限判断写在视图里根据request.user.role决定数据范围学生永远看不到别人的申请这是数据隔离的底线。第二status作为查询参数做可选筛选前端切换标签页时只传一个参数后端不用改代码。order_by(-id)保证最新的申请排在最前符合使用习惯。3.2 状态流转接口谁能改、改成什么必须写死资助系统最容易翻车的地方就是状态被随意修改。学生提交后自己又改回草稿或者审核员跳过审核中直接标已通过流程就乱了。解决办法是把合法流转写成一个映射表任何修改都先校验。# 状态流转规则当前状态 - 允许流转到的状态 ALLOWED_TRANSITIONS { draft: [submitted], submitted: [reviewing, rejected], reviewing: [approved, rejected], approved: [granted], rejected: [draft], # 驳回后允许学生修改重新提交 granted: [], # 已发放为终态 } class ApplicationStatusView(APIView): permission_classes [IsAuthenticated] def post(self, request, pk): app Application.objects.get(pkpk) target request.data.get(status) # 校验目标状态是否在允许范围内 if target not in ALLOWED_TRANSITIONS.get(app.status, []): return Response({code: 4001, msg: 非法的状态流转}, status400) # 只有审核角色能操作审核类状态 if target in (reviewing, approved, rejected) and request.user.role ! reviewer: return Response({code: 4003, msg: 无审核权限}, status403) app.status target app.save() return Response({code: 0, msg: 操作成功})ALLOWED_TRANSITIONS这张表是整个流程的黑匣子所有合法路径一目了然。granted对应空列表意味着一旦发放就不能再改这是终态保护。角色校验和状态校验分开写是因为它们回答的是两个不同问题这个状态能不能到以及这个人有没有资格操作。分开后报错信息更精确前端也好提示。3.3 审核记录每一次操作都要留痕状态改了但谁改的、什么时候改的、什么意见必须单独记一张表。这不是可选项资助系统涉及公平性没有审核记录公示阶段被质疑时你拿不出证据。# 审核时同步写入审核记录 class ReviewView(APIView): permission_classes [IsAuthenticated] def post(self, request, pk): app Application.objects.get(pkpk) result request.data.get(result) # pass 或 reject comment request.data.get(comment, ) target approved if result pass else rejected if target not in ALLOWED_TRANSITIONS.get(app.status, []): return Response({code: 4001, msg: 当前状态不可审核}, status400) app.status target app.save() ReviewRecord.objects.create( applicationapp, reviewerrequest.user, resultresult, commentcomment ) return Response({code: 0, msg: 审核完成})审核和写记录放在同一个请求里用数据库事务包起来更稳Django 里加transaction.atomic装饰器。如果审核成功但记录写入失败数据就不一致了这种问题在答辩演示时一旦出现很难解释。4. 前端联调与权限控制让界面跟着角色走4.1 用 axios 封装统一请求把 token 带上前端所有请求都走一个封装好的 axios 实例避免每个页面重复写 baseURL 和请求头。// request.js 统一请求封装 import axios from axios const request axios.create({ baseURL: http://127.0.0.1:8000/api, timeout: 10000, }) // 请求拦截自动携带 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截统一处理错误码 request.interceptors.response.use( res res.data, err { if (err.response err.response.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(err) } ) export default request拦截器做了两件事请求发出前自动加 token响应回来后统一处理 401。401 意味着登录过期直接清 token 跳登录页比在每个页面单独判断省事得多。timeout设 10 秒本地开发够用如果后端查询慢可以调大但别设成无限等待否则接口挂了页面会一直转圈。4.2 路由守卫不同角色进不同页面学生登录后应该看到我的申请审核员看到待审核列表管理员看到全部数据。这个分流在路由层面做比在页面里判断更干净。// router/index.js 路由守卫 router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.requiresAuth !token) { next(/login) // 未登录跳登录页 } else if (to.meta.roles !to.meta.roles.includes(role)) { next(/403) // 角色不符跳无权限页 } else { next() } })路由的meta里声明requiresAuth和roles守卫统一判断。这样新增页面时只要配好 meta不用改守卫逻辑。注意前端路由守卫只是体验优化真正的权限校验必须在后端做前端能绕过后端不能省。4.3 申请表单草稿和提交走同一个接口学生填表时经常填一半要保存所以表单要有保存草稿和提交两个按钮但后端可以用同一个创建/更新接口靠状态字段区分。// 提交申请action 为 save 存草稿为 submit 直接提交 async function submitForm(action) { const payload { academic_year: form.academicYear, difficulty_level: form.level, status: action submit ? submitted : draft } const res await request.post(/applications/, payload) if (res.code 0) { ElMessage.success(action submit ? 提交成功 : 草稿已保存) } }这里status由前端传但后端必须再校验一次学生只能传draft或submitted不能传approved。前端传什么不可信这是接口安全的基本功。5. 避坑与排查那些让我返工三次的问题5.1 跨域报错 CORS别只会加一个头现象前端调接口浏览器控制台报has been blocked by CORS policy请求根本没到后端。原因前后端端口不同浏览器同源策略拦截。有人图省事在响应里加Access-Control-Allow-Origin: *但带 token 的请求携带凭证时通配符不被允许。解决用django-cors-headers配置里写明确的前端地址并开启凭证支持。# settings.py 跨域配置 INSTALLED_APPS [corsheaders] MIDDLEWARE.insert(0, corsheaders.middleware.CorsMiddleware) CORS_ALLOW_CREDENTIALS True CORS_ALLOWED_ORIGINS [http://localhost:5173, http://127.0.0.1:5173]注意CorsMiddleware要尽量放在中间件靠前位置否则可能被其他中间件提前拦截。5.2 保存草稿失败因为字段设了非空现象点保存草稿报 400提示difficulty_level不能为空。原因建表时把困难等级设成了NOT NULL但草稿阶段学生还没选等级。解决把difficulty_level改成允许为空模型里加nullTrue, blankTrue。凡是草稿阶段可能没填的字段都要允许为空等提交时再用业务校验强制必填。5.3 列表分页后筛选失效现象前端切到第二页筛选条件丢了又回到全部数据。原因筛选参数只存在组件状态里翻页请求没带上。解决把筛选条件统一存到一个查询对象里翻页和筛选都从这个对象取值拼参数。别在翻页时只传page把status、academic_year一起带上。5.4 金额字段用浮点数对账差几分钱现象发放金额累加后和台账差 0.01。原因数据库用FLOAT存金额浮点运算有精度误差。解决金额一律用DECIMAL(10,2)Django 里用DecimalField(max_digits10, decimal_places2)。涉及钱的地方浮点数就是后悔药都买不回来的坑。5.5 删除学生导致资助记录消失现象测试时删了一个学生历史发放记录跟着没了。原因外键用了CASCADE。解决涉及历史记录的关联改成PROTECT删除前先检查是否有关联数据。测试环境图方便用 CASCADE上线前一定要改回来。6. 进阶技巧把系统从能跑做到敢演示到这一步系统基本能跑通了但毕业设计答辩和真实使用之间还差几个细节。我一般会补三样东西成本不高但演示效果和可信度提升明显。第一样是数据导出。学工老师最需要的功能之一就是把筛选后的名单导成 Excel。用openpyxl在后端生成文件前端用responseType: blob接收下载。# 导出资助名单为 Excel from openpyxl import Workbook from django.http import HttpResponse def export_grants(request): wb Workbook() ws wb.active ws.append([学号, 姓名, 困难等级, 资助金额, 发放时间]) qs GrantRecord.objects.select_related(application__student) for g in qs: s g.application.student ws.append([s.student_no, s.name, g.application.difficulty_level, float(g.amount), g.grant_time.strftime(%Y-%m-%d)]) resp HttpResponse( content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet ) resp[Content-Disposition] attachment; filenamegrants.xlsx wb.save(resp) return respselect_related一次性把关联的学生信息查出来避免循环里逐条查库导致 N1 问题。导出几百条数据时这个优化能让响应从几秒降到几百毫秒。第二样是操作日志。除了审核记录登录、导出、删除这类敏感操作也建议记一条日志字段就四个操作人、操作类型、目标对象、时间。答辩时老师问怎么保证数据可追溯这张表就是答案。第三样是状态机的前端可视化。在申请详情页用步骤条Element Plus 的el-steps把draft → submitted → reviewing → approved → granted画出来当前状态高亮。这个改动很小但演示时一眼就能让评委看懂流程比口头解释强得多。最后说个我自己的习惯每次改完状态流转规则我都会手动把六种状态两两组合跑一遍看哪些该拦、哪些该放。这个笨办法帮我抓到过好几次规则漏洞——比如驳回后重新提交这条路径一开始忘了允许学生被驳回后就卡死了。系统能不能用往往不取决于功能多而取决于异常路径有没有堵死。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表