多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Java Web毕设选题:高校竞赛成果管理系统设计与实现

Java Web毕设选题:高校竞赛成果管理系统设计与实现 java Web方向做毕设选什么题一直是很多同学纠结的地方。如果不想碰太冷门的技术又希望系统有完整业务场景、有权限控制、有报表统计、答辩能讲清楚高校学生竞赛成果管理系统是一个很稳的选题。这类基于Web的系统核心是把学生学科竞赛的申报、审核、统计全流程搬到线上正好卡在Java Web项目“比增删改查高级一点又不至于复杂到做不完”的位置。这篇把我做这个项目的完整思路、表结构、关键实现和踩坑记录都写出来给打算选这个题的同学一个可以直接参考的版本。1. 项目定位这个系统到底解决什么问题1.1 竞赛成果管理到底管什么高校里的学科竞赛成果包括“挑战杯”、数学建模、程序设计大赛、电子设计大赛等等每年数量不小。过去很多学校是靠学生填Excel表、发邮件给辅导员再由学院教务员汇总成一张张大表报到教务处。看起来很常规实际一操作全是火坑学生填的获奖名称五花八门文件名格式不统一证书图片要么不传要么传错同一份成果学院报了一遍、学生自己又交一遍教务处统计时还得手动去重。竞赛成果管理系统要解决的就是这些具体问题把“学生申报—教师审核—管理员终审—数据统计”这条线做成闭环。这一类系统的典型用户分三类学生登录后填报个人竞赛成果上传证书、成绩单附件查看审核进度。指导教师/学院审核人审核本学院学生的申报材料通过或驳回驳回时写原因。系统管理员/教务处人员管理竞赛信息、审核最终结果、按学院按年份按竞赛级别统计导出。整个系统的业务核心并不是信息展示而是状态流转。一份成果从草稿到提交、待审核、审核通过、被驳回、定稿归档每一步都要有明确的状态和角色操作边界。1.2 为什么这个选题性价比高从毕设选题的角度这个项目有几个很实际的优势。第一业务场景真实。竞赛成果管理在几乎所有高校都存在需求不是凭空捏造的你做出来的功能点每一处都能找到对应的现实场景写开题报告和论文时素材非常充足。第二技术点覆盖度适中。它有单点登录、JWT或Session会话管理、RBAC权限控制也涉及文件上传、表单校验、父子表数据维护再加上统计报表的聚合查询能放进论文里的技术亮点不少。第三工作量可控。这个系统没有复杂的算法没有高并发没有消息队列核心就是CRUD加状态机加统计一个人做一个月到两个月完全来得及不像电商系统那样容易做成一锅粥。我也见过有同学擅自加了一堆功能比如站内信、在线聊天结果自己都不知道怎么讲答辩时把重点放在无关功能上反而把主线的审核流程讲淡了。做毕设先保证主线清晰这是最重要的一条原则。1.3 角色权限划分先行做这个系统之前先把权限边界想清楚因为后面的菜单、接口、页面都要围绕角色来设计。我在项目里定义了三种角色没有再细分。角色核心操作学生申报成果、修改未审核信息、查看审核结果审核教师查看本学院学生申报、审核或驳回、按学院查询统计管理员竞赛信息维护、终审、全校统计、用户管理这里要注意一个细节指导教师到底按学院过滤还是按指导老师本人过滤需求里要定义清楚否则代码改起来很麻烦。我做的是按学院过滤因为大部分高校的竞赛审核确实是学院教务办统一负责的。2. 技术选型从后端到前端的一次性规划2.1 后端框架选择主流的组合有两种SSMSpring SpringMVC MyBatis和 Spring Boot。现在做毕设我建议直接用 Spring Boot原因不是SSM不行而是Spring Boot把配置简化了项目结构更清爽答辩时你能用更多时间讲业务逻辑而不是讲XML配置。版本上注意一下我用的是 Spring Boot 2.7.x JDK 1.8。为什么不追新因为学校机房、老师的运行环境很多还是JDK8你用一个JDK17的版本现场演示时环境配不起来是很尴尬的。字节码版本过高导致无法启动的问题我在帮别人看项目时遇到过太多次。持久层我用 MyBatis很多教程推 MyBatis-Plus说实话MyBatis-Plus在开发效率上确实高BaseMapper自带CRUD省很多时间条件构造器写统计查询也方便。如果代码量控制得好这个选题用 MyBatis-Plus 和 MyBatis 区别不大你觉得哪个把握大就选哪个关键是理清楚表关系和SQL逻辑。2.2 前端和服务端渲染前端我用的 Bootstrap jQuery Thymeleaf 服务端渲染统计页面用 ECharts 画图表。选这套方案是因为它是毕设场景里容错率最高的组合。前后端分离对毕业设计不是不行但你需要额外处理跨域、前端构建、开发环境依赖Node.js现场演示时要同时开前端服务和后端服务不确定性多。服务端渲染的思路是后端Controller返回视图名称Thymeleaf负责把数据渲染到HTML里。它比JSP好在语法简洁比Vue项目好在不需要构建工具对Java背景的同学友好很多。页面用Bootstrap栅格布局整体观感不会差而且响应式兼容老师用不同分辨率的屏幕演示也不会翻车。2.3 后端目录结构与分层我的后端按经典三层架构来组织controller、service、mapper一层层分清楚。讲解时需要理由充分。com.example.competition ├── controller # 接口和页面跳转控制 ├── service # 业务逻辑事务边界在这里 ├── mapper # MyBatis的Mapper接口 ├── entity # 数据库实体对象 ├── dto # 视图模型避免实体直接暴露给前端 ├── config # 拦截器、WebMvc配置、文件上传配置 ├── interceptor # 登录/权限拦截器 ├── common # 统一结果封装、异常处理、工具类 └── CompetionApplication.java这样一个结构答辩时老师问“你的项目怎么分层的”你可以很清晰地答出controller不写业务、service管事务、mapper只做数据访问每一层职责明确。2.4 环境准备清单开发环境我列一个对照表第一次接触的同学可以照着准备组件推荐版本备注JDK1.8兼容性最好IDEIntelliJ IDEA 2024社区版够用MySQL5.7 或 8.08.0 注意驱动名不同Maven3.8配置阿里云镜像加速Spring Boot2.7.x稳定且教程多前端Bootstrap 4 jQuery ECharts不需要额外构建工具IDEA 2024 创建Web项目时直接选 Spring Initializr依赖勾选 Spring Web、Thymeleaf、MyBatis选MyBatis-Plus就自己加依赖、MySQL Driver。不需要手动建Web目录Spring Boot内置Tomcat这点和传统打war包的老项目不一样。3. 数据库设计竞赛成果系统的地基3.1 核心表结构数据库设计是这个项目最值得花时间的部分。表建得好后面所有查询都很顺表里有明显冗余或者缺字段后期就得返工。我设计了六张核心表用户表 tb_user字段类型说明idbigint 主键自增用户IDusernamevarchar(50) 唯一学号/工号passwordvarchar(255)MD5或BCrypt加密保存real_namevarchar(50)真实姓名roletinyint0学生 1教师 2管理员college_idbigint所属学院IDphonevarchar(20)联系方式create_timedatetime创建时间学院表 tb_college字段就三个id、name、code。这里我多说一句学院id是很重要的外键学生和教师都关联到学院后面“按学院统计”全靠这个字段做关联。竞赛信息表 tb_competition字段类型说明idbigint竞赛IDnamevarchar(200)竞赛名称leveltinyint0国家级 1省级 2校级organizervarchar(200)主办单位competition_datedate比赛时间register_deadlinedate报名截止日期remarkvarchar(500)备注成果申报表 tb_result这是全系统最核心的表。字段类型说明idbigint成果IDstudent_idbigint申报学生用户IDcompetition_idbigint关联竞赛award_namevarchar(200)获奖名称award_leveltinyint一等奖/二等奖/三等奖/优秀奖award_datedate获奖日期certificate_novarchar(100)证书编号attachment_idbigint证书附件IDstatustinyint0草稿 1待审核 2已通过 3已驳回submit_timedatetime提交时间audit_user_idbigint审核人用户IDaudit_timedatetime审核时间audit_reasonvarchar(500)审核意见/驳回原因附件表 tb_attachment字段类型说明idbigint附件IDresult_idbigint关联成果IDfile_namevarchar(200)原始文件名file_urlvarchar(500)存储路径file_sizebigint文件大小字节upload_timedatetime上传时间审核日志表 tb_log字段类型说明idbigint日志IDuser_idbigint操作人actionvarchar(50)操作行为如submit、approve、rejecttarget_idbigint操作对象如成果IDdetailvarchar(500)操作说明create_timedatetime操作时间3.2 字段类型与命名上的细节用户名、角色、状态这些尽量用数字编码而不是字符串因为字符串一旦拼接或者拼写不一致统计就会出错。比如角色用0/1/2成果状态用0/1/2/3后面写SQL时用数字比较即可。时间字段统一用datetime不要在Java端用字符串传时间容易出时区问题。MySQL 8.0连接时jdbcUrl要设置serverTimezone例如jdbc:mysql://localhost:3306/competition_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse3.3 去重校验的逻辑竞赛成果系统特别容易反复提交。同一个学生同一个竞赛同一个奖项理论上只能申报一次。这块我的做法是在tb_result表上加了唯一索引alter table tb_result add unique index uk_student_competition (student_id, competition_id);同时Service层在新增时先做一次查询校验给用户明确提示“该竞赛成果已申报请勿重复提交”。数据库索引是最后一道防线业务代码里先拦一道双保险。4. 核心功能实现申报、审核、统计一条线4.1 登录与权限控制这个项目的登录用Session即可不需要上Spring Security除非你想把安全框架作为一个答辩亮点。我用的是一个简单的拦截器加角色判断实现容易讲清楚代码量也不大。拦截器主要做两件事检查用户是否登录检查当前角色的访问范围。public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login); return false; } String requestUri request.getRequestURI(); if (requestUri.startsWith(/admin/) user.getRole() ! 2) { response.setStatus(403); return false; } if (requestUri.startsWith(/teacher/) user.getRole() 0) { response.setStatus(403); return false; } return true; } }从代码可以看到我是按URL前缀区分角色区域的。生产系统会用更细粒度的方法但毕设以逻辑清晰、能答辩为主这个设计足够。4.2 成果申报与文件上传学生的申报页面是一个表单加一个文件上传控件。这里有两个非常常见的坑我提前提示一下第一文件不能直接扔到项目根目录。很多人做文件上传直接存成D:/temp/xxx.jpg然后把绝对路径存进数据库妥妥的大型翻车现场。因为项目换台电脑部署路径就不对了。正确做法是配置一个本地上传目录把相对路径存到数据库访问时通过虚拟映射来读取。在application.yml中配置upload: dir: ${user.dir}/uploads/然后加一个WebMvc配置把本地目录映射成URL路径Configuration public class WebConfig implements WebMvcConfigurer { Value(${upload.dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/uploads/**) .addResourceHandler(file: uploadDir); } }这样文件保存下来后数据库中只需要记录/uploads/2025/03/xx.pdf这样的路径浏览器直接可以访问换服务器也没问题。第二文件名一定要重命名。学生上传的证书图经常是“微信图片_202405138123.jpg”这种文件名包含中文和空格路径里出现这些字符有时候会404。我的做法是统一用UUID或时间戳重命名后缀保留原扩展名public String storeFile(MultipartFile file) { String original file.getOriginalFilename(); String ext original ! null ? original.substring(original.lastIndexOf(.)) : ; String newName UUID.randomUUID().toString().replace(-, ) ext; // 按日期分目录存储 String datePath new SimpleDateFormat(yyyy/MM).format(new Date()); File dir new File(uploadDir datePath); if (!dir.exists()) dir.mkdirs(); file.transferTo(new File(dir, newName)); return /uploads/ datePath / newName; }4.3 审核流程的状态机审核流程是业务核心也是最值得写进论文的模块。状态流转定义清楚了代码逻辑就清晰了学生提交后status从0变为1教师审核时可以改为2通过也可以改为3驳回管理员终审同理。驳回必须填写原因这些原因在学生的详情页展示。审核的Service方法必须加事务控制否则审核状态改了但日志没写进去出了问题无法追溯。这段代码我简化一下Transactional(rollbackFor Exception.class) public void audit(Integer resultId, Integer status, String reason, User operator) { Result result resultMapper.selectById(resultId); if (result null || result.getStatus() ! 1) { throw new BizException(当前成果不在待审核状态请刷新后操作); } // 审核人只能处理本学院的成果 if (!result.getCollegeId().equals(operator.getCollegeId()) operator.getRole() ! 2) { throw new BizException(无权限审核该成果); } resultMapper.updateStatus(resultId, status, reason, operator.getId(), new Date()); logMapper.insert(new Log(operator.getId(), audit, resultId, status 2 ? 审核通过 : 驳回 reason, new Date())); }这个状态机看起来简单但实际开发时你还会遇到一个业务边界问题被驳回的成果学生修改后是直接回到待审核状态还是重新走一遍流程两种都行但必须保证每次审核的日志都有据可查。我采用的是驳回后学生可以修改再提交提交后status重新变为1。4.4 统计分析模块的实现统计功能是最容易出效果的部分也是答辩时最吸引老师的点。我用ECharts做了两个统计图一个是“各学院获奖数量柱状图”一个是“竞赛级别占比饼图”再加一张按年份、学院、级别组合条件查询的明细表。统计查询的SQL要提前写好演示时才不会卡壳。柱状图的SQL大概是select c.name as college_name, count(r.id) as award_count from tb_college c left join tb_user u on u.college_id c.id left join tb_result r on r.student_id u.id and r.status 2 group by c.id, c.name order by award_count desc注意这里用了left join而不是inner join目的是把没有获奖记录的学院也查出来数量为0也要显示在图表上图形完整才不突兀。饼图的逻辑是按竞赛级别统计通过成果数量select case when (select level from tb_competition cc where cc.id r.competition_id) 0 then 国家级 when (select level from tb_competition cc where cc.id r.competition_id) 1 then 省级 else 校级 end as level_name, count(*) as cnt from tb_result r where r.status 2 group by level_name子查询的性能其实不需要担心数据量小重点是想清楚关联关系。5. 常见问题与排查实录5.1 IDEA 2024创建Web项目的坑现在很多同学用IDEA 2024创建项目如果直接选了默认的Spring Boot 3.x那JDK最低要求是17。如果你的机器装的是JDK8项目启动直接报错。建议创建项目时把Spring Boot版本降为2.7.x或者先把JDK换成17。不要在这个问题上纠结毕设稳定压倒一切。创建成功后如果发现没有application.yml文件项目一样能跑但建议自己新建一个配置文件统一放这里比properties格式看着舒服层级也清晰。5.2 端口被占用Spring Boot默认端口是8080如果被其他程序占了启动日志会报Port 8080 was already in use。解决办法很简单两种一是找到占用进程杀掉Windows命令netstat -ano | findstr 8080 taskkill /pid 1244 /f二是在application.yml里改端口比如改成8081server: port: 8081我建议用第二种项目不会因为环境差异现场翻车。5.3 中文乱码问题中文乱码在项目里通常有两个来源。一个数据库连接没设置utf8这个用我之前写的带characterEncoding的jdbcUrl就能解决。另一个是文件上传时中文文件名乱码这个在文件重命名后就不存在了因为你根本不用原始文件名做存储。还有一点如果使用Thymeleaf模板页面中文乱码记得在HTML的meta标签统一设置UTF-8。5.4 上传的文件在浏览器里打不开这个问题出现频率相当高。如果数据库存的是绝对路径比如D:\java_project\uploads\1.jpg浏览器访问时是没法直接拼接成URL的。你需要做的是相对路径加映射即我在4.2里写的addResourceHandlers方式。如果页面重写了WebMvcConfigurer导致映射失效注意EnableWebMvc注解不要乱加它会关闭Spring Boot的自动Web配置静态资源全部失效。5.5 分页查询总数据量不对列表页加分页时学生端只能看自己的教师端只能看本学院的但管理员可以看到全部。这里的权限过滤如果只写在SQL的where条件里分页count查询也要带上同样的条件。很多人count和list两个SQL条件没对齐就会导致“总条数和实际数据对不上”的问题。6. 答辩讲解与项目亮点提炼6.1 答辩时候最值得讲的四个点答辩现场老师问你的问题不会太多但会集中在“这个系统有什么亮点”“你做了哪些工作”上。我建议从这四个方向准备第一权限设计。讲清楚为什么用三种角色、怎么通过拦截器实现的权限控制、为什么管理员和教师看到的菜单不同。可以进一步解释RBAC模型哪怕只是用到了一部分也能说明你有设计思维。第二状态机与审核流程。讲清楚一份成果的完整生命周期草稿—提交—审核—通过/驳回哪个角色在哪个状态能做什么操作为什么用事务保证数据一致性。第三文件上传与访问方案。讲清楚为什么不存绝对路径怎么做到换一台机器部署依然能访问附件。这个点虽然不复杂但能体现你对实际部署场景的考虑。第四统计分析的SQL设计。讲清楚怎么用聚合查询加条件筛选得到图表数据为什么用left join保持数据完整在数据量较小时避免子查询的替代方案。6.2 项目演示的准备技巧演示这个系统时我建议准备一组演示数据把每个角色的操作流程都过一遍。比如事先用管理员账号建好三个竞赛用学生账号提交一份成果并上传一张证书图片教师账号审核通过再提交一份驳回的到管理员账号终审然后打开统计页面展示图表变化。整个流程跑通比背代码更能说服人老师看到的是一个能实际运转的管理系统而不是一个半成品。纸面项目报告里我建议把表结构放到附录把核心SQL和审核流程放到正文论文的核心逻辑围绕“申报—审核—统计”展开。很多同学的论文把大量篇幅写环境搭建但那些东西不是论文重点业务设计和流程实现才是。7. 最后再聊几句做这个项目时我有一个很深的体会毕设项目不需要炫技需要的是完整。把登录、分页、文件上传、审核流、统计报表这几条线扎扎实实走通再配上清晰的项目结构和测试数据就已经超过大多数同类选题了。你不需要把所有新技术都堆上去而是要把自己写过的每一行代码的逻辑都讲明白对老师提出的每一个“为什么”都能接得住。如果你正打算用这个题目做毕设建议先把表结构设计好再动手写代码。表结构就是地基地基稳了后面的开发会顺很多。也建议在设计表结构时就把“唯一索引防重复申报”和“附件相对路径存储”考虑进去这两处细节会在你后期开发时帮你省下不少返工的时间。祝你项目顺利。
返回列表