资讯动态

基于SSM框架的高校奖学金评定系统设计与实现

发布时间:2026/10/13 3:50:06 来源:尧图企业网站定制
奖学金评定这件事做过的人才知道有多头疼。每学期末辅导员办公室堆成山的申请表、纸质成绩单、加分证明评审小组关起门来对着Excel来回比对学生等公示等到焦虑公示期还有人质疑分数算错又重新人工核算……我当年做这个高校学生奖学金评优系统的毕业设计就是因为亲眼看着身边的同学被这套流程折腾得够呛。用Java的SSM框架把全流程搬到线上之后申请、审核、打分、公示、异议处理一条龙搞定不光毕业设计拿了优秀后来还真被学院的老师拿去用了两个学期。这篇文章就把我整个设计和实现的过程掰开揉碎讲一遍从需求分析到数据库建模从权限设计到动态评分再到文件上传、Excel导入这些容易翻车的细节最后附上我自己踩过的坑和排查经验。无论你是正在找毕设方向还是想在学校里搞一套真正能用的管理系统都可以直接参考。1. 项目背景与需求拆解1.1 高校奖学金评定为什么需要一套系统很多人觉得奖学金评定不就是“成绩排名前几名就发钱”吗真做起来完全不是这么回事。高校奖学金通常分好几类有学业奖学金、励志奖学金、单项奖学金、社会捐赠奖学金等每一种的评定标准都不一样。拿学业奖学金来说学习成绩只占一部分权重还要叠加思想品德分、文体活动加分、社会工作加分、学术科研加分最后按综合分排名。过去这些加分项靠学生自己填写申请表、附证明材料辅导员逐一核验评审委员会人工打分。问题显而易见纸质材料容易丢失加分标准不统一同一个活动不同班级加分规则不一样公示期学生质疑也没有一个正规的反馈渠道。我做这个系统之前在某个院校的教务处蹲过两天观察他们的流程印象特别深的是评审老师桌前堆着两排A4纸手里拿着计算器在算综合分旁边本子上记着一串备注。所以这套系统要解决的其实不是“计算”的难题而是“流程”的混乱——让从学生申请到最终公示的每一步都有记录、有依据、可追溯。1.2 角色与核心流程梳理系统的用户角色我一开始就定了四种学生、班级辅导员、院系评审管理员、校级管理员还预留了一个总管理员。不同角色在流程里做的事情完全不同学生发起申请时要把基本信息、成绩数据、获奖经历、社会实践经历填进去并上传证明材料的扫描件。辅导员先做一轮资格初审主要是核对申请条件——成绩有没有挂科、提交的材料是否齐全、加分项是否属实。辅导员初审通过后材料进到院系评审环节这里通常会按奖学金类型设置不同的评分规则评审小组给各项打分系统自动汇总出综合排名。院系评审结果上报到校级管理员由校级管理员审核公示内容并在系统里发布公示。公示期内学生可以提交异议申诉管理员处理申诉后再确认最终名单。这个流程画成图并不复杂但隐藏着两个关键设计点第一状态流转必须严格受控申请从“草稿”到“已提交”再从“辅导员初审”到“院系评审”每一步都不能跳第二每一步操作都要留痕谁在什么时间把状态改成了什么要完整记录不然公示期出问题没法追溯。1.3 技术选型背后的权衡技术栈我选了Java SSM也就是Spring Spring MVC MyBatis这套组合。现在Spring Boot已经很流行了为什么还要用SSM两个原因。第一这是我毕业设计的要求学校教学大纲里指定的技术路线就是SSM框架这也是很多高校计算机专业毕设的经典选题方向。第二SSM虽然配置比Spring Boot繁琐但它把分层结构暴露得很彻底对理解Java Web开发的底层原理非常有帮助。在面试时能讲清楚Spring IoC怎么管理Bean、Spring MVC的DispatcherServlet怎么分发请求、MyBatis怎么把Mapper接口和XML映射文件绑定比单纯说“我用了Spring Boot自动配置”更有说服力。前端我用了JSP Bootstrap jQuery组合。有人可能会说都什么年代了还用JSP但说实话毕设项目的核心价值在业务逻辑和流程设计上前端保证干净好用就行。Bootstrap做后台管理界面非常快jQuery处理表格交互和AJAX提交也很顺手。如果你想让项目看起来更现代一些也可以换Vue或者Layui但核心的后端设计思路完全一样。数据库用的MySQL这个没什么悬念。服务器用Tomcat做容器构建工具用的Maven整个项目大概是一个标准的Maven父子工程结构——一个parent聚合模块下面分管理后台admin和后台服务web两个子模块后台服务里放核心业务逻辑和接口管理后台放页面和控制器。2. 系统总体设计与数据库建模2.1 分层架构设计项目的包结构我是按经典的三层架构来组织的这一点在写论文时也是重点论述对象。Controller层只做参数接收和视图转发不掺和任何业务逻辑Service层承载核心业务包括状态校验、评分汇总、事务控制Dao层通过MyBatis的Mapper接口跟数据库打交道SQL写在XML映射文件里。如果只做到这三层项目可以跑但会越写越乱。我在实际开发中增加了一个DTO数据传输对象层和一个枚举常量包效果非常好。DTO用来解决“页面需要的数据”和“数据库存的数据”不一致的问题。比如学生提交申请时页面传来的表单数据需要拆成基本信息、加分项列表、材料文件三个部分分别入库如果没有DTOController的参数列表会写得又臭又长用Map又不安全。加了DTO之后用一个ScholarshipApplyDTO对象接住整个表单Service层再拆开处理代码清爽很多。VO视图对象我用了但不多主要是给前端返回统计结果之类的复合数据比如某个院系的申请人数统计。很多人一开始分不清DTO和VO的区别我的理解很简单DTO是接口的入参VO是接口的出参。思路清晰之后前后端联调的时候接口文档天然就规范了。2.2 数据库表设计与核心字段数据库设计是这类管理系统的灵魂我花了至少三分之一的时间在这上面。核心表一共十一张左右这里挑几张重点讲。用户表user需要区分角色我设计了role字段做区分但权限是另一套表单独管理的没有直接把权限字段贴在用户表上这样以后扩展角色会灵活很多。学生表student通过user_id和用户表关联额外存了学号、院系、专业、班级、入学年份这些基本信息。注意学号要加唯一索引这个在导入数据时踩过坑后面细说。奖学金类型表把每种奖学金的名称、等级、金额、适用范围、评定周期存起来。奖项类型和“评审要求”其实是多对多关系一个类型的奖学金可能要求多条评审条件一条评审条件也可能被多个奖学金类型引用所以单独建了一张关联表。申请表是核心中的核心字段包括申请学生ID、奖学金类型ID、学年学期、成绩排名、班级排名、绩点、德育成绩、综合测评成绩、加分项汇总分数、申请状态、提交时间等。加分项分数我单独建了一张加分项明细表来存理由很简单——表格页面上的“综合分”是一个计算结果如果哪天评审规则变了涉及加分项的计算逻辑变了必须能回溯原始数据重新计算而不是把算好的总分当成源头数据。材料表存学生的资质证明文件路径和类型支持多文件上传每个申请可以对应多条材料记录。评审记录表记录评审小组成员给每个申请的各项打分包括评价内容和总分。公示表存公示的起止时间、公示内容、状态。异议处理表接住学生的申诉关联申请ID、申诉原因、处理结果和处理时间。2.3 关键表关系说明表之间的关系我用外键在数据库层做了约束同时在应用层也做了校验。很多人觉得外键影响性能就不爱用但管理类系统数据量根本到不了需要考虑外键性能的量级外键的完整性约束反而能防止很多脏数据问题。比如新增材料记录时如果申请ID在申请表里不存在外键约束直接就能拦住不用等代码运行到一半才发现问题。申请明细和加分项明细是1对N的关系评审记录跟申请、评审用户、奖学金类型都有外键关联。公示异议表跟申请表的关系是一个申请可以发起多次异议所以设计成1对N而不是在申请表上加一个“是否异议”字段了事这样方便追溯到历史记录。这里有一个设计决策值得展开说综合分的计算我放在Service层完成而不是存到数据库然后靠SQL去算。因为加分项规则太多有按比例折算的、有封顶的、有条件判断的用SQL写这些逻辑又难维护又难读。Service层里用策略模式维护加分计算规则以后学校调整评审细则改一个策略类就够不用动数据库结构。3. 核心功能模块实现与实操细节3.1 登录鉴权与角色权限实现登录这块我用了Spring MVC的拦截器加Session来做没有引入Spring Security。原因很直接毕设项目的重点是奖学金业务流程不是安全攻防Spring Security的过滤链配置和权限表达式学习成本比较高如果学期时间紧容易把自己拖进去。用拦截器实现一个简单的角色路由校验代码量不大效果也足够。我定义了AdminInterceptor和StudentInterceptor两个拦截器前者拦截所有/admin/**的路径检查Session里的loginUser是否存在、角色是否匹配后者拦截/student/**路径。要注意的一个坑Spring MVC的拦截器默认只拦截Controller里面的方法不拦截静态资源但JSP页面如果放在WEB-INF下默认是安全的如果某些页面直接放在webapp根目录下要记得在拦截器配置里把静态资源路径排除掉否则刷新页面时CSS和JS全挂了看起来就像样式丢了。权限模型我用了“用户-角色-权限”三张表但在代码里没有做细粒度的按钮级权限只做到了菜单级。比如辅导员登录后能看到“待审核申请”菜单学生登录后看不到。评审管理员能看到“评分管理”菜单。如果你觉得按钮级别权限更能加分可以在Shiro的授权里加注解实现但工作量会大不少。Session的管理还有一个细节学生提交了申请之后我要在Session里实时刷新他的申请状态不然辅导员刚审核通过一个申请学生这边点刷新页面还是“审核中”用户就会觉得系统有延迟。我在审核操作完成后主动重置了相关学生的Session里存储的申请状态值这个小小的处理让整个系统体验提升很明显。3.2 奖学金申请与材料上传模块奖学金申请是整个系统的入口也是学生用得最多的功能。页面设计上学生选择奖学金类型后系统会先做一次资格预校验比如直接查有没有挂科记录、是不是贫困生、是不是必修课全部通过如果预校验不过直接给出红字提示不让学生进入材料填写页面。这一步能把无效请求挡在服务端之外减轻后续审核压力。材料上传用的是Commons FileUpload配置了上传大小限制。这里有个坑我没躲过去默认的FileUpload限制单文件大小但如果学生一次传10个文件每个文件都在限制以内总额却可能超出服务器内存上传过程中会报到内存溢出。解决方案是在Commons FileUpload里同时设置fileSizeMax单文件限制和sizeMax总请求大小限制这样一次请求的总体积就被框死了。上传文件的命名我用了UUID原始文件名后缀存储路径按学年和学号分了目录比如/upload/2024-2025/202100123/好处是后续人工去服务器抽查材料时路径非常直观好找。另外我在表里存的是相对路径而不是绝对路径这样项目换服务器部署时不用改数据库里的路径。这一点看起来不起眼但如果你把D:\upload\这种绝对路径存进数据库下次部署到Linux服务器就等着全部改一遍吧。3.3 评分评审模块的实现思路评审模块的核心逻辑分为两部分评分和汇总。评分由评审管理员给每个申请的各维度打分比如学习成绩占比70%、思想品德占比10%、文体实践占比10%、科技创新占比10%——这个权重最好做成可配置的不要写死在代码里。我在数据库里加了评分规则表每条规则包含奖学金类型、维度名、权重上限。评审打分时页面动态加载对应奖学金类型的规则按规则录入分数然后计算加权总分。这里有一个容易出错的点有些学校奖学金评审采用“去掉一个最高分、去掉一个最低分”的机制如果有三位评审老师打分得分不能直接算平均数。我花了点心思把这个也做进去了在Service层写了均值计算逻辑会先去掉异常分数再求平均。毕设阶段做进去的好处是论文答辩时评委老师会认为你考虑问题很周全不是只做了个简单CRUD。评审过程中重要的一环是“排名预览”。系统实时展示当前奖学金类型的申请学生排名但排名数据不允许导出避免评审结果在未公示前泄露。我用了PageHelper插件做分页排名预览接口没有加入缓存因为数据量不大每次直接从库里查综合分排序结果是最新的也避开了一致性问题。3.4 公示与异议申诉流程公示这块要处理的核心是“时间窗”和“状态锁”。公示开始后系统自动锁定该批次奖学金申请的所有数据管理员不能再修改评分结果。公示期内学生可以对公示名单里的某个申请发起异议必须填写异议原因和上传证据文件。公示期结束后管理员处理所有未解决的异议处理完成后最终名单才能解锁进入“已确认”状态。状态锁我用数据库的乐观锁来实现。申请表里放了一个version字段每次更新时在更新语句里加上where version #{oldVersion}如果更新影响行数为0说明并发情况下数据已经被别人改过这时程序抛出异常或提示“数据已被修改请刷新后重试”。这个方法比直接锁表要优雅也让答辩时可以多聊两句并发控制的话题。异议处理的界面我做了双栏布局左边是异议列表右边是对应的申请详情和证明材料。管理员处理时可以选择“认定异议成立修改分数并重新排名”或“异议不成立维持原结果”两种操作都会记录处理日志并通知发起人。处理日志我用了AOP切面统一记录注解标在处理方法上就自动记录不用每个方法手动写日志代码省了非常多重复工作。4. 关键代码实现与踩坑实录4.1 用MyBatis实现多条件动态查询奖学金申请列表的查询条件是出了名的多按学生姓名、学号、奖学金类型、申请状态、学年、院系、排序方式……我最开始用拼接SQL字符串的方式写写到最后自己都快看不下去后来换成MyBatis的动态SQL用where加if标签解决。这里贴一段当年写核心查询的骨架代码思路select idselectApplyList resultMapApplyResultMap SELECT a.*, s.student_name, s.student_no, st.college_name FROM scholarship_apply a LEFT JOIN student s ON a.student_id s.id LEFT JOIN student_info st ON s.student_info_id st.id where if teststudentName ! null and studentName ! AND s.student_name LIKE CONCAT(%, #{studentName}, %) /if if testscholarshipTypeId ! null AND a.scholarship_type_id #{scholarshipTypeId} /if if teststatus ! null AND a.status #{status} /if if testcollegeId ! null AND st.college_id #{collegeId} /if /where ORDER BY a.create_time DESC /select这段代码本身不复杂但有一个细节值得注意where标签会自动去掉第一个条件前面的AND避免手写WHERE 11这种不优雅的写法。另外LIKE拼接的CONCAT用法比直接在#{}里塞%参数更安全不会出现SQL注入问题也能正常走索引。4.2 文件上传与在线预览在线预览这个需求一开始是老师提的说评审人员不想下载每个附件再看希望直接在浏览器里看到材料内容。PDF预览好做用浏览器的内嵌PDF插件就行。但JPG、PNG这种图片格式的预览在各浏览器上的行为不太一致IE上尤其不好用。我的处理方案是后端根据文件扩展名返回不同的Content-Type并设置Content-Disposition: inline这样浏览器能预览就预览不能预览就下载。如果是Word或Excel文件要在线预览就比较折腾了。我没有在毕设阶段引入OpenOffice转换用的是最简单的方案——后端把Office格式统一提示用户下载不强行做预览。说实话这个问题如果要做完整可以接第三方的文档预览服务但成本高、有依赖毕设阶段不太划算。答辩时跟我导师坦诚交流过这个取舍他也认为在一个管理系统中存储、追溯、权限控制比文档转换优先级更高。文件的上传我是分两步走的先把文件以临时状态保存到磁盘等整个申请表单提交成功后再把临时文件标记为正式状态。这样做的好处是如果学生填了一半发现信息有问题关闭页面离开不会在服务器上留下一堆垃圾文件。我写了一个定时任务每天清理超过24小时未提交的临时文件这个清理逻辑成了后来老师夸“系统有人性化考虑”的一个点。4.3 Excel导入导出的典型问题奖学金类型设置、学生基本信息的批量导入导出Excel处理我用的是POI库。刚开始写导入时我按行读数据然后一条条往数据库插10条数据没问题数据量到了5000条就明显卡顿。后来加了批量插入用SqlSession的batch模式速度提升非常明显。批量插入还有一个隐藏的好处是减少了事务开销因为一次提交一个批次而不是每行提交一次。POI处理Excel时遇到最多的坑是单元格类型判断。学生信息表里“学号”这一列如果Excel单元格格式是文本读出来是字符串直接可用但如果格式是常规数字学号读出来会变成double类型比如202100123变成2.02100123E8。我在导入工具类里加了一个强制转换方法先判断cell.getCellType()数值类型统一转成BigDecimal去掉小数点后再转字符串字符串类型直接trim后使用。这个小函数看着不起眼但没用它之前导入的数据里出现过十几次学号错乱排查起来非常痛苦。导出功能的实现相对简单但要注意大数据量内存溢出。我用的方式是分批查询配合SXSSFWorkbook它能在内存里只保留一定行数的数据其余写入磁盘临时文件几百MB的Excel导出也不会把JVM撑爆。如果直接把一张大表的全部数据加载到内存再导出数据量到几十万行时非常容易OOM。5. 常见问题与排查技巧实录5.1 高频问题排查把整个项目跑起来的过程中遇到的奇葩问题不少整理一个速查表方便你对照系统启动后部分页面报404后台无报错日志。大概率是Controller的请求映射遗漏或者是Spring MVC注解扫描的包路径没配对把ComponentScan的basePackage改到控制器所在包即可。我之前把mvc:annotation-driven写进了错误的位置导致部分注解失效排查了两天才发现是配置文件的namespace顺序问题Spring的配置文件对节点顺序有严格要求Bean定义要在后、mvc和context的前缀配置要在前。上传文件时提示“The request was rejected because its size exceeds the configured maximum”。这就是我之前说的Commons FileUpload总大小限制问题。去commons-fileupload的配置里同时设置maxFileSize和maxRequestSize。注意如果是Spring Boot项目对应设置是spring.servlet.multipart.max-file-size和spring.servlet.multipart.max-request-size。Excel导入数据时部分记录无法插入报唯一键冲突。原因基本是数据表里学号字段有唯一索引但Excel中存在重复学号或学号两侧有不可见空格。处理办法导入前先做一次重复检查给出“第X行学号重复”的提示入库前对学号做trim和正则校验比如^[0-9]{6,12}$之类不符合的直接跳过并记录错误原因。评审评分保存后排名没有变化数据被覆盖。查看日志后多半是并发问题A评审和B评审同时保存时后保存的人把先保存的结果覆盖了。解决办法就是前面提到的乐观锁version字段或者考虑评分保存时对整个申请进行行锁用SELECT ... FOR UPDATE。管理类系统并发用户不多行锁其实就够用。学生登录后看到所有申请记录而只应看到自己的。这个问题通常是SQL里少了AND student_id #{currentStudentId}条件或者Controller里获取当前登录学生ID时取错了Session key。我在登录时统一把当前学生信息放进了Session的loginStudent对象并且写了一个工具方法获取当前学生ID避免每个Controller里重复写Session取值逻辑。5.2 性能优化的几个小技巧管理类系统的数据量一般不会特别大但做毕设的时候如果查全列表会很慢大概率是N1查询的问题。比如列表页面循环调用Mapper查每个学生的材料数量500条申请记录就是500次查询优化方式是把材料数量一次性查出来用GROUP BY apply_id得到结果集然后在内存中匹配。列表页分页查询时建议用PageHelper的分页插件它能自动拦截MyBatis的SQL加LIMIT很好用。但注意一个坑PageHelper的分页参数是挂在当前线程的ThreadLocal上的如果查询方法后面又紧跟了另一个查询分页条件会自动传染到第二条SQL上导致第二条查询也加了LIMIT。解决办法是分页查询和普通查询不在同一个Service方法里连续调用或者干脆把第二种查询改用PageHelper.clearPage()清掉。系统里公示页面的访问量其实不大但首页的“奖学金类型介绍”和“评审进度统计”每次刷新都要查库。我加了一层简单的定时缓存每十分钟更新一次统计数据数据一致性要求不高缓存就能扛住大部分访问。加上缓存之后首页从几百毫秒降到几十毫秒体验完全不一样。JSP页面的静态资源压缩也很重要。我用Maven的resource插件做了一次CSS和JS的简单压缩上线部署时把注释和空白去掉页面体积能小三分之一。如果嫌麻烦不压缩也没问题但至少要把JSP文件放到WEB-INF下避免被用户直接输入URL访问。5.3 环境部署容易忽略的细节部署这套系统涉及的环境问题很多人在自己电脑上开发好好的一到服务器就各种问题。Java环境版本不一致是最常见的。本地用的JDK 1.8服务器装的JDK 11Tomcat版本如果是8.5以下会直接报UnsupportedClassVersionError。解决方案很简单服务器上装对应版本的JDK或者把项目编译的javac参数改成对应版本。这两个问题花了我一下午教训就是先在本地用Docker起一个和服务器一致的环境跑一遍。MySQL的时区问题也容易踩。中国时区是UTC8如果服务器的serverTimezone没设置MyBatis操作DATETIME字段会出现八小时的偏差。在JDBC连接URL上加serverTimezoneAsia/Shanghai能解决但如果服务器用的UTC时区而数据库连接串却是Asia/Shanghai就会出现写入和读取的时间明明在同一个库却差八小时的情况。排查这种问题最快的办法是在数据库客户端里手动查一遍看看存进去的值到底是多少再对比应用日志里打印的时间哪边对不上就修复哪边。部署到服务器之后文件上传目录记得要给Tomcat运行用户写权限否则上传材料时会出现FileNotFoundException但Tomcat日志里看不到明显报错。Linux的环境变量、日志的滚动策略、数据库连接池大小这些小东西虽然跟业务逻辑无关但一个没配好系统在服务器上跑两天就出幺蛾子。6. 一点个人经验总结做这个毕设最大的收获倒不是熟练记住了SSM框架的API而是把“流程”和“状态”这两个词彻底想明白了。以前写代码总想着把功能堆出来就行这次逼着自己去跟真实的评审老师聊、去看他们在纸上怎么打勾怎么算分才知道一个看起来简单的按钮背后要关联多少状态和权限判断。很多同学做管理系统喜欢一开始就埋头写代码写到一半发现流程不通回来改表结构来来回回浪费两三周。我的建议是不管题目多简单第一周逼自己把用例图画清楚把每个状态的流转写成一页纸贴在显示器旁边代码写得再丑方向不会错。另一个建议是答辩之前一定要把项目的“边界”想清楚。你做的不是淘宝不需要分布式、不需要秒杀、不需要超大流量。把校内奖学金评定的流程理顺、权限分清、状态可追踪就已经是一个合格且完整的系统。答辩时如果你能主动说出系统设计中有哪些取舍、某个功能为什么不做、某个方案为什么这么选比堆砌一堆高大上但没跑通的中间件要加分得多。最后再分享一个小技巧项目里所有涉及“金额”“分数”“人数”的地方前端展示统一用BigDecimal格式化后端所有计算用BigDecimal别用double。你可能会觉得分数而已差0.01无所谓但奖学金公示的时候一分之差就可能是几百块钱计算误差会被学生拿放大镜看的。用BigDecimal虽然写起来啰嗦了一点但能让你在答辩时非常硬气地说一句系统所有金额和分数计算均采用高精度类型避免浮点误差。

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

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

免费获取报价 →
↑