ARTICLE DETAIL

资讯详情

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

Spring Boot+Vue校园竞赛管理系统:从设计到部署的完整实战

Spring Boot+Vue校园竞赛管理系统:从设计到部署的完整实战 简介本资源是一套面向计算机专业本科生及Java初学者的毕业设计级实战项目聚焦校园竞赛全流程数字化管理需求解决传统人工方式下信息发布滞后、报名混乱、成绩统计低效等实际问题。压缩包为RAR格式大小27.36MB包含完整Spring Boot后端源码、Vue.js前端工程、MySQL建库建表SQL脚本及配套毕业论文参考文档覆盖用户权限、竞赛发布、在线报名、成绩录入与统计分析等核心模块。已有53人下载学习适用于课程设计、大作业实践或毕业设计选题。项目结构规范、注释清晰、数据库设计合理附带可直接运行的配置说明与功能演示逻辑便于快速部署调试论文部分涵盖系统分析、技术选型、实现细节与测试结果为答辩与文档撰写提供有力支撑。 每年一到毕业设计季管理系统类课题就成了计算机专业的热门选择。我在做毕业设计时选的就是校园竞赛管理系统技术栈落在Spring Boot Vue上配合完整的SQL脚本和毕业论文骨架最后打包成了一个完整的交付物。今天把整套系统的设计思路、核心代码逻辑、数据库设计以及踩坑经验完整拆开讲一遍正好给正在做类似课题、或者准备用Spring Boot Vue做管理系统类毕设的同学一个真实参考。这篇内容适合两类人一类是正在选毕设课题、需要一个完整项目参考的学生另一类是刚接触前后端分离开发、想弄懂一个管理系统到底是怎么从零到一落地的开发者。下面讲的不是概念性知识而是我在实际开发中真正用到、真正踩过坑的经验。1. 为什么竞赛管理系统是毕业设计里的稳妥之选1.1 课题价值的三个判断标准选毕设课题时老师最看重的是三点业务逻辑是否清晰、技术方案是否合理、工作量是否可控。竞赛管理系统在这三点上都有天然优势。校园竞赛管理面向的是学校里的各类学科竞赛、技能大赛、创新创业比赛。核心业务就是竞赛发布—报名—作品提交—评委评审—成绩公示这一条完整链路。它不像商城系统那样涉及大量支付、库存逻辑也不像企业OA那样业务规则复杂但它包含了管理系统最典型的增删改查、多角色权限、文件上传、状态流转这些核心功能足够撑起一篇毕业论文的体量又不会做到一半发现做不完。从工作量上说一个人完成它是完全可控的。整个系统拆成后端、前端、数据库三块后端30到40个接口前端15到20个页面SQL脚本十几张表论文写5到6章每一块都有明确边界。做管理系统最怕的就是业务无限扩展竞赛管理这类课题天然就有边界校级竞赛管理用户无非是学生、教师、管理员三个角色流程无非是发布、报名、评审、公示这就保证了你在答辩前能真正交付一个完整可运行的系统。1.2 Spring Boot Vue为什么是这套黄金组合这套组合现在几乎是管理系统类毕设的标配但选它不是因为大家都在用而是因为它在单人开发场景下是效率和质量最平衡的方案。先看后端。Spring Boot把SSM时代繁琐的XML配置全部自动化了内嵌Tomcat容器打包成jar直接运行非常适合独立开发。配合MyBatis-Plus实体类和基础Mapper都不用自己手写CRUD能省出大量时间去做业务逻辑。而且Spring Boot的资料在互联网上是最全的遇到问题基本搜索就能解决这对毕设周期紧张的同学来说极其重要。再看前端。Vue我用的是Vue 3配合Element Plus组件库做管理系统类的后台界面效率非常高。表格、表单、弹窗、分页这些元素Element Plus全都有现成组件只需要关注业务逻辑不需要从零写样式。Vue的响应式机制让数据变化驱动界面更新这件事变得非常直观数据绑定、计算属性、事件处理这些概念即使你之前没有系统学过前端跟着文档上手也能在两周内把页面搭得像模像样。前后端分离还有一个好处论文里的系统设计图和接口文档会显得非常规范。你可以画前端结构图、后端结构图、数据库ER图这些在论文里都是很扎实的内容。2. 系统整体设计从功能清单到ER模型的推导过程2.1 角色与权限的边界划分竞赛管理系统涉及三类角色第一件事就是划清每个角色能做什么。系统管理员教务处或教学办老师维护基础数据创建竞赛、审核竞赛、设置评审规则、管理用户、发布公告。教师评审查看分配给自己的竞赛、查看报名队伍的作品、按评分维度打分、填写评审意见。学生浏览竞赛公告、在线报名个人赛或组队赛、上传作品、查看自己队伍的报名状态和最终成绩。这三类角色的权限在数据库里可以用role字段表示但在设计时我用的是RBAC模型的正向设计思路——先定义角色再给角色分配权限。实际落地时由于竞赛管理系统的菜单和操作不是特别多我用了一种轻量级RBAC实现用户表存role前端根据role动态生成菜单后端拦截器根据角色判断接口访问白名单。这种方式比完整的用户-角色-权限三张表轻量很多对于项目规模来说刚刚好论文里也好解释。2.2 数据表的设计思路数据库是整个系统的地基设计得不好后期写代码全是坑。竞赛管理系统的核心表我梳理后总共12张撑起全部业务的是下面这几张。用户表sys_user主键、用户名、密码BCrypt加密存储、昵称、角色、学院、班级/工号、联系方式、创建时间。竞赛表competition主键、竞赛名称、竞赛简介、类型个人赛/团队赛、允许报名人数上限、报名开始时间、报名结束时间、初审开始时间、初审结束时间、决赛时间、状态、创建人、创建时间。这里时间字段要设计成多个阶段因为竞赛有明确的周期流转。报名表registration主键、竞赛ID、用户ID如果是团队赛需要和队伍表关联、队伍ID、报名时间、状态待审核/通过/拒绝、审核意见。个人赛报名的场景一条记录搞定团队赛需要配合队伍表。队伍表team主键、队伍名称、竞赛ID、队长ID、成员ID列表、创建时间。跨专业组队很常见所以队伍成员要支持多个人我用的方案是成员ID列表用逗号分隔存储查询时再拆出来。对于毕设项目这种冗余设计完全够用比专门建一张队伍成员关联表省事很多论文里解释为以空间换简单性也说得通。作品表work主键、竞赛ID、报名ID、作品名称、文件路径、上传时间、对文件的描述。作品表和一个具体的报名记录关联这样无论个人赛还是团队赛都能通过报名ID找到作品。评分表score主键、竞赛ID、报名ID/作品ID、评委ID、各维度得分、总分、评审意见、评分时间。评分表要支持一个作品多个评委打分所以主键不能只靠报名ID需要加上评委ID做联合唯一约束。公告表notice主键、标题、内容、发布人、发布时间。2.3 从ER模型到SQL脚本的落地设计完表之后写SQL落地时我建议注意几个细节。一是所有表的主键都用bigint自增不要用UUID排序和索引效率都更好。二是时间字段统一用datetime千万不要有的用timestamp有的用datetime后面JSR303校验和前端格式化的时候会非常痛苦。三是所有涉及金额、分数、人数的字段都用int或decimal不要用float防止精度问题。建表SQL的关键片段我保留了一份回头论文附录里也会放。拿竞赛表举例状态字段我用的是int类型存储枚举值0表示未开始、1表示报名中、2表示评审中、3表示已结束、4表示已取消。用int而不是varchar的原因是后端判断状态流转时用数字判断更稳定不会出现报名中和报名中 这种带空格匹配不上的坑。报名表有个需要注意的地方在数据库层面加上联合唯一索引防止同一个用户对同一个竞赛重复报名。这个索引在页面按钮上控制一次在数据库层面再兜底一次是毕设答辩时能加分的细节老师问到怎么防止重复报名就能直接拿出这个方案。3. Spring Boot后端三层架构落地的取舍与核心接口设计3.1 工程初始化的分层逻辑后端我采用的是标准的Controller、Service、Mapper三层结构同时加了Entity层和DTO层。很多初学者会问Entity和DTO有什么区别直接拿实体类不就行了我的经验是竞赛管理系统虽然不算大但在修改竞赛信息、提交报名、提交评分这类场景里前端传过来的字段和数据库字段不是一一对应的。比如报名接口前端除了传竞赛ID还会传一个复选框组——是否同意竞赛纪律声明。这个字段数据库里根本没有如果直接用Registration实体接收就会报参数绑定错误。所以我在所有写操作接口统一使用DTO接收参数再用Service里手动把DTO转成Entity。这样做的额外好处是可以通过DTO的注解做参数校验比如NotNull、Size这些后端把第一道参数关前端体验会更友好。工程结构大致是controller接收请求、参数校验、调用service、返回统一结果service业务逻辑事务控制mapper数据库操作继承MyBatis-Plus的BaseMapperentity数据库表映射dto接口入参对象vo接口返回对象config配置类包括跨域、拦截器、文件上传配置common统一返回结果、异常处理、常量类3.2 登录认证JWT拦截器方案前后端分离架构下session方案不太好使因为后端服务和无状态API的分离天然不太兼容session的会话保持。我最终用了JWTJSON Web Token做登录态管理。流程是用户登录成功后后端生成一个JWT字符串返回给前端前端把token存在localStorage里每次请求在请求头带上Authorization字段后端写一个拦截器拦截所有非白名单的接口解析token解密出用户ID和角色放入ThreadLocal上下文里供后续业务使用。这个方案最需要处理的坑是token过期和角色权限控制。我在token里放了userId、role两个字段和过期时间过期时间设置成2小时。拦截器里第一步校验token是否合法、是否过期第二步根据请求路径判断角色是否在白名单内。比如以/admin开头的接口只允许role为admin访问以/teacher开头的只允许教师访问。拦截器的核心逻辑大概是这样的public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; } String token request.getHeader(Authorization); // 校验token是否有效 Claims claims JwtUtil.parseToken(token); if (claims null) { // 返回401状态码让前端跳转到登录页 response.setStatus(401); return false; } // 从token中取角色校验接口权限 String role claims.get(role).toString(); String uri request.getRequestURI(); if (uri.startsWith(/admin) !admin.equals(role)) { response.setStatus(403); return false; } // 将userId放入ThreadLocal UserContext.setUserId(Long.parseLong(claims.get(userId).toString())); return true; }注意OPTIONS请求必须放行这是处理前端跨域预检请求的必须步骤很多人第一次写会漏掉导致前端明明带了token还是报403。3.3 竞赛报名的并发与事务控制报名是整个系统里最核心的写操作。一个竞赛有报名人数上限所以要防止最后一个人报进来时总人数超了的情况。单机部署场景下用两层控制就够了。第一层报名时先查询当前报名人数是否小于竞赛人数上限小于才允许插入第二层在报名表的联合唯一索引和竞赛表的报名人数上限之间做数据库层面的约束。更稳妥的做法是用乐观锁。我在竞赛表里加了一个version字段更新报名人数时带上where version #{version}条件如果更新影响行数为0说明版本号变了有人并发抢报就提示报名人数已满或系统繁忙请重试。这个方案实现简单不需要引入Redis分布式锁在毕业设计场景下已经足够可靠。报名接口的事务也需要注意。同一个竞赛报名成功后要做两件事insert报名记录、update竞赛表的已报名人数。这两步必须放在同一个事务里我用Transactional注解搞定。写到这里想提醒一下Spring的Transactional默认只处理RuntimeException如果业务里抛的是自定义检查异常记得用rollbackFor Exception.class指定回滚条件不然会出现报名记录插入了人数没更新这种数据不一致问题。3.4 评分模块的幂等设计评委打分这个功能最容易出现的问题是评委重复提交分数。比如评委先打了一次分然后觉得不合理又打了一次结果数据库里出现了两条评分记录。解决办法是给报名表和评委ID加联合唯一索引。我在评分表设计时给competition_id, registration_id, judge_id加了唯一索引这样唯一索引自动保证一个评委只能对同一作品打一次分。第二次打分时数据库会报唯一约束异常我在Service层捕获这个异常转换成您已经对该作品评分请勿重复提交的友好提示。这里的核心思路是不确定业务里会不会重复提交时把唯一索引当作最后一道防线比单纯在代码里先查询再插入靠谱得多。除了幂等评分还要考虑平均分计算。系统在展示最终成绩时我设计的是取所有评委去掉最高分和最低分后的平均分。这个逻辑在SQL里可以用子查询写但更直观的做法是在Service里查出所有评委的分数用Java代码排序后掐头去尾算平均。对毕设项目来说接口响应够快Java算更直观、好调试论文里也更好描述算法思路。4. Vue前端页面结构、组件复用与权限路由的落地4.1 前端工程化初始化与目录规划前端用的是Vue 3 Vite Pinia Vue Router Element Plus这套主力组合。Vite创建项目的命令很简单npm create vitelatest后按提示选Vue模板即可。为什么选Vite不选Webpack最直接的感受是启动速度快保存代码后热更新几乎是秒级的这对开发调试效率的提升非常明显。做毕设期间我会频繁改代码看效果Vite的体验比老一代构建工具好太多。目录结构我分成如下views页面组件按模块建子目录比如competition、registration、review、user、noticecomponents通用组件比如分页组件、文件上传组件、富文本组件router路由配置storePinia状态管理主要存用户信息和tokenapi接口请求封装文件utils工具类比如request.js封装axios4.2 动态路由与权限菜单的实现管理系统的菜单要根据用户角色动态显示。学生登录后只能看到竞赛公告、我的报名、我的作品教师登录后有评分管理菜单管理员登录后有全部管理菜单。我在router配置上使用了路由守卫配合动态路由的实现方式。具体做法是前端路由分为两部分第一部分是固定的基础路由比如登录页、注册页、首页第二部分是业务路由先在权限配置文件里给每个路由标记allowedRoles字段登录后根据当前用户角色在路由守卫中过滤出可见路由再用router.addRoute动态添加到路由表里。const whiteList [/login, /register]; router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (!token) { if (whiteList.includes(to.path)) { next(); } else { next(/login); } return; } if (to.path /login) { next(/); return; } // 判断路由角色权限 const requiredRoles to.meta.roles; const userRole localStorage.getItem(role); if (requiredRoles !requiredRoles.includes(userRole)) { next(/403); return; } next(); });菜单侧边栏的渲染则是根据当前路由表结构自动生成的这样权限控制做到了看不见的页面进不去看得见的页面才渲染。4.3 报名页面的完整流程与组件封装学生端报名页面是整个前端最有代表性的页面。它要干的事有展示竞赛基本信息、展示当前报名人数、选择是个人报名还是组队报名团队赛时、填写队伍信息、勾选参赛承诺、提交。数据展示部分我用el-descriptions组件展示竞赛详情用el-progress展示报名进度条。提交报名的表单用el-form加rules校验规则。这里有个实践细节前端校验是给用户的第一道关卡但后端一定要做二次校验因为接口可以被绕过不能相信前端的校验结果。文件上传组件我用的是Element Plus的el-upload处理时需要把文件先上传到后端后端保存后返回文件路径再把路径拼到报名表单里一起提交。上传时需要设置请求头里的tokenel-upload组件的headers属性可以配置el-upload :actionuploadUrl :headersuploadHeaders :on-successhandleUploadSuccess namefile el-button typeprimary上传作品/el-button /el-uploadconst uploadHeaders { Authorization: localStorage.getItem(token) };踩过的一个坑是el-upload默认上传成功后会把响应解析成JSON但如果后端返回的JSON字段名不符合前端预期on-success里取不到文件路径。我的处理方式是后端统一返回结构是{ code: 200, data: { url: xxx } }前端在on-success里从res.data.url取值再把url回填到formData里保持一致。5. 论文与答辩这个系统答辩时评审老师最爱问的三个问题5.1 论文结构的优先级毕业论文的写作顺序我的建议是先写需求分析再写系统设计最后写实现与测试。逻辑是老师读到系统设计时需要清楚这个系统要解决什么问题所以需求分析是地基而实现章节是验证设计需要引用设计里的图、表和接口定义。需求分析章节不要写得像教科书要结合你实际的调研来写。比如你可以写通过调研部分高校竞赛管理现状发现存在报名信息分散、评审流程不透明、成绩汇总耗费人力等问题然后自然引出系统的三个核心需求——竞赛信息发布与查询、在线报名与作品提交、评审打分与成绩公示。这种写法比空谈系统具有用户管理功能要扎实得多。数据库设计的章节要放ER图、数据库表结构。这里的表结构不要放全部字段的字段名只放关键表的关键字段并配上字段说明。重点是让老师明白每一张表是干什么的、表之间怎么关联、为什么不这样设计会出问题。比如报名表和竞赛表之间的关系我用了外键逻辑关联但没在数据库里强制外键约束目的是保留数据操作的灵活性。这个设计取舍可以在论文里说明体现你对数据库设计的思考。5.2 展示Demo的黄金顺序答辩现场演示系统时演示顺序比功能本身更重要。我的推荐顺序是先展示登录功能演示不同角色登录后的界面差异再进入管理员页面创建一场竞赛接着切换学生账号完成报名、上传作品再切换到教师账号打分最后回到管理员页面展示成绩发布和公告。这个顺序是一条完整业务链从竞赛创建到成绩公示一气呵成既展示了整个系统的工作流程又不会跳来跳去让老师跟不上。整个演示时间控制在8到10分钟最合适。演示前把测试数据准备好不要现场临时创建太多内容。老师问为什么这个页面空白是最尴尬的情况。5.3 答辩现场的高频问题归档了身边同学被问到的、加上我自己被问到的三个高频问题。第一个问题是为什么选Spring Boot Vue回答时不要只说因为这个火要结合项目实际Spring Boot能快速构建RESTful API内置容器方便部署Vue组件化开发效率高配合Element Plus能快速构建后台管理界面前后端分离架构使得项目结构清晰便于分工和后期维护。第二个问题是数据一致性怎么保证参考上面的报名场景回答事务 唯一索引 乐观锁三层方案然后展开讲一下乐观锁的version字段机制。这个问题答得好很加分。第三个问题是如果用户量变大系统怎么优化回答方向数据库加索引、Redis缓存热点数据、图片上传改用对象存储、服务端加负载均衡。不用真的做但要能说出思路。6. 踩坑记录从部署到演示的五个真实教训6.1 时间字段的类型不一致我在开发初期把竞赛报名时间设计成String类型存数据库想着前端传什么就存什么结果做时间比较的时候发现字符串比较日期完全不可靠比如2024-10-9和2024-10-10字符串比较会认为前者更大。后来统一改成datetime类型实体类用LocalDateTime配置了Jackson的日期格式化全局规则才彻底解决。这个坑提醒我数据库里涉及时间的字段一开始就要用datetime不要偷懒用varchar。6.2 跨域配置那点事前后端分离模式下前端跑在localhost:5173后端跑在localhost:8080跨域问题一开始就存在。我用Spring Boot的CorsFilter配置类统一处理allowedOriginPatterns填了allowedMethods填了GET、POST、PUT、DELETE、OPTIONSallowedHeaders也填了allowCredentials设成true。这里最坑的是allowCredentials和allowedOrigin不能同时使用Spring Boot会报错。处理方式是allowedOriginPatterns用这个参数允许在带凭证的情况下匹配所有来源很多教程没提这个细节抄了旧代码就会报错。6.3 文件上传大小限制作品文件上传功能上线后发现超过1MB的PDF上传后会报错控制台提示MaxUploadSizeExceededException。原因是我在application.yml里没有设置Spring Boot的servlet上传大小限制默认只有1MB。改成如下配置就好了spring: servlet: multipart: max-file-size: 50MB max-request-size: 50MB配合在后端全局异常处理里捕获这个异常返回文件过大的提示比前端直接报网络错误要友好很多。6.4 SQL严格模式下的group by报错做成绩统计时我想按竞赛分组统计平均分写了一串group by查询结果在MySQL 5.7以上版本直接报错。原因是MySQL默认开启了ONLY_FULL_GROUP_BY模式select的字段必须出现在group by子句中或者被聚合函数包裹。解决方法是把select里不需要分组的字段去掉只保留分组字段和聚合函数。这个坑写SQL的人大概率会遇到提前踩掉能省很多时间。6.5 前端白屏路由配置的隐性错误开发过程中遇到过几次前端页面白屏控制台报错指向Vue Router的警告。排查下来最典型的一个原因是在路由配置里使用了懒加载但没有按照Vite的要求把组件路径写对。比如页面文件在views/competition/index.vue路由里写成component: () import(../views/competition/index.vue)时相对路径如果算错构建不会报错但运行时就是白屏。我的建议是懒加载路径统一用相对于项目根目录的绝对路径写法比如/views/competition/index.vue通过vite配置alias把指向src避免相对路径算错。另一个白屏原因是Tabs组件里嵌入了Form表单Form的model数据和resetFields调用时机不对导致页面初始化渲染报错。这种情况用Vue DevTools调试时能看到组件渲染错误堆栈定位起来不算难。这些坑单独看都很小但正好是毕业设计开发过程中最容易消耗时间的部分。把这些记录下来也是帮我自己在写论文系统测试与调试章节时提供了实打实的素材。如果你也在做这个类似项目希望这篇文章能帮你少走一段弯路。做管理系统最怕的不是业务复杂而是花一晚上调一个没意义的bug。把这些基础的坑提前排掉你就能把精力花在真正能拿分的功能实现上。本文还有配套的精品资源点击获取
返回列表