1. 项目定位与业务场景还原
1.1 奖学金评定到底在管什么
先聊一个很现实的问题:奖学金评定这件事,在高校里到底有多麻烦?
每个学期末或者每年九月,辅导员就要开始收材料,学生们整理成绩单、获奖证书、志愿时长证明、体测成绩,一份一份提交。班委汇总到学院,学院教学秘书核对学分绩点,辅导员核对综合素质加分,最后还要开评审会,公示,接受申诉。这一套流程走下来,纸质材料的传递量非常大。遇到材料不齐、加分项有争议、公示期学生提出异议的情况,反复沟通的成本更是成倍上升。
这套 SpringBoot + Vue 的奖学金评定管理系统,就是把上面这一整套流程搬到线上。学生在线提交申请,系统自动计算学分绩点和综测加分,辅导员和评审老师在线审核,公示结果一键发布,学生可以在系统里查看自己的审批进度和最终结果。也就是说,它不只是一个 CRUD 管理系统,而是一个有完整业务状态的审批流转系统。
作为毕业设计来讲,这个题目的价值在于它麻雀虽小、五脏俱全:有角色权限、有业务流程、有文件上传、有数据统计、有前后端分离的接口交互。一个系统该有的模块它基本都有了,但复杂度又控制在一个本科生能独立完成的范围内,不会出现需求大到做不完的情况。
1.2 系统角色与核心功能清单
这个系统在设计上把用户分成四类角色,每一类角色看到的界面和能做的操作是完全不同的:
| 角色 | 核心职责 | 关键操作 |
|---|---|---|
| 学生 | 提交奖学金申请 | 填写申请信息、上传证明材料、查看审批进度 |
| 辅导员 | 初审 | 核对学生信息、确认综测加分、提交初审意见 |
| 学院评审 | 复审与评定 | 查看汇总数据、调整加分项、确定推荐名单 |
| 管理员 | 系统配置 | 用户管理、奖学金类型配置、批次管理、公示管理 |
这个角色划分看起来简单,但实际做起来要注意一个关键点:不同角色的数据隔离。学生只能看到自己的申请记录,辅导员能看到所带班级的全部学生,学院评审能看到整个学院的数据。这个权限控制的粒度,在后端设计时要落实到每一行数据的查询条件上,而不能只靠前端路由隐藏。
2. 技术选型:为什么是 SpringBoot + Vue
2.1 前后端分离的优势
很多人在选毕设技术栈时会纠结:用 JSP + Servlet 行不行?用 SSM 行不行?答案是都行,但 SpringBoot + Vue 是当前最适合作为毕设展示的组合。
SpringBoot 最直接的好处是省掉了大量 XML 配置。早期 SSM 整合要把 Spring、SpringMVC、MyBatis 三套配置写明白,光是 applicationContext.xml 就够折腾一星期。SpringBoot 用自动配置把这些全部简化了,一个核心注解带一个 application.yml 就能把项目跑起来,这对于预算有限的毕设周期来说节省了非常多的时间。
前端用 Vue 的价值在于组件化开发。奖学金管理系统的页面很多都是列表页和表单页的组合,这类页面用组件封装后可以大量复用。比如学生信息卡片、申请状态标签、文件上传组件,在系统的各个页面里反复出现。如果还是用模板引擎做服务端渲染,每写一个页面都要重新拼一遍 HTML,维护成本非常高。
另外,前后端分离还有一个对毕设答辩特别有利的点:可以清晰展示接口文档。后端提供规范的 RESTful API,前端通过 Axios 调用,答辩时可以直接讲接口设计,演示用 Postman 测试接口,这比传统的 JSP 页面跳转方式更有技术含量。
2.2 项目目录与分包结构
后端项目我建议按如下分包方式组织,这个结构在答辩时被问到可以讲得很有条理:
com.example.scholarship ├── controller // 控制层,接收前端请求 ├── service // 业务逻辑层 ├── mapper // 数据访问层(MyBatis-Plus) ├── entity // 数据库实体类 ├── dto // 前端传输对象,用于接口参数封装 ├── vo // 视图对象,用于接口返回数据封装 ├── config // 全局配置(拦截器、跨域、文件上传等) ├── common // 通用类(统一返回结果、异常处理、工具类) └── utils // 工具类(JWT工具、Excel导入导出等)前端 Vue 项目的结构对应为:
src ├── api // 接口请求封装,按模块拆分 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // 状态管理(Vuex) ├── views // 页面组件 └── utils // 请求工具封装(axios 拦截器)这个分包不是随便分的。后端的 Controller 只负责参数接收和结果返回,所有业务判断都放在 Service 层,这样测试和排查问题时逻辑清晰。前端把 API 请求统一放到 api 目录,每个页面不会直接写 Axios 请求,而是调用封装好的函数,接口变更时只需要改一个文件。
3. 数据库设计与核心表结构
3.1 从业务流程推导数据表
数据库设计是整个系统的地基。我不建议上来就画 ER 图,更好的思路是先梳理业务流程,从流程里找出必须记录的数据。
奖学金评定的核心流程可以概括为:管理员发起评定批次 → 学生提交申请并上传材料 → 辅导员初审 → 学院评审 → 公示 → 归档。把这个流程走一遍,下面的表就自然浮现出来了:
- 用户表(sys_user):学生、辅导员、评审人员、管理员的基础信息,用 role 字段区分
- 学生信息表(student_info):学号、姓名、班级、专业、年级等,和用户表一对一关联
- 奖学金类型表(scholarship_type):奖学金名称、等级、金额、评定条件
- 评定批次表(review_batch):学年的评定批次,比如"2024-2025学年综合测评"
- 申请记录表(application_record):学生提交的申请,包含申请批次、奖学金类型、状态
- 综测加分表(comprehensive_score):学生的思想品德、学术科研、社会实践、文体活动等加分详情
- 审批记录表(approval_record):每一级审批的操作记录,谁在什么时间审核了什么
- 公示记录表(publicity_record):公示开始时间、结束时间、公示内容、状态
这里我想特别强调审批记录表。很多初做管理系统的同学会忽略这个表,只在申请记录里加一个 status 字段。但实际业务中,评审老师需要看到这个学生的材料是谁初审的、什么时候提交的、中间有没有退回修改,这些都是审计追踪的内容。毕设答辩时,加了这个表就能展示你对业务流程的理解深度。
3.2 状态字段的设计与流转
申请记录的 status 字段是整个系统的核心状态。我见过有人用 VARCHAR 存中文状态,比如"待审核""已通过""已拒绝",这样虽然直观,但程序里到处是字符串比较,非常容易出错。
更好的做法是用整数定义状态常量,在代码里统一管理:
1 - 草稿(学生保存未提交) 2 - 待初审(学生已提交,等待辅导员审核) 3 - 待评审(辅导员初审通过,等待学院评审) 4 - 待公示(评审通过,准备公示) 5 - 公示中 6 - 已通过(公示无异议,正式生效) 7 - 已退回(审核不通过,退回学生修改) 8 - 已拒绝(最终未通过)数据库里存数字,代码里用常量类或者枚举类来定义这些状态。这样既保证了效率,也避免了写错字符串导致的脏数据。状态流转必须在 Service 层写清楚:什么状态下允许做什么操作。比如只有 status = 2 时辅导员才能审核,只有 status = 3 时学院评审才能评分,这种状态机的约束是保证数据准确的关键。
3.3 SQL 脚本的初始化设计
项目的 SQL 脚本要包括建库、建表、初始化数据三部分。初始化数据很容易被忽略,但对毕设来说特别重要,因为答辩时如果你现场打开系统,里面要有一批能演示的数据。
我建议初始化脚本至少包含以下内容:
- 一个管理员账号(admin,密码加密后存储)
- 一个辅导员账号、一个学生账号,方便演示不同角色的登录与权限
- 3~5 条专业信息、若干个班级信息
- 20 条左右学生信息
- 2~3 种奖学金类型(一等奖学金、二等奖学金、优秀学生干部等)
- 1 个进行中的评定批次和对应的申请记录
注意密码在数据库里绝对不能存明文,后端要用 BCrypt 加密,初始化脚本里的账号密码需要是加密后的密文。答辩时可以说清楚这个安全处理,是很加分的点。
4. 后端核心模块设计与实现
4.1 登录认证与权限拦截
这个系统的登录认证用的是 JWT(JSON Web Token)方案。流程是:用户输入账号密码,后端验证通过后生成一个 Token 返回给前端,前端存在 localStorage 里,每次请求在 Header 里带上 Token,后端通过拦截器校验 Token 有效性。
JWT 相比 Session 方案更适合前后端分离。Session 依赖 Cookie,跨域时需要复杂配置;JWT 是无状态的,后端不需要存会话信息,天然支持前后端分离部署。
权限控制方面,后端可以用 Spring 的拦截器实现。自定义一个AuthInterceptor,在preHandle方法里校验 Token,然后从 Token 中解析出用户 ID 和角色,存入 ThreadLocal 或请求上下文。写一个@RequireRole注解,加在 Controller 方法上,拦截器里判断角色是否匹配,不匹配直接返回 403。这个方法实现简单,答辩的时候讲起来也清楚。
4.2 奖学金评定流程的状态机实现
这里分享一个我在实际开发中的做法:把流程控制封装成独立的 Service,而不是散落在各个 Controller 里。
比如定义一个ReviewFlowService,提供这几个方法:
public void submitApplication(Long applicationId, Long studentId) public void firstReview(Long applicationId, Long counselorId, Boolean pass, String comment) public void secondReview(Long applicationId, Long reviewerId, Boolean pass, String comment) public void startPublicity(Long applicationId) public void completePublicity(Long applicationId) public void reject(Long applicationId, String reason)每个方法内部先校验当前状态是否允许当前操作,再更新状态、插入审批记录。这样做的好处是,评审流程的所有入口都在一个类里,排查问题时只需要看这一个文件,不用满项目找谁在改 status。
以submitApplication为例,核心逻辑大概是:先判断申请记录是否属于当前学生,然后判断状态是否为草稿,接着校验必填材料和加分项是否完整,最后把状态改为待初审。
4.3 综测加分与成绩计算逻辑
综合测评成绩的计算是这个系统里最有业务含金量的部分。通常分为学习成绩和综合素质两部分,学习成绩占 70%,综合素质占 30%,综合素质内部再细分思想品德、学术科研、社会实践、文体活动等,每类加分按比例换算。
在代码实现上,我建议把公式配置化。在设计奖学金类型表时加一个formula_config字段(JSON 格式),存类似{"academic": 0.7, "moral": 0.1, "research": 0.1, "social": 0.1}。计算时通过工具类读取配置,动态计算总分。这样如果学院政策调整了权重,管理员可以直接在系统里修改配置,不需要改代码重新部署。
加分项的材料验证是另一个重点。学生会提交各种获奖证书照片,上传时系统只做格式和大小的检查,但评审阶段老师需要在线查看这些图片并给出评分意见。因此,申请记录表要有一个关联的附件表,每条附件记录包括文件地址、文件名、上传时间、附件类型(成绩单、证书、志愿证明等),方便评审老师按类型筛选查看。
5. 前端 Vue 页面与交互设计
5.1 前端路由与页面结构
前端页面按照角色模块划分,路由配置可以清晰地分成三个分组:
- 学生端:
/student/dashboard、/student/apply、/student/myRecord - 管理端:
/admin/user、/admin/type、/admin/batch、/admin/approval - 个人中心:
/profile、/password
路由守卫生效后,未登录的用户跳转到登录页,登录用户只能访问自己角色范围内的路由。注意一点:路由守卫只是用户体验层面的控制,真正的权限校验必须放在后端,前端不能作为安全边界。
5.2 表格页与表单页的复刻模板
奖学金管理系统里大量使用"表格 + 搜索 + 分页 + 弹出表单"这种页面模式。以学生申请记录页为例,页面上方是筛选条件(学年批次、奖学金类型、审批状态),下方是表格,右侧操作按钮根据当前行的状态显示不同内容。比如待初审的行显示"审核",已通过的行显示"查看详情"。
开发这类页面的时候,合理的做法是封装一个通用查询逻辑,用一组 query 参数绑定搜索表单,请求列表接口时带上这些参数,后端用 MyBatis-Plus 的Page分页插件直接处理。前端拿到返回的分页数据后,把总条数交给el-pagination组件,换页时更新 query 的 pageNum 和 pageSize,重新请求。
这里想提醒一个容易丢分的细节:表单校验。奖学金申请表单里有学号、学分绩点、综测成绩、申请奖种等多个字段,每个字段都要有具体的规则。Vue 的 el-form 支持 rules 配置,比如学分绩点必须为 0 到 5 之间的数字、申请理由不能为空、材料附件至少上传一个。答辩评委如果看到表单提交时没有任何校验,很容易判定系统性不够完整。
5.3 状态标签与操作按钮的联动
前端展示申请状态时,直接用数字会很难看,建议封装一个StatusTag组件,传入状态值,自动渲染对应颜色和文字:
| 状态值 | 标签颜色 | 标签文字 | 对应操作 |
|---|---|---|---|
| 1 | 灰色 | 草稿 | 编辑、提交 |
| 2 | 蓝色 | 待初审 | 无(学生端只读) |
| 3 | 橙色 | 待评审 | 无(学生端只读) |
| 4 | 紫色 | 待公示 | 查看详情 |
| 5 | 青色 | 公示中 | 查看详情 |
| 6 | 绿色 | 已通过 | 查看详情、打印公示页 |
| 7 | 红色 | 已退回 | 编辑、重新提交 |
| 8 | 灰色 | 已拒绝 | 查看驳回原因 |
实际测试中发现,操作按钮的显示逻辑必须和状态值一一对应。如果状态是 2 待初审,但学生端的操作按钮还显示"编辑",就会导致学生在老师审核期间修改数据,造成状态混乱。这种细节问题最容易在联调时出现,建议在开发时就先列出状态 × 角色的操作矩阵,再在代码里严格对应。
6. 接口文档设计与前后端联调技巧
6.1 统一返回格式与状态码
前后端联调最怕接口格式不统一。这个系统设计了统一的返回格式,所有后端接口都返回相同结构的 JSON:
{ "code": 200, "message": "操作成功", "data": { "records": [], "total": 20 } }code为 200 表示成功,其他值表示不同错误类型。常用的错误码约定为:401 未登录或 Token 过期、403 没有权限、404 资源不存在、500 服务器内部错误、1001 参数校验失败、1002 业务规则不满足。前端 Axios 的响应拦截器统一判断code,如果不是 200 就弹出message提示,这样前端就不需要在每个接口单独写错误提示了。
6.2 接口文档的编写思路
接口文档可以在项目里用接口文档工具管理(比如 Swagger 或前端手写的 Markdown 文档),对毕设来说,一份 Markdown 接口文档就够了。每个接口需要包含这些要素:请求地址、请求方式、请求参数(名称、类型、是否必填、说明)、返回结果示例、错误码说明。
以"提交奖学金申请"这个接口为例:
- 请求地址:
POST /api/application/submit - 请求参数:
applicationId(Long,必填,申请记录 ID) - 业务说明:学生提交申请,状态从草稿转为待初审
- 返回示例:
{"code": 200, "message": "提交成功", "data": null} - 错误码:1002 表示当前状态不允许提交,401 表示未登录
编写接口文档时建议按模块分组:认证模块、学生信息模块、奖学金类型模块、评定批次模块、申请与审批模块、公示模块、统计模块。文档的价值不只是给前端看,答辩时评委也会翻,格式清晰就是印象分。
6.3 前后端联调的调试经验
联调阶段最常见的三个问题,我在实际开发中都踩过:
第一个是跨域问题。前端项目在localhost:8080,后端在localhost:9090,直接请求会被浏览器拦截。后端需要配置跨域,可以用一个CorsConfig类实现,也可以用拦截器统一设置响应头。我建议开发环境用后端开启跨域的方式,上线后用 Nginx 反向代理来解决。
第二个是日期格式化问题。后端返回 LocalDateTime 时,默认格式是一长串带 T 的字符串,前端的日期组件解析不了。解决办法是在后端的统一 JSON 配置里设置日期格式:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8第三个是 BigDecimal 和浮点数的精度问题。奖学金金额、学分绩点这些数据绝对不能用 float 或 double,要用 BigDecimal。前端展示金额时也要用toFixed(2)保留两位小数,避免出现 10.000000001 这种乱码式展示。
7. 本地部署与测试运行指南
7.1 环境准备
要在本地把这个系统跑起来,需要准备以下环境:
- JDK 1.8 以上(建议 JDK 8,兼容性最好)
- Maven 3.6 以上
- MySQL 5.7 或 8.0
- Node.js 14 以上(Vue 前端构建需要)
- 开发工具:后端可以用 IDEA,前端可以用 VSCode
启动步骤分为四步:
第一步,创建数据库并初始化。在 MySQL 里执行 SQL 脚本,脚本会自动建库、建表并插入初始数据。
第二步,修改后端配置。在application.yml中修改数据库连接的用户名和密码。注意 MySQL 8.0 的驱动类名和 5.7 不同,驱动版本也要对应。
第三步,启动后端。在项目根目录执行mvn spring-boot:run,或者用 IDEA 直接运行启动类。看到日志输出"Started Application"就说明启动成功。
第四步,启动前端。进入前端项目目录,先执行npm install安装依赖,再执行npm run serve启动开发服务器。浏览器访问http://localhost:8080,使用初始化脚本里的管理员账号登录。
7.2 运行中遇到的典型问题排查
这里整理一下最常见的几类排查问题和对应思路:
| 问题现象 | 常见原因 | 解决的切入点 |
|---|---|---|
| 前端页面能打开,但列表数据加载失败 | 后端没启动或端口不对 | 先用浏览器直接访问后端接口地址,确认是否返回 JSON |
| 登录成功后刷新页面又回到登录页 | 刷新时 Vuex 状态丢失,没有从 localStorage 恢复用户信息 | 在路由守卫里加判断,有 Token 且未过期则放行 |
| 文件上传失败 | 上传文件大小超过 Spring 默认限制 | 在配置中调大spring.servlet.multipart.max-file-size和max-request-size |
| 数据库中文乱码 | 连接 URL 中缺少编码参数 | 在jdbc.url后面加上characterEncoding=utf8 |
| 接口返回 401 循环跳转 | 前后端判断 Token 过期的逻辑不一致 | 确认后端返回的 code 与前端 Axios 拦截器判断的 code 一致 |
7.3 打包部署的两种方式
如果要在服务器上正式部署,有两种方式。
第一种是前后端分离部署。后端用mvn package打成 JAR 包,服务器上使用nohup java -jar启动,前端用npm run build生成 dist 静态文件,交给 Nginx 托管,并配置反向代理,把/api开头的请求转发到后端的 9090 端口。这种方式结构清晰,也更容易排查问题。
第二种对于毕设演示来说更实用:开发环境下直接前面说的两种服务同时跑。后端 IDEA 里启动,前端终端npm run serve,演示时切换两个窗口展示,反而比打包部署更直观。答辩的时候重点在于讲清楚项目和代码逻辑,而不是展示部署脚本。
8. 项目答辩展示要点与常见提问
8.1 演示路线的安排建议
毕业答辩的现场演示时间通常不会太长,需要在有限时间里展示系统的核心亮点。我见过很多同学演示了十分钟还在登录主页和列表页之间来回切换,这是比较浪费时间的安排。
建议的演示路线是:用一个预置好的学生账号登录,展示提交奖学金申请,然后退出登录;用辅导员账号登录,展示审批列表和审核操作;再退出,用学院评审账号登录,展示最终评定和公示操作。这样一条主线走完整个业务闭环,评委能看到系统全部的关键能力。最后再用管理员账号登录,展示用户管理和奖学金类型配置,说明系统的可维护性。
8.2 答辩时高频率被问到的内容
根据实际答辩经验,评委最常问的问题集中在这几个方面:
第一个是权限控制问题:"如果学生直接拿管理员的接口地址访问,能不能绕过前端限制?"正确的回答是:不能。后端在拦截器里通过 Token 拿到用户角色,每次接口调用都会校验角色,没有权限直接返回 403。
第二个是密码安全问题:"数据库里的密码是不是明文存储?"要回答:密码用 BCrypt 加密,同一密码每次加密结果不同,即使数据库泄露,也无法直接反推出明文。
第三个是并发问题:"如果多个学生同时提交申请,会不会数据错乱?"可以回答:申请提交操作以申请记录 ID 为维度,数据库行级锁保证同一申请记录不会被并发修改;另外通过状态字段做了乐观锁控制。
第四个是扩展性问题:"如果要增加一个'国家奖学金'类型,需要改代码吗?"回答:不需要。奖学金类型做了表结构设计,管理员在后台新增即可,学生端会自动出现新的可申报奖学金选项。
这些问题其实是考察对项目的真实理解程度。平时写代码时就关注这些点的人,答辩会顺利很多。
我自己做这类系统的一个体会是:代码写完只是第一步,把每个设计决策背后的理由想清楚,才是参与一个完整项目得到的真正能力。奖学金评定管理系统里,状态机的流转、角色的权限控制、成绩计算规则的动态化设计,这几块逻辑想明白了,以后换成别的管理系统,思路是一样的。
最后分享一个小经验:接口文档和 SQL 脚本这两份资料,一定要在项目开发过程中同步维护,不要等代码写完了再补。当初我为了省事,把接口文档放在最后两天集中写,结果发现一半接口的参数已经改过了,对着代码作为依据来回核对,反而多花了三倍的时间。从前端开发联调的第一天起就同步更新文档,项目结束时文档就是干净的,省心很多。