做毕设选题目的时候,奖学金评定管理系统这东西看着不起眼,真做起来门道不少。它不像电商秒杀那样有高并发噱头,也不像推荐系统那样能晒算法,但胜在业务流程完整——学生申请、辅导员初审、评审打分、结果公示、数据导出,一整套走下来,Spring Boot该练的技术点基本都能碰到。我这两年指导过不少学生用这个题目,有人做得顺顺利利答辩过关,也有人卡在文件上传、并发提交这类细节上反复折腾个把星期。这篇就把这个系统的完整思路、核心实现、踩坑记录全摊开讲,适合正在选毕设题目、或者已经动手写Spring Boot项目的读者参考。
1. 项目定位:奖学金评定到底在解决什么痛点
1.1 高校奖学金评定的真实场景
先看业务本身。某高校每年评奖学金,流程大概是这样的:学院发通知,学生在规定时间内提交申请表和证明材料,辅导员初审材料是否齐全,评审委员会根据绩点、德育分、竞赛加分等维度打分排名,最后学院公示名单。听起来不复杂,但实际执行起来问题很多。
纸质申请表来回交、材料格式五花八门、Excel表格统计经常出错、学生不知道自己材料有没有被收到、评审进度更是一头雾水。更麻烦的是,加分规则年年调整,同一个奖项去年用一套标准,今年又改了,手工核对特别容易漏。一个三百人的年级,光材料清点就能让辅导员加两天班。
这个系统做的事情,就是把上面这条链路线上化。学生在线填申请、传材料,辅导员后台审核,评审委员会按配置好的规则打分,管理员发布公示结果。每个环节留痕,谁在什么时间做了什么操作都有记录,出问题随时可以回溯。
1.2 系统角色的业务边界
角色设计是这块业务的核心,不能上来就怼着一张表做权限。我常用的划分方式是这样的:
| 角色 | 核心权限 | 业务动作 |
|---|---|---|
| 学生 | 申请、查询 | 维护个人信息、提交申请、上传材料、查看审核进度与结果 |
| 辅导员 | 初审 | 核对材料完整性、驳回或通过、查看本班申请列表 |
| 评审委员 | 终审打分 | 按评分项打分、填写评审意见、批量审核 |
| 系统管理员 | 配置与管理 | 设置奖学金类型、配置评分规则、发布公示、导出名单、用户管理 |
业务上有个容易忽略的点:奖学金类型不是一个写死的字典,而是需要能动态配置的。因为每学期的奖学金项目可能不同,比如校级一等奖学金、励志奖学金、企业奖学金,各自的名额、金额、申请条件和材料要求都不一样。如果把这些硬编码在代码里,下一学期规则一变就要改代码重新部署,非常被动。所以设计时要单独建一张奖学金配置表,申请流程和评分规则都跟着配置走。
1.3 为什么这个题目适合做毕业设计
选题这件事,大部分人的纠结是:太简单显得水,太复杂又做不完。奖学金评定系统刚好卡在中间偏上的位置,难度可控但覆盖面广。
常见的课程设计只做单表增删改查,毕业设计则要求系统化思维,这个题目天然就有多角色协作、多步骤流程、状态流转、文件存储、权限控制这些点,每一块都能单独拿出来写进论文的"系统设计与实现"章节。再加上奖学金评定涉及量化打分,还能顺带把数据统计和可视化做进去,比如按专业统计获奖比例、按奖项类型分析申请人数趋势,这些都是答辩时的加分项。
和纯商城类题目相比,奖学金系统的业务边界清晰,不需要模拟支付、不需要处理库存,复杂度集中在流程控制上,更适合本科生在有限周期内交付一个完整可演示的工程。
2. 技术选型与架构设计:为什么是Spring Boot这套组合
2.1 后端框架选型
后端用Spring Boot几乎是这个题目的默认答案,原因不复杂:生态成熟、资料多、招人认。对毕设来说,Spring Boot的自动配置特性省掉了一大堆XML配置,让新手能把精力放在业务逻辑而不是环境搭建上。
但有几个具体选型值得展开说说。
持久层我建议用MyBatis-Plus而不是原生MyBatis。原生MyBatis写简单CRUD要手写XML,效率低没意义;MyBatis-Plus内置了单表操作、分页插件、自动填充、逻辑删除,基本覆盖这个系统的日常需求,遇到复杂查询比如多表联查、动态条件筛选时,再自己写XML就行。使用率最高的三个特性是分页查询、条件构造器和自动填充。
数据库用MySQL,5.7或者8.0都行,注意8.0的驱动类名和时区配置跟5.7有差异,后面踩坑部分会细说。如果想让项目更有看点,可以引入Redis做验证码缓存和轮询公告缓存,但这个属于锦上添花,不是核心。
2.2 前端方案的取舍
前端是这个题目最纠结的地方,很多人在这里反复横跳,我得说清楚各自的代价。
方案一是前后端分离,Vue2加Element UI做后台管理端,学生端也用Vue写。优点是界面好看、组件现成、答辩演示很加分,缺点是工作量大,如果之前没接触过Vue,光环境搭建和跨域调试就能耗掉两周。
方案二是服务端渲染,用Thymeleaf加Bootstrap。优点是项目结构简单,不用处理跨域,表单提交服务端处理再返回页面,非常适合只熟悉Java、前端水平一般的同学。缺点是页面交互弱一些,但说实话,奖学金系统这种表单密集型应用,Bootstrap已经够用了。
方案三是用现成的后台管理框架生成前端,比如若依这类脚手架,直接改业务页面。这个省时间,但有个风险:生成器代码量大,框架自身的表结构和使用约束你得先读懂,如果改动时破坏了框架约定,排查起来很痛苦。
我个人的建议是:如果时间充裕且想冲高分,选前后端分离;如果主要目标是稳妥交付,选Thymeleaf方案;除非你之前已经用过若依,否则别在毕设季临时换框架学一套新东西。
2.3 数据库表结构设计
数据库设计决定这个项目能写到什么深度。奖学金评定系统核心表我列一下,照着这个扩展基本不会跑偏。
- user(用户表):统一存学生、辅导员、评委、管理员,用role字段区分。单独建学生信息表会带来JOIN和用户同步的麻烦,统一用户表再加profile字段更省事。
- scholarship(奖学金配置表):名称、类型、名额、金额、申请开始/结束时间、材料要求说明。
- application(申请记录表):学生ID、奖学金ID、当前状态、提交时间、最后更新时间。
- material(材料表):申请ID、材料名称、文件路径、上传时间。
- score_detail(评分明细表):申请ID、评分项ID、得分、评委ID、评分时间。一个申请对应多条评分明细,用一条总分数做冗余字段方便排序。
- review_record(审核记录表):申请ID、操作人ID、动作(通过/驳回/退回)、意见、时间。
- announcement(公示表):标题、内容、关联的奖学金ID、发布时间。
- rule_config(评分规则表):评分项名称、满分、权重、是否启用。
这个表结构有两点值得注意。第一,application表里一定不要只存最终结果,要留状态字段并配合review_record记录流转历史,答辩时评审会问"怎么保证流程可追溯",这就是答案。第二,score_detail和application是一对多关系,每个评委打分都是一行,最后统计总分时用SQL聚合,不要在多个评委同时打分时各自更新application表的总分字段,否则容易出现并发覆盖问题。
2.4 项目分层与目录规范
Spring Boot项目分层这件事,网上有各种说法,但奖学金系统这种规模的工程,别搞太复杂。我的建议是controller-service-mapper三层,加一个config包放配置类,一个common包放统一返回结果、异常处理和工具类,一个entity包放数据库实体,一个dto包放接收前端参数的类。
这里有个新人常犯的错:直接把数据库实体类拿去接收前端参数。奖学金的申请表单里有"获奖日期""证明材料文件名"这些字段,数据库表里根本不存在,如果你直接把前端数据烘进实体类,要么报字段不存在,要么前端字段和表字段互相污染。正确做法是单独建一个ApplicationDTO,校验逻辑写在这层,再用Service把DTO转成实体。这个细节写进论文里能体现你有工程意识。
3. 核心业务模块实现:从申请到公示的完整链路
3.1 用户认证与权限控制
登录认证是每个系统都绕不开的模块。毕设项目如果还在用session,也不是不能跑,但我还是推荐用JWT做token认证,理由有三点:前后端分离场景下JWT天然支持,状态无需要后端额外存;答辩时能讲清楚无状态认证的原理,比"session跨域有问题"更有说服力;后续想扩展APP或小程序端,认证逻辑基本不用改。
实现上,登录接口校验用户名密码成功后,用JWT工具类生成token,把用户ID和角色写进claim。然后写一个拦截器继承HandlerInterceptor,在preHandle里从请求头取token、解析校验、把用户信息放进ThreadLocal。权限控制不用上Spring Security那一套重型方案,用一个自定义注解加角色判断就够了。比如给辅导员批量审核接口加上@RequireRole("COUNSELOR")注解,拦截器里判断当前用户角色是否匹配,不匹配直接返回403。这种方式代码量小,而且论文里说"基于拦截器+自定义注解实现细粒度权限控制",听上去就很扎实。
密码存储一定用哈希,不要明文入库。BCrypt是Spring Security自带的好东西,就算不用Security全家桶,单独引spring-security-crypto依赖也能用BCryptPasswordEncoder做加密校验。
3.2 学生在线申请流程
学生端申请这个模块,流程设计比代码本身重要。核心状态机要提前定义好,我一般设这几个状态:
- 草稿:学生填写未提交,可以随时修改。
- 待审核:已提交,等待辅导员初审。
- 初审通过:辅导员确认材料齐全,进入评审打分阶段。
- 初审驳回:材料不齐或不符合条件,退回给学生,学生可以补充后重新提交。
- 评审完成:评委打完分,保存总分和排名。
- 已通过/未通过:最终结果。
- 已公示:结果已被管理员发布。
状态流转的核心原则是:每一步只能从前一个合法状态过来,不能跳状态。比如待审核状态下不能直接改成已通过,必须经过评审完成。
代码层面的关键点在事务控制。学生提交申请时,要同时插入application记录和material记录,如果材料插入失败,申请主记录就永远是一条孤儿数据。所以提交接口的Service方法要加上@Transactional(rollbackFor = Exception.class),并且注意事务只对RuntimeException和rollbackFor指定的异常回滚,如果你在事务方法里把业务异常用try-catch吞掉了,事务照样不会回滚,这个坑我见过不止一次。
3.3 量化评分与规则配置
评审打分是系统最有业务价值的部分,也是论文里最能写深度的模块之一。奖学金评审通常不是一人拍板,而是多个评委按维度打分,最后取平均分或总分。
评分规则的思路是在rule_config表里配好评分项,比如"学业成绩占比40%""学科竞赛加分30%""社会实践20%""德育表现10%"。每个奖项可以关联一套规则,存储时用JSON格式存评分项数组,也可以拆成rule_config和rule_item两张表。对毕设来说,一张表存奖项ID和JSON配置就够了,JSON用fastjson或Jackson解析都行,但要处理异常格式,防御式编程在这里很加分。
打分界面做成了表单,评委进入待评审列表,点开某个申请后,各项评分框依次填写,分数前端会做范围校验,但后端校验绝不能省,因为接口可以被直接调用绕过前端。校验逻辑包括:每个评分项必须在0到满分之间、总分是否超限、当前申请状态是否允许打分。打分提交时,ScoreDetail按评委加评分项粒度为一条记录,防止同一个评委重复打分——数据库层面给application_id、judge_id、item_id加唯一索引,要比代码里先查后插可靠得多。
3.4 多级审核与流程留痕
审核流程是这套系统的骨架,也是容易被写成一坨屎的地方。很多新手把所有审核逻辑堆在同一个Service方法里,用大量if-else判断当前角色和动作,结果是代码越来越长,自己都看不懂。
我建议对审核动作建模。定义一个ReviewAction枚举,包括APPROVE(通过)、REJECT(驳回)、SUBMIT(提交)、PUBLISH(发布)等。审核接口接收的参数就是申请ID加上一个动作ID,Service里用一个策略表或switch分发到对应的处理方法。每个动作执行时,校验前置状态,更新状态字段,插入一条审核记录,这样一个方法的职责就非常清晰,测试也好写。
这里有一个从实际场景里来的经验:辅导员初审通过之后,如果评审委员把某个申请驳回了,是直接结束流程还是退回辅导员阶段?不同学校规则不一样,所以代码里不要把流程写死,建议在scholarship_config里加一个audit_mode字段,用SIMPLE表示直接终审,用MULTI表示二级审核。这个设计在答辩时可以主动讲,会显得你对业务流程理解很深入。
3.5 结果公示与数据导出
公示模块由一个公告发布接口加一个公示列表页面组成。管理员选择已经评审完成的奖学金,一键发布公示,公示内容从该奖学金的晋级名单中生成。公示列表对学生可见,同时显示公示起止时间,公示期内学生可以反馈异议,这个反馈功能用一个feedback表存投诉内容、关联的申请ID和反馈人,管理员能看到并处理。
数据导出是个容易被低估的模块。很多学生做一个系统,界面操作挺顺,一提到导出Excel就卡住了。用Apache POI写导出逻辑并不复杂,但要处理几个细节:列名要清晰、内容要合法、大数据量的分页导出。更省事的方案是用EasyExcel,这个阿里开源的库封装了POI,读写Excel的内存占用小很多,而且注解式指定列名和顺序,代码非常短。答辩时如果评委问"能不能导出审批表打印",你只要说用了EasyExcel写个导出模板,现场跑一遍基本就是送分题。
4. 实操过程与关键代码实现
4.1 数据库初始化脚本
这里给出核心表的精简DDL,重点关注状态字段、唯一索引和冗余字段的设计:
CREATE TABLE `application` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `student_id` bigint(20) NOT NULL COMMENT '学生用户ID', `scholarship_id` bigint(20) NOT NULL COMMENT '奖学金配置ID', `status` varchar(20) NOT NULL DEFAULT 'DRAFT' COMMENT '当前状态', `total_score` decimal(10,2) DEFAULT NULL COMMENT '评审总分冗余', `rank_no` int(11) DEFAULT NULL COMMENT '排名', `create_time` datetime NOT NULL, `update_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_student_scholarship` (`student_id`, `scholarship_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;给student_id和scholarship_id加唯一索引,是为了防止同一个学生对同一个奖学金重复提交申请。这个约束用代码检查也能做,但数据库层面的唯一索引才是最后一道防线。并发场景下两个请求同时插入,唯一索引会在数据库层直接拦住一个,比先查询再插入的代码可靠得多。
再看评分明细表:
CREATE TABLE `score_detail` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `application_id` bigint(20) NOT NULL, `judge_id` bigint(20) NOT NULL, `item_name` varchar(50) NOT NULL, `score` decimal(10,2) NOT NULL, `create_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_app_judge_item` (`application_id`, `judge_id`, `item_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;评委对同一个申请的同一评分项只能打一次分,这是由唯一索引保证的。如果你只用代码判断"是否已经打过",高并发时两个请求同时通过查询判断,就会插入重复数据。
4.2 申请提交接口实现
申请提交是典型的"事务+状态校验"场景,代码骨架如下:
@Service public class ApplicationServiceImpl implements ApplicationService { @Transactional(rollbackFor = Exception.class) public Long submit(ApplicationSubmitDTO dto) { // 1. 校验申请时间是否在奖学金配置的范围内 Scholarship scholarship = scholarshipMapper.selectById(dto.getScholarshipId()); if (scholarship == null || !scholarship.isInApplyPeriod()) { throw new BizException("当前不在申请时间内"); } // 2. 校验唯一性(数据库唯一索引做兜底) Long count = applicationMapper.selectCount(new LambdaQueryWrapper<Application>() .eq(Application::getStudentId, dto.getStudentId()) .eq(Application::getScholarshipId, dto.getScholarshipId())); if (count > 0) { throw new BizException("请勿重复申请"); } // 3. 保存申请主记录 Application application = new Application(); BeanUtils.copyProperties(dto, application); application.setStatus(ApplicationStatus.PENDING_REVIEW.getCode()); applicationMapper.insert(application); // 4. 批量保存材料 if (CollectionUtils.isNotEmpty(dto.getMaterials())) { for (MaterialDTO materialDTO : dto.getMaterials()) { Material material = new Material(); material.setApplicationId(application.getId()); material.setName(materialDTO.getName()); material.setFilePath(materialDTO.getFilePath()); materialMapper.insert(material); } } return application.getId(); } }这里有个细节:DTO里的字段和实体字段不完全一致,所以不能用BeanUtils直接复制整个对象,需要把表单特殊字段单独处理。比如申请时间不能由前端传,要在后端取当前时间;状态字段前端传什么都没用,一律由后端默认赋值。
4.3 文件上传的工程化处理
证明材料上传是另一个高频踩坑点。用Spring MVC的MultipartFile接收文件,核心是存储路径的处理。开发环境把路径写死在配置里没关系,但要注意操作系统差异——Windows路径分隔符是反斜杠,Linux是正斜杠,用File.separator处理,或者统一用斜杠让Java自行适配。
文件命名一定要防重。直接用原始文件名会出两个问题:不同学生可能传同名文件导致覆盖,非法字符可能导致路径穿越风险。安全做法是用UUID加时间戳拼新文件名,原始文件名只作为展示用存数据库字段。
文件大小限制也别漏掉。一般材料都是图片和PDF,建议限制每个文件不超过5MB,类型限定jpg、png、pdf。Spring的spring.servlet.multipart.max-file-size默认只有1MB,不调整的话传大文件会直接报错,而且报错信息不容易第一时间想到是这个配置的问题。
4.4 使用MyBatis-Plus做动态条件分页查询
后台管理页面列表很多,每个列表基本都要按条件筛选加翻页。闲得慌去手写分页SQL是纯浪费时间,MyBatis-Plus的分页插件配置好之后,直接用LambdaQueryWrapper写条件就行:
Page<Application> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Application> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(StringUtils.isNotBlank(scholarshipId), Application::getScholarshipId, scholarshipId) .eq(StringUtils.isNotBlank(status), Application::getStatus, status) .like(StringUtils.isNotBlank(keyword), Application::getStudentName, keyword) .orderByDesc(Application::getCreateTime); applicationMapper.selectPage(page, wrapper);这段代码非常典型的体现了Wrapper的威力:eq的第一个参数是boolean条件,只有当前端传了筛选值时才拼接,条件为空就跳过。写完这个,管理端所有列表查询的逻辑基本就被统一了,不用为每个列表单独写一个if判断分支。
分页插件记得在配置类里注册,新版MyBatis-Plus用MybatisPlusInterceptor加PaginationInnerInterceptor,数据库类型要指定MYSQL,这个配置漏了的话page查询不会自动拼LIMIT,数据量一大就会把全表数据查出来。
5. 常见问题与排查技巧实录
5.1 文件上传总报错,页面却看不到详细原因
这是最高频的问题。表现是接口返回500,前端控制台只有"Internal Server Error",后端日志也经常被吞掉。
排查顺序先确认三件事:第一,spring.servlet.multipart.enabled是否开启,默认是true没错,但如果你用了自定义的WebMvcConfigurer,可能把它覆盖掉了;第二,multipart.max-file-size是否满足材料大小;第三,上传目录是否有写入权限。我见过最隐蔽的一种情况是,Linux服务器下application.yml里写的"s:/data/upload",忘了创建这个目录,Spring创建文件时抛出FileNotFoundException,但日志被全局异常处理器的catch逻辑吞了,只返回一个空洞的失败提示。
解决方案是写一个全局异常处理器,把MultipartException、MaxUploadSizeExceededException单独捕获,返回明确中文提示。既方便排查,也直接提升了用户体验。
5.2 学生重复提交申请导致脏数据
明明代码里有查重逻辑,线上还是出现了同一个学生对同一个奖学金的两条申请。原因通常是前端多次点击提交按钮,或者学生开两个页面同时提交。数据库层面如果没有唯一索引兜底,代码查重永远有并发窗口。
解决方案就是我前面说的:在application表上建立(student_id, scholarship_id)唯一索引,代码查重和数据库约束双保险。同时前端在提交成功后disable按钮并改成"提交中"状态,双击请求直接拦掉。
如果项目里已经有重复数据,先写SQL合并清理,再补唯一索引,顺序不能反,索引加不上也会报错。
5.3 登录状态莫名其妙失效
前后端分离项目里常见这个问题。表现是调试时登录明明成功了,token也存进localStorage了,但下一秒请求就401。
排查步骤:打开浏览器开发者工具,看请求头的Authorization是不是真的带上了。很多人在前端封装的axios里写了请求拦截器,但拦截器代码没生效,或者拦截器读本地token的key名和后端解析时不一致。另一个典型问题是JWT过期时间配太短,比如把过期时间设成30分钟,学生填个申请表单超过30分钟就被踢下线了。对毕设演示场景来说,token过期时间建议设到24小时以上,避免答辩现场演示时突然过期。
还有一个很多人没意识到的问题:第一次排查跨域时图省事在Controller上加@CrossOrigin,之后又写了一个全局CorsFilter,两者叠加导致预检请求返回了多个Allow-Origin头,浏览器直接拦截。建议统一用一种方式配置CORS,不要混用。
5.4 日期时间字段在前后端传输中的时区问题
Spring Boot默认Jackson序列化LocalDateTime时,格式是一长串数组数字,前端根本没法显示。更隐蔽的问题是MySQL驱动和服务器时区不一致,导致存入数据库的时间比实际时间早8小时或者晚8小时。
处理方式很简单:application.yml里设置时间格式和时区,数据库连接串后面加serverTimezone=Asia/Shanghai,Jackson的LocalDateTime序列化格式也显式指定。三个地方统一用Asia/Shanghai,就能避免绝大多数时间问题。
5.5 逻辑删除导致的数据关联失效
很多教程推荐用逻辑删除代替物理删除,MyBatis-Plus也提供了@TableLogic注解,加了之后delete操作自动变成update操作。但新手容易忽略的是:加了@TableLogic后,所有查询都会被自动追加deleted=0条件,如果你在关联表里用原生SQL或者子查询,MyBatis-Plus不会自动帮你加这个条件,结果就是逻辑删除的数据还在,但列表查不出来,或者统计数字对不上。
对策是:凡是涉及逻辑删除字段的表,查询全部走MyBatis-Plus的wrapper,不要手写原生SQL;确需手写SQL时,记得手动加deleted=0条件。
6. 从启动到答辩:把项目做成能交付的毕业设计
6.1 代码之外必做的三件事
第一件事是把README写好。不要只写一句"奖学金管理系统"就完事。README里至少包含:版本环境清单(JDK、Maven、MySQL版本)、启动步骤(建库、改配置、启动后端、启动前端)、默认账号密码、目录结构说明。我见过太多学生答辩前三天跑来问"为什么启动不了",最后发现是JDK版本和课程作业里用的版本不一致,出问题就出在环境说明缺失。
第二件事是准备测试数据。系统里一定要有真实可演示的数据,不要给评委看空表。设计一组完整的模拟数据:三个角色账号、五个学生申请、不同类型的奖学金配置、完整的评分记录和公示记录。演示时必须从学生登录开始走一遍全流程,让评审看到状态变化。
第三件事是录制演示视频。不要让答辩现场当观众面操作翻车,环境问题遇上网络问题都不可控,提前录好一段流畅操作的视频是保底方案,现场演示时也可以边操作边用视频配合。
6.2 论文结构的常规套路
论文核心章节一般分四块:需求分析、系统设计、系统实现、系统测试。奖学金系统的论文有天然的素材优势,需求分析可以画用例图,系统设计可以画E-R图和架构图,系统实现贴核心代码配流程图,系统测试列功能测试用例和结果表格。
这里有个容易丢分的地方:很多论文的测试章节只写"功能正常"四个字。正确的做法是列出具体的测试用例,例如"测试用例TC-01:学生提交申请后,状态变为待审核,辅导员登录可见该申请",然后附上预期结果、实际结果、是否通过。这种表格一张纸就能体现出你做了系统性测试,比在答辩现场嘴上说"我都测过"有说服力得多。
6.3 可扩展的方向建议
如果学有余力想加分,可以考虑三个方向。第一个是数据可视化,把申请人数、获奖率、各学院获奖占比用图表展示,后端聚合数据,前端用ECharts渲染,这个对就业面试也有直接帮助。第二个是消息通知,学生提交后、辅导员审核通过后,发邮件或小程序订阅消息通知,技术上可以对接简单的消息队列,或者直接用线程池做异步发送。第三个是移动端适配,做一个简化版的小程序或APP,学生端的使用场景本来就是随时随地填材料,移动端更贴合真实需求。
这几个扩展方向不需要全做,挑一个认真做成模块,就足够在论文的创新点章节里写一大段了。但有一个前提:核心流程必须稳定可靠,别在扩展功能上花掉了修复核心Bug的时间。
我个人带过这么多学生做这个题目的体会是:奖学金系统技术难度不算高,真正拉开差距的是工程完成度和对业务细节的理解。状态机是否清晰、异常情况是否考虑周全、材料文件是否真的能下载打开、权限控制是否所有接口都生效,这些细节决定了答辩时你是被评委追问搪塞过去,还是能在台上从容地解释每一个设计决策。最后分享一个很实在的小技巧:把所有接口的测试用例表、数据库E-R图、状态流转图放在一个doc里随身带着,答辩前自己过一遍,比临时翻代码找思路管用得多。