学科竞赛管理系统这个题目在毕设里属于常青树了。每年一到毕业季总有一批计算机专业的同学为选题发愁而基于Spring Boot的管理系统类题目因为技术栈清晰、业务逻辑直观、工作量适中一直是热门选择。不过热门归热门真正能把一个竞赛管理系统讲透、做到能跑、能答、能过答辩的项目源码并不多见。这篇文章就把我整理的这套基于Spring Boot的学科竞赛管理系统完整地拆开来看从业务场景到技术选型、从数据库建模到核心代码走读、从本地启动到服务器部署一条线走下来拿到源码的同学能跑通、能看懂、能讲明白不在答辩现场卡壳。这套系统我在整理时是冲着直接可用去的不是那种写了一堆代码却跑不起来的半吊子项目。无论你是想拿它当毕设底子还是想通过学习它掌握Spring Boot项目的完整开发链路这篇文章都能给你一个清晰的坐标系。1. 从选题到成型这个系统解决的真实场景1.1 竞赛管理这件事到底麻烦在哪里很多没接触过实际竞赛组织工作的同学第一反应是竞赛管理不就是报名发通知吗真正走进这个场景才会发现一场学科竞赛从发起到结束涉及的角色、流程和状态远比你想象的复杂。先说角色。一场校级甚至省级的学科竞赛参与方至少有三类学生参赛者、教师指导老师/评审老师、管理员教务处或学院负责老师。学生要查看竞赛通知、在线报名、上传作品、查看成绩教师要对报名信息进行初审、对作品进行评审、录入成绩管理员要发布竞赛信息、设置报名时间、分配评审任务、审核最终结果、发布获奖公告。这三类角色如果还靠Excel和邮件来协作那整个竞赛周期里的沟通成本会高到难以想象。再说流程。一个完整的竞赛活动闭环是这样的管理员发布竞赛通知包括竞赛名称、级别、参赛对象、报名起止时间→ 学生在线报名并提交个人信息和作品材料 → 管理员或指导老师进行资格审核 → 审核通过后进入竞赛阶段 → 评委录入作品成绩或评审结果 → 系统按规则生成获奖等级 → 管理员发布最终获奖公示 → 学生查看自己的成绩和奖项。这中间的任何一个环节出现信息不对称都会引发大量线下沟通。1.2 系统功能边界如何划定我在设计这套项目时没有盲目堆功能而是紧扣一个真正会被使用的系统这个标准划定了清晰的功能边界。学生端核心功能包括查看竞赛列表与详情、在线报名提交个人信息与作品摘要、查看报名审核状态、上传作品附件、查看自己历史参赛记录和获奖情况、修改个人资料。教师/评委端核心功能包括对分配给自己的参赛作品进行评分、查看并管理自己指导学生的报名记录、维护个人基本信息。管理员端核心功能包括竞赛通知的发布与编辑、竞赛类别按学科门类或竞赛等级管理、报名信息的审核与筛选、评审任务分配、成绩与获奖数据的录入和修正、获奖名单的最终发布、系统用户的管理禁用、编辑、重置密码。这个边界划定的逻辑是每个功能都能在真实竞赛组织流程中找到对应场景没有为了凑功能而做无关紧要的模块。答辩时老师如果问你这个系统为什么只有这些模块你可以很自然地回答因为设计时遵循了按业务角色和流程闭环划分模块的原则。2. 技术栈组合与选型复盘2.1 后端框架为什么是Spring Boot这个项目本身就叫基于Spring Boot的学科竞赛管理系统所以Spring Boot是绝对的核心。但选它的理由不只是因为题目如此而是它确实是这类管理系统的合适选择。Spring Boot相比传统SSM架构Spring Spring MVC MyBatis最大的价值在于自动配置和简化部署。传统SSM项目光配置文件就要写一堆web.xml、spring-mvc.xml、mybatis-config.xml、applicationContext.xml每个配置还得手动管理依赖版本稍有不慎就版本冲突。Spring Boot把这些都通过starter机制封装好了引入一个spring-boot-starter-web就能开箱即用默认内嵌Tomcat直接把项目打成jar包就能跑这对毕设场景来说太友好了。更重要的是Spring Boot的核心机制——自动配置、依赖注入、面向切面编程、Starter模式——是当前Java后端开发的主流技能点。用这个项目做毕设既能保证工作量又能让答辩评委一眼看出你掌握了主流的开发方式。2.2 持久层MyBatis Plus带来的效率提升数据访问层我选了MyBatis PlusMP。很多同学在学校里学的是原生MyBatis写一个简单的单表CRUD都要先写接口方法、再写XML映射文件、还要配置resultMap。而MyBatis Plus在MyBatis基础上做了大量增强最核心的就是BaseMapper接口——继承它之后insert、deleteById、selectById、updateById、selectList这些基础操作直接就能用连SQL都不用写。举个例子项目里学生报名时要插入一条报名记录传统MyBatis写法要写一个insert方法加一个insert的XML语句用MyBatis Plus就是一个save(registration)调用的事。这种效率提升对于需要快速完成项目的毕设场景非常实用。同时MyBatis Plus的分页插件、条件构造器LambdaQueryWrapper也在项目里大量使用。比如管理员查看报名列表时需要按竞赛ID筛选、按审核状态筛选、按学生姓名模糊搜索、还要分页。用LambdaQueryWrapper可以这么写LambdaQueryWrapperRegistration wrapper Wrappers.lambdaQuery(); wrapper.eq(Registration::getCompetitionId, compId) .eq(StringUtils.isNotBlank(status), Registration::getStatus, status) .like(StringUtils.isNotBlank(keyword), Registration::getStudentName, keyword) .orderByDesc(Registration::getCreateTime); PageRegistration page registrationService.page(new Page(current, size), wrapper);这代码既简洁又清晰答辩时被问条件查询怎么实现的直接指着这一段讲就行。2.3 前端方案使用Vue构建前后端分离关于前端我选了Vue Element UI的前后端分离方案而不是传统的Thymeleaf服务端渲染。原因是第一当前企业级Java开发中前后端分离已经是绝对主流毕设用前后端分离在答辩时是加分项第二Vue生态成熟Element UI组件库开箱即用能快速搭建出样子不错的页面第三前后端通过JSON交互能体现你对HTTP协议和RESTful API设计的理解。为了让项目跑起来不费劲前端工程用Vue CLI构建npm run dev启动开发服务器通过axios和后端接口通信。开发阶段配置代理把/api开头的请求转发到后端的8080端口避免跨域问题部署阶段把前端打包成静态资源可以用Nginx托管也可以丢进后端项目的static目录个人不推荐后者维护不太方便。这里额外提醒一句很多同学的前后端分离项目跑不通80%的问题出在跨域配置上。如果你要用Vue记得在Spring Boot里做一个全局CORS配置类或者用CrossOrigin注解这个细节务必处理好。3. 数据库设计五张核心表的建模过程3.1 基于业务流程的实体识别数据库设计是这类管理系统的灵魂也是答辩时老师重点考察的部分。我在设计表结构时不是凭空想象字段而是沿着业务流程逐步识别实体。先梳理实体用户user包含学生、教师、管理员三种角色、竞赛competition、报名记录registration、成绩/获奖result、公告announcement。这五张核心实体表基本覆盖了系统全部业务。为了让功能更完善我还加了竞赛类别表和文件表但核心还是这五张。3.2 核心表的字段设计与关联关系用户表在这套系统里的设计比较有代表性。它没有做复杂的用户角色分表那种一个用户多个角色的RBAC模型而是用了单表role字段直接区分。表结构设计如下CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码(BCrypt加密), real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, role tinyint(4) NOT NULL DEFAULT 2 COMMENT 角色: 0-管理员 1-评委/教师 2-学生, student_no varchar(50) DEFAULT NULL COMMENT 学号, college varchar(100) DEFAULT NULL COMMENT 学院, phone varchar(20) DEFAULT NULL COMMENT 联系电话, email varchar(100) DEFAULT NULL COMMENT 邮箱, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 状态: 0-禁用 1-正常, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;竞赛表kill的是发布竞赛通知所需字段竞赛名称、级别校级/省级/国家级、类别、主办方、报名开始/结束时间、竞赛开始时间、竞赛地点/线上说明、竞赛详情描述、状态字段草稿/已发布/报名中/已结束这个状态可以根据时间自动流转也可以管理员手动调整。报名表是业务核心表它把用户和竞赛关联起来还承担着审核状态的流转。关键字段有竞赛ID、学生ID、团队名称、团队成员信息、指导老师姓名、作品名称、作品简介、附件路径、审核状态待审核/通过/不通过以及审核备注。成绩/获奖表记录学生在一场竞赛中的最终成绩字段包括竞赛ID、学生ID、总分、排名、获奖等级一等奖/二等奖/三等奖/优秀奖、证书编号、是否公示。这张表的设计直接关系到学生端能否正确显示获奖信息以及管理员能否进行公示管理。这五张表的关系并不复杂用户和竞赛是多对多通过报名表关联报名表和结果表是一对一一条报名记录对应一个评审结果竞赛和公告是一对多。这种设计符合第三范式要求又没有为了范式强行拆表导致查询复杂。3.3 状态流转的设计思路项目中涉及大量状态比如竞赛状态、报名审核状态。我推荐用整数枚举来表示状态而不是直接存字符串。原因有两点一是存整数占用空间小、查询效率高二是在Java代码里可以建一个枚举类统一管理状态含义避免魔法值散落各处。写一个枚举示例public enum RegistrationStatus { PENDING(0, 待审核), APPROVED(1, 已通过), REJECTED(2, 已拒绝); private final int value; private final String desc; RegistrationStatus(int value, String desc) { this.value value; this.desc desc; } // getter... }这样在业务代码里写if (registration.getStatus() RegistrationStatus.APPROVED.getValue())语义清晰也不会出现字符串比较时多一个空格少一个空格导致的神秘bug。答辩时提到状态以枚举方式管理避免魔法值也是一个不错的细节亮点。4. 跑通项目从下载到启动的完整记录4.1 本地环境准备清单很多同学拿到源码后第一步就卡住大部分是因为环境不匹配。我这里按照项目实际测试过的环境版本整理了一份清单照着准备基本不会出问题。后端环境JDK 1.8推荐8u202以上版本、Maven 3.6、IntelliJ IDEA 2020.3。这里有一个小点要特意说明Maven建议用阿里云镜像仓库不然拉取依赖会非常慢特别是刚解压完Maven还没下载过中央仓库依赖的电脑。在settings.xml里配置镜像即可这一步不提前做后面启动能等到怀疑人生。数据库环境MySQL 5.7或8.0都行。项目里我用的MySQL 8.0因为8.0在性能、字符集支持等各方面都更好。需要提醒的是不同版本数据库驱动的URL配置不一样如果你用的是5.7记得把驱动的URL里的com.mysql.cj.jdbc.Driver替换成5.7对应的com.mysql.jdbc.Driver事实上8.0驱动是兼容5.7的能连上就行但有时会有时区告警建议统一用8.0省心。前端环境Node.js 14npm 6Vue CLI 4.x。Node版本不要太新太新的版本和某些老项目依赖会不兼容实测下来Node 16.x很稳。4.2 配置文件的修改点逐个说启动前必须在application.yml里改干净的几个配置是数据库连接信息、服务端口、文件上传路径。数据库连接配置是重灾区。很多同学直接复制粘贴根本不看用户名密码对不对结果项目起不来。这里把它写出来server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/competition_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: 你本机的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver注意serverTimezoneAsia/Shanghai这个参数。不加它8.0版本的驱动连接时会因时区问题直接抛异常这是新人最常见的坑。文件上传路径单独说明。项目里学生上传作品附件、管理员上传证书图片都会保存到服务器本地指定目录。默认配置如下file: upload-dir: D:/competition-upload/ # Windows下示例 # upload-dir: /data/competition/upload/ # Linux服务器下示例这个目录需要提前创建好否则上传时可能因为目录不存在报错。路径的读取在代码里通过Value(${file.upload-dir})注入改配置就能改存储位置不用动逻辑代码。4.3 从前到后启动步骤第一步创建数据库。在MySQL命令行里执行CREATE DATABASE competition_db DEFAULT CHARACTER SET utf8mb4;然后把项目里带的sql/competition_db.sql文件导入mysql -u root -p competition_db sql/competition_db.sql如果Windows用户没配MySQL环境变量就用Navicat或DataGrip图形化导入一样的效果。第二步启动后端。用IDEA打开后端工程确认Maven自动导入依赖完成。找到启动类一般是CompetitionApplication上面有SpringBootApplication注解右键Run即可。看到类似Tomcat started on port(s): 8080的日志就说明启动成功了。第三步启动前端。命令很简单cd frontend npm install npm run servenpm install 拉取依赖时可能有点慢甚至卡在某一个包上可以配置一下npm使用淘宝镜像源npm config set registry https://registry.npmmirror.com再试。启动成功后在浏览器访问http://localhost:8081Vue CLI默认端口一般是一个八零几零。4.4 内置测试账号系统自带了三组测试账号在sql脚本里已经预置好了直接登录即可角色用户名密码登录后可操作的内容管理员admin123456竞赛发布、用户管理、报名审核、成绩录入、公告管理评委/教师teacher123456查看自己相关竞赛的报名信息、作品评分学生student123456查看竞赛、在线报名、上传作品、查看成绩和获奖登录前先确认数据库里有user表的数据如果导入后没数据手动insert一条即可。5. 核心代码走读登录、权限与报名状态流转5.1 登录认证的实现逻辑登录认证没有用复杂的Spring Security JWT方案而是采用Session 拦截器的方式实现的。为什么不直接用Spring Security坦白说Spring Security学习曲线较陡配置复杂对毕设项目来说性价比不高。用Session 自定义拦截器足够满足需求而且代码逻辑清晰答辩时能讲得很明白。登录接口的核心逻辑是这样的接收前端传来的用户名和密码用LambadQueryWrapper查询用户表校验用户名是否存在、密码是否正确、账号状态是否正常如果全部通过就把用户对象塞进Session并返回用户信息给前端。密码校验使用BCrypt加密算法注册时对明文密码做BCrypt加密登录时用BCryptPasswordEncoder的matches方法校验。为什么不用MD5MD5是快速哈希没有加盐机制撞库攻击能直接破解常见弱密码BCrypt内置随机盐每次加密结果不同安全性高一个量级。在毕设答辩里提一句密码加密选型考虑过MD5但最终选择BCrypt因为它自带盐值抗暴力破解能力更强绝对是一个加分回答。5.2 拦截器的权限控制权限控制器是用HandlerInterceptor实现的自定义拦截器。它有三个核心方法preHandle在请求处理前执行postHandle在处理后执行afterCompletion在视图渲染后执行。权限控制主要在preHandle里完成。实现思路是定义两个拦截器路径规则一个拦截所有请求检查用户是否登录未登录则返回401状态码或重定向到登录页再针对/admin/**、/teacher/**这类路径加一个角色校验拦截器检查当前登录用户的role值是否匹配。登录是通用校验角色是精细化校验两者组合使用。Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User user (User) session.getAttribute(user); if (user null) { response.setStatus(401); response.getWriter().write(未登录或登录已过期); return false; } // 角色校验 if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod (HandlerMethod) handler; RequireRole requireRole handlerMethod.getMethodAnnotation(RequireRole.class); if (requireRole ! null !requireRole.value().contains(user.getRole())) { response.setStatus(403); response.getWriter().write(无权限访问); return false; } } return true; }这里用到了自定义注解RequireRole在Controller方法上标注需要的角色类型。这种方式比写死路径匹配更灵活也更好讲因为涉及到Java注解和AOP思想答辩时能聊的东西就多了。如果你时间充裕甚至可以把拦截器里角色校验的逻辑抽成一个切面用AOP的方式实现那就是一个系统扩展性强的故事。5.3 报名状态机的落地报名状态的设计是整个业务逻辑里最有意思的部分它直接关系着竞赛管理是否公平有序。在这个系统里报名状态分为待审核、已通过、已拒绝三种。流程是学生提交报名后进入待审核状态管理员或评委审核后更新为已通过或已拒绝被拒绝时携带备注说明原因。实现上我用状态枚举管理通过Service层的事务方法控制流转。为什么不把状态流转放在Controller层因为Controller层只负责接收参数和返回结果业务规则应该沉淀在Service层这样既便于维护又能在多处复用。举例来说后台上传成绩的入口和前台查询成绩的校验逻辑都依赖状态值写在Service里一处修改全局生效。这里附一段报名审核的Service实现片段Transactional(rollbackFor Exception.class) public void auditRegistration(Long registrationId, Integer auditResult, String rejectReason) { Registration registration this.getById(registrationId); if (registration null) { throw new BusinessException(报名记录不存在); } if (!RegistrationStatus.PENDING.getValue().equals(registration.getStatus())) { throw new BusinessException(该报名已审核请勿重复操作); } registration.setStatus(auditResult); registration.setAuditRemark(auditResult.equals(RegistrationStatus.REJECTED.getValue()) ? rejectReason : null); this.updateById(registration); }Transactional确保审核操作要么整体成功要么整体回滚不会出现状态改了但备注没写进去的中间状态。这个例子在答辩现场可以直接用来说明我掌握了事务控制理解数据一致性。5.4 比赛成绩录入与报表导出比较实用的功能是成绩录入后的报表导出。管理员可以按照竞赛ID导出该竞赛的获奖名单Excel。这里用的是Apache POI。POI是Java处理Excel的老牌库能读也能写但API比较繁琐。如果你嫌麻烦可以用EasyExcel这是阿里开源的在POI基础上的封装API简洁很多性能也好。用POI导出获奖名单的核心代码思路是这样的先查出一场竞赛的所有成绩记录关联学生姓名、学号、学院然后创建工作簿、创建Sheet、创建标题行、遍历数据填充行。导出时还可以设置单元格样式加粗、边框、居中让Excel直接能用。XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(获奖名单); String[] headers {姓名, 学号, 学院, 作品名称, 获奖等级, 指导老师}; // 设置标题行样式并合并单元格... // 遍历成绩列表逐行写入...写完导出逻辑后Controller接口返回ResponseEntitybyte[]并设置Content-Disposition头让浏览器弹下载。前后端分离场景下还可以用window.open触发下载。这个功能做出来后在系统演示环节相当能打——老师一看你连Excel导出都做了就会觉得这个系统是完整可落地的。6. 答辩追问环节评委最可能问的五个问题6.1 为什么用MyBatis Plus而不是JPA这道题基本是必问因为Spring Boot官方默认推崇Spring Data JPA。评委问这个问题的潜台词是你对技术选型有思考吗还是随手拿了一个ORM框架。我的答辩思路是先承认JPA很强——它通过实体对象自动建表、自动维护关系映射开发效率极高。但本项目的核心是可控的SQL。竞赛管理系统的业务虽然不复杂但涉及报名统计、成绩排名、多条件联合筛选这类复杂查询JPA的自动SQL在复杂查询场景下不够直观一旦需要调优就麻烦。MyBatis Plus保留了手写SQL的灵活性又有BaseMapper带来的CRUD便捷性因此选MP是可控性和效率的平衡。这个回答展示的是我做过多框架对比的样子比单方面吹某个框架好得多。6.2 多人在线同时报名会不会有并发问题评委问并发问题常常是想判断你对多线程和数据库事务有没有概念。朴素的数据库操作是先查询是否已报名再insert这在单用户场景下没问题但高并发下两个用户查到同样结果都去insert就产生了重复报名。应对方法是给报名表的competition_id student_id加一个唯一索引从数据库层面兜底重复报名。同时用INSERT ... ON DUPLICATE KEY UPDATE或先捕获DuplicateKeyException向前端返回请勿重复报名。在代码里还可以用synchronized或分布式锁但对这个系统来说唯一索引才是简单又可靠的方案。6.3 数据库字段设计违反范式了吗这个问题考察的是你对数据库设计理论的掌握程度也看你会不会活学活用。例如报名表里冗余了一个competition_name冗余字段严格按第三范式确实不该存因为竞赛名称已经存在于竞赛表了。但这里的理由是报名列表页需要频繁展示竞赛名称每次查询都要join竞赛表数据量一大反而慢。在教育管理系统里读多写少是常态适当冗余换取查询性能是常见设计权衡。回答时先承认理论上第三范式不应该冗余这个字段再解释实际开发中在业务查询十分频繁且数据变更不频繁的场景下会做有意识的冗余设计这就把数据库理论化为了实践能力。6.4 如果比赛时间修改了系统的状态能自动更新吗这题的坑在于系统里有个状态字段它标识着竞赛处于报名中、评审中、已结束等阶段。很多字段是管理员手动改的但如果管理员忘记操作状态和实际时间就对不上。推荐的处理方案是增加一个定时任务用Spring自带的Scheduled注解定时扫描竞赛表根据当前时间和报名截止时间自动更新状态。启动类加上EnableScheduling在Service类里写一个定时方法Scheduled(cron 0 0 */1 * * ?) // 每小时执行一次 public void autoUpdateCompetitionStatus() { // 查询所有未结束的竞赛判断时间是否已过deadline自动更新状态 }这段逻辑看起来简单但代码里用了Spring的定时任务机制在答辩时讲我用定时任务实现了系统状态的自动流转又是一个加分的点。6.5 用户密码如何保证安全密码安全的问题比较基础但被问到的概率很大。答案是BCrypt加密存储不透露密码原文。密码传输层建议配合HTTPS但开发环境不需要纠结。另外一个值得说的点是系统里管理员可以重置用户密码重置后的默认密码也应该是BCrypt加密后再入库而不是明文。只要在代码里走同一个加密工具类统一逻辑。7. 部署上线与日常排错记录7.1 本地跑得好好的服务器上却起不来这种情况太常见了十个同学部署九个遇到过。最常见的几类问题端口问题。服务器安全组没有放行8080端口外网访问不到。检查方式很简单在浏览器访问http://服务器IP:8080能通就没有问题。或者用curl -v 服务器IP:8080在服务器本机试一次能通说明端口监听正常再查安全组。数据库连接问题。服务器上MySQL的用户名密码和application.yml里不一致。本地能连不代表服务器上也能连。建议在配置文件里使用环境变量注入数据库密码的方式不要硬编码在代码里password: ${DB_PASSWORD}这样一个项目无论部署到哪里只要在环境中配置了合适的DB_PASSWORD就能启动本地开发用本地值服务器上用服务器值也不怕密码泄露。Java版本问题。服务器上安装的JDK版本和本地不一致特别是本地用JDK 17而服务器是JDK 8的情况直接会ClassNotFoundException或UnsupportedClassVersionError。部署前先java -version确认一下。7.2 打包和启动的正确姿势后端打包用Maven的package命令在项目根目录执行mvn clean package -DskipTests打出来的jar包在target/目录下。上传到服务器后启动nohup java -jar competition-system.jar app.log 21 重点是nohup和一个是让进程不因SSH断开而终止一个是放到后台运行。日志重定向到app.log出问题直接看日志比对着屏幕懵圈高效得多。前端打包npm run build产物在dist/目录下上传到Nginx的html目录修改Nginx配置把/api路径反向代理到后端8080端口location /api/ { proxy_pass http://127.0.0.1:8080/; }这样前端页面和接口走同一个域名不存在跨域问题。7.3 字节跳动Java面试题热度背后的一个信号最近相关热搜榜上涌现了大量springboot相关的问题——springboot项目全局过滤器处理上传pdf文件时xss攻击、springboot版本太高、springboot整合activemq还有一个高频词是github不能访问 加速-这倒是有需要的人自己去想办法解决。这些热搜词的集中出现释放了一个信号Spring Boot已经从要不要学的阶段进入了怎么用好的阶段。对毕设学生来说理解这套竞赛管理系统只是第一步借这个项目去搞明白全局过滤器、XSS防护、多模块构建的思路才是真正的价值所在。7.4 实际部署中值得注意的两个细节第一个是文件上传路径权限。上传目录的权限必须设置为可写。如果你用的是/data/competition/upload/在Linux服务器上一个chmod -R 755是跑不掉的否则启动不报错但一上传就500。第二个是MySQL的sql_mode问题。MySQL 5.7默认启用了only_full_group_by如果你在项目里写了group by查询本地跑得好但服务器上报错this is incompatible with sql_modeonly_full_group_by就说明服务器数据库的sql_mode配置比本地严格。方案是在MySQL配置里把only_full_group_by去掉或者调整SQL写得符合规范。这个问题极其隐蔽很多同学部署后遇到直接懵这里特意写出来作为提醒。8. 读完源码之后的自主升级建议8.1 从能跑到能讲的复盘路径拿到项目后第一时间跑通不是终点真正要在答辩时讲明白还得带着问题去读代码。我的建议是按照启动流程 → 登录流程 → 报名流程 → 成绩管理流程四条主线去走一遍。登录走一遍能搞清楚拦截器和Session的作用域报名流程走一遍能理解Service层的事务和状态流转成绩管理流程走一遍能明白文件上传的存储路径和Excel导出的数据来源。读完一遍后尝试自己给系统加一些小功能——比如统计每个竞赛的报名人数做一个排行榜、给学生增加一个我的报名日历视图、或者给管理员做一个按学院统计获奖情况的图表。不一定要做得非常完善但这个动手过程能让你对项目的熟悉程度远超那些背代码的同学答辩时说话底气完全不同。8.2 一个提升项目含金量的小改法如果答辩时间还充裕建议把系统从前端Vue 2升级到Vue 3 Vite或者把Controller层返回的Map换成统一的ResponseResult类型再统一加一个全局异常处理器。这两个改动代码量不大但项目的现代感一下就出来了。特别是ResponseResult统一返回结构把如下代码return Result.success(data); return Result.error(参数错误);统一处理后异常处理、参数校验都收口到一处代码整洁度提升明显。评委看到你不仅会写CRUD还关注工程化设计印象分一下就上去了。这套学科竞赛管理系统的源码我已经打包整理好包含后端完整Java代码、前端Vue工程、数据库建表脚本和一份快速部署说明文档需要的同学可以直接在评论区留言或私信我看到都会发。也欢迎已经在做的同学来交流讨论遇到具体的报错信息把日志贴出来我看到后都会帮忙一起定位。
