ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

SpringBoot+Vue高校竞赛管理系统设计与实现全解析

SpringBoot+Vue高校竞赛管理系统设计与实现全解析 1. 系统整体架构与业务模块拆解1.1 项目定位2025年高校竞赛管理为什么还需要一套新系统先说个真实场景。很多高校的学科竞赛管理至今还在用“微信群接龙Excel汇总教务老师手动审核”这套流程。学生报名要反复填表指导老师要挨个核对名单教务管理员要统计各类赛事数据评审专家要看纸质作品。一轮校赛下来光信息核对就要花掉好几个工作日。更别提多个赛事同时启动的时候数据混乱、文件丢失、通知遗漏都是家常便饭。这套SpringBootVue的高校学科竞赛平台管理系统就是奔着解决这些问题去的。它的核心定位不是“做一个漂亮的网页”而是把竞赛从“发布—报名—组队—提交作品—评审—成绩公布”这条完整链路搬到线上让不同角色在同一套数据体系里协作。技术选型上后端用SpringBoot做服务端框架持久层用MyBatis操作MySQL数据库前端用Vue构建交互页面这是目前JavaWeb课程设计、毕业设计里最主流、也最容易被面试官认可的组合。这个系统适合谁来参考如果你是计算机相关专业的学生正在做课程设计或毕业设计想找一个“技术栈主流、业务逻辑完整、能讲清楚设计思路”的项目模板这套系统的拆解过程会非常有用。如果你是刚入职的初级开发想学习一个多角色权限系统怎么落地前端和后端如何联调MyBatis的Mapper层怎么写才规范同样能从这篇文章里拿到可直接复用的套路。1.2 三层架构与角色权限模型先看整体架构我习惯把这类管理系统拆成三个层次来看。表现层就是Vue前端负责页面渲染、表单校验、路由跳转通过Axios发起HTTP请求。后端是SpringBoot用Controller接收请求Service处理业务逻辑Mapper也就是DAO层通过MyBatis与MySQL交互。数据层就是MySQL数据库存储用户、赛事、报名、作品、评审结果等数据。这个项目最值得琢磨的是角色权限模型。学科竞赛平台至少有四类角色学生查看竞赛列表、报名参赛、组建队伍、提交作品、查看个人成绩。指导老师审核学生报名、对学生作品给出指导意见、查看自己指导队伍的参赛情况。评审专家对分配到的作品进行打分、填写评审意见、提交评审结果。系统管理员维护竞赛信息、配置报名时间、管理用户、发布公告、统计各类数据。在实现上前后端都要做权限控制。后端用一个拦截器或过滤器校验Token根据用户的角色字段判断接口是否可访问前端用Vue Router的导航守卫在路由跳转时判断当前用户的角色和登录状态。只做前端隐藏按钮而不做后端拦截是这个类项目最常见的错误后面我会重点讲。1.3 核心业务流程串讲把业务流程理清楚是写代码之前最重要的一件事。我用一个校级程序设计大赛的完整流程来走一遍第一步管理员创建竞赛填写竞赛名称、级别校级/省级/国家级、类型个人赛/团体赛、报名开始时间、报名截止时间、作品提交截止时间、竞赛说明。此时竞赛状态为“未开始”前台学生看不到。第二步管理员一键发布竞赛状态变为“报名中”学生端首页展示该竞赛学生可以发起报名。团体赛需要组队队长创建队伍生成一个队伍邀请码其他成员通过邀请码加入队伍人数达到最低要求后队长提交报名。第三步指导老师审核报名信息确认队伍成员资格无误后通过。这里有个设计细节很多项目会漏掉“指导老师确认”环节直接让学生报名就生效这在真实竞赛场景里是不合理的因为高校竞赛通常需要指导老师对学生资格负责。第四步竞赛进入“进行中”状态后队伍在截止时间前提交作品。作品可以是文件上传源代码压缩包、论文PDF、演示视频也可以是填写在线表单作品简介、项目亮点。第五步管理员分配评审任务将作品按批次分配给多位评审专家。评审专家登录后看到自己的待评审列表对作品打分并填写评语。系统按规则计算最终成绩——比如去掉最高分和最低分取平均值或者按权重计算。第六步管理员发布成绩学生和指导老师端可以看到名次、奖项系统还能导出统计报表。这个流程串起来之后你会发现所有功能都围绕“竞赛状态”这条主线在转状态机设计得清晰权限划分自然也就清晰了。2. 技术选型与方案设计细节2.1 SpringBoot Vue组合的优势以及为什么不是其他方案先回答一个高频疑问都2025年了为什么还要用SpringBoot搭配Vue而不是Spring Cloud微服务或者前后端不分离的Thymeleaf模板方案原因很实在。对于高校竞赛平台这种中等规模的管理系统并发量不会特别高核心诉求是“业务逻辑清晰、开发效率高、易于维护”SpringBoot单体应用完全够用。它内置Tomcat简化了依赖管理和配置配合Maven或Gradle一条命令就能打包部署。如果你用微服务架构光服务拆分、注册中心、配置中心这些基础设施就要搭半天对课程设计和中小型项目来说属于过度设计。前端选Vue而不是React也不是说React不行而是Vue的学习曲线更平缓模板语法更贴近传统HTML思维。更重要的是Vue生态里有Element UI或Element Plus这样成熟的中后台组件库表格、表单、分页、弹窗、上传组件都现成特别适合快速搭建管理系统界面。持久层选MyBatis而不是JPA主要考虑两点一是MyBatis允许你手写SQL对复杂查询、多表联查、动态SQL的控制粒度更细竞赛平台里“按竞赛查询报名记录并关联用户和队伍信息”这类统计查询手写SQL更直接二是在国内Java技术栈的就业市场里MyBatis的使用率仍然很高作为课程设计选MyBatis面试时也更有的聊。2.2 数据库表设计与关键字段解析数据库设计是整个项目的基石。表设计得合理后面写Mapper能省一半力气。这个系统我建议至少包含以下几张核心表用户表user用户ID、用户名、密码BCrypt加密存储、真实姓名、学号/工号、角色、学院、邮箱、手机号、创建时间。密码千万不要明文存储这是底线。竞赛表competition竞赛ID、名称、级别、类型、描述、开始报名时间、截止报名时间、作品截止提交时间、状态、创建人ID、发布时间。队伍表team队伍ID、竞赛ID、队长ID、队伍名称、邀请码、队员人数、指导老师ID、报名状态。队伍成员表team_memberID、队伍ID、用户ID、加入时间。多对多关系一定要拆出来不要把成员ID塞在一个字段里逗号分隔那是反范式设计。作品表work作品ID、队伍ID、竞赛ID、标题、简介、文件URL、提交时间、最终得分、状态。评审表review评审ID、作品ID、评审专家ID、评分、评语、评审时间。公告表notice公告ID、标题、内容、发布时间、发布人ID。几个字段的关键细节我单独说一下。状态字段建议用整数或字符串表示枚举值比如竞赛状态用0未开始、1报名中、2进行中、3已结束不要存中文。这样代码里写判断逻辑清爽查询效率也高。邀请码字段必须唯一生成时用UUID截取前6位再查重即可。性别、学院这类固定选项字段如果用字符串存储建议用常量类统一管理避免前端传什么存什么导致数据脏乱。还有一个幂等设计值得注意学生重复点击报名按钮如果后端没有校验就会插入两条报名记录。解决办法是在队伍表加唯一索引竞赛ID 队长ID同时报名接口先查再插保证同一场竞赛一个学生只能创建一支队伍。2.3 请求路径与统一返回格式设计接口设计那块我建议你从一开始就约定统一的返回结构。不要一会儿返回{data: xxx}一会儿又返回{result: xxx}前端会疯掉。定义一个通用的Result类包含三个字段code状态码200成功400业务错误401未登录403无权限500服务器异常。message给前端展示的提示信息。data真正的业务数据。所有Controller的方法统一返回这个Result对象。这样Vue那边就能封装一个统一的响应拦截器code等于200时直接取data非200时弹错误提示不用每个页面重复处理。请求路径按模块划分RESTful风格命名/api/auth/login登录/api/auth/register注册/api/competition/list竞赛列表/api/competition/detail/{id}竞赛详情/api/team/create创建队伍/api/team/join加入队伍/api/work/submit提交作品/api/review/score评审打分/api/admin/stats/overview管理端数据统计路径中用复数名词、清晰的动词表达语义接口设计完前端开发甚至不用问后端就能猜个八九不离十。3. 核心功能模块的落地实现细节3.1 后端从Controller到Mapper的完整链路以“创建队伍”这个功能为例我带着你走一遍后端代码的完整链路这是面试时讲项目最需要的能力能把一个请求从前到后讲清楚。先看Controller层接收前端传来的参数调用Service层方法再用Result包装返回RestController RequestMapping(/api/team) public class TeamController { Autowired private TeamService teamService; PostMapping(/create) public Result createTeam(RequestBody Valid TeamCreateDTO dto, RequestAttribute(userId) Long userId) { TeamVO teamVO teamService.createTeam(dto, userId); return Result.success(teamVO); } }注意两个细节。第一RequestBody接收JSON参数第二RequestAttribute(userId)里的userId来自登录拦截器登录成功后把当前用户ID放到Request属性中Controller直接取用不需要每次把用户ID传进前端参数里。Service层做业务校验和事务控制Service Transactional(rollbackFor Exception.class) public class TeamServiceImpl implements TeamService { Autowired private TeamMapper teamMapper; Override public TeamVO createTeam(TeamCreateDTO dto, Long userId) { // 1. 校验竞赛是否处于报名中 Competition competition competitionMapper.selectById(dto.getCompetitionId()); if (competition null || competition.getStatus() ! 1) { throw new BizException(竞赛不存在或不在报名时间内); } // 2. 校验当前用户是否已报名 int count teamMapper.countByCompetitionAndLeader(dto.getCompetitionId(), userId); if (count 0) { throw new BizException(您已报名该竞赛无需重复报名); } // 3. 生成队伍记录 Team team new Team(); team.setCompetitionId(dto.getCompetitionId()); team.setLeaderId(userId); team.setName(dto.getTeamName()); team.setInviteCode(generateInviteCode()); team.setStatus(0); teamMapper.insert(team); // 4. 队长自动成为队员 TeamMember member new TeamMember(); member.setTeamId(team.getId()); member.setUserId(userId); teamMemberMapper.insert(member); return convertToVO(team); } }这段逻辑里最有含金量的是Transactional注解。创建队伍需要同时插入team表和team_member表两步操作必须保证原子性如果队员插入失败队伍记录也要回滚否则数据库里会出现一支没有成员的“幽灵队伍”。MyBatis的Mapper层这样写public interface TeamMapper { Insert(INSERT INTO team(competition_id, leader_id, name, invite_code, status) VALUES(#{competitionId}, #{leaderId}, #{name}, #{inviteCode}, #{status})) Options(useGeneratedKeys true, keyProperty id) int insert(Team team); Select(SELECT * FROM team WHERE competition_id #{competitionId} AND leader_id #{leaderId}) Team selectByCompetitionAndLeader(Param(competitionId) Long competitionId, Param(leaderId) Long leaderId); Select(SELECT COUNT(*) FROM team WHERE competition_id #{competitionId} AND leader_id #{leaderId}) int countByCompetitionAndLeader(Param(competitionId) Long competitionId, Param(leaderId) Long leaderId); }这里用了注解式SQL。如果项目里的SQL复杂、需要动态拼接我更推荐XML文件写法。举个例子管理端“按条件查询队伍列表”条件是混合的可能按竞赛、状态、队伍名称模糊搜索组合这时在XML文件里写动态SQL最方便select idselectTeamPage resultTypecom.example.vo.TeamVO SELECT t.*, c.name AS competition_name, u.real_name AS leader_name FROM team t LEFT JOIN competition c ON t.competition_id c.id LEFT JOIN user u ON t.leader_id u.id where if testcompetitionId ! null AND t.competition_id #{competitionId} /if if teststatus ! null AND t.status #{status} /if if testkeyword ! null and keyword ! AND (t.name LIKE CONCAT(%, #{keyword}, %) OR u.real_name LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY t.create_time DESC /selectwhere标签自带SQL修剪能力会智能去掉第一个多余的AND。这是MyBatis最实用、面试最容易问到的动态SQL特性没有之一。3.2 前端Vue路由守卫 Axios拦截器 组件化页面前端部分我挑三个核心点讲掌握了这三个整个前端的骨架就立住了。第一个是路由守卫。Vue Router提供的beforeEach钩子里做登录判断和权限校验router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.path /login) { next(); return; } if (!token) { next(/login); return; } const role localStorage.getItem(role); const allowedRoles to.meta.roles; if (allowedRoles !allowedRoles.includes(role)) { next(/403); return; } next(); });to.meta.roles是路由配置里自定义的字段比如管理端路由可以写成meta: { roles: [admin] }学生端路由写meta: { roles: [student] }。角色不匹配直接跳403页这个体验比弹窗报错要好得多。第二个是Axios拦截器。请求拦截器统一携带Token响应拦截器统一处理状态码和错误信息axios.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers.Authorization token; } return config; }); axios.interceptors.response.use( response { const res response.data; if (res.code 401) { localStorage.removeItem(token); router.push(/login); return Promise.reject(new Error(未登录)); } if (res.code ! 200) { Message.error(res.message); return Promise.reject(new Error(res.message)); } return res.data; }, error { Message.error(网络异常请稍后重试); return Promise.reject(error); } );这段代码的价值在于你写业务页面时完全不用关心错误处理逻辑直接await http.post(...)拿data去用就行拦截器把脏活累活都干完了。第三个是页面组件化。以竞赛列表页为例拆成三个子组件CompetitionFilter.vue筛选条件栏、CompetitionTable.vue竞赛表格、CompetitionModal.vue新增/编辑弹窗。父组件维护数据请求逻辑子组件只负责展示和触发事件。组件划分的粒度以“一个组件只干一件事”为准。前端还有一个高频需求是文件上传。提交作品时用Element Plus的el-upload组件注意两点上传接口通常会单独配置不带Authorization头或者用请求拦截器统一加action属性指向后端上传地址上传成功后后端返回文件URL前端把URL保存进表单数据里而不是把整个File对象序列化提交。很多新手在这个地方踩坑formData里塞[object Object]后端完全拿不到文件名。3.3 数据交互与状态管理从Vuex/Pinia到后端缓存设计小项目不引入状态管理也能跑但一旦涉及用户信息共享、多页面需要同步登录态就值得把状态管理用起来了。2025年了新项目建议直接选Pinia而不是Vuex它对TypeScript的支持更好API设计也更简洁。Pinia里维护一个userStore负责保存用户基本信息、登录和退出export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , userInfo: {} }), actions: { async login(loginForm) { const data await http.post(/api/auth/login, loginForm); this.token data.token; this.userInfo data.userInfo; localStorage.setItem(token, data.token); localStorage.setItem(role, data.userInfo.role); }, logout() { this.token ; this.userInfo {}; localStorage.clear(); router.push(/login); } } });后端这边MySQL的数据交互除了基本CRUD还有两个细节值得提。第一个是分页查询。前端经常用pageSize和pageNum两个参数后端用PageHelper或者手写LIMIT offset, pageSize。这里要注意MySQL的深分页问题LIMIT 100000, 20时MySQL要扫描前100020条记录再丢弃前100000条性能很差。优化思路是使用“延迟关联”先查主键ID再用子查询限制偏移量。当然竞赛平台数据量没那么大但面试时能说出这个优化点会加分不少。第二个是MyBatis缓存。一级缓存是SQLSession级别的默认开启同一个Session内重复查询会命中缓存。二级缓存是Mapper级别的默认关闭需要显式开启。注意一个非常隐蔽的问题如果开启了二级缓存而你的Mapper里用了update操作MyBatis会自动清空缓存。但如果两个表通过联表查询返回数据其中一个表更新了另一个Mapper的缓存并不会被主动清空就会出现读到旧数据的情况。解决方案是不要对关联表随意开启二级缓存宁可查库也别读脏数据。4. 实操指南从零搭建到跑通全流程4.1 环境准备JDK、Maven、Node、MySQL的版本搭配动手写代码之前先把环境搭好。这套组合我实测过很多次版本搭配给一个稳定的方案JDK 1.8或11推荐11。Spring Boot 2.7.x对JDK 8和11都兼容得很好。如果用了Spring Boot 3.x则必须JDK 17但3.x的一些第三方组件兼容性还需要踩坑课程设计阶段不建议自找麻烦。Maven 3.6.3或以上用阿里云镜像加速依赖下载。Node.js 16.x或18.x对应npm 8。Vue CLI 5.x或者直接上Vite构建。MySQL 5.7或8.0两者在基本语法上没有太大差异。8.0默认字符集是utf8mb4对中文支持更好推荐8.0。数据库连接池用Druid或HikariCP。Spring Boot默认的HikariCP性能就很好没必要换。MyBatis与Spring Boot集成用mybatis-spring-boot-starter版本2.3.x。Spring Boot 2.7搭配MyBatis Starter 2.3.x是稳定组合。4.2 初始化项目骨架与关键配置文件Spring Boot项目可以直接在start.spring.io生成勾选Web、MyBatis、MySQL Driver依赖。生成后再手动引入mybatis-spring-boot-starter和druid-spring-boot-starter。配置文件application.yml里有几项是必须确认的spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/contest_platform?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 100MB max-request-size: 100MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里列的每项都有讲究。map-underscore-to-camel-case开启后数据库的invite_code字段能自动映射到Java对象的inviteCode属性省去大量人工映射配置。serverTimezone必须设置否则MySQL 8.0会报时区错误。log-impl设为StdOutImpl开发阶段在控制台直接打印SQL日志排查问题效率翻倍上线前记得关闭。前端的vue.config.js里开发环境代理配置module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } };这里不配置代理的话前端每次请求都要写http://localhost:8080/api/xxx还跨域。配置代理后Vue开发服务器帮我们把请求转发到后端浏览器看到的请求是同源的跨域问题从根源上绕开了。4.3 初始化数据库脚本与测试数据数据库脚本要覆盖建库、建表、插入初始数据三步。初始数据至少包括一个管理员账号密码用BCrypt加密后的字符串、几个学生账号、一个指导老师账号、一个评审专家账号以及两三条竞赛记录。有数据才能调试列表展示、分页查询这些功能。SQL脚本里密码加密值怎么生成写一个简单的测试类用BCryptPasswordEncoder生成或者直接在单元测试里打印出来。不要手动改密码字段否则登录永远失败还找不到原因。4.4 启动、联调与常见报错处理后端启动很简单运行main方法即可。前端在项目根目录执行npm install安装依赖再npm run serve启动开发服务器。如果npm install慢或失败换成淘宝镜像源一把梭npm config set registry https://registry.npmmirror.com联调阶段碰到的第一个坎通常是跨域配置了代理就不该有这个问题。如果还有检查代理的target是否指向后端实际端口以及后端是否允许了CORS。我在后端统一加一个CORS配置类双保险防止某些环境下代理不生效。前端报错白屏时打开F12看Console两个高频错误一是Component name should always be multi-wordVue的ESLint规则要求组件名至少两个单词命名改成CompetitionList就好二是Cannot read property xxx of undefined通常是接口返回数据结构和前端预期不一致先看Network面板里后端返回的JSON长什么样再调整前端取值逻辑不要一上来就怀疑后端代码错了。后端启动报Failed to configure a DataSource多半是application.yml没写对数据源配置或者依赖没引入。报Table xxx doesnt exist检查数据库有没有执行建表脚本以及表名是否和实体类注解对得上。5. 常见问题排查与避坑指南5.1 问题速查表按开发阶段排序我整理了这套系统开发过程中出现频率最高的10个问题按开发阶段排好可以当手边的速查表用。阶段问题现象根本原因解决方案环境搭建npm install 卡死或超时npm官方源访问慢换成淘宝镜像源环境搭建后端启动闪退提示时区错误MySQL连接URL未设置serverTimezoneURL追加serverTimezoneAsia/Shanghai数据库中文乱码数据库或表的字符集不是utf8mb4建库时指定utf8mb4连接URL加characterEncodingutf8后端登录接口报密码不匹配密码加密方式不一致统一使用BCryptPasswordEncoder加密和校验后端前端传了JSON但后端接收为null缺少RequestBody注解或DTO字段名不匹配检查Controller注解和前端参数名后端MyBatis查询字段返回null数据库字段与实体属性映射不上开启map-underscore-to-camel-case或加Results注解后端插入数据报主键冲突业务约束没校验重复提交插入前查重加唯一索引前端登录后刷新页面就跳回登录页路由守卫读取不到Token登录后把Token存localStorage刷新后再读一次前端跨域请求被拦截开发环境proxy未配置或后端未开CORS配置devServer.proxy后端加CORS配置类全栈文件上传成功但页面不显示图片上传URL是相对路径部署后路径丢失后端配置虚拟路径映射存完整访问URL5.2 登录鉴权与接口安全容易被忽略的细节登录鉴权是这类系统的核心。最简单的实现是登录成功后生成一个UUID字符串作为Token存到Redis或内存里前端请求时带上后端拦截器校验。如果要追求规范用JWT轻量实现也可以。不管用哪种有三个安全细节我必须提醒你。第一拦截器放行规则要谨慎。登录、注册、验证码、前端静态资源要放行其余接口全部拦截。注意/api/admin/**还要额外校验管理员角色不能只校验登录态否则普通学生用户手动调用管理接口就能删数据。第二密码必须密文存储。明文密码存数据库是致命的低级错误一旦数据库泄露所有账号就全暴露了。用BCrypt加密每个用户加密后的密文都不一样这是目前最推荐的方案。第三文件上传要做类型和大小校验。后端不能只靠前端限制因为攻击者完全可以绕过前端直接调接口。上传接口要检查文件扩展名和MIME类型限制大小最好重命名文件并存储到独立的目录或对象存储服务里。5.3 性能优化与数据一致性从“能跑”到“跑得稳”课程设计阶段“能跑”和“跑得稳”之间的差距往往就在几个细节上。索引设计是最基础却最容易被忽视的。竞赛平台里查询频率最高的场景是按竞赛查队伍、按队伍查成员、按作品查评审记录。对应的外键字段都要建索引。MySQL的索引不是越多越好每个索引都会拖慢写入速度所以只给查询条件里出现频率高的字段建索引比如competition表的status字段、team表的competition_id和leader_id、work表的team_id。数据一致性上报名和组队这类操作要加事务。事务应该放在Service层用Transactional(rollbackFor Exception.class)注解。这里rollbackFor必须是Exception.class默认情况下只回滚RuntimeException如果业务代码抛的是自定义CheckedException不配这个参数就不会回滚数据就乱了。文件存储路径建议用配置项管理不要硬编码在代码里。本地存储要配置虚拟路径映射否则前端访问不到。生产环境如果文件量大可以接MinIO或阿里云OSS代码改动其实不大只要抽象一个FileStorageService接口本地实现和云存储实现互换即可。5.4 写给二次开发者的建议如何把系统改得更好这套系统如果你要拿去交作业或者作为毕设起点我强烈建议你至少做三件事来增加差异化。第一加入公告推送和消息通知模块。竞赛发布、报名审核结果、作品被评审这些事件都值得触发站内消息。如果时间充裕接入WebSocket做实时通知面试时讲出来会非常加分。第二加入数据可视化。管理端首页展示竞赛报名趋势图、各学院参赛人数对比、竞赛级别分布等图表用ECharts实现。这类图表的难点不在写代码而在后端的统计SQL比如“统计近6个月各月新增报名数量”用DATE_FORMAT(create_time, %Y-%m)分组就能实现前端拿到数据后灌进饼图柱状图即可。第三加入Excel导入导出。学生名单批量导入、成绩报表导出用EasyExcel实现。EasyExcel对大数据量支持更好POI在10万行数据时很容易OOMEasyExcel的流式读取可以避开。还有一件事要提醒如果项目里用了前端模板或者后端Controller模板提交代码前一定要全局搜索把模板自带的示例代码清干净比如README里的默认话术、页脚版权信息这类别让老师觉得你是CV工程师。6. 我的实操心得与调优经验这套系统我自己完整跑通过其中有几个环节是最容易让人失去耐心的我把亲测有效的经验分享给你。第一个经验先做数据库设计再做代码至少要把表结构画出来。我见过太多同学一边写代码一边改表结构最后Mapper里的SQL和实体类对不上报错一片红。花一个晚上把ER图、字段、状态流转全部定下来后面写代码的速度至少快一倍。第二个经验联调阶段不要一次性写完所有接口再测试。前后端同学配合时每完成一个模块就立即联调一个模块。竞赛创建做完了马上让前端对接跑通有问题当场改。全部写完再联调一旦出bug你根本不知道是前端传递参数的问题还是后端SQL写错了排查成本高得多。第三个经验学会读日志。后端控制台打印的SQL日志是排查数据问题最有力的工具你看到select * from team where ...就能确认MyBatis最终执行了什么样的SQL是不是和预期一致。前端F12的Network面板看一下请求的Payload和Response95%的问题都能定位。很多同学一报错就慌其实大部分bug都是参数没对上日志就是最好的破案线索。如果这个项目你打算继续往纵深扩展推荐的方向是接入对象存储服务统一管理文件引入Redis缓存竞赛列表和用户会话再用Docker把前后端分别打成镜像一条命令部署起来。这三个方向无论哪一个都能让这个课程设计变成简历上拿得出手的实战项目。
返回列表