高校学生竞赛成果管理系统毕设方案:Spring Boot+Vue3全栈实践

发布时间:2026/10/9 11:25:00
高校学生竞赛成果管理系统毕设方案:Spring Boot+Vue3全栈实践 如果你正在纠结计算机毕设选什么题大概率会刷到“高校学生竞赛成果管理系统”这个方向尤其在Java Web这个大类里。说实话我第一次看到这题目也觉得很普通第一反应是“又一个全栈CRUD”换成学生、课程、成绩的操作而已。但真正动手之后才发现它把三个独立场景揉在了一个系统里学生竞赛成果申报、学院和学校两级审核、以及按学院/级别/年份的统计分析。正因为它看着不复杂做起来又刚好覆盖了Web开发的核心技术点我才最终选了它。前前后后写了六周从需求设计到部署答辩全走了一遍。这篇文章不写虚的直接把我最终的技术方案、数据库设计、核心代码思路、踩过的坑全部列出来给准备选这个题目的同学一份“施工参考”。1. 这个项目到底在解决什么问题需求拆解决定技术选型1.1 从标题里拆出三个业务闭环这个系统的完整名称可以拆成三段高校学生竞赛成果管理、数字化管理平台、申报与统计分析。第一段说的是领域第二段说的是形态第三段才是核心功能。申报与统计分析意味着系统不只存数据而是要处理一条完整的业务链学生参加竞赛获得奖项后登录系统填写成果信息、上传证书附件学院审核真实性学校复核认定最后按学院、竞赛级别、获奖等级等维度生成统计报表。如果只是做一套增删改查答辩一定很浅。但把流程和统计做出来它就变成一个能讲清楚业务逻辑的项目。我最后确定的角色有三类普通学生、学院管理员、学校管理员。学生负责报名竞赛和申报成果学院管理员负责审核本学院学生的申报材料学校管理员负责最终认定和查看全局统计。还有一种可选角色是指导教师但为了控制复杂度我把教师信息设计成了成果申报里的一个字段没有单独做教师登录端。这个取舍让我少写了两张关联表功能却不缺。1.2 模块清单别一上来就写代码很多同学拿到题目第一件事是建工程这是最容易返工的做法。我花了两天画页面草图和模块清单最后定下来这么几个模块系统管理、竞赛管理、报名管理、成果申报、审核管理、统计分析、个人中心。系统管理管用户、学院和角色竞赛管理维护竞赛信息报名管理让学生先报名再基于报名记录申报成果审核管理负责状态流转和审核日志统计分析做排行榜、趋势图和导出。每个模块之间是闭环的不是零散的功能点。这个清单导出的结论是系统需要至少八张核心表而不是常见的三张。后面数据设计会具体说明。1.3 技术选型Spring Boot 3 MyBatis Plus前端为什么用 Vue3我选择的技术栈是后端 Spring Boot 3.1.5 MyBatis Plus 3.5.3 MySQL 8.0前端 Vue3 Vite Element Plus统计图用 ECharts导出用 EasyExcel。选 Spring Boot 而不是 JavaWeb 原生 Servlet是为了把精力放在业务逻辑上不用自己写过滤器、配置连接池、封装 JSON。Spring Boot 内置 Tomcat打包后一个 jar 就能跑答辩部署非常方便。MyBatis Plus 的好处是单表查询可以少写很多 XML分页插件、逻辑删除、自动填充都是现成的。但我不建议在这个项目里使用 Spring Security因为系统只有三个角色用拦截器加注解就能控制权限自己写一遍反而能讲清楚认证授权原理。如果引入 Spring Security学习成本高答辩时还容易被追问底层细节。前端选 Vue3 是因为统计报表需要异步渲染如果只用 Thymeleaf 服务端渲染ECharts 的数据接口会写得比较别扭。Vue3 Element Plus 做后台管理界面速度很快表格组件封装了分页、排序、表单校验。唯一的代价是要维护前后端两个工程但这也是目前企业开发的主流形态写在毕业论文里更说得过去。层级使用技术为什么选它后端Spring Boot 3.1.5内置Tomcat打包部署简单生态成熟持久层MyBatis Plus单表CRUD省代码分页和逻辑删除开箱即用数据库MySQL 8.0免费稳定课程设计和毕设最常用前端Vue3 Element Plus组件完善开发后台管理界面效率高图表ECharts免费开源仪表盘和趋势图效果好导出EasyExcel大数据量导出不爆内存API简单2. 数据模型与表结构设计一张成果表撑起申报、审核和统计2.1 核心实体关系报名和申报要分开我的表设计里最关键的决策是把报名和成果申报分成两张表。competition_apply存储学生报名记录achievement_apply存储获奖成果信息。如果学生参加一个竞赛赛前报名一次赛后获奖再申报一次两个动作时间差可能好几个月状态完全不同。主表achievement_apply的字段包括申报编号apply_no、学生student_id、竞赛competition_id、获奖等级award_level、获奖名称award_name、证书编号certificate_no、证书文件certificate_url、原始文件名certificate_file_name、申报状态status、提交时间、审核人、审核时间、审核备注、认定积分。设计时把证书编号设为逻辑上的唯一约束同一学生同一竞赛不能重复申报。这里要多说一句不要把所有字典值直接存中文字符串比如获奖等级、竞赛级别代码里统一用数字编码。competition.level的约定是 1 校级、2 省级、3 国家级achievement_apply.award_level的约定是 1 一等奖、2 二等奖、3 三等奖、4 优秀奖。页面展示时再转换成中文。这样做一是数据库存储干净二是统计 SQL 不用like %国家级%这种模糊匹配三是后续如果要改显示名称只改前端和枚举就能搞定。2.2 审核状态机的设计六个状态四条流转路径成果申报的status字段我设计了六种状态0 草稿、1 待学院审核、2 学院已通过待学校审核、3 学院退回、4 学校退回、5 学校已认定。草稿是学生填了一半没提交的数据不计入统计。提交后进入待学院审核学院管理员审核通过后进入待学校审核学校复核通过后状态变为已认定只有已认定数据才参与统计分析。退回操作有两个层级学院退回和学校退回。退回后学生可以修改重新提交重新提交后状态回到 1。这里必须注意状态流转在代码里要写死不能允许前端直接传from_status和to_status否则有人就能把退回数据改成已认定。我实现时写了一个StateMachine类用 Map 定义合法流转不在合法列表里的直接抛业务异常。2.3 MyBatis Plus 实体类生成建表 SQL小技巧但别指望官方功能网上搜“mybatisplus根据java实体类生成创建表的sql语句”会看到各种说法。实际 MyBatis Plus 官方没有这个能力它只是 ORM不负责自动建表。我的做法是先设计好实体类再用一段自己的反射代码读取TableName、TableField和字段类型生成初步的CREATE TABLE语句打印出来之后手动补充索引和注释。这样做的好处是字段不会漏修改实体类时能保持表结构同步意识。主要字段类型映射规则写在本地工具类里比如 String 映射到VARCHAR(255)Integer 映射到INTLocalDateTime 映射到DATETIME。每次改成实体运行一个 main 方法就能看到完整建表语句再复制到 SQL 文件统一管理。这种方式比手敲几十行 DDL 不容易出错也比引入 Flyway 更轻量。2.4 行级权限的数据范围设计题目里没写权限要求但高校系统天然有“学院”这个维度所以行级权限必须做。我的做法很朴素给user表加了一个college_id字段学院管理员登录后查询成果列表的 Service 层会在QueryWrapper里强制拼接college_id 当前用户所属学院。学校管理员不传college_id就能看全部数据。学生登录后只能操作自己的数据。核心原则是权限校验必须放在后端不能只靠前端隐藏按钮。因为前端传什么参数后端都能收到如果只隐藏按钮懂接口的人用 Postman 改参数就能越权。我的自定义注解RequiresRole(college_admin)加在审核接口上拦截器解析 JWT 后校验角色再配合数据权限拼接双保险。3. 申报审批流程的实现细节状态流转、附件管理与 PDF 打印3.1 申报表单的分步校验学生申报流程看起来是一个表单实际上要校验三类数据竞赛是否已经结束、当前学生是否已申报过该竞赛、证书编号是否已被占用。第一类判断在competition表里比对end_date第二类用student_id competition_id查询唯一记录第三类用certificate_no查重。业务校验放在 Service 层不能只依赖前端必填校验。竞赛结束时间这个条件很容易被忽略。我一开始没加学生还没比赛就能申报成果演示时被老师当场指出来因为数据逻辑明显不对。后来在提交接口里加了if (competition.getEndDate().isAfter(LocalDate.now())) throw new BusinessException(竞赛尚未结束不能申报成果)。3.2 审核流程的核心代码事务一定要包住状态更新和日志审核操作本质上要做两件事更新成果状态、写一条审核日志。这两步必须在同一个事务里否则可能出现状态改了但日志丢了或者日志记录了但状态没变的情况。我的核心实现如下Transactional(rollbackFor Exception.class) public void audit(AuditRequest request) { AchievementApply apply achievementApplyMapper.selectById(request.getApplyId()); if (apply null) { throw new BusinessException(申报记录不存在); } Integer fromStatus apply.getStatus(); Integer toStatus stateMachine.transfer(fromStatus, request.getResult(), request.getRole()); apply.setStatus(toStatus); apply.setAuditRemark(request.getRemark()); apply.setAuditUserId(CurrentUser.getId()); apply.setAuditTime(LocalDateTime.now()); achievementApplyMapper.updateById(apply); AuditLog log new AuditLog(); log.setBizId(apply.getId()); log.setFromStatus(fromStatus); log.setToStatus(toStatus); log.setOperatorId(CurrentUser.getId()); log.setRemark(request.getRemark()); auditLogMapper.insert(log); }事务注解下面必须加上rollbackFor Exception.class不然只对 RuntimeException 生效如果抛出的是自定义业务异常默认是不会回滚的。这是我写完第一版后测出来的问题。3.3 附件上传文件名乱码与路径穿越证书附件是成果申报的证明材料通常上传 PDF 或图片。保存时我用UUID 原始扩展名重新生成存储文件名数据库里同时保存存储路径和原始文件名下载时用原始文件名拼响应头。这样做有两个好处一是避免程序目录下出现中文文件名导致 Linux 下读取失败二是避免用户上传../../etc/passwd这种路径穿越字符。下载文件名要处理编码否则浏览器下载中文文件会乱码String fileName URLEncoder.encode(apply.getCertificateFileName(), UTF-8).replaceAll(\\, %20); response.setHeader(Content-Disposition, attachment; filename*UTF-8 fileName);Spring Boot 3 接收上传文件的限制是默认 1MB证书 PDF 经常超。需要在配置文件里调大spring: servlet: multipart: max-file-size: 20MB max-request-size: 20MB3.4 Web 页面 PDF 打印用浏览器原生的打印能力成果审核认定后学生需要打印一张成果认定表存档。很多同学一看“打印”就接第三方 PDF 库其实完全没必要。控制台页面里放一个“打印认定表”按钮新窗口打开一张纯 HTML 模板页把成果信息填进去然后调用浏览器打印。打印时选择“另存为 PDF”就能得到 PDF 文件。这套方案简单、免费、不依赖 Linux 中文字体。关键是打印样式要有media print隐藏按钮和导航设置 A4 纸边距media print { body { width: 210mm; margin: 0; padding: 15mm; } .no-print { display: none !important; } .print-content { box-shadow: none; border: none; } }注意不要在打印模板里用 Flex 布局的圣杯式导航打印出来会乱。我的认定表全部用 Table 实现兼容性最好。3.5 手机号登录的取舍毕设建议模拟搜索热词里有“web 一键获取手机号”但高校竞赛系统登录主体是学生学号登录远比手机号合理。我没有做手机验证码登录而是在个人中心绑定手机号字段作为找回密码使用。如果确实要做验证码登录建议在后端写一个模拟发送接口把验证码打印到控制台避免对接真实短信服务商。毕设阶段不需要真的花钱发短信这个取舍能省下至少三天对接时间。4. 统计分析模块报表需求比想象中更能暴露 SQL 水平4.1 统计指标定义积分规则要可配置统计分析不只是 COUNT 一下需要定义业务指标。我参考了很多高校的竞赛管理办法最终定了四个统计项各学院获奖总数排行、国家级省级获奖分布、指导教师积分排行、近五年获奖趋势。积分规则是国家级一等奖 5 分、二等奖 4 分、三等奖 3 分省级一等奖 3 分、二等奖 2 分、三等奖 1 分校级成果不参与积分排行只参与数量统计。这个规则我放在数据库字典表里程序里只读取积分权重不硬编码。如果后续学校调整积分管理员可以直接改字典不用改代码重新部署。4.2 SQL 聚合从统计需求反推查询语句最常用的统计查询是按学院分组统计获奖数量SELECT c.id AS college_id, c.name AS college_name, COUNT(a.id) AS award_count FROM achievement_apply a INNER JOIN student s ON a.student_id s.id INNER JOIN college c ON s.college_id c.id WHERE a.status 5 AND a.create_time BETWEEN #{startDate} AND #{endDate} GROUP BY c.id, c.name ORDER BY award_count DESC, c.id ASC统计接口传入年份、竞赛级别、获奖等级等筛选条件在 SQL 里用动态if判断拼接而不是三个接口各写一套。这里有一个经验统计接口返回的是一个 DTO不要返回ListMapString,Object让前端猜字段名自己定义清楚比如collegeId、collegeName、awardCount。前端 ECharts 拿到这个结构直接绑定不用做大量数据转换。4.3 积分排行的排序细节积分排行不能只按score倒序因为多个学院可能同分这时候需要次要排序条件。我的 SQL 是ORDER BY score DESC, award_count DESC, college_id ASC保证每次刷新结果顺序稳定。如果不加学院 ID 作为最后排序条件同样数据在 MySQL 里可能因为物理存储顺序不同而产生不稳定排序答辩时会显得很不严谨。指导教师积分排行的逻辑复杂一点一份成果如果有多个指导教师积分要按指导教师分摊比如两位教师共同指导每位计一半。我在achievement_teacher关联表里加了一个share_weight字段默认 1多人时手动调整。计算积分时用score * share_weight / total_weight避免一个成果被两位指导教师重复加满积分。4.4 ECharts 图表数据接口的结构设计统计模块的接口返回格式我统一用StatisticResultVO里面包含categories和series{ categories: [计算机学院, 机械学院, 外语学院], series: [ { name: 国家级, data: [12, 8, 1] }, { name: 省级, data: [18, 15, 6] } ] }前端用柱状图还是折线图只取决于数据结构是否友好。我踩过的一个坑是接口返回字段名用了后端驼峰awardCount前端 JS 里却用了award_count结果图表一直空白浏览器控制台也不报错。这种问题浪费时间所以接口返回前最好打印一次 JSON前端先对照格式再写图表代码。5. 安全与部署毕设系统不能一登录就被人脱库5.1 登录认证与密码存储密码存储必须用 BCrypt不要用 MD5。MD5 是摘要算法不是加密算法彩虹表一碰就穿。我在项目里引入了spring-security-crypto做 BCrypt 密码散列PasswordEncoder的encode方法校验密码。数据库里的密码字段长度设置为 60存 BCrypt 哈希后的字符串。登录方案我选了 JWT 拦截器没有用 Session。原因是前端是 Vue3 独立部署请求带Authorization头比较清晰也方便和论文里“无状态认证”的章节呼应。拦截器里做三件事解析 token、获取当前用户、将用户信息放入 ThreadLocal。用一个BaseContext类保存当前用户对象Service 层任意位置都能取到。5.2 接口越权防护角色校验 数据权限校验越权攻击是管理系统最常见的漏洞评审老师也容易问。我做了两层防护。第一层是角色校验用自定义注解标注接口允许的角色第二层是数据权限校验在 Service 里判断记录归属。比如学院管理员审核一条成果时除了检查角色还必须确认该成果所属学生的college_id和当前管理员的college_id一致不一致直接抛异常。if (apply.getCollegeId() ! currentUser.getCollegeId()) { throw new BusinessException(无权审核其他学院的申报数据); }这里有个容易忽略的点学院管理员不能通过修改请求参数applyId来审核别人学院的记录因为校验放在后端执行和后端数据比对不信任前端输入。5.3 数据库初始化脚本与备份我建议建表 SQL 和维护数据分开schema.sql只放建表语句data.sql放基础数据包括 5 个学院、3 个角色、6 个测试账号、10 条竞赛、20 条演示成果。这样拿到新机器执行两个 SQL 文件系统就能跑起来答辩换电脑也不慌。备份用 MySQL 的mysqldump命令写一个小脚本每天凌晨执行保留 7 天备份。5.4 免费部署云服务器学生机 Nginx 反代部署方案我推荐云服务器学生机配置 2C4G 足够跑 Spring Boot MySQL。前端打包后是静态文件用 Nginx 托管后端 jar 用nohup java -jar启动。Nginx 配置里把/api前缀的请求转发到后端 8080 端口其余请求访问前端静态目录。server { listen 80; server_name demo.example.com; root /var/www/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里要注意如果后端接口有文件上传Nginx 默认请求体大小限制是 1MB需要在location里加client_max_body_size 20m;否则上传证书 PDF 会报 413 错误。这个坑我是在部署后才发现的。5.5 答辩演示脚本提前准备好三条路径答辩最容易翻车的是现场数据不够或者临时抱佛脚录入数据。我准备了三个账号和一条演示路径用学生账号登录创建一个新的成果申报并提交切换到学院管理员账号审核通过切换学校管理员账号最终认定最后到统计页面看刚才的数据是否出现在学院排行里。这条路径覆盖了全流程而且能看到数据变化比单纯点击菜单有说服力。6. 开发中的实测踩坑每一条都可能让你进度慢两天6.1 中文文件名下载乱码第一个坑是下载附件时文件名乱码。老写法response.setHeader(Content-Disposition, attachment;filename fileName)在中文文件名下绝对乱码除非浏览器自动转码。正确做法是用filename*UTF-8格式并给文件名做URLEncoder.encode。这个改动虽小但不改的话演示时文件名变成%E4%B8%AD%E6%96%87.pdf很掉价。6.2 Spring Boot 3 的 jakarta 包名迁移Spring Boot 3 从 javax.servlet 换成了 jakarta.servlet很多老教程和网上的代码片段直接 import 会报错。比如HttpServletRequest要写jakarta.servlet.http.HttpServletRequest。如果是新手遇到编译报错别慌大概率是包名问题先把 javax 改成 jakarta。我在第一周就因为这个浪费了半天。6.3 MyBatis Plus 逻辑删除和证书编号唯一索引冲突成果表的证书编号需要唯一我一开始在数据库建了唯一索引同时启用了 MyBatis Plus 的逻辑删除字段deleted。实际运行发现删除一条成果后再插入同证书编号的新成果数据库唯一索引会冲突因为逻辑删除并没有真正删除物理记录。解决办法是去掉唯一索引改为 Service 层查重并且查询时用selectList连逻辑删除的数据一起查出来判断。这样才能保证历史记录被删后编号可以复用。6.4 事务方法在同一个类内调用导致失效这是一个经典问题Spring 事务默认通过 AOP 代理实现同类内部方法调用不会经过代理所以audit方法调本类另一个带Transactional的方法时事务会失效。我写审核逻辑时最初把状态更新和日志插入放在两个方法里自己在类内部调用结果日志插入失败时状态更新已经提交数据不一致。后来把所有数据库操作合并到一个事务方法里问题才解决。建议写审核、删除这类多表操作时把代码放在同一个方法体内别拆得太碎。6.5 EasyExcel 导出时内存占用过高一开始导出全部成果数据我用 EasyExcel 的EasyExcel.write().sheet().doWrite(list)当数据量不到一万条时没问题但导出前如果筛选条件为空又全表查询内存会涨得很明显。后来我在导出接口里加了筛选条件必填限制并要求所有导出必须传年份这样数据量可以控制如果确实要全量导出再用流式的分批查询。毕设系统不追求大数据限制导出范围更实用。6.6 前端日期少 8 小时统计趋势图按年份分组但后端返回的LocalDateTime被 JSON 序列化成 UTC 时间导致前端 ECharts 显示的数据错位。解决方案是在application.yml里配置 Jackson 时区spring: jackson: time-zone: GMT8 date-format: yyyy-MM-dd HH:mm:ss数据库连接参数也要加serverTimezoneAsia/Shanghai双管齐下才能保证前后端看到的时间一致。7. 如果你也想选这个题目我能给的最实在建议7.1 哪些地方做了是加分项如果你想拿高分不要停留在增删改查。把下面任意两点做扎实答辩时都能讲出亮点行级权限控制、两级审核状态机、统计分析积分规则、证书附件上传下载、PDF 打印、EasyExcel 导出。其中行级权限和状态机是我强烈推荐的它们是“为什么这个系统不能用一个 Excel 解决”的最好答案。7.2 哪些地方不要贪不要为了炫技引入 Redis、消息队列、微服务。高校学生竞赛数据量根本不需要这些评委听到你用微服务第一反应不是觉得厉害而是问服务拆分依据、分布式事务方案这样反而容易扣分。也不要追求界面过度动画Element Plus 默认风格已经很耐看把时间留给功能和论文。7.3 时间分配建议我建议这样安排第一周做需求分析和数据库设计第二周做完基础 CRUD 和登录第三周做申报和审核流程第四周做统计分析第五周做文件上传导出和部署第六周写论文和准备演示。数据库设计千万别压缩时间后面的所有功能开发都在为第一周的表结构买单。做这个项目最大的体会是“管理系统”好不好不看页面有多精致而看你能不能把业务逻辑讲透为什么状态要这么流转统计积分怎么算数据权限怎么防越权。把这些想清楚这个毕设就真的立得住了。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询