尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于SpringBoot+Vue的高校竞赛管理系统开发实战

基于SpringBoot+Vue的高校竞赛管理系统开发实战 这套“高校学科竞赛平台管理系统”我前前后后做过完整版本也在开源社区帮人排查过不少类似项目的问题。它的业务本质不复杂——无非就是竞赛发布、报名、作品提交、评审打分、成绩公示——但因为涉及学生、教师、评委、管理员多种角色再加上“报名—提交—评审—公示”这种带状态流转的流程做起来就比普通增删改查要讲究不少。今天我就拿一套基于SpringBootVueMyBatisMySQL的实现从数据库设计、后端接口、前端页面、打包部署到踩坑实录完完整整拆一遍。先说清楚这个系统适合谁。如果你是准备毕业设计的同学这套技术栈是最稳妥的选择SpringBoot后端 Vue前端 MyBatis数据层MySQL存储每一层都是国内企业用的主流方案答辩时技术点也容易讲。如果你是公司里要做同类活动的管理系统这套东西改改业务就行流程里的状态机设计、权限控制、文件上传这些模块都是可以直接复用的。哪怕你只是想学一下前后端分离项目怎么落地把源码跑起来对照着看一遍接口和页面的对应关系也比光看书进步快得多。我不会只给你堆源码截图和启动步骤。更重要的是把每个设计决策背后的“为什么”讲清楚为什么用户表要拆角色和权限为什么报名表要有唯一索引为什么评审打分要单独建子表而不是在竞赛表里加几个字段这些想明白了你改需求、加功能、答答辩都稳得多。1. 项目在解决什么问题——设计思路与业务拆解1.1 这类系统的真实业务痛点高校里的学科竞赛从校级选拔到省级、国家级报送中间环节特别多。传统的处理方式基本是发通知靠QQ群和官网新闻报名靠表格收集作品交电子版靠网盘链接或者邮箱评审靠评委线下碰头打分最后成绩公示靠手工整理Excel。这个流程里最麻烦的不是某一个环节而是信息在不同渠道之间流转换容易出错——报名表收重了、作品文件过期了、打分表统计错了这些情况我见过太多次。学科竞赛平台的定位就是把整条线上化。平台要管四类人学生负责报名和交作品教师负责审核和指导评委负责打分和评审管理员负责配置竞赛、管理用户、发布公告。围绕这几类人系统的核心功能就清楚了竞赛管理创建竞赛、设定报名时间窗口、配置竞赛阶段、设定评审规则报名管理学生在线报名、教师审核资格、管理员手动调整异常报名作品管理学生按格式提交作品、支持多个附件、允许在截止前更新版本评审管理给评委分配作品、评委独立打分、系统自动汇总成绩系统管理用户、角色、菜单权限、操作日志、新闻公告这个业务模型其实覆盖了大多数“报名-审核-提交-评定”类系统的共性。细心的读者会发现把“竞赛”换成“课题”“项目”“展会”把“作品”换成“论文”“方案”“展品”这套系统的骨架可以套用到很多场景这也是我推荐你彻底吃透它的原因。1.2 技术选型这套组合好在哪SpringBoot Vue MyBatis MySQL是当前国内Java后端项目里出现频率极高的组合。先聊聊为什么是SpringBoot而不是SpringMVC或者更重的SpringCloud。SpringBoot最大的价值是“约定优于配置”。以前用SpringMVC光配置xml就要写半天数据源、事务管理器、视图解析器、包扫描每一样都要手动声明。SpringBoot把这些全部做成自动配置你只需要在application.yml里写数据库地址和账号密码项目就能跑起来。这对中小型管理系统是决定性的——它把开发者的精力从配置架构解放到了业务实现上。Vue在前端层面的优势是组件化和响应式。竞赛平台这类页面列表页、表单页、详情页、审核页很多结构是重复的——同一个表格换不同的列同一个表单换不同的字段。Vue把页面拆成组件后这些重复部分以组件为单位复用开发效率提升非常明显。Vue 2.7到Vue 3这一段组合式API让逻辑复用更进一步后面我会具体讲。MyBatis是一个半自动的ORM框架跟Hibernate那种全自动的相比它在SQL层面给了开发者最大的控制权。竞赛平台里报表统计、复杂条件查询很多比如“查询所有正处在评审阶段的竞赛及各竞赛的报名人数和平均分”这种SQL你很难用纯ORM的API写或者写出来也没法看。MyBatis让你直接写SQL、自己控制映射优雅得多。MySQL就不用多说了免费、稳定、资料多中小型项目的数据量下性能绰绰有余。这套组合搭配起来开发周期短、运行成本低、排错资料丰富作为管理系统的技术底座非常合适。2. 核心数据模型数据库设计与表结构规划数据库设计决定了这个项目能走多远。我见过很多人上来就建表做完功能才发现表结构不合理回头改表改到怀疑人生。这里我把一套经过实践检验的表结构拆给你。2.1 模块划分与表结构总览整个库我倾向于按业务域拆成五组表第一组是用户与权限域用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。这种RBAC基于角色的访问控制模型在管理系统中是标准做法。第二组是竞赛域竞赛表存竞赛基本信息竞赛阶段表存报名、提交、评审、公示各阶段的时间窗口这两个分开存是为了支持竞赛动态配置阶段。第三组是报名与作品域报名表、作品表、作品附件表。之所以要单独建作品附件表是因为一个作品可能对应多个文件说明书、展示视频、源代码压缩包一对多关系归到子表更规范。第四组是评审域评审配置表、评审打分表。评审配置存评分项和权重打分表存每个评委对每个作品的评分明细。这里有一个容易犯的错直接在作品表里加几个“评委1打分”“评委2打分”字段这是严格的反模式后患无穷。第五组是辅助域新闻公告表、操作日志表、字典表。字典表用来存竞赛类型、作品类型这类枚举值比在代码里写死灵活得多。这样一个库的框架就立住了。你去看市面上的管理系统源码只要设计是用了心的结构基本逃不出这套框架。2.2 报名表与作品表的设计细节报名表是业务的核心枢纽设计上有几处必须注意。一是唯一索引建表时要在竞赛id用户id上加联合唯一索引从数据库层面杜绝一个学生对同一竞赛重复报名。这一点比只做后端判断可靠得多因为并发请求同时到达时Java代码里的if判断防不住数据库唯一索引却能保证只有一条插入成功。二是状态字段的设计。报名状态一般有“待审核”“已通过”“已驳回”“已撤销”四种。状态值我建议用tinyint存数字0/1/2/3分别对应在Java侧用枚举类做映射。不推荐直接存中文因为在数据库里比较和统计都不方便一旦改了中文文案所有业务代码都得跟着动。三是时间字段统一用datetime一律存服务器本地时间前端展示时再转时区。别在这里玩花样时区坑能把人整疯。作品表要特别注意“版本”这个概念。竞赛一般允许学生在提交截止前多次更新作品所以作品表里要有版本号字段每次提交或更新都生成一条记录或者覆盖现行记录并保留历史版本号。保留历史版本的做法在正式竞赛中很有用——如果学生误操作覆盖了原文件管理员还有机会恢复。作品附件表的文件名一定要做重命名处理不能直接存用户上传的原始文件名。因为不同学生可能上传相同名字的文件比如“项目报告.docx”如果都按原名存后上传的就会覆盖先上传的。我习惯用UUID作为磁盘文件名原始文件名单独存一个字段下载时再把原始名返回给前端。2.3 权限数据模型用户、角色、菜单怎么落库权限这块很多课设级别的项目直接省略掉所有登录用户看到的界面一样。但这套系统有多角色区分权限设计是硬需求。我的方案是一张用户表不直接关联权限而是通过角色表和菜单表做两级关联。用户表里有个role字段或者独立的用户角色关联表角色表里关联菜单id前端根据登录用户返回的菜单列表动态生成侧边栏后端在接口层面用注解校验角色权限。菜单表里有个字段比较关键type。一级菜单是目录二级是菜单三级是按钮。像“删除报名”“审核通过”这种操作其实是挂到菜单表里的按钮级权限这样前端可以精确控制到按钮显隐后端接口也可以精确拦截到单个操作。这套模型做出来之后新增一个角色只需要给它勾选菜单不用改代码这也是答辩时能加分的点。3. 后端落地SpringBootMyBatis的关键实现3.1 工程分层包结构划分的思路我见过不少源码后端所有类一股脑丢在同一个包下面controller里面直接写SQLservice又空又薄。这种代码跑起来没问题但维护的时候是真难受。合理的分层应该是controller只接收参数、校验基础格式、调用service、组装返回结果service业务逻辑的所在地事务边界也在这层mapper数据访问接口只负责数据库交互entity数据库表映射实体dto接收前端参数的传输对象vo返回给前端的视图对象config配置类如跨域配置、拦截器配置、文件上传配置common统一返回结果、异常处理、工具类这套分层的核心原则是“各层职责单一”。Controller不要写SQLMapper不要写业务判断Service不要直接操作HttpServletRequest。按这个约束写出来的代码别人接手的时候很轻松。3.2 MyBatis配置要点MyBatis接入SpringBoot核心配置就在application.yml里。我直接给一份我常用的配置spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/competition?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: yourpassword mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.competition.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl注意几个地方。url里的参数不是随便加的characterEncodingutf8防止中文乱码serverTimezoneAsia/Shanghai防止时区导致的日期偏差useSSLfalse是为了规避MySQL 8.0默认开启SSL导致的安全警告allowPublicKeyRetrievaltrue在MySQL 8.0上面连接时容易踩坑。map-underscore-to-camel-case这个要开数据库字段的snake_case下划线命名才能自动映射到实体的camelCase驼峰命名。log-impl建议只在开发环境开启控制台直接打印SQL语句方便调试。生产环境一定要关掉不然日志里全是SQL又占空间又暴露表结构。3.3 核心业务逻辑竞赛流程状态机竞赛平台的业务核心是流程控制。一个竞赛从创建到结束状态转变是有规则的不能从“报名中”直接跳到“已结束”必须经过“评审中”再到“公示中”。我在系统里用了一个比较简单但可靠的状态机实现。竞赛表里有个status字段0草稿、1报名中、2作品提交中、3评审中、4成绩公示中、5已结束。每个状态之间的转移写在一个方法里转移前做合法性校验public void transition(Long competitionId, Integer targetStatus) { Competition competition competitionMapper.selectById(competitionId); // 校验当前状态到目标状态是否允许 if (!canTransition(competition.getStatus(), targetStatus)) { throw new BusinessException(非法状态流转: competition.getStatus() - targetStatus); } competition.setStatus(targetStatus); competitionMapper.updateById(competition); } private boolean canTransition(Integer current, Integer target) { // 定义一个二维数组或Map表示允许的转移关系 // 例如 0 - 1, 1 - 2, 2 - 3, 3 - 4, 4 - 5 // 其余都返回 false }为什么不用工作流引擎比如Flowable、Activiti因为竞赛流程相对固定不涉及复杂的流程分支、会签、驳回重走用工作流引擎属于杀鸡用牛刀还会让项目依赖变得臃肿。这个状态机方法配合数据库里的乐观锁版本号字段已经足够应对高并发场景下的并发状态更新。3.4 文件上传与访问的落地姿势作品提交涉及到文件上传这是竞赛平台里最容易出问题的模块。上传要处理好三件事文件存哪里、文件名怎么处理、访问路径怎么映射。本地开发时我习惯把文件存在项目外的绝对路径比如/usr/local/competition/upload/而不是存在项目目录下。因为SpringBoot项目打包成jar运行后项目内部目录在临时文件系统里重启就会丢文件而且没法轻松扩容。生产环境里这个目录最好挂载独立数据盘。文件名用UUID重命名String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String storedName UUID.randomUUID().toString().replace(-, ) ext;这一步一定要做。你想象一下两个不同学生都上传了“开题报告.docx”如果按原名存储后上传的就把先上传的覆盖了。UUID重命名后文件在磁盘上的存储名和原始名彻底解耦下载时再把originalFilename存到数据库里用于响应头返回。文件访问路径本地开发可以配置SpringBoot的静态资源映射Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceHandler(file: uploadDir /); } }这样前端通过http://localhost:8080/files/xxx.jpg就能直接访问上传的文件。生产环境则推荐用Nginx映射下面部署章节会说具体配置。4. 前端落地Vue项目的搭建与页面实现4.1 工程初始化和路由设计前端我用Vue 3 Vite Element Plus这套组合相比Vue 2的WebpackVite开发服务器的启动速度是秒级的改代码热更新也快得多。创建项目npm create vitelatest competition-web cd competition-web npm install npm install vue-router4 axios element-plus element-plus/icons-vue pinia安装完依赖第一步是配置路由。路由分成两块静态路由和动态路由。静态路由只有两个页面——登录页和404页。动态路由是根据登录用户的角色动态生成的每个菜单项对应一组路由。// 静态路由 const staticRoutes [ { path: /login, component: () import(/views/Login.vue) }, { path: /404, component: () import(/views/NotFound.vue) }, { path: /, redirect: /dashboard } ]; // 动态路由登录后通过接口获取菜单数据再addRoute function buildDynamicRoutes(menus) { const routes []; menus.forEach(menu { routes.push({ path: menu.path, name: menu.name, component: () import(/views${menu.component}), meta: { title: menu.title, roles: menu.roles } }); }); return routes; }路由守卫是必须的。在router.beforeEach里做两件事判断用户是否登录通过token判断目标路由是否需要权限。这两步都通过了才放行。4.2 axios封装接口对接的规范做法前后端分离项目axios不封装直接到处用后面维护绝对痛苦。我的封装习惯是配置baseURL、请求头加token、响应拦截器统一处理错误码。import axios from axios; import { ElMessage } from element-plus; import router from /router; const service axios.create({ baseURL: /api, timeout: 15000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] Bearer token; } return config; }); service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { if (res.code 401) { localStorage.removeItem(token); router.push(/login); } ElMessage.error(res.message || 请求失败); return Promise.reject(new Error(res.message)); } return res; }, error { ElMessage.error(error.message || 网络异常); return Promise.reject(error); } ); export default service;baseURL为什么在开发环境用/api因为我在Vite里配了代理转发把/api开头的请求转发到后端8080端口这样前端页面和后端接口是同域的开发阶段就不会有跨域问题。生产部署时再用Nginx做同样的转发前后端代码都不用改只需要调整代理配置。401统一跳登录页这段要特别留意。token过期是一个很常见的业务场景如果每个请求都自己写一遍判断逻辑代码量爆炸不说漏一处就会导致接口报错但用户还停留在登录页面的尴尬局面。4.3 列表页、表单页、权限按钮的实现重点前端页面多了之后你会发现其实就三类页面在反复出现列表页、表单页、详情页。列表页我用Element Plus的el-table el-pagination组合。分页参数固定为pageNum和pageSize后端返回结构固定为{ records, total }。这样所有列表接口可以共用同一个分页处理逻辑省掉大量重复代码。表单页要注意的是回显逻辑。新增和编辑共用同一个表单组件通过一个id字段判断当前是新增还是编辑模式。编辑时先调详情接口拿到数据再给表单赋值。这里有个细节表单赋值要在dialog完全打开后进行否则在确保数据已经在Vue的响应式系统里注册的时候可能会出bug。用nextTick处理即可。按钮级权限是前端一个容易忽略的点。竞赛管理页面上“审核通过”“驳回”“删除”这些按钮不是所有角色都能看到的。我的做法是写一个自定义指令v-permissionapp.directive(permission, { mounted(el, binding) { const requiredPerms binding.value; const userPerms store.state.user.permissions; const hasPermission requiredPerms.some(perm userPerms.includes(perm)); if (!hasPermission) { el.parentNode el.parentNode.removeChild(el); } } });这样在按钮上直接写el-button v-permission[competition:review] clickhandleReview审核/el-button没有权限的角色按钮在DOM渲染后直接被移除了。页面逻辑和权限判断解耦代码可读性好不少。5. 从源码到部署——全流程环境配置指南光有源码跑不起来等于零。这里把从零搭建运行环境到部署上线的完整流程走一遍每一步都给出实操细节。5.1 本地环境准备后端需要JDK 8或JDK 17、Maven 3.6、MySQL 5.7或8.0、IDEA前端需要Node.js 14和npm。JDK版本这块有个容易踩的坑SpringBoot 2.7及以下用JDK 8就行但如果你拿到的是SpringBoot 3.x的源码那就必须配JDK 17因为SpringBoot 3基于Jakarta EEjavax包拿不到了。启动报错ClassNotFoundException / NoClassDefFoundError和这个有很强的关系。MySQL的安装教程网上很多我提醒几个细节。一是MySQL 8.0安装时记得选UTF8MB4字符集这里面包括Emoji和生僻字的完整支持。二是root账号密码要记住本地库密码忘了很麻烦会重置的话还好不会的话只能卸载重装。三是连接MySQL时要用“localhost”或者“127.0.0.1”如果你的MySQL装在本机不要填成服务器IP。前端需要先装Node.js然后是npm源问题。国内直接跑npm install经常会卡住建议先换淘宝镜像npm config set registry https://registry.npmmirror.com5.2 初始化数据库与启动后端数据库脚本一般在源码里的sql目录下可能叫competition.sql或init.sql。用Navicat或者MySQL命令行执行mysql -u root -p competition.sql执行完检查一下应该能看到十几张表。核对一下表数量和数据字典如果发现有表缺失大概率是SQL脚本执行中断了重新执行一遍即可。然后修改application.yml里的数据库密码改成你自己的。启动之前还需要确认Maven能正常拉取依赖。如果依赖下载失败检查一下Maven的镜像配置国内用阿里云镜像会快很多mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror后端启动成功后控制台会输出Spring Boot的启动日志最后一行是“Started Application in x.xxx seconds”之类。然后访问http://localhost:8080/api/auth/login这种接口测试连通性。如果访问不通先用curl测试不要一上来就用前端页面问题定位要缩小范围。5.3 Linux服务器部署Nginxjar正式部署时我推荐把后端打成一个可执行jar包配合Nginx做反向代理和静态资源服务。这是目前最主流也最稳的部署方式。后端打包前先检查application.yml里的环境配置数据库地址要从localhost改成服务器内网IP或公网IP密码也要换成生产环境的。然后执行mvn clean package -DskipTests打包成功后target目录下会出现一个competition-platform-0.0.1-SNAPSHOT.jar。把jar上传到服务器使用nohup启动nohup java -jar competition-platform-0.0.1-SNAPSHOT.jar --server.port8080 app.log 21 这样启动的好处是关闭终端后程序继续运行日志输出到app.log排查问题时方便查看。前端在打包之前有一个关键配置要改。查看项目根目录下的vite.config.js里面需要设置base路径如果你的系统打算部署在域名根路径下base设成’/‘即可如果部署在子路径下比如http://yourdomain/competition/base必须设成’/competition/‘否则前端资源路径全是绝对路径部署到子目录后页面样式全丢。前端打包npm run build打包完成后dist目录就是静态文件。把它上传到服务器比如/var/www/competition。然后Nginx配置server { listen 80; server_name yourdomain.com; # 前端页面 location / { root /var/www/competition; index index.html; try_files $uri $uri/ /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; } # 上传文件访问 location /files/ { alias /usr/local/competition/upload/; } }这段配置里try_files那行是解决Vue路由的history模式刷新404问题的Vue Router的history模式路径是虚拟的Nginx直接按文件找找不到必须回退到index.html。proxy_pass的末尾带斜杠要特别注意它会把匹配到的/api/前缀去掉再转发给后端这样后端Controller里的请求映射不用带/api前缀。加了alias的/files/就是直接映射到服务器磁盘目录的。配置改完reload一下Nginxnginx -s reload这样一套系统就上线了。访问域名能看到登录页面说明前后端对接完全正常。6. 常见问题与排查技巧实录不管源码是买的还是自己写的跑起来总会遇到一堆问题。这里把我在实操和帮人排错中遇到的高频问题整理成一份速查表附上排查思路和解决方案。6.1 前后端联调时的跨域问题开发环境下前端跑在5173端口Vite默认后端跑在8080端口前后端端口不同浏览器就会触发跨域拦截。很多新手一上来就想着在后端加CrossOrigin或者全局CORS配置其实在开发环境最优解是配置Vite的代理server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这行的核心逻辑是浏览器看到前端页面请求的路径是/api/xxx和页面本身同源就不会拦截Vite开发服务器收到这个请求后转发到后端的8080端口。跨域问题直接消失。如果在生产环境依然遇到跨域通常是部署架构的问题。比如前端页面放在一个域名后端接口在另一个域名这就真的跨域了。解决方案不是在后端无限加白名单而是用Nginx在同一域名下做路由转发把/api/的请求转发给后端。6.2 MyBatis的常见坑数字字符比较、缓存数据不一致MyBatis有个很隐蔽的坑就是XML映射文件里的单个数字字符比较。if teststatus 1这段代码看起来没问题但MyBatis解析时Ognl表达式会把1解析成字符串“1”而Java里整数和字符串用比较永远返回false这个条件就永远不生效。你在写动态SQL做筛选时如果条件死活不匹配优先检查是不是这个原因。解决方法是加单引号反转if teststatus 1或者更好的方式是传参时直接传Integer类型而不是前端传字符串。前端传状态的数字时明确用Number类型Number(1)这种转换不可少。MyBatis的缓存也是个容易踩坑的点。一级缓存默认开启作用范围是同一个SqlSession。在SpringBoot里SqlSession的生命周期通常和一次数据库操作绑定所以一级缓存问题不算大。真正麻烦的是二级缓存。如果配置了二级缓存不同的SqlSession会共享缓存。当你在一个事务里更新了数据但缓存没有及时清空另一个查询就可能读到旧数据。在管理系统中数据实时性要求较高我的建议是直接关闭二级缓存换来心智负担的最小化mybatis: configuration: cache-enabled: false6.3 SQL查询结果与预期不符排查SQL标签类的错误最快的方式是打开MyBatis的SQL日志输出。在开发环境的配置里加入logging: level: com.example.competition.mapper: debug或者使用MyBatis Log Plugin这类插件IDEA装上后能直接看到带参数值的完整SQL排查问题效率至少提升一倍。看到的日志SQL直接拷到Navicat里执行比对结果很快能定位是SQL写错了还是参数传错了。实际排错中我发现Java侧传参和SQL语句是分开的直接看日志里的SQL是没有SQL参数的真实值这一点对不熟悉MyBatis日志格式的人来说很有迷惑性。用MyBatis Log Plugin的好处就是它把参数拼接进SQL直接生成一条可执行的完整语句。6.4 Vue打包后布局异常与刷新404Vue项目本地开发一切正常打包部署后页面布局乱掉这个问题的根源几乎都是publicPath配置错误。静态资源比如js、css的路径如果用了绝对路径“/assets/xxx.js”部署到子目录后网站的静态资源请求全跑到域名的根路径下去找了自然找不到。Vite项目的解决方式是在vite.config.js里设置base: ./这样打包后的静态资源路径全变成相对路径可以部署到任意目录下。但有一个瑕疵是路由的history模式和相对路径base会冲突建议使用hash模式路由或者在部署时固定目录层级。刷新页面404的问题同样常见。原因我前面已经说了Vue Router的history模式路径是虚拟的Nginx直接把/xx/xx路径当成文件去找自然404。解决方案就是在Nginx配置里加上try_files $uri $uri/ /index.html;这也是为什么index.html会收到所有无法匹配到真实文件的请求前端路由再按自己的规则渲染对应页面。6.5 SpringBoot版本太高导致的兼容性问题这个问题在2025年的今天越来越常见。网上很多教程、源码用的还是老写法但新手创建项目时SpringBoot已经到3.x了。最典型的冲突是javax和jakarta命名空间不兼容导致编译报错找不到包。遇到这个问题我建议如果只是为了跑通项目学习业务逻辑把SpringBoot版本降到2.7.x是最省事的。如果你一定要用3.x那就得把代码里的javax全部替换成jakarta包括Redis、Security等配套组件的依赖也要换成兼容版本工作量大不少。还有一行容易遗漏就是数据库驱动。MySQL 5.x用com.mysql.jdbc.DriverMySQL 8.x用com.mysql.cj.jdbc.Driver。SpringBoot自动配置能识别但在MyBatis的XML或者配置里手写了Driver类名时就得特别小心版本对应。6.6 MySQL连接和SQL层面的常见报错连接MySQL 8.x时报“Public Key Retrieval is not allowed”的错误是因为MySQL 8.x默认的caching_sha2_password认证插件需要安全连接而客户端没有认证密钥。在JDBC URL里加allowPublicKeyRetrievaltrue就行这个我在前面配置里提到了这里再强调一次因为它真的会卡掉一大批人。数据库时间是空或时间对不上大概率是时区配置的问题。数据库连接串没有serverTimezone参数或设成了GMT会导致时间差8小时。用Asia/Shanghai是最省心的。如果数据量上来后出现慢查询优先检查有没有给外键字段和查询频繁的字段加索引。报名表的竞赛id、用户id作品表的竞赛id这些字段没有索引的话数据量上万后查询就会明显变慢。用EXPLAIN关键字看一下执行计划重点看type列如果出现ALL全表扫描基本就是索引没建好。最后再分享一个排查SQL问题时我常干的事把MyBatis打出的SQL复制到Navicat里执行然后用EXPLAIN查看执行计划。这个习惯帮我排查过不少慢查询和数据不一致的问题。大多数时候问题能在这一步里找到答案不需要复杂的事后分析。做这类管理系统数据库设计和流程状态控制是最核心的部分。表结构设计好了接口写起来顺风顺水状态流转控制好了系统逻辑不会绕过边界条件出现脏数据。这套源码跑通之后有余力的话你还可以给自己加几个进阶挑战在作品提交模块接一个在线预览功能或者把评分统计做成可视化图表。把这些问题都解决了你对这套技术栈的理解绝对不输给大部分岗位要求里写的“熟悉SpringBootVue”。
返回列表