简介:这是面向高校计算机专业毕业设计、课程设计场景的健身房预约管理系统完整源码包。项目基于Java、SpringBoot、Vue与MySQL构建前后端分离架构,包含微信小程序用户端与管理后台,覆盖用户注册登录、课程预约、教练信息浏览、课程与教练管理、系统设置等核心模块,下载后按环境配置即可直接运行,项目完整无缺,适合需要完整可演示项目的毕业生或初学者参考。资源包共1641个文件,以Java、Vue、JS等前后端源码和微信小程序WXML/WXSS页面文件为主,同时包含数据库SQL脚本、Maven构建配置、PNG/JPG图片素材及项目开发说明,整体体积16.69MB,目录结构清晰,便于直接导入开发环境。已有121人学习下载;项目经严格调试,可完整呈现健身房预约业务流程,对理解小程序与SpringBoot前后端交互、MySQL数据表设计具有实际参考价值,尤其适合毕业设计快速落地与二次开发。
1. 健身房预约管理系统:小程序、SpringBoot、Vue、MySQL 四件套怎么串起来
不少人和我说,从网上下了一套健身房预约管理系统的源码,但卡在“环境跑不起来”这一步,连数据库都连不上,更别提看到预约流程。这套基于微信小程序 + Java SpringBoot + Vue + MySQL 的健身房预约管理系统,是典型的三端分离结构:用户端在小程序里完成注册、选时段、预约、签到,管理员通过 Vue 后台管理课程与教练,SpringBoot 统一暴露接口,MySQL 存业务数据。它适合正在做毕设、课设,或者想快速抄一套完整前后端分离项目来练手的人。下面我按自己实际跑通的顺序,从工程结构、后端启动、小程序联调、后台打包到避坑细节一次讲清楚。
2. 先拆技术栈与工程结构:三端边界、目录职责与四张核心表
2.1 三端职责与请求链路
在动手部署之前,先把项目切成三块看:微信小程序端、SpringBoot 后端、Vue 管理后台。小程序端负责 C 端用户的登录、浏览课程、预约、取消、签到,界面偏移动交互;Vue 管理后台负责管理员维护教练、课程、时段,以及查看预约记录;SpringBoot 后端是唯一的数据出口,所有写操作都要经过它的事务控制,避免预约库存超卖或重复预约。
常见工程结构我会这样组织:backend放 SpringBoot 的 Maven 工程,miniapp放微信小程序原生工程,admin-web放 Vue 管理后台。源码包如果按这个思路拆,你拿到手后先不要急着启动,先看三个目录的README或配置文件,确认端口约定。通常后端跑在localhost:8080,Vue 开发服务器跑在8081,小程序通过wx.request把请求指向后端的局域网 IP 或域名。
请求链路可以理解为:小程序或 Vue 页面发起 HTTP 请求 → SpringBoot Controller 接收 → Service 层做业务校验与事务处理 → Mapper 操作 MySQL → 返回统一 JSON。前端不直接连数据库,这也是安全和后续扩展的基础。小程序端因为微信平台限制,正式上线必须配 HTTPS 域名,但本地开发可以勾选“不校验合法域名”。
启动顺序上,我一般会先启动 MySQL,再启动 SpringBoot,最后才开 Vue 和小程序开发者工具。如果后端还没起来就先调小程序,页面只会白屏加一串request:fail,容易让人误以为代码有问题。反过来,先把后端接口用 curl 或浏览器测通,再让前端对接,排错范围会小很多。
2.2 核心数据表:member、course、timeslot、booking
健身房预约的核心业务围绕“谁能约、约什么、什么时间、约没约上”展开。常见设计是四张核心表:用户表member存手机号、昵称、会员状态;课程表course存课程名称、教练ID、人数上限;时段表timeslot存某天某时段的开始结束时间;预约表booking存用户ID、时段ID、预约状态、创建时间。如果源码里还有教练表coach和管理员表admin,也不要觉得意外,他们只是把课程归属和后台登录拆得更细。
这样的表结构设计有几个好处:第一,用户和课程不会直接耦合,预约关系的中间表可以独立扩展,比如以后要加私教课,只需要在course里增加类型字段,不用改动预约主链路。第二,即便业务规则调整,比如允许同一个用户约多个时段,也不需要重构表,只改约束就行。
SQL 建表时我建议把约束提前想清楚,不要光靠 Java 代码判断。比如booking表对(user_id, timeslot_id)加唯一索引,这是防止重复预约的最后一层防线;timeslot的剩余名额可以通过course的人数和已预约数计算,但更稳妥的方式是给timeslot加一个remain字段,在事务里先SELECT ... FOR UPDATE再扣减。下面给出一段简化版建表 SQL,注意字段类型和索引:
CREATE TABLE `member` ( `id` bigint NOT NULL AUTO_INCREMENT, `phone` varchar(20) NOT NULL, `nickname` varchar(50) DEFAULT '', `status` tinyint DEFAULT '1' COMMENT '1正常 0冻结', PRIMARY KEY (`id`), UNIQUE KEY `uk_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `course` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `coach_id` bigint DEFAULT NULL, `max_people` int DEFAULT '10', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `timeslot` ( `id` bigint NOT NULL AUTO_INCREMENT, `course_id` bigint NOT NULL, `start_time` datetime NOT NULL, `end_time` datetime NOT NULL, `remain` int DEFAULT '10', PRIMARY KEY (`id`), KEY `idx_course_id` (`course_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `booking` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL, `timeslot_id` bigint NOT NULL, `status` tinyint DEFAULT '1' COMMENT '1已预约 2已签到 3已取消', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_timeslot` (`user_id`, `timeslot_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这段 SQL 里uk_phone和uk_user_timeslot就是数据库层的“后悔药”:哪怕业务代码漏写了重复判断,数据库也会把重复预约挡下来。status字段用tinyint存状态值,比字符串省空间,查询也快。create_time用DEFAULT CURRENT_TIMESTAMP,插入时不用手动填,减少一处出错可能。注意这个语法依赖 MySQL 5.6 以上版本,如果用的是老版本,建表会直接报语法错误。
2.3 接口风格与统一返回体
前后端分离的项目最怕接口返回格式各写各的。这个资源里 SpringBoot 后端一般会抽一个Result<T>统一返回体,里面包含code、message、data三个字段。前端判断code === 200就算成功,否则弹message。我建议你拿到源码后先搜Result类,确认成功和失败的状态码,再开始联调,避免前端把 200 当成功、后端却返回 0 的错位。
统一返回体的代码通常长这样:
public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "success"; r.data = data; return r; } public static <T> Result<T> error(Integer code, String message) { Result<T> r = new Result<>(); r.code = code; r.message = message; return r; } }这里的code不要只放 200 和 500,建议业务错误从 4001 开始编号,比如 4001 表示参数错误、4002 表示预约冲突、4003 表示课程不存在。前端拿到非 200 的 code 时,直接读取message展示给用户,这样后端改提示文案时前端不用动。接口路径上,习惯用/api/user/**、/api/admin/**区分端,后续加 Shiro 或 Spring Security 也好按路径配权限。
另外要注意日期格式。后端返回给前端的LocalDateTime默认可能是一长串数字或ISO格式,小程序端不能直接用。配置里加上统一的日期格式化,或者用@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss"),能省很多解析代码。管理后台的分页接口,建议统一用page和pageSize入参,返回体里带total、list,小程序和 Vue 都能复用同一套分页组件。
3. 把后端跑起来:SpringBoot 项目配置、建库与接口自测
3.1 环境清单与版本选型
后端要跑起来,最稳的组合是 JDK 8 + Maven 3.6.x + MySQL 5.7。这个资源如果用的是 SpringBoot 2.x,JDK 8 完全够用;如果源码里是 SpringBoot 3.x,那必须配 JDK 17,否则启动直接报UnsupportedClassVersionError。我的习惯是先看pom.xml里的spring-boot-starter-parent版本号,再决定装哪个 JDK,别上来就装最新的 JDK 21。
MySQL 版本也有讲究。本地开发建议用 MySQL 5.7 或 8.0,注意 8.0 的驱动和连接串写法略有不同。如果你下载的源码里application.yml用的是com.mysql.jdbc.Driver,那大概率是给 5.7 用的;8.0 要改成com.mysql.cj.jdbc.Driver,并且连接串里要加serverTimezone=Asia/Shanghai。装 MySQL 时务必记好 root 密码,后面建库建表都要用。如果你在 Windows 上装 MySQL 5.7 时老是在服务启动那一步转圈,常见原因是以前残留的my.ini里指定了不存在的目录,删掉重新初始化即可。
环境清单我用表格给你列出来,照着装就好:
| 组件 | 推荐版本 | 用途 |
|---|---|---|
| JDK | 8 或 17 | 编译运行 SpringBoot |
| Maven | 3.6.3 | 依赖管理 |
| MySQL | 5.7 / 8.0 | 数据存储 |
| Node.js | 14/16/18 | 运行 Vue 管理端 |
| 微信开发者工具 | 最新稳定版 | 运行小程序 |
提示:如果机器上已经装了多个 JDK,启动前用
java -version确认当前默认版本,不然 Maven 可能用到错误的环境。
3.2 application.yml 配置与数据库初始化
拿到源码后,第一步不是启动,而是改数据库配置。打开backend/src/main/resources/application.yml,把数据源地址、用户名、密码改成你本机的值。常见配置如下:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/gym_booking?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这里的serverTimezone=Asia/Shanghai是 MySQL 8 的必需品,不加会报时区错误。jackson.time-zone保证返回给前端的日期是北京时间,否则小程序端显示的时间会差 8 小时。mybatis-plus的逻辑删除配置是可选的,如果源码用了这个,建议保留,删除预约记录时自动变成update而不是物理删除。
数据库初始化脚本一般在sql目录或doc/sql目录下,文件名类似gym_booking.sql。用命令行导入最直接:
mysql -u root -p gym_booking < gym_booking.sql如果gym_booking数据库不存在,需要先创建:CREATE DATABASE gym_booking DEFAULT CHARACTER SET utf8mb4;。导入成功后执行SHOW TABLES;,看到member、course、timeslot、booking等表,说明数据没问题。注意不要直接打开 SQL 文件复制到图形化工具里执行,文件里如果有DELIMITER开头的存储过程,命令行导入更不容易出错。
3.3 启动后端并用 Swagger 做冒烟测试
启动后端我一般用 Maven 命令,不用 IDE 按钮,因为这样能看到完整日志。在backend目录执行:
mvn clean install -DskipTests mvn spring-boot:run如果源码里有 Swagger 或 Knife4j 依赖,启动成功后访问http://localhost:8080/doc.html就能看到接口文档。没有 Swagger 也别急,直接看 Controller 里的@RequestMapping路径,用 curl 测试。比如测试获取课程列表:
curl -X GET "http://localhost:8080/api/course/list" \ -H "Content-Type: application/json"返回体应该是{"code":200,"message":"success","data":[...]}结构。如果返回 500,先看 SpringBoot 控制台堆栈,90% 是数据库连接问题或者表名对不上。这里有个小技巧:启动日志里出现Tomcat started on port(s): 8080才说明端口正常,如果被占用,会在日志里报Port already in use,改用server.port=8081即可。
冒烟测试的目的是先把后端这条线跑通,把接口能通、数据库能查这件事坐实,再去碰小程序和 Vue,否则后面全是双端联调的脏活。如果源码里没有 Swagger,你也可以临时加入springdoc-openapi依赖,但最好先跑通再说,别在没启动成功时引入新变量。
4. 小程序端与 Vue 管理端:从登录授权到预约下单
4.1 微信小程序登录与手机号获取
小程序端第一个绕不开的是登录。微信小程序现在推荐用wx.login拿 code,再把 code 发给后端,由后端调微信接口换 openid。但健身房预约这类业务通常还要绑定手机号,所以一般流程是:先wx.login静默登录,再通过“获取手机号”按钮让用户授权手机号,后端把手机号和 openid 绑定到member表。
用户在页面上点击“微信一键登录”时,会触发一个button,open-type设为getPhoneNumber,然后在回调里拿加密数据。简化版的小程序端请求封装是这样:
function request(path, method, data) { return new Promise((resolve, reject) => { wx.request({ url: 'http://localhost:8080/api' + path, method: method || 'GET', data: data || {}, header: { 'Content-Type': 'application/json' }, success(res) { if (res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message, icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); }这里url写localhost只能用于开发者工具模拟。真机调试时,必须把localhost换成电脑的局域网 IP,比如http://192.168.1.100:8080,同时微信开发者工具必须勾选“不校验合法域名”。如果你是第一次接手这个项目,先把小程序里的接口 baseURL 抽成一个常量,统一改一处,别在十几个页面里硬编码。
后端处理code换 openid 时,需要调用微信接口https://api.weixin.qq.com/sns/jscode2session,把appid和secret换成一个openid和session_key。这个secret不要写在前端或application.yml里明文暴露,最好放在环境变量或配置中心。拿到 openid 后先查member表,没有则插入新用户;有则直接返回用户信息。手机号获取那块,最新版微信已经改成每次都要用户主动点击授权,code是一次性的,后端拿着临时 code 去换手机号,缓存时间很短,要尽快调接口。
4.2 预约与取消的接口对接细节
预约接口的核心参数是courseId、timeslotId和userId。下单时最好把userId从全局变量里取,别让用户自己传,防止越权。调用预约接口的伪代码如下:
async function bookCourse() { try { const data = await request('/booking/add', 'POST', { timeslotId: this.data.selectedTimeslotId, courseId: this.data.courseId }); wx.showToast({ title: '预约成功', icon: 'success' }); } catch (e) { // 错误提示已经在 request 里处理 } }后端在高峰期可能会出现同一时段多人同时抢预约的情况,单纯的在 Java 里先查再更新会有超卖风险。常见做法是用数据库行锁或乐观锁:UPDATE timeslot SET remain = remain - 1 WHERE id = ? AND remain > 0,更新影响行数为 0 就说明名额已经没了。你拿到源码后可以看 Service 层是否用了事务注解@Transactional,并且确认remain > 0条件写在 SQL 里而不是 Java 里。
取消预约则要判断时间边界,通常开场前 30 分钟内不允许取消,防止破坏课程安排。这个规则如果你在源码里没找到,可以写在application.yml的自定义配置里,比如booking.cancel-minute-limit=30,方便后续调整。取消操作不要删记录,把status改成 3,并把timeslot.remain加回去,否则数据统计会失真。
联调时如果发现前端参数名和后端@RequestBody里的字段对不上,优先看后端代码里的实体字段,比如timeslotId在 Java 里可能是timeslotId,在 MySQL 里是timeslot_id,MyBatis 会自动做驼峰映射,但 JSON 键名必须和 Java 字段一致。对不上时就报 400 参数错误,前端控制台能看到具体原因。
4.3 Vue 管理后台的 API 封装与打包回 SpringBoot
Vue 管理后台面向管理员,功能包括课程管理、教练管理、预约记录查看。它的接口调用我用 axios 封装,别忘了携带 token。如果后端没有做登录鉴权,管理端可以先用简单的账号密码登录,后端返回 token 后前端存到localStorage,axios 拦截器里统一加上Authorization头。以下是 Vue 端的 axios 拦截器片段:
import axios from 'axios'; const service = axios.create({ baseURL: process.env.VUE_APP_BASE_URL || 'http://localhost:8080/api', timeout: 10000 }); service.interceptors.request.use(config => { const token = localStorage.getItem('admin_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); service.interceptors.response.use(response => { if (response.data.code !== 200) { // 统一弹错误提示 } return response.data; });开发时用npm run serve启动 Vue,Vite 或 Webpack 默认端口在8081附近,浏览器里访问的是管理界面。部署上线时,最省事的方式是把 Vue 打好包扔进 SpringBoot 的static目录,让后端一起出。执行npm run build后会生成dist目录,把dist里的内容拷贝到backend/src/main/resources/static,重启后端后直接访问http://localhost:8080/就是管理后台。
这里有个坑:Vue 打包后访问接口的路径是相对路径还是绝对路径,取决于vue.config.js里的publicPath。如果你直接把 dist 放进 SpringBoot,publicPath最好设为'./',否则刷新时资源路径会 404。另外,如果 Vue 用了history路由模式,SpringBoot 需要配置 history fallback,否则刷新非首页路径会出现 404,常见做法是加一个 Controller 把未匹配到接口的前端路由转发到index.html。
环境变量也要注意。.env.development里的VUE_APP_BASE_URL指向开发后端,.env.production里如果留空,则打包后请求会发到当前域名下的/api,正好匹配我们塞进 SpringBoot 的路径。切换环境时别只改代码,要检查.env文件。
5. 避坑与常见问题:五个能把人卡半天的细节
5.1 环境类翻车:JDK 版本、Maven 仓库与 MySQL 时区
现象:后端启动时控制台报
UnsupportedClassVersionError,错误信息里能看到class file version。原因:本机 JDK 版本低于源码编译版本,比如用 JDK 8 去跑 SpringBoot 3.x 的 class。解决:打开pom.xml看<java.version>,再把 IDEA 或 Maven 的 JDK 切到对应版本。如果嫌切来切去麻烦,直接装 JDK 8 和 JDK 17 两套,用环境变量按项目切。启动 SpringBoot 3.x 时,JDK 8 一定不行,不要浪费时间排查依赖。现象:Maven 依赖下载极慢,或
mvn clean install卡在Downloading半天没反应。原因:默认 Maven 中央仓库在国内访问不稳定。解决:修改 Maven 的settings.xml,把中央仓库镜像换成阿里云镜像。镜像配置写在mirrors标签里,注意<mirrorOf>要写central,不然不生效。改完后再执行一次mvn clean install,你会看到下载速度像换了个网络。现象:MySQL 启动时报
The server time zone value '?D1ú±?' is unrecognized。原因:MySQL 8 的 JDBC 连接串没加serverTimezone参数。解决:在application.yml的url末尾追加&serverTimezone=Asia/Shanghai,同时把driver-class-name改成com.mysql.cj.jdbc.Driver。还有个连带问题:MySQL 8 默认字符集是utf8mb4,如果建表脚本里写了ENGINE=InnoDB DEFAULT CHARSET=utf8,中文昵称和课程名称存储可能没问题,但特殊表情符号会丢,建议统一用utf8mb4。
5.2 业务逻辑与联调:重复预约、库存扣减、顶部导航栏
现象:同一个用户对同一时段快速点了两次预约,第一次成功,第二次也返回成功,数据库中产生两条记录。原因:业务层只做了“先查再插”,并发下两个线程都查到没有记录,于是都执行插入。解决:第一层在
booking表加唯一索引uk_user_timeslot,第二层在插入时捕获DuplicateKeyException并转为“请勿重复预约”提示。如果修改表结构不方便,至少要在 Service 方法上加@Transactional并把查询和插入放到同一个事务里。并发量不大的场景,这招也够用。现象:真机调试小程序时,请求一直
fail,但开发者工具里一切正常。原因:真机上localhost指向的是手机自己,不是电脑;而且没在小程序后台配置 request 合法域名。解决:开发阶段把接口地址改为电脑局域网 IP,并在开发者工具右上角打开“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。如果后端也需要手机能访问,注意关掉电脑防火墙,或至少放行 8080 端口。遇到请求返回的数据和预期不一致,用 Charles 抓包看请求体和返回体,比在代码里打日志快得多。现象:自定义导航栏的小程序页面顶部内容被刘海屏遮住,点击预约按钮位置偏上。原因:没有适配状态栏高度,直接用了
position: fixed定到顶部 0。解决:用wx.getWindowInfo()或wx.getMenuButtonBoundingClientRect()获取状态栏高度,把自定义导航栏的padding-top设成这个高度。这个坑在 iPhone 上尤其明显,安卓通常没问题,但也要一起适配。如果你不想管这个细节,用小程序的默认导航栏最省事,但界面观感会差一些。
6. 进阶验证与可用性检查:用一次性数据跑通完整预约闭环
6.1 从注册到签到的全链路自测
我习惯在新环境上跑通一个完整预约闭环再交差。你不需要把全部用例都执行一遍,但至少要走通“注册 → 登录 → 选课程 → 选时段 → 预约 → 取消 → 再预约 → 签到”这个主路径。准备一组一次性数据:一个测试手机号、一个课程、两个可用时段。用 curl 或 Postman 依次调用接口,把每一步的返回码记下来。如果预约成功后再次预约同一时段返回 4002/“已预约”,说明唯一索引生效;如果名额只剩 1 时两个人同时下单只有一个成功,说明库存扣减逻辑可靠。
签到接口要额外确认:是否校验了当前时间在课程开始前后各 15 分钟内?如果不校验,管理员后台手动签到就形同虚设。校验通过后用UPDATE booking SET status = 2 WHERE id = ?更新状态,不要用删除操作。
6.2 安全与越权自查
这类毕业设计项目最容易被老师问的问题就是越权。你要检查后端接口里,是否每个操作都从 token 或 session 里拿当前用户,而不是接受前端传入的userId。比如取消预约接口,路径是/api/booking/cancel/{id},如果 Service 只删bookingId不看用户,那么 A 用户就能取消 B 用户的预约。正确的做法是DELETE FROM booking WHERE id = ? AND user_id = ?,把用户 ID 作为条件之一。
另一个常见隐患是管理端接口没有鉴权。如果/api/admin/course/delete接口裸奔,任何人拿到地址都能删课程。简单方案是加一个拦截器,校验Authorization头里的 token 是否存在且未过期。如果源码里没做,至少要给自己留个 TODO,别等项目答辩时让老师当场演示越权漏洞。
从那以后,我每次拿到新项目源码,都会强制走一遍“看 pom 版本 → 改数据库配置 → 启动后端 → 小程序真机联调 → 管理端打包”的流程,顺手把容易翻车的地方记在项目的README里。这份资源里的代码只要按上面的顺序操作,大概率半天内就能跑起来,过程中遇到的问题也都不是玄学,绝大多数是环境或边界条件。希望帮到你。
本文还有配套的精品资源,点击获取