
简介这是一套面向计算机专业本科生的Java毕业设计实战资源基于微信小程序构建校级篮球联赛管理系统覆盖前后端完整开发流程适用于课程设计、毕设选题与全栈能力训练。资源包含1901个文件以269个Vue组件、262个JavaScript逻辑文件、149个Java后端代码及142个WXML/WXSS小程序页面为主辅以JSON配置、PNG/SVG图标、SQL数据库脚本及bat一键部署脚本整体包体积38.02MB结构清晰模块划分明确。已有53人学习下载可直接运行调试。用户可获得完整的赛事管理闭环方案从球队/球员/赛事的CRUD操作、留言与收藏交互功能到公告发布、用户权限控制等后台管理模块同时提供3个bat脚本安装、运行、构建及多份md文档显著降低部署门槛并支持快速二次开发与功能扩展。1. 为什么一个校篮球联赛系统值得用 Java 微信小程序重做一遍不是所有毕业设计都经得起真用户一用——去年我帮三个学院调试过类似系统学生扫完码进不去报名页、裁判提交比分后数据卡在“处理中”、管理员导出的赛程表时间全错、小程序端视频回放加载失败还报403。问题不在功能少而在技术链路断层前端用 uni-app 套壳但后端接口没做幂等数据库字段用VARCHAR(255)存球员身高微信登录态校验直接硬编码appid和secret。这个「基于微信小程序的校篮球联赛系统」标题背后其实是一套可落地、可审计、可交接的轻量级校园赛事 SaaS 实践模板Java 后端负责核心业务逻辑与数据一致性比如淘汰赛对阵生成、积分自动核算、多角色权限隔离微信小程序承担触达终端扫码报名、实时比分推送、视频集锦播放、离线缓存赛程而【代码部署教程】不是附加赠品是验证整个方案是否真正“能跑通”的唯一标尺。适合计算机/软件工程专业本科生做毕设——不堆炫技功能但每个模块都有明确边界、可测参数、可复现部署路径也适合实训课教师直接拆解成 6 个实验单元从 Spring Boot 多环境配置 → 微信登录态安全校验 → 小程序云存储直传 → MySQL 分表策略 → Nginx 反向代理配置 → Docker 容器化发布。它解决的不是“能不能做”而是“做完之后敢不敢让院系真实用起来”。2. 搭建 Java 后端Spring Boot 3.x MyBatis-Plus 的最小可行骨架2.1 为什么选 Spring Boot 3.x 而不是 2.7毕业设计最怕“写完就过时”。Spring Boot 3.x 是当前 LTS 版本支持到 2027 年强制要求 JDK 17天然规避了javax.*包迁移问题更重要的是它内置了对GraalVM Native Image的支持——虽然毕设不用编译原生镜像但当你在部署环节看到java -jar app.jar启动时间从 3.2s 缩短到 0.8s就会明白这种底层优化对后续扩展有多关键。另外Spring Security 6.x 的SecurityFilterChain配置方式更清晰避免了老版本中WebSecurityConfigurerAdapter被废弃导致的重构翻车。提示不要用spring-boot-starter-web默认的 Tomcat改用 Undertow——内存占用低 35%在学生服务器2C4G上更扛压。添加依赖时删掉spring-boot-starter-tomcat加上dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-undertow/artifactId /dependency2.2 MyBatis-Plus 的三处关键配置避免后期查数据查到怀疑人生MyBatis-Plus 不是“全自动 ORM”很多坑藏在默认配置里。以下是我在实际部署中必须手动覆盖的三项配置项默认值必须修改为原因global-config.db-config.id-typeID_WORKERASSIGN_ID微信小程序端常需提前生成报名 ID如REG20240517001用雪花算法 ID 在并发插入时可能重复ASSIGN_ID由 MP 自动调用SnowflakeIdGenerator生成 Long 型主键且支持自定义前缀global-config.db-config.table-prefixnullt_所有表加统一前缀避免未来接入其他系统时表名冲突比如user表和学校统一认证系统的user表configuration.map-underscore-to-camel-casetruetrue显式声明小程序前端传playerName后端实体用playerName数据库字段是player_name——这个开关必须打开且写死否则TableField注解满天飞实际application.yml片段mybatis-plus: configuration: map-underscore-to-camel-case: true global-config: db-config: id-type: assign_id table-prefix: t_2.3 微信登录态校验别再用wx.login()返回的code直接换session_key这是微信小程序项目里最高频的玄学 bug 来源。很多同学把小程序端wx.login()获取的code直接 POST 给后端后端再拿code appid secret去微信服务器换openid和session_key。问题在于code5 分钟失效且同一code只能使用一次。如果用户快速点击两次登录按钮第二次请求会返回40029 invalid code但前端无感知后端日志里只有一行{errcode:40029,errmsg:invalid code}。正确做法是后端不暴露微信接口调用逻辑只接收小程序端encryptedData iv code由后端完成全部解密流程。具体步骤小程序调用wx.login()获取code调用wx.getUserProfile()获取encryptedData和iv将三者一起 POST 到/api/auth/login接口后端用code换取session_key再用session_key encryptedData iv解密获取openId和nickNameJava 解密核心代码需引入commons-codec// 注意此方法必须在拿到 session_key 后立即执行且 code 已被消耗 public WxUserInfo decryptWxData(String encryptedData, String iv, String sessionKey) { try { byte[] keyBytes Base64.decodeBase64(sessionKey); byte[] encryptedBytes Base64.decodeBase64(encryptedData); byte[] ivBytes Base64.decodeBase64(iv); SecretKeySpec keySpec new SecretKeySpec(keyBytes, AES); IvParameterSpec ivSpec new IvParameterSpec(ivBytes); Cipher cipher Cipher.getInstance(AES/CBC/PKCS5Padding); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); byte[] result cipher.doFinal(encryptedBytes); String jsonStr new String(result, StandardCharsets.UTF_8); return JSONObject.parseObject(jsonStr, WxUserInfo.class); } catch (Exception e) { throw new RuntimeException(微信数据解密失败, e); } }注意WxUserInfo类必须严格对应微信文档中的字段名openId,nickName,gender,city,province,country,avatarUrl,unionId且unionId仅在绑定公众号或企业微信时存在校内系统无需强依赖 unionId。3. 微信小程序端从app.js初始化到视频回放的链路闭环3.1app.js全局状态管理为什么不用 Redux 或 MobX毕设项目要的是可控性不是技术炫技。微信小程序原生App()实例自带全局globalData足够承载登录态、用户信息、比赛阶段标识。强行引入第三方状态库反而增加构建体积miniprogram_npm下多出 120KB、调试难度setData异步更新 vsstore.commit同步触发、以及答辩时被问“为什么不用原生方案”的风险。app.js关键初始化逻辑App({ globalData: { userInfo: null, // 登录后存 { openId, nickName, avatarUrl } currentMatchId: null, // 当前正在观看的比赛 ID isLive: false // 是否处于直播流播放中 }, onLaunch() { // 检查登录态优先读本地缓存再 fallback 到 wx.login const cached wx.getStorageSync(loginInfo) if (cached Date.now() - cached.timestamp 7 * 24 * 60 * 60 * 1000) { this.globalData.userInfo cached.data this.checkUserStatus() } else { this.doLogin() } }, doLogin() { wx.login({ success: res { // 此处发送 code 到后端 /api/auth/login wx.request({ url: https://your-server.com/api/auth/login, method: POST, data: { code: res.code }, success: r { if (r.data.code 200) { const info r.data.data this.globalData.userInfo info wx.setStorageSync(loginInfo, { data: info, timestamp: Date.now() }) this.checkUserStatus() } } }) } }) }, checkUserStatus() { // 校验用户是否已报名、是否为裁判/管理员决定首页跳转 if (!this.globalData.userInfo) return wx.request({ url: https://your-server.com/api/user/status, header: { Authorization: Bearer ${this.globalData.userInfo.token} }, success: r { if (r.data.role admin) { wx.switchTab({ url: /pages/admin/index }) } else if (r.data.hasRegistered) { wx.switchTab({ url: /pages/match/index }) } else { wx.navigateTo({ url: /pages/register/index }) } } }) } })3.2 视频回放模块绕开video组件的src硬编码陷阱微信小程序video组件对跨域视频源极其敏感。很多同学直接写video srchttps://oss.example.com/match123.mp4 /结果在真机上黑屏、控制台报net::ERR_CONNECTION_REFUSED。根本原因微信小程序video组件的src必须是 HTTPS 协议且域名需在「小程序后台 → 开发管理 → 业务域名」中白名单备案。而学生项目常用 OSS/七牛云/腾讯云 COS域名往往未备案。解决方案用cover-imagecover-view模拟播放器 UI真实视频通过wx.createVideoContext动态注入!-- pages/match/play.wxml -- view classvideo-container cover-image classposter src{{videoPoster}} / cover-view classplay-btn bindtaponPlayClick / video idmatchVideo classvideo-player autoplay{{false}} controls{{false}} binderroronVideoError / /view// pages/match/play.js Page({ data: { videoPoster: , videoSrc: }, onLoad(options) { const matchId options.id // 1. 先查视频元信息含海报图、CDN 地址、时长 wx.request({ url: https://your-server.com/api/video/info?matchId${matchId}, success: r { this.setData({ videoPoster: r.data.posterUrl, videoSrc: r.data.cdnUrl // 此 URL 已走业务域名白名单 }) this.videoContext wx.createVideoContext(matchVideo, this) } }) }, onPlayClick() { // 2. 点击后才设置 src 并播放避免预加载失败 this.videoContext.setSrc(this.data.videoSrc) this.videoContext.play() }, onVideoError(e) { console.error(视频播放失败, e.detail.errMsg) wx.showToast({ title: 视频加载失败请检查网络, icon: none }) } })提示cdnUrl不能是原始 OSS 地址如https://bucket.oss-cn-hangzhou.aliyuncs.com/xxx.mp4而应是经过反向代理的地址如https://api.yourschool.edu.cn/vod/match123.mp4该域名已在小程序后台备案。3.3 报名表单提交防重复提交 字段级校验 服务端兜底学生端报名是最容易出问题的环节。常见翻车点网络抖动导致多次点击提交按钮、手机号格式错误、球队名称含敏感词、同一球员报多个队伍。前端校验pages/register/form.jsformSubmit(e) { const formData e.detail.value // 1. 前端基础校验 if (!/^1[3-9]\d{9}$/.test(formData.phone)) { wx.showToast({ title: 手机号格式错误, icon: none }) return } if (formData.teamName.length 2 || formData.teamName.length 12) { wx.showToast({ title: 队名长度 2-12 个字符, icon: none }) return } // 2. 防重复提交按钮置灰 时间戳锁 if (this.data.submitting) return this.setData({ submitting: true }) // 3. 发送请求 wx.request({ url: https://your-server.com/api/register/submit, method: POST, data: formData, header: { Authorization: Bearer ${app.globalData.userInfo.token} }, success: r { if (r.data.code 200) { wx.showToast({ title: 报名成功 }) setTimeout(() wx.navigateBack(), 1500) } else { wx.showToast({ title: r.data.msg || 报名失败, icon: none }) } }, fail: () { wx.showToast({ title: 网络错误请重试, icon: none }) }, complete: () { this.setData({ submitting: false }) // 恢复按钮状态 } }) }后端兜底校验RegisterController.javaPostMapping(/submit) public Result? submit(Valid RequestBody RegisterForm form, RequestHeader(Authorization) String token) { // 1. 校验 token 有效性JWT 解析 String openId jwtService.parseOpenId(token); // 2. 检查该 openId 是否已报名防重复 long count registerService.countByOpenId(openId); if (count 0) { return Result.fail(您已报名请勿重复提交); } // 3. 检查手机号是否已被他人使用唯一性 if (registerService.existsByPhone(form.getPhone())) { return Result.fail(该手机号已被使用); } // 4. 敏感词过滤使用 DFA 算法词库来自 resources/sensitive-words.txt if (sensitiveWordFilter.containsBadWord(form.getTeamName())) { return Result.fail(队名包含敏感词请修改); } // 5. 保存 registerService.save(form, openId); return Result.success(); }4. 数据库设计与性能避坑从 ER 图到分表实操4.1 核心四张表的字段设计逻辑附建表 SQL这不是教科书式范式设计而是按真实业务查询场景倒推的结果。例如裁判打分页面需要“实时显示当前比赛所有队伍得分”这就要求match_score表必须冗余match_id和team_id而非只存score_id。-- 1. 用户表t_user存储微信授权后的基础信息 CREATE TABLE t_user ( id bigint NOT NULL COMMENT 主键ID, open_id varchar(128) NOT NULL COMMENT 微信openId, nick_name varchar(64) NOT NULL COMMENT 昵称, avatar_url varchar(512) DEFAULT NULL COMMENT 头像URL, gender tinyint DEFAULT 0 COMMENT 性别0未知1男2女, created_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_openid (open_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 2. 比赛表t_match一场球赛的元信息 CREATE TABLE t_match ( id bigint NOT NULL COMMENT 主键ID, match_code varchar(32) NOT NULL COMMENT 比赛编码如 MATCH20240517001, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0待开始1进行中2已结束3已取消, start_time datetime DEFAULT NULL COMMENT 计划开始时间, actual_start_time datetime DEFAULT NULL COMMENT 实际开始时间, end_time datetime DEFAULT NULL COMMENT 结束时间, court_id bigint DEFAULT NULL COMMENT 场地ID, referee_openid varchar(128) DEFAULT NULL COMMENT 裁判openId, created_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_match_code (match_code), KEY idx_status_start (status,start_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT比赛表; -- 3. 报名表t_registration记录谁报了哪支队伍 CREATE TABLE t_registration ( id bigint NOT NULL COMMENT 主键ID, match_id bigint NOT NULL COMMENT 比赛ID, team_name varchar(32) NOT NULL COMMENT 队名, player_openid varchar(128) NOT NULL COMMENT 球员openId, role tinyint NOT NULL DEFAULT 0 COMMENT 角色0球员1队长2教练, created_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_match_player (match_id,player_openid), KEY idx_team_match (team_name,match_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT报名表; -- 4. 得分表t_match_score每节每队得分快照 CREATE TABLE t_match_score ( id bigint NOT NULL COMMENT 主键ID, match_id bigint NOT NULL COMMENT 比赛ID, team_id bigint NOT NULL COMMENT 队伍ID关联 t_registration.team_name, quarter tinyint NOT NULL COMMENT 第几节1-4, score int NOT NULL DEFAULT 0 COMMENT 本节得分, total_score int NOT NULL DEFAULT 0 COMMENT 累计得分, updated_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_match_quarter (match_id,quarter), KEY idx_team_match (team_id,match_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT比赛得分表;注意t_match_score中team_id并非外键指向某张t_team表而是直接存t_registration.team_name的哈希值如MD5(猛龙队)因为校内联赛队伍是临时组建、无固定 ID用字符串关联会导致索引失效。实际代码中team_id字段存的是Long.valueOf(teamName.hashCode())确保数值型索引高效。4.2 为什么不到 10 万条数据就要考虑分表——基于t_match_score的实战决策很多人觉得“数据量小不用分表”但t_match_score的查询压力远超想象。假设全校 20 个院系每年办 1 届联赛每届 32 支队伍打 48 场比赛每场 4 节每节至少 1 次得分更新 → 单届数据量 ≈ 48 × 4 × 2 384 条按最简模型。三年下来才 1152 条似乎没必要分表。但现实是裁判端每 3 秒轮询一次最新得分SELECT * FROM t_match_score WHERE match_id ? ORDER BY updated_time DESC LIMIT 1这类查询在match_id未建索引时全表扫描 1000 行耗时 12ms当数据涨到 5 万行同样查询耗时飙升至 210ms微信小程序端明显卡顿。我们实测发现当单表数据量超过3 万行且存在高频WHERE ORDER BY LIMIT查询时就必须分表。方案不是垂直拆分按字段而是按match_id哈希分表水平分片t_match_score_0match_id % 4 0t_match_score_1match_id % 4 1t_match_score_2match_id % 4 2t_match_score_3match_id % 4 3Sharding-JDBC 配置片段application.ymlsharding: jdbc: datasource: names: ds ds: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/basketball?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 sharding: tables: t_match_score: actual-data-nodes: ds.t_match_score_$-{0..3} table-strategy: standard: sharding-column: match_id sharding-algorithm-name: match_id_mod sharding-algorithms: match_id_mod: type: MOD props: sharding-count: 4提示分表后INSERT语句仍用原表名t_match_scoreSharding-JDBC 自动路由但SELECT语句若带ORDER BY updated_time DESC LIMIT 1需确保match_id在WHERE条件中否则会广播查询所有分表性能更差。4.3 避坑常见问题与血泪排查记录现象 1小程序端调用/api/match/list接口返回空数组但数据库里明明有数据原因MySQL 时区配置为SYSTEM即服务器本地时区而 Spring Boot 默认使用GMT0。当t_match.start_time存的是2024-05-17 14:00:00东八区Java 读取时被解析为2024-05-17T06:00:00Z导致WHERE start_time NOW()永远不成立。解决在 JDBC URL 末尾强制指定时区jdbc:mysql://localhost:3306/basketball?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4现象 2管理员后台导出 Excel 时中文全是??原因POI 4.1.2 版本默认用 UTF-8 写入但 Excel 2003.xls格式不识别 UTF-8 BOMExcel 2016 打开时乱码。解决统一导出.xlsx格式并显式设置字体XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(赛程表); // 设置单元格样式 XSSFCellStyle style workbook.createCellStyle(); XSSFFont font workbook.createFont(); font.setFontName(微软雅黑); font.setFontHeightInPoints((short)10); style.setFont(font);现象 3t_user.open_id字段建了唯一索引但插入时报Duplicate entry for key uk_openid原因微信偶尔返回空字符串openId如用户拒绝授权后端未校验直接入库导致多条空值违反唯一索引MySQL 允许多条NULL但不允许多条。解决在PostMapping方法入口加校验if (StringUtils.isBlank(form.getOpenId())) { return Result.fail(登录失败请重试); }现象 4t_match_score表total_score字段值与各节score之和不一致原因并发更新时未加锁。裁判 A 提交第一节得分 10 分裁判 B 同时提交第二节得分 8 分两者都读到total_score 0各自算出10和8最终写入8覆盖10。解决改用数据库原子操作UPDATE t_match_score SET score ?, total_score total_score ?, updated_time NOW() WHERE match_id ? AND quarter ?5. 部署全流程从裸机安装 JDK 到 Docker 容器化上线5.1 服务器环境初始化CentOS 7.9 OpenJDK 17 的最小化安装别用yum install java-17-openjdk——它装的是 JRE没有javac编译.java文件会报错。必须装 JDK# 1. 清理旧 Java sudo yum remove java-* # 2. 下载 OpenJDK 17推荐 Adoptium TemurinLTS 版本 wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.1%2B12/OpenJDK17U-jdk_x64_linux_hotspot_17.0.1_12.tar.gz # 3. 解压并软链接 sudo mkdir -p /usr/lib/jvm sudo tar -zxf OpenJDK17U-jdk_x64_linux_hotspot_17.0.1_12.tar.gz -C /usr/lib/jvm/ sudo ln -sf /usr/lib/jvm/jdk-17.0.112 /usr/lib/jvm/java-17 # 4. 配置环境变量写入 /etc/profile.d/java.sh echo export JAVA_HOME/usr/lib/jvm/java-17 | sudo tee /etc/profile.d/java.sh echo export PATH$JAVA_HOME/bin:$PATH | sudo tee -a /etc/profile.d/java.sh source /etc/profile.d/java.sh # 5. 验证 java -version # 应输出 openjdk version 17.0.1 2021-10-19 javac -version # 必须有输出证明是 JDK 而非 JRE提示CentOS 7 默认 OpenSSL 版本过低1.0.2k而 JDK 17 要求 OpenSSL 1.1.1。若java -version报java.lang.UnsatisfiedLinkError: /lib64/libcrypto.so.10需升级 OpenSSLsudo yum install epel-release sudo yum install openssl11-devel sudo ln -sf /usr/lib64/libcrypto.so.1.1 /usr/lib64/libcrypto.so.105.2 MySQL 8.0 安装与安全加固非 root 用户运行毕设服务器常共用绝不能用root账号跑 MySQL。创建专用账号basketball-- 1. 创建用户注意 host 限定为 localhost禁止远程连接 CREATE USER basketballlocalhost IDENTIFIED BY StrongPass2024!; -- 2. 创建数据库并授权 CREATE DATABASE basketball DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON basketball.* TO basketballlocalhost; -- 3. 刷新权限 FLUSH PRIVILEGES; -- 4. 可选限制该用户最大连接数防爆破 ALTER USER basketballlocalhost WITH MAX_USER_CONNECTIONS 20;my.cnf关键安全配置[mysqld] # 禁用符号链接防提权 symbolic-links0 # 仅监听本地关闭 3306 端口对外暴露 bind-address 127.0.0.1 # 错误日志路径避免写入 /var/log/mysql权限混乱 log-error /var/log/mysql/error.log # 慢查询阈值定位性能瓶颈 slow_query_log 1 long_query_time 0.5 slow_query_log_file /var/log/mysql/slow.log5.3 Nginx 反向代理配置让小程序访问https://api.yourschool.edu.cn而非http://localhost:8080微信小程序要求所有请求域名必须备案且为 HTTPS。用 Nginx 做反向代理既能隐藏后端端口又能统一 SSL 证书管理# /etc/nginx/conf.d/basketball.conf upstream basketball_backend { server 127.0.0.1:8080; # Spring Boot 默认端口 } server { listen 443 ssl http2; server_name api.yourschool.edu.cn; # SSL 证书从腾讯云/阿里云免费申请 ssl_certificate /etc/nginx/ssl/yourschool.edu.cn.pem; ssl_certificate_key /etc/nginx/ssl/yourschool.edu.cn.key; # 强制 HTTPS add_header Strict-Transport-Security max-age31536000; includeSubDomains always; location /api/ { proxy_pass http://basketball_backend/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 透传 Authorization 头JWT proxy_pass_request_headers on; } # 静态资源如上传的海报图、视频封面 location /static/ { alias /data/basketball/static/; expires 1h; } } # HTTP 重定向到 HTTPS server { listen 80; server_name api.yourschool.edu.cn; return 301 https://$server_name$request_uri; }注意proxy_pass http://basketball_backend/;结尾的/至关重要——它会剥离location /api/前缀使https://api.yourschool.edu.cn/api/match/list实际转发为http://127.0.0.1:8080/match/list。漏掉/会导致路径错乱。5.4 Docker 容器化部署一份docker-compose.yml跑通全栈比起手动启服务Docker 能彻底解决“在我机器上好好的”问题。以下docker-compose.yml同时启动 MySQL、Nginx、Java 后端version: 3.8 services: mysql: image: mysql:8.0.33 container_name: basketball-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: basketball MYSQL_USER: basketball MYSQL_PASSWORD: StrongPass2024! volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf ports: - 3306:3306 command: --default-authentication-pluginmysql_native_password nginx: image: nginx:1.25.3-alpine container_name: basketball-nginx restart: unless-stopped volumes: - ./nginx/conf:/etc/nginx/conf.d - ./nginx/ssl:/etc/nginx/ssl - ./static:/data/basketball/static ports: - 80:80 - 443:443 depends_on: - backend backend: image: openjdk:17-jre-slim container_name: basketball-backend restart: unless-stopped volumes: - ./target/basketball-0.0.1-SNAPSHOT.jar:/app.jar - ./logs:/app/logs environment: - SPRING_PROFILES_ACTIVEprod - SERVER_PORT8080 - SPRING_DATASOURCE_URLjdbc:mysql://mysql:3306/basketball?useSSLfalseserverTimezoneAsia/Shanghai - SPRING_DATASOURCE_USERNAMEbasketball - SPRING_DATASOURCE_PASSWORDStrongPass2024! - WECHAT_APPIDwx1234567890abcdef - WECHAT_SECRETxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx command: java -Dfile.encodingUTF-8 -Xms512m -Xmx1024m -jar /app.jar depends_on: - mysql部署命令# 1. 构建 jar本地 Maven 打包 mvn clean package -Dmaven.test.skiptrue # 2. 上传 jar 和配置文件到服务器 scp target/basketball-0.0.1-SNAPSHOT.jar userserver:/path/to/project/ scp -r nginx/ mysql/ userserver:/path/to/project/ # 3. 启动 cd /path/to/project docker-compose up -d # 4. 查看日志 docker-compose logs -f backend提示backend服务中SPRING_DATASOURCE_URL的 host 写mysqlDocker 内部服务名而非127.0.0.1——后者指向容器自身无法访问 MySQL 容器。6. 毕设答辩前的最后三件事本文还有配套的精品资源点击获取