基于SpringBoot和微信小程序的校篮球联赛系统:毕业设计实战与部署避坑指南
简介这份资源是面向高校计算机相关专业毕业设计场景的完整项目包主题为基于微信小程序的校篮球联赛系统适合正在准备毕设、需要可运行案例与配套部署教程的本科或高职学生也可供课程设计参考。项目围绕联赛业务展开涵盖球队、球员、赛事信息与赛事详情的增删改查以及留言、收藏、公告和用户权限等管理模块功能划分贴近真实校园赛事运营需求。压缩包共1901个文件约38.02MB其中vue与js文件构成前端页面与交互逻辑java文件承载后端接口wxml与wxss对应小程序端视图与样式json、sql、md等分别用于配置、建表与说明文档另有png、svg等图片资源。目前已有52人学习下载。整体目录结构清晰前后端代码与数据库脚本齐备读者可据此梳理赛题实现思路、理解模块间调用关系并借助部署教程完成本地运行与二次开发适合作为毕设选题落地的参考方案。1. 校篮球联赛系统从赛程混乱到小程序端一键查战报每年秋季学期高校篮球联赛一开打体育部办公室里最常听到的一句话就是“今天下午哪几场在哪个场几点”——赛程靠 Excel 在群里传来传去比分靠手写记录再拍照发群球员想查自己场均得分得翻半天聊天记录。这套基于微信小程序的校篮球联赛系统要解决的就是把报名、赛程编排、比分录入、积分榜和球员数据这几件事收进一个入口让裁判、领队和观众各看各的。它适合正在做 Java 毕业设计、想找一个业务闭环完整又不过度复杂的题目的同学也适合想练手微信小程序项目实例的开发者。整套方案是 Java 后端加微信小程序前端代码和部署教程配套跑通之后你能拿到一个能演示、能答辩、能继续加功能的完整系统。2. 技术选型与整体架构为什么是 SpringBoot 微信小程序2.1 后端为什么选 SpringBoot 而不是 Servlet 手写毕业设计里最常见的翻车点是后端用原生 Servlet JSP 硬写写到权限校验和 JSON 返回时全是重复代码答辩前一周还在改乱码问题。我一般会直接上 SpringBoot理由很实在内嵌 Tomcat 不用单独配服务器、starter 依赖把 MyBatis 和 MySQL 驱动一次拉齐、RestController天然返回 JSON 给小程序用。对校篮球联赛这种“增删改查为主、少量统计”的业务SpringBoot 的开发速度比手写 Servlet 快三到五倍而且代码结构清晰答辩时讲分层Controller / Service / Mapper也站得住。选型上还有一层考虑微信小程序前端只能通过 HTTPS 请求后端接口不能直连数据库。所以后端必须是一个独立的 Web 服务SpringBoot 打包成 jar 直接java -jar就能跑部署教程里这一步最省心。2.2 数据库表怎么设计才够用又不臃肿校篮球联赛的核心实体其实就六个用户球员/裁判/管理员、球队、球员、比赛、比分记录、积分榜。很多同学一上来设计十几张表结果一半用不上。我建议先落这六张字段够用即可。表名关键字段说明userid, openid, nickname, rolerole 区分 player/referee/adminteamid, name, coach, logo球队基本信息playerid, user_id, team_id, number, position球员归属球队match_infoid, team_a, team_b, match_time, court, statusstatus 标记未开始/进行中/已结束scoreid, match_id, team_a_score, team_b_score, recorder比分由裁判录入rankingid, team_id, win, lose, points积分榜可定时重算openid是小程序登录后微信返回的用户唯一标识用它做用户主键关联比让用户自己注册账号体验好得多。match_info里的status字段很关键前端根据它决定显示“未开始”还是“查看战报”。2.3 小程序端页面结构怎么划分小程序端不要一上来堆十几个页面。按角色分核心页面控制在八个以内首页赛程概览、赛程列表、比赛详情、积分榜、球队列表、球队详情、个人中心、裁判录入页。首页用swiper轮播今日重点比赛下面用列表展示最近赛程。裁判录入页只在rolereferee时显示入口用wx.getStorageSync读本地缓存的角色信息做判断。这里有个微信小程序顶部导航栏高度的坑自定义导航栏时不同机型状态栏高度不一样必须用wx.getSystemInfoSync().statusBarHeight动态计算否则 iPhone 和安卓机上标题会被顶掉或压住。这个细节在部署教程里会专门提。3. 后端接口与数据库落地从建表到跑通第一个接口3.1 建库建表与初始化数据先在 MySQL 里建库字符集用utf8mb4否则球队名里的生僻字会变问号。下面是最小可用的建表脚本直接抄。-- 创建数据库字符集必须 utf8mb4 CREATE DATABASE basketball_league DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE basketball_league; -- 用户表openid 来自微信登录 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) UNIQUE NOT NULL, nickname VARCHAR(50), role VARCHAR(20) DEFAULT player, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 球队表 CREATE TABLE team ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, coach VARCHAR(30), logo VARCHAR(255) ); -- 比赛表status: 0未开始 1进行中 2已结束 CREATE TABLE match_info ( id INT PRIMARY KEY AUTO_INCREMENT, team_a INT NOT NULL, team_b INT NOT NULL, match_time DATETIME, court VARCHAR(50), status TINYINT DEFAULT 0, FOREIGN KEY (team_a) REFERENCES team(id), FOREIGN KEY (team_b) REFERENCES team(id) ); -- 比分表 CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, match_id INT NOT NULL, team_a_score INT DEFAULT 0, team_b_score INT DEFAULT 0, recorder VARCHAR(50), FOREIGN KEY (match_id) REFERENCES match_info(id) );逻辑说明user表用openid做唯一键保证同一微信用户不会重复插入。match_info用外键关联team防止录入不存在的球队。score单独一张表而不是塞进match_info是为了后续可以记录每节比分或修改历史扩展性更好。参数上status用 TINYINT 而不是字符串查询和索引效率更高。初始化几条测试数据方便前端联调INSERT INTO team (name, coach) VALUES (计算机学院, 张老师), (机械学院, 李老师); INSERT INTO match_info (team_a, team_b, match_time, court, status) VALUES (1, 2, 2025-11-20 15:00:00, 东区篮球场1号, 0);3.2 SpringBoot 项目结构与核心依赖用 IDEA 新建 SpringBoot 项目勾选 Spring Web、MyBatis Framework、MySQL Driver。pom.xml里关键依赖如下版本按你本地 JDK 选JDK8 配 SpringBoot 2.7.x 最稳。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependencyapplication.yml里配数据库连接注意时区参数serverTimezoneAsia/Shanghai不写的话比赛时间会差 8 小时这是血泪经验。spring: datasource: url: jdbc:mysql://localhost:3306/basketball_league?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.entity server: port: 8080参数说明mapper-locations指向 XML 映射文件位置type-aliases-package让 XML 里可以直接写类名不用全限定名。server.port默认 8080如果被占用改成 8081前端请求地址要同步改。3.3 赛程查询接口从 Mapper 到 Controller先写实体类MatchInfo字段和表对应。然后 Mapper 接口Mapper public interface MatchMapper { // 查询所有未结束的比赛按时间升序 ListMatchInfo selectUpcoming(); // 根据 id 查比赛详情 MatchInfo selectById(Integer id); }对应的 XMLselect idselectUpcoming resultTypeMatchInfo SELECT m.id, m.match_time, m.court, m.status, ta.name AS teamAName, tb.name AS teamBName FROM match_info m JOIN team ta ON m.team_a ta.id JOIN team tb ON m.team_b tb.id WHERE m.status ! 2 ORDER BY m.match_time ASC /select逻辑说明这里用 JOIN 把球队名一起查出来前端就不用再发一次请求查队名。status ! 2过滤掉已结束的比赛首页只展示待打和进行中的。参数上resultType写MatchInfo是因为配了别名包如果没配要写全限定类名。Controller 层RestController RequestMapping(/api/match) public class MatchController { Autowired private MatchMapper matchMapper; GetMapping(/upcoming) public Result upcoming() { return Result.success(matchMapper.selectUpcoming()); } GetMapping(/detail/{id}) public Result detail(PathVariable Integer id) { return Result.success(matchMapper.selectById(id)); } }Result是统一返回体包含code、msg、data三个字段。小程序端判断code 200再取data。这个统一返回体在部署教程里会强调不然后端报错时前端拿到一堆 HTML 报错页排查起来像黑匣子。4. 小程序端联调与裁判录入把接口真正用起来4.1 微信登录换 openid 与本地缓存角色小程序端第一步是登录。调用wx.login拿到 code发给后端后端用 code 换 openid。这里后端需要调微信接口部署教程里会给出完整方法。前端拿到 openid 后存本地// 登录并缓存用户信息 wx.login({ success(res) { wx.request({ url: http://localhost:8080/api/user/login, method: POST, data: { code: res.code }, success(r) { // 后端返回 openid 和 role wx.setStorageSync(openid, r.data.data.openid); wx.setStorageSync(role, r.data.data.role); } }); } });逻辑说明wx.login的 code 只能用一次必须马上发给后端。wx.setStorageSync同步存储后续页面用wx.getStorageSync(role)判断是否显示裁判入口。参数上url里的localhost在真机调试时要换成电脑局域网 IP否则手机访问不到这是新手最常卡住的地方。4.2 赛程列表页渲染与下拉刷新赛程列表页用onLoad请求接口wx.request拿数据后setData渲染。加下拉刷新提升体验Page({ data: { matches: [] }, onLoad() { this.loadMatches(); }, loadMatches() { wx.request({ url: http://localhost:8080/api/match/upcoming, success: (res) { if (res.data.code 200) { this.setData({ matches: res.data.data }); } } }); }, onPullDownRefresh() { this.loadMatches(); wx.stopPullDownRefresh(); } });逻辑说明onPullDownRefresh里请求完必须调wx.stopPullDownRefresh()否则下拉圈一直转。setData只更新变化的数据不要整个页面重设。参数上matches数组里每条包含teamAName、teamBName、matchTimeWXML 里用wx:for循环渲染。4.3 裁判录入比分与积分榜重算裁判在比赛详情页录入比分提交后后端更新score表并把match_info.status改成 2同时触发积分榜重算。积分榜重算逻辑放在 Service 里public void recalcRanking() { // 清空旧积分榜 rankingMapper.deleteAll(); // 查出所有已结束比赛 ListMatchInfo finished matchMapper.selectFinished(); MapInteger, int[] stats new HashMap(); // teamId - [win, lose, points] for (MatchInfo m : finished) { Score s scoreMapper.selectByMatchId(m.getId()); int[] a stats.getOrDefault(m.getTeamA(), new int[3]); int[] b stats.getOrDefault(m.getTeamB(), new int[3]); if (s.getTeamAScore() s.getTeamBScore()) { a[0]; a[2] 2; b[1]; } else { b[0]; b[2] 2; a[1]; } stats.put(m.getTeamA(), a); stats.put(m.getTeamB(), b); } // 写入 ranking 表 stats.forEach((teamId, v) - rankingMapper.insert(teamId, v[0], v[1], v[2])); }逻辑说明胜一场积 2 分负一场积 0 分这是校联赛常见规则你可以按实际规程改。stats用 Map 累加避免多次查库。参数上selectFinished查status2的比赛deleteAll先清空再插入保证积分榜不会重复累加。5. 部署与联调避坑那些让答辩前夜崩溃的问题5.1 小程序请求域名校验与本地调试现象真机预览时所有接口报“不在以下 request 合法域名列表中”。原因微信开发者工具默认校验域名而本地是 IP 地址。解决开发阶段在开发者工具“详情-本地设置”里勾选“不校验合法域名”但正式发布必须配 HTTPS 域名。答辩演示用开发者工具或真机开调试模式即可。5.2 数据库时区导致比赛时间差 8 小时现象后端存进去的match_time是 15:00小程序显示 23:00。原因JDBC 连接没指定时区MySQL 按 UTC 解析。解决连接串加serverTimezoneAsia/Shanghai同时确认 MySQL 全局时区。这个坑我踩过两次改完立刻正常。5.3 跨域问题让前端一直请求失败现象开发者工具里接口 200但浏览器或某些环境报 CORS。原因小程序本身不受浏览器同源策略限制但如果你用浏览器测接口就会遇到。解决后端加全局跨域配置或者只在开发者工具里测。加配置更省心Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true); } }5.4 自定义导航栏高度在不同机型错位现象iPhone 上标题正常安卓机上标题被状态栏压住。原因写死了padding-top。解决用wx.getSystemInfoSync().statusBarHeight动态设置单位是 px配合rpx换算。这个细节在部署教程里有完整代码。5.5 裁判录入重复提交导致比分翻倍现象裁判手抖点两次提交比分变成两倍。原因接口没做幂等。解决前端提交后禁用按钮后端在score表加match_id唯一索引重复插入直接报错拦截。后悔药就是加唯一约束比事后改数据省事。6. 进阶把系统做成能持续迭代的毕业设计6.1 用定时任务自动生成积分榜快照积分榜每次比分录入都重算数据量大时会慢。进阶做法是用 Spring 的Scheduled每天凌晨重算一次或者比分录入后发消息异步算。对校联赛这种量级直接同步算完全够但你可以把这个作为答辩亮点讲“可扩展性”。Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点 public void autoRecalc() { recalcRanking(); }参数说明cron表达式六位分别秒分时日月周。启动类加EnableScheduling才生效。6.2 球员数据统计与场均得分在score表基础上加一张player_score表记录每场每个球员的得分。查询时用SUM和COUNT算场均SELECT p.name, t.name AS teamName, SUM(ps.points) AS total, ROUND(SUM(ps.points) / COUNT(DISTINCT ps.match_id), 1) AS avg FROM player_score ps JOIN player p ON ps.player_id p.id JOIN team t ON p.team_id t.id GROUP BY p.id ORDER BY avg DESC;这个查询直接给前端做得分榜答辩时演示效果很好。注意COUNT(DISTINCT match_id)防止同一场多次记录导致场均偏低。6.3 验证系统是否跑通的三个检查点第一后端启动无报错访问http://localhost:8080/api/match/upcoming返回 JSON。第二开发者工具里登录后openid能存进 Storage。第三裁判录入比分后积分榜页面刷新能看到变化。三个都过系统就算跑通可以录演示视频了。6.4 我一般会留的后手做毕业设计最怕答辩前改需求。我的习惯是数据库脚本单独存一份init.sql随时能重建后端接口用 Postman 存一份集合改完一键回归小程序端把请求地址抽成config.js一个常量换环境只改一处。这套系统代码量不大但把部署教程里的环境配置、时区、跨域、导航栏高度这几个点提前处理掉能省下大量调试时间。希望帮到你。本文还有配套的精品资源点击获取