ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

校篮球联赛系统实战:Java后端+微信小程序实现赛程编排与实时比分

校篮球联赛系统实战:Java后端+微信小程序实现赛程编排与实时比分 简介这份资源是面向高校计算机相关专业毕业设计场景的完整项目包主题为基于微信小程序的校篮球联赛系统适合正在准备毕设、需要可运行案例与配套代码参考的本科或高职学生。项目采用Java后端与微信小程序前端组合覆盖球队、球员、赛事信息、赛事详情、公告及用户等模块的增删改查并包含留言与收藏两类交互管理功能划分贴近真实联赛运营流程。压缩包共1901个文件约38.02MB其中vue与js文件构成前端页面与逻辑java文件承载后端接口wxss与wxml对应小程序视图层另有json配置、png与svg图片素材、sql建库脚本及md说明文档整体结构完整、便于按模块检索。资源附有部署教程可帮助读者快速理解前后端联调方式与目录组织思路。目前已有53人学习适合作为毕设选题落地、功能扩展或答辩演示的参考方案。1. 校篮球联赛系统从赛程混乱到小程序端实时同步这套 Java 后端怎么搭院系篮球联赛最头疼的不是打球是赛程。二十支队伍、小组循环加淘汰赛、场地只有两块、裁判要排班、比分靠微信群吼——去年我们院体育部用 Excel 排了三天开赛后还是出现「同一队两场撞时间」「比分记错导致出线算错」的翻车现场。这套基于微信小程序的校篮球联赛系统核心就是把这堆事搬到线上管理员在后台建赛事、排赛程、录比分球员和观众在小程序里看赛程、查积分榜、收比赛通知。技术栈是 JavaSpring Boot MyBatis-Plus做后端微信小程序做前端MySQL 存数据。它适合正在做计算机毕业设计、想找一个业务闭环完整又不太冷门的题目的同学也适合真想把院系联赛管起来的体育部。下面按「后端怎么设计 → 小程序怎么对接 → 部署怎么落地 → 坑在哪」的顺序讲透代码和参数都能直接抄。2. 后端骨架Spring Boot MyBatis-Plus 怎么撑起赛事、球队、赛程三张核心表先把数据模型想清楚后面写接口就是顺水推舟。校篮球联赛的业务对象其实就四类赛事一个赛季、球队、球员、比赛对阵记录。赛程和积分榜都是「比赛」这张表算出来的派生视图不要单独建表存积分否则改一次比分要同步三处必出脏数据。2.1 四张核心表的设计与建表 SQL我一般用 MyBatis-Plus 的实体类反向生成建表语句省得手写字段对不上。下面是最小可用的一组表结构字段名和 Java 实体一一对应。-- 赛事表一个赛季一条记录 CREATE TABLE league ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(64) NOT NULL COMMENT 赛事名称如2024院系篮球联赛, season VARCHAR(16) NOT NULL COMMENT 赛季如2024春, start_date DATE NOT NULL, end_date DATE NOT NULL, status TINYINT DEFAULT 0 COMMENT 0未开始 1进行中 2已结束, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 球队表 CREATE TABLE team ( id BIGINT NOT NULL AUTO_INCREMENT, league_id BIGINT NOT NULL COMMENT 所属赛事, name VARCHAR(32) NOT NULL COMMENT 队名, college VARCHAR(32) COMMENT 所属院系, group_no TINYINT COMMENT 分组号小组赛用, PRIMARY KEY (id), KEY idx_league (league_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 比赛表赛程和比分的唯一来源 CREATE TABLE match_game ( id BIGINT NOT NULL AUTO_INCREMENT, league_id BIGINT NOT NULL, home_team_id BIGINT NOT NULL COMMENT 主队, away_team_id BIGINT NOT NULL COMMENT 客队, match_time DATETIME NOT NULL COMMENT 开赛时间, court VARCHAR(32) COMMENT 场地, home_score INT DEFAULT NULL COMMENT 主队得分未开赛为NULL, away_score INT DEFAULT NULL, stage TINYINT DEFAULT 0 COMMENT 0小组赛 1淘汰赛, status TINYINT DEFAULT 0 COMMENT 0未开始 1进行中 2已结束, PRIMARY KEY (id), KEY idx_league_time (league_id, match_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;match_game是整张系统的枢纽。积分榜不落库每次查询时用 SQL 聚合算出来这样比分一改榜单立刻跟着变没有同步延迟。status字段控制比赛生命周期只有status2的比赛才计入积分避免未打完的比赛污染排名。2.2 积分榜聚合查询一条 SQL 算出胜场、净胜分和排名积分榜是观众看得最多的页面也是最容易写错的地方。常见做法是遍历所有比赛在 Java 里累加但队伍一多就是 N 次查询。我一般直接用一条 SQL 把主客场的得分合并后聚合。SELECT t.id AS teamId, t.name AS teamName, COUNT(*) AS played, SUM(CASE WHEN g.win_team t.id THEN 1 ELSE 0 END) AS win, SUM(CASE WHEN g.win_team ! t.id AND g.win_team IS NOT NULL THEN 1 ELSE 0 END) AS lose, SUM(CASE WHEN g.home_team_id t.id THEN g.home_score - g.away_score ELSE g.away_score - g.home_score END) AS diff FROM team t JOIN ( SELECT home_team_id, away_team_id, home_score, away_score, CASE WHEN home_score away_score THEN home_team_id WHEN home_score away_score THEN away_team_id ELSE NULL END AS win_team FROM match_game WHERE league_id #{leagueId} AND status 2 ) g ON g.home_team_id t.id OR g.away_team_id t.id WHERE t.league_id #{leagueId} GROUP BY t.id, t.name ORDER BY win DESC, diff DESC;逻辑说明内层子查询先把每场比赛的胜者算出来平局win_team为 NULL篮球一般不打平但小组赛可能有特殊情况外层再按球队聚合。diff是净胜分排名规则是「先看胜场再看净胜分」这是院系联赛最常用的规则。参数leagueId从接口传入status 2保证只统计已结束的比赛。如果你们联赛规则是「胜场相同看相互战绩」那这条 SQL 要再改但 90% 的院系比赛用净胜分就够了。2.3 赛程自动编排避免同队撞时间的排程接口手动排赛程是翻车重灾区。我写了一个简单的轮转排程接口输入球队列表和可用时间段输出不冲突的对阵表。核心约束只有两条同一时间段一支队只能打一场同一块场地同一时间段只能有一场比赛。/** * 单循环赛程生成轮转法 * param teamIds 参赛队ID列表 * param slots 可用时间段每个元素是[开始时间, 场地] */ public ListMatchGame generateSchedule(ListLong teamIds, ListTimeSlot slots) { ListMatchGame result new ArrayList(); int n teamIds.size(); // 奇数队补一个虚拟队保证轮转能整除 if (n % 2 ! 0) { teamIds.add(-1L); n; } int rounds n - 1; int slotIndex 0; for (int r 0; r rounds; r) { for (int i 0; i n / 2; i) { Long home teamIds.get(i); Long away teamIds.get(n - 1 - i); if (home -1L || away -1L) continue; // 跳过虚拟队 TimeSlot slot slots.get(slotIndex % slots.size()); MatchGame m new MatchGame(); m.setHomeTeamId(home); m.setAwayTeamId(away); m.setMatchTime(slot.getStartTime()); m.setCourt(slot.getCourt()); result.add(m); slotIndex; } // 固定第一支队其余轮转 Long last teamIds.remove(n - 1); teamIds.add(1, last); } return result; }逻辑说明轮转法的思路是固定第一支队其余队顺时针轮转每轮生成 n/2 场比赛。slots是管理员在后台配置的可用时间段列表按顺序分配天然保证同一时间段不会重复。参数teamIds是参赛队 IDslots里每个TimeSlot包含开始时间和场地。注意虚拟队-1L的处理奇数队时必须补否则轮转会数组越界。这个算法生成的是单循环如果要小组赛加淘汰赛小组赛用这个淘汰赛按小组排名手动或再写一个对阵树生成。3. 小程序端对接登录、赛程列表和比分实时刷新怎么做后端接口写好了小程序端要解决三件事用户身份怎么认、赛程怎么展示、比分怎么及时更新。微信小程序的登录体系和普通 Web 不一样不能直接发账号密码得走wx.login换openid那套。3.1 微信登录换 openid 与手机号获取的完整链路小程序端调用wx.login拿到临时code传给后端后端拿code加上小程序的appid和secret去微信服务器换openid和session_key。这是标准流程但有两个坑code只能用一次五分钟过期session_key不能下发给前端。// 小程序端登录并获取手机号 wx.login({ success(res) { if (res.code) { wx.request({ url: https://your-domain.com/api/auth/login, method: POST, data: { code: res.code }, success(loginRes) { // 后端返回自定义 token存本地 wx.setStorageSync(token, loginRes.data.token); } }); } } }); // 获取手机号必须由 button 的 open-type 触发 // button open-typegetPhoneNumber bindgetphonenumberonGetPhone onGetPhone(e) { if (e.detail.code) { wx.request({ url: https://your-domain.com/api/auth/phone, method: POST, header: { Authorization: wx.getStorageSync(token) }, data: { phoneCode: e.detail.code } }); } }逻辑说明wx.login的code换openid在后端做前端只拿后端签发的 token。手机号获取必须用button的open-typegetPhoneNumber用户点击授权后拿到phoneCode再传给后端换真实手机号。参数上appid和secret配在后端配置文件里绝对不能写在小程序代码里否则被人扒出来就能冒充你的小程序。token建议用 JWT有效期设 7 天过期让前端重新登录。3.2 赛程列表与积分榜的接口约定和分页参数小程序端展示赛程最怕一次拉全部数据。院系联赛虽然数据量不大但养成好习惯列表接口必须分页按时间倒序或正序由前端传参决定。GetMapping(/matches) public ResultPageMatchVO listMatches( RequestParam Long leagueId, RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size, RequestParam(required false) Integer status) { PageMatchGame p new Page(page, size); LambdaQueryWrapperMatchGame qw new LambdaQueryWrapper(); qw.eq(MatchGame::getLeagueId, leagueId); if (status ! null) { qw.eq(MatchGame::getStatus, status); } qw.orderByAsc(MatchGame::getMatchTime); return Result.ok(matchService.page(p, qw)); }逻辑说明page和size是分页参数默认第一页十条。status可选前端传 0 只看未开始传 2 只看已结束。返回的Page对象里带total和records小程序端用records渲染列表用total判断是否还有下一页。注意orderByAsc按开赛时间正序观众最关心「下一场什么时候打」所以未开始的比赛应该排前面。如果要做「最近比赛」模块再加一个按时间倒序的接口。3.3 比分更新的轮询方案与 WebSocket 取舍比分实时刷新是个经典问题。小程序原生支持 WebSocket但维护长连接对毕业设计来说偏重而且部署时还要处理反向代理的升级配置。我一般先用轮询简单可靠。// 比赛详情页每 15 秒拉一次比分 Page({ data: { match: {} }, onLoad(options) { this.matchId options.id; this.loadMatch(); this.timer setInterval(() this.loadMatch(), 15000); }, onUnload() { clearInterval(this.timer); // 页面卸载必须清定时器 }, loadMatch() { wx.request({ url: https://your-domain.com/api/match/${this.matchId}, success: (res) this.setData({ match: res.data.data }) }); } });逻辑说明setInterval每 15 秒请求一次比赛详情onUnload里必须clearInterval否则页面关了定时器还在跑内存泄漏。15 秒是折中值太短费流量太长观众觉得卡。如果比赛进行中需要更实时可以在status1时把间隔调到 5 秒比赛结束后停止轮询。WebSocket 方案适合同时在线人数多的场景但院系联赛通常几十人同时看轮询完全够用部署也少一层麻烦。4. 部署落地从本地跑通到服务器上线的完整步骤毕业设计答辩前最怕「本地能跑服务器上白屏」。部署这件事把每一步拆开做比一次性打包上传靠谱得多。下面按「本地验证 → 数据库准备 → 后端打包 → 小程序配置」的顺序走。4.1 本地环境验证JDK、Maven 和 MySQL 的版本对齐先确认本地能跑通再谈服务器。版本不对齐是新手最常见的翻车点Spring Boot 3.x 要求 JDK 17MyBatis-Plus 3.5.x 对 Spring Boot 3 的支持要选对 starter。组件推荐版本说明JDK17Spring Boot 3.x 最低要求8 也能跑但建议 17Maven3.8用于依赖管理和打包MySQL8.0utf8mb4 字符集支持 emoji 存队名Spring Boot3.2.x与 MyBatis-Plus 3.5.5 搭配微信开发者工具最新稳定版调试小程序端本地验证命令mvn clean package能打出 jarjava -jar target/xxx.jar能启动浏览器访问http://localhost:8080/api/league/list有返回就算本地通了。数据库连接串写在application.yml里本地用localhost:3306密码别用 root 空密码答辩时被看到不专业。4.2 服务器部署jar 包上传、systemd 守护与 Nginx 转发服务器上不需要装 TomcatSpring Boot 内置了。把 jar 传上去用 systemd 守护进程挂了自动重启。# 上传 jar 到服务器 scp target/league-system.jar useryour-server:/opt/league/ # 创建 systemd 服务文件 sudo vim /etc/systemd/system/league.service[Unit] DescriptionLeague System Afternetwork.target mysql.service [Service] Userwww WorkingDirectory/opt/league ExecStart/usr/bin/java -jar /opt/league/league-system.jar --spring.profiles.activeprod Restartalways RestartSec10 [Install] WantedBymulti-user.targetsudo systemctl daemon-reload sudo systemctl enable league sudo systemctl start league sudo systemctl status league # 看是否 active (running)逻辑说明ExecStart里--spring.profiles.activeprod指定生产配置数据库连接串写在application-prod.yml里。Restartalways保证进程异常退出后自动拉起RestartSec10是重启间隔。Nginx 负责把 443 端口的请求转发到后端 8080同时处理小程序要求的 HTTPS。server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/cert/your.pem; ssl_certificate_key /etc/nginx/cert/your.key; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }小程序正式版要求所有请求走 HTTPS且域名要在微信后台配置白名单。开发阶段可以在开发者工具里勾选「不校验合法域名」但上线前必须配好。4.3 小程序端配置合法域名、appid 与体验版发布小程序端要改三个地方appid换成自己的请求域名换成服务器域名然后在微信公众平台配置request合法域名。开发者工具里点「上传」在公众平台「版本管理」里设为体验版扫码就能在真机上测。正式发布要提交审核审核时注意不能有测试数据、不能有未完成的页面入口、隐私政策要写清楚收集了哪些信息手机号、openid。毕业设计一般用体验版演示就够不用走正式审核。5. 避坑与排查这套系统最容易翻车的 5 个地方5.1 现象小程序请求全部失败报「不在以下 request 合法域名列表中」原因微信开发者工具默认校验域名服务器域名没加到白名单或者用了 HTTP 而非 HTTPS。解决开发阶段在「详情 → 本地设置」勾选「不校验合法域名」上线前在公众平台「开发管理 → 开发设置 → 服务器域名」里把https://your-domain.com加进request合法域名。注意域名不能带端口必须是 443。5.2 现象积分榜排名和实际不符胜场相同的队顺序乱了原因ORDER BY win DESC, diff DESC里diff是净胜分但如果两队净胜分也相同MySQL 返回顺序不确定。解决再加一个稳定的排序字段比如ORDER BY win DESC, diff DESC, t.id ASC。另外确认status 2的条件没漏未结束的比赛被算进去会导致胜场虚高。5.3 现象比分更新后小程序页面不刷新要手动退出重进原因轮询定时器没生效或者onUnload里没清定时器导致多个定时器叠加反而请求混乱。解决检查setInterval是否在onLoad里调用onUnload里是否clearInterval(this.timer)。另外小程序切到后台时定时器会被暂停切回来要重新触发一次请求可以在onShow里补一次loadMatch()。5.4 现象服务器上 jar 启动报「Access denied for user」本地却正常原因生产数据库密码和本地不一致或者 MySQL 用户没有远程访问权限。解决检查application-prod.yml里的数据库连接串确认用户名密码正确。如果数据库和 backend 不在同一台机器要授权GRANT ALL ON league.* TO user% IDENTIFIED BY password;并FLUSH PRIVILEGES;。生产环境建议数据库只监听内网不要暴露公网端口。5.5 现象赛程生成后出现同一队同一时间打两场原因轮转法里虚拟队-1L没跳过或者slots数量少于同时段比赛数导致时间复用。解决确认if (home -1L || away -1L) continue;这行在且slots的长度至少能覆盖一轮的比赛数。如果场地只有一块slots里每个时间段只能出现一次不能循环取模否则就会撞时间。我一般会在生成后加一个校验遍历结果检查同一matchTime下是否有重复teamId有就抛异常提示管理员增加时间段。6. 进阶技巧用 MyBatis-Plus 自动生成建表 SQL 和接口文档最后一个实用技巧能省掉大量手写 SQL 和接口文档的时间。MyBatis-Plus 有个Db工具类可以根据 Java 实体类反向生成建表语句改字段时不用手动同步 SQL。配合 Swagger或 SpringDoc自动生成接口文档答辩时直接打开文档页面演示比截图专业得多。// 根据实体类生成建表 SQL import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.FastAutoGenerator; public class SqlGenerator { public static void main(String[] args) { FastAutoGenerator.create(jdbc:mysql://localhost:3306/league, root, password) .globalConfig(builder - builder.author(you).outputDir(/tmp/gen)) .strategyConfig(builder - builder .addInclude(league, team, match_game) // 要生成的表 .entityBuilder().enableLombok()) .execute(); } }逻辑说明FastAutoGenerator是 MyBatis-Plus 的代码生成器addInclude指定要处理的表outputDir是输出目录。它会生成 Entity、Mapper、Service、Controller 一整套建表 SQL 在resources下。参数上数据库连接串换成你自己的enableLombok省去写 getter/setter。注意生成前先备份现有代码生成器会覆盖同名文件。接口文档用 SpringDoc加一个依赖就行dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.3.0/version /dependency启动后访问http://localhost:8080/swagger-ui/index.html所有接口和参数一目了然。答辩时老师问「你这个接口怎么调的」直接打开页面点「Try it out」比口头解释强十倍。我自己的习惯是每次改完实体类先跑一遍生成器更新 SQL再启动服务看 Swagger 有没有报错最后才去改小程序端。这个顺序能保证后端永远是「活的」不会出现文档和代码两张皮。做毕业设计最怕的就是最后一周发现数据库字段和代码对不上那时候再改就是血泪教训。希望帮到你。本文还有配套的精品资源点击获取
返回列表