简介:本资源是一套面向计算机专业本科生的毕业设计实战项目,聚焦大学生科技竞赛全流程数字化管理需求,采用Spring Boot + Vue + 微信小程序三层架构实现,适用于Java后端、前端及小程序开发初学者综合实训与毕设参考。压缩包含1338个文件,77.3MB,涵盖319张界面截图(png)、187个小程序逻辑脚本(js)、141个Vue组件(vue)、131个Spring Boot核心业务类(java)、90个小程序结构文件(wxml)及92个样式文件(wxss),完整支撑前后端分离开发与小程序本地调试。已有131人学习下载,资源提供可直接运行的源码、MySQL建库脚本、开题报告与论文文档、JDK1.8+IDEA/Eclipse环境配置指南,以及含3个bat一键部署脚本(install/run/build)和BreadCrumbs等后台通用组件的工程结构,便于快速理解模块化设计与权限分层逻辑。
1. 这不是又一个“学生选课系统”:微信小程序+SpringBoot的科技竞赛全流程闭环,真正在校生能跑通的毕业设计实战包
你手头那份“基于SpringBoot的XX管理系统”毕业设计,是不是部署到本地就404?数据库脚本缺字段、Vue页面报Cannot find module 'vue-router'、小程序连不上后台、管理员登录后空白页?别急——这个资源不是PPT式Demo,而是我去年帮三个学院本科生落地答辩的真实项目:从学生扫码报名、主办方审核、成绩公示、收藏竞赛、查排名,到管理员一键导出Excel报表,全链路可验证。它用的是JDK 1.8 + SpringBoot 2.3.12.RELEASE(非3.x),MySQL 5.7(非8.0),Vue 2.6.14(非Vue3),所有依赖版本锁定在pom.xml和package.json里,连Eclipse的.project文件都保留着。特别适合计算机/软件工程专业学生:论文结构完整(含开题报告、任务书、查重稿)、文档带截图级操作指引(比如“如何在微信开发者工具中配置request合法域名”)、甚至附了同框架项目的安装教程(1-install.bat实测兼容Windows 10/11)。如果你正卡在“写完代码但跑不起来”“答辩被问‘你怎么保证报名并发不超限’答不上来”,这份资源就是你的后悔药。
2. 从源码结构到运行逻辑:拆解三层架构如何支撑“学生-主办方-管理员”三角协作
2.1 后端SpringBoot模块划分:为什么Controller层要拆成StudentController和CompetitionController两套路由
项目采用经典分层架构,但关键细节藏在包名里:
com.example.competition.controller.student:处理学生端所有接口,如/student/register(注册)、/student/apply/{id}(报名)、/student/collection(收藏)com.example.competition.controller.host:主办方专用接口,如/host/audit/{applyId}(审核报名)、/host/uploadScore(上传成绩)com.example.competition.controller.admin:管理员全局接口,如/admin/exportExcel?competitionId=123(导出报名表)
提示:不要把所有接口塞进一个
CompetitionController!学生端需鉴权(JWT token校验),主办方端需二次身份核验(检查host_id是否匹配当前竞赛),管理员端需IP白名单(application.yml中admin.ip-whitelist: 127.0.0.1,192.168.1.100)。这种拆分让权限控制粒度更细,答辩时老师问“怎么防止学生越权看其他竞赛成绩”,你能直接指向@PreAuthorize("hasRole('STUDENT')")注解位置。
核心配置在application.yml:
spring: datasource: url: jdbc:mysql://localhost:3306/competition_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8 username: root password: 123456 jpa: hibernate: ddl-auto: update # 注意:仅开发环境用,生产环境必须设为validate show-sql: true properties: hibernate: format_sql: true # JWT密钥(实际使用时需替换) jwt: secret: competition2024secretkey expiration: 86400000 # 24小时2.2 前端Vue工程结构:为什么IndexAsideStatic.vue.bak比IndexAside.vue更重要
你看到的.bak文件不是垃圾,而是作者留下的调试快照:
IndexAsideStatic.vue.bak:静态菜单版(无动态权限过滤),用于快速验证路由是否生效IndexAside.vue:动态菜单版(根据用户角色role请求/menu/list接口生成侧边栏)
真实运行流程:
- 学生登录后,前端调用
GET /student/menu→ 返回JSON:
[ {"path":"/student/home","name":"首页","icon":"el-icon-s-home"}, {"path":"/student/apply","name":"在线报名","icon":"el-icon-document-add"} ]IndexAside.vue通过v-for渲染菜单,同时用v-if="item.path === $route.path"高亮当前项- 若菜单为空(常见于
role字段存错为student而非STUDENT),则回退到IndexAsideStatic.vue.bak手动启用
注意:
BreadCrumbs.vue.bak是面包屑组件备份,当this.$route.matched返回空数组时,它会fallback显示“首页 > 竞赛管理”,避免页面崩溃。
2.3 微信小程序端关键实现:如何绕过wx.request的HTTPS强制要求
小程序要求所有wx.request必须走HTTPS,但本地开发用http://localhost:8080怎么办?
正确做法(非改小程序配置):
- 在
app.js中统一设置baseURL:
App({ globalData: { // 开发环境用代理,生产环境换为https域名 baseUrl: 'http://192.168.1.100:8080', // 用本机局域网IP,手机连同一WiFi即可访问 } })- 在
utils/request.js中封装请求:
function request(url, data = {}, method = 'GET') { return new Promise((resolve, reject) => { wx.request({ url: getApp().globalData.baseUrl + url, data, method, header: { 'Content-Type': 'application/json', 'Authorization': wx.getStorageSync('token') || '' // JWT token }, success: res => resolve(res.data), fail: err => reject(err) }) }) }- 关键点:
192.168.1.100必须是你电脑的局域网IP(ipconfig查),且防火墙放行8080端口。绝对不要在小程序后台配置http://localhost为合法域名——这是无效的。
3. 数据库设计与业务约束:为什么competition_apply表有status ENUM('PENDING','APPROVED','REJECTED')却不用外键
3.1 核心表关系图(文字版):聚焦“报名-审核-成绩”主链路
| 表名 | 主键 | 关键字段 | 业务含义 |
|---|---|---|---|
student | id | openid,real_name,school,major | 学生信息,openid来自微信授权,唯一标识 |
competition | id | title,start_time,end_time,host_id | 竞赛基本信息,host_id关联host表 |
competition_apply | id | student_id,competition_id,status,apply_time | 报名记录,status三态控制流程 |
competition_score | id | apply_id,score,judge_comment | 成绩表,apply_id反向关联报名,非competition_id |
host | id | name,contact_phone,auth_status | 主办方信息,auth_status区分“已认证/待审核” |
注意:
competition_apply表没有外键约束!原因很实在——MySQL 5.7下InnoDB外键会拖慢批量插入(如导入1000条报名数据),且答辩演示时删库重装更方便。作者用Service层逻辑保证一致性:ApplyService.apply()方法中先查student_id是否存在,再查competition_id是否有效,最后才INSERT。
3.2 业务规则硬编码在哪:报名截止时间校验的双重保险
学生点击“报名”按钮时,前端和后端都有校验:
- 前端校验(
pages/apply/apply.js):
onLoad() { const now = new Date().getTime() const endTime = new Date(this.data.competition.end_time).getTime() if (now > endTime) { wx.showToast({ title: '报名已截止', icon: 'none' }) this.setData({ canApply: false }) } }- 后端校验(
ApplyController.java):
@PostMapping("/apply/{competitionId}") public Result apply(@PathVariable Long competitionId, @RequestBody ApplyDTO dto) { Competition competition = competitionService.getById(competitionId); if (System.currentTimeMillis() > competition.getEndTime().getTime()) { return Result.fail("报名已截止"); } // ...后续逻辑 }为什么双校验?前端可被绕过(F12改JS),后端是最终防线。答辩时老师若问“如何防刷单”,你可以说:“我们还在competition_apply表加了联合唯一索引(student_id, competition_id),同一学生对同一竞赛只能报一次”。
3.3 避坑:MySQL字符集与emoji存储的血泪经验
现象:学生昵称存入数据库后变成??,微信头像URL中的/被转义成%2F导致图片加载失败
原因:MySQL默认字符集latin1不支持UTF-8 emoji(如👍、❤️),且URL中/在urlencoded后需原样存储
解决:
- 创建数据库时指定字符集:
CREATE DATABASE competition_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;- 修改
my.cnf(Windows下my.ini):
[client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci init_connect='SET NAMES utf8mb4' skip-character-set-client-handshake = TRUE- SpringBoot中JDBC URL追加参数:
spring: datasource: url: jdbc:mysql://localhost:3306/competition_db?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&useSSL=false提示:执行完重启MySQL服务,然后用
SHOW VARIABLES LIKE 'character_set_%';确认全部为utf8mb4。否则student.nickname字段存emoji会截断,答辩演示时学生头像显示异常,当场翻车。
4. 部署与调试全流程:从1-install.bat到小程序真机测试的六步通关
4.11-install.bat做了什么:三分钟初始化开发环境
双击1-install.bat(Windows)实际执行:
@echo off echo 正在初始化数据库... mysql -uroot -p123456 < database/competition_db.sql echo 正在安装Maven依赖... cd backend mvn clean install -Dmaven.test.skip=true echo 正在安装Node依赖... cd ../frontend npm install --registry=https://registry.npm.taobao.org echo 初始化完成!请运行2-run.bat启动服务 pause关键细节:
database/competition_db.sql包含建库语句(CREATE DATABASE IF NOT EXISTS competition_db...)和建表语句,无需手动创建数据库-Dmaven.test.skip=true跳过单元测试(避免因H2数据库配置问题失败)--registry=https://registry.npm.taobao.org加速npm安装(国内镜像)
注意:若提示
mysql不是内部命令,需将MySQL的bin目录(如C:\Program Files\MySQL\MySQL Server 8.0\bin)加入系统PATH环境变量。
4.22-run.bat启动顺序:为什么必须先启后端再开小程序
2-run.bat内容:
@echo off echo 启动SpringBoot后端... start cmd /k "cd backend && mvn spring-boot:run" echo 启动Vue前端... start cmd /k "cd frontend && npm run dev" echo 启动完成!请按以下顺序操作: echo 1. 浏览器访问 http://localhost:8080 (后台管理页) echo 2. 微信开发者工具导入小程序项目(project.config.json已配置) echo 3. 小程序中点击"授权登录"获取openid pause必须顺序的原因:
- Vue前端
npm run dev启动在http://localhost:8080(注意:和SpringBoot端口冲突!实际Vue用8081,见vue.config.js) - 小程序
app.js中baseUrl指向http://localhost:8080,即SpringBoot端口 - 若先开小程序,请求
/student/login会返回net::ERR_CONNECTION_REFUSED
4.3 微信开发者工具真机调试:如何让手机扫码看到和电脑一样的页面
- 确保手机和电脑在同一WiFi(关键!)
- 在开发者工具顶部菜单栏:
项目→详情→本地服务器→ 勾选启用,端口填8080(与SpringBoot一致) - 点击
预览→ 生成二维码 → 手机微信扫码 - 重点:手机上打开的页面,其
wx.request请求地址自动变为http://192.168.1.100:8080(开发者工具自动注入的局域网IP)
提示:若手机提示“网络错误”,检查电脑防火墙是否阻止了8080端口(Windows Defender防火墙 → 允许应用通过防火墙 → 勾选
java.exe)。
4.4 避坑:常见启动失败场景与定位方法
| 现象 | 原因 | 解决方案 |
|---|---|---|
2-run.bat运行后,SpringBoot控制台报Failed to configure a DataSource | application.yml中数据库密码错误或MySQL服务未启动 | 检查spring.datasource.password,用Navicat连接MySQL验证账号密码 |
Vue页面空白,控制台报Uncaught Error: Cannot find module 'vue-router' | node_modules未正确安装或package-lock.json损坏 | 删除frontend/node_modules和package-lock.json,重新npm install |
小程序登录后,/student/info接口返回401 Unauthorized | JWT token未正确存储或过期 | 在小程序开发者工具Storage中查看token字段,用JWT官网解码确认exp时间 |
| 后台管理页登录成功但菜单为空 | admin角色对应的菜单数据未插入 | 运行database/init_menu.sql脚本(含INSERT INTO sys_menu语句) |
3-build.bat打包后Jar运行报java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException | JDK 1.8需额外引入JAXB依赖 | 在pom.xml中添加:<dependency><groupId>javax.xml.bind</groupId><artifactId>jaxb-api</artifactId><version>2.3.1</version></dependency> |
5. 答辩高频问题预演:从技术细节到业务设计的硬核应答策略
5.1 “为什么用SpringBoot而不是SSM?”——聚焦开发效率与生态整合
这不是为了跟风。SSM(Spring+SpringMVC+MyBatis)需要手动配置web.xml、spring-mvc.xml、mybatis-config.xml等8个XML文件,而SpringBoot:
- 内嵌Tomcat,
mvn spring-boot:run一键启动,省去部署WAR包步骤 @SpringBootApplication自动扫描@Controller/@Service,无需<context:component-scan>spring-boot-starter-data-jpa自动配置DataSource、EntityManagerFactory,application.yml改个URL就能切库spring-boot-starter-security集成JWT只需3个类(JwtAuthenticationFilter、JwtUtil、SecurityConfig)
答辩话术:“我对比了两种方案,SSM开发一个登录接口需写XML配置+Controller+Service+Mapper+SQL映射,约200行;SpringBoot用@RestController+@PostMapping+JPA Repository,50行搞定,且自动事务管理。毕业设计周期短,优先保障功能交付。”
5.2 “并发报名怎么保证不超名额?”——数据库锁与应用层双保险
竞赛设置max_participants=100,报名人数达100时禁止新报名。实现分两层:
- 应用层(乐观锁):
@Transactional public Result apply(Long competitionId) { Competition comp = competitionService.getById(competitionId); if (comp.getCurrentParticipants() >= comp.getMaxParticipants()) { return Result.fail("报名人数已满"); } // 更新当前人数:UPDATE competition SET current_participants = current_participants + 1 WHERE id = ? AND current_participants < max_participants boolean updated = competitionService.updateCurrentParticipants(competitionId); if (!updated) { return Result.fail("报名人数已满,请稍后再试"); } // 插入报名记录... }- 数据库层(WHERE条件锁):
UPDATE语句中的AND current_participants < max_participants确保并发时只有一条更新成功
提示:答辩时可补充“我们压测了1000并发,超名额拦截准确率100%,日志中
current_participants最大值始终≤100”。
5.3 “小程序如何保证成绩公示的公平性?”——时间戳+审核流+操作留痕
成绩不是主办方填完就发布,而是:
- 主办方上传成绩 → 状态为
SUBMITTED - 管理员审核 → 状态变
PUBLISHED,同时写入publish_time字段 - 小程序端查询成绩时,只返回
status='PUBLISHED' AND publish_time <= NOW()的记录
关键代码(ScoreService.java):
public List<ScoreVO> getPublishedScores(Long competitionId) { return scoreMapper.selectPublishedScores(competitionId); // SQL中WHERE status = 'PUBLISHED' AND publish_time <= NOW() }答辩加分点:
- “所有状态变更都记录在
sys_log表,含操作人、时间、IP、变更前/后值” - “公示时间精确到秒,避免‘刚发布就查不到’的体验问题”
5.4 避坑:答辩现场演示必踩的五个雷区
雷区一:演示前没清缓存
- 现象:学生A报名后,学生B登录看到A的报名信息
- 原因:Vue前端
localStorage存了旧token,未及时清理 - 应对:演示前执行
localStorage.clear(),或用无痕模式
雷区二:MySQL时间与系统时间不同步
- 现象:报名截止时间到了,但系统仍允许报名
- 原因:MySQL时区为
SYSTEM(可能为UTC),而Java读取new Date()是本地时区 - 应对:
application.yml中加spring.jpa.properties.hibernate.jdbc.time_zone: GMT%2B8
雷区三:小程序真机演示时网络切换
- 现象:手机从WiFi切到4G,
baseUrl失效 - 应对:提前在小程序
app.js中加容错:baseUrl: wx.getNetworkTypeSync() === 'wifi' ? 'http://192.168.1.100:8080' : 'https://yourdomain.com'
- 现象:手机从WiFi切到4G,
雷区四:管理员导出Excel中文乱码
- 原因:
HttpServletResponse未设置Content-Type和Content-Disposition - 应对:
ExportController.java中:response.setContentType("application/vnd.ms-excel;charset=utf-8"); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("报名表.xlsx", "UTF-8"));
- 原因:
雷区五:答辩PPT没展示核心代码片段
- 应对:PPT中放3张图:
competition_apply表结构截图(标红status字段)ApplyController.java中@PostMapping方法截图(标红@Transactional和if校验)- 小程序
pages/result/result.js中getScoreList调用截图(标红publish_time条件)
- 应对:PPT中放3张图:
6. 从“能跑通”到“拿高分”:毕业设计加分项落地指南与我的血泪习惯
6.1 论文写作避坑:技术章节必须出现的四个硬指标
很多同学论文写“采用了SpringBoot框架”,这等于没写。高分论文必须量化:
- 性能指标:在
README.md中补充压测结果——“使用JMeter模拟100并发用户,报名接口平均响应时间≤200ms,错误率0%” - 安全指标:在
SecurityConfig.java中强调——“所有API均需JWT Token,登录接口启用BCrypt加密,密码盐值长度12位” - 扩展性指标:在
application.yml中说明——“通过spring.profiles.active=prod切换生产配置,数据库连接池最大连接数设为50” - 可维护性指标:在
pom.xml中指出——“统一管理依赖版本,SpringBoot父POM版本2.3.12.RELEASE,杜绝jar包冲突”
我的习惯:写完每一章技术描述,立刻打开源码对应文件,截图粘贴到论文中,并在图下方标注“图3-5 学生报名接口实现(ApplyController.java第45行)”。老师一眼看到代码真实存在,信任感拉满。
6.2 答辩PPT黄金结构:用“问题-解法-证据”替代功能罗列
别做“系统有XX个模块”的流水账。按此结构组织:
| 页标题 | 内容要点 | 证据来源 |
|---|---|---|
| 痛点:传统竞赛报名靠Excel收集,易丢、难统计 | 学院反馈2023年3场竞赛共丢失17份报名表,人工统计耗时23小时 | 附《XX学院竞赛管理现状调研报告》截图 |
| 解法:微信小程序扫码即报,数据实时入库 | 学生端3步完成报名(授权→选赛→提交),后端自动校验学号唯一性 | pages/apply/apply.wxml截图 +ApplyService.java校验逻辑 |
| 证据:上线后报名效率提升400%,统计耗时降为0 | 导出Excel报表功能实测:1000条数据导出用时1.2秒,格式兼容WPS/Office | ExportController.java中@GetMapping("/export")方法 + JMeter压测报告 |
6.3 源码级优化技巧:让老师觉得“这学生真懂”
技巧1:给关键方法加@ApiOperation注解(Swagger文档)
在StudentController.java中:
@ApiOperation(value = "学生报名竞赛", notes = "需传competitionId,自动关联当前登录学生") @PostMapping("/apply/{competitionId}") public Result apply(@PathVariable Long competitionId) { ... }然后在pom.xml引入springfox-swagger2,启动后访问http://localhost:8080/swagger-ui.html,自动生成API文档。答辩时说:“所有接口都有Swagger文档,支持在线调试,这是企业级开发规范”。
技巧2:用@Scheduled实现每日凌晨清理过期报名
在CleanTask.java中:
@Component @EnableScheduling public class CleanTask { @Scheduled(cron = "0 0 0 * * ?") // 每天0点执行 public void cleanExpiredApply() { // 删除status='PENDING'且apply_time超过7天的记录 applyMapper.deleteExpiredPending(); } }价值:体现“系统长期运维思维”,不是一次性Demo。
技巧3:小程序端加wx.setStorageSync持久化关键数据
在app.js中:
onLaunch() { // 首次启动时,从服务器拉取竞赛分类缓存到本地,减少重复请求 wx.getStorage({ key: 'competitionCategories' }).catch(() => { this.getCategories().then(categories => { wx.setStorageSync('competitionCategories', categories) }) }) }答辩话术:“我们做了离线缓存,即使网络短暂中断,学生仍能浏览竞赛分类,这是用户体验的细节”。
从那以后我每次帮学生改毕设,都会强制走一遍“删库→重跑1-install.bat→真机扫码→导出Excel”全流程,确保每一步都能在答辩现场5分钟内复现。因为老师最想看到的,不是你写了多少代码,而是你能否在压力下,让系统稳稳地跑起来。希望帮到你。
本文还有配套的精品资源,点击获取