☰
基于微信小程序的健身房预约平台:SpringBoot后端设计与实现
2026/10/11 4:25:33 网站建设 项目流程

最近不少同学在选题时都会考虑微信小程序相关的系统,健身房预约平台算是出场率很高的一类。这个题目看起来不复杂,但真正动手以后,你会发现它同时踩到了小程序登录、后端接口设计、数据库并发控制、前端状态同步这几条线,任何一个环节没理顺,都会在联调阶段反复折腾。这篇文章就着“基于微信小程序的健身房预约平台 + SpringBoot 后端”这个课设/毕设题目,把从需求拆解到落地的完整链路讲一遍。适合正在选型、写文档、或者已经在开发中卡住的同学,尤其是有答辩压力、想把项目讲出亮点的人。

我不会把官方文档抄一遍,而是把真正编写代码时容易忽略的点,以及我自己调试时踩过的坑,都摊开来说。项目本身的代码量不算大,但每一个模块都有值得展开的技术细节。下面直接进正文。

1. 项目整体设计与需求拆解

1.1 核心角色与功能:用户、教练、管理员

健身房预约平台,往小了说是一个课表 + 预约记录,往大了说则涉及会员管理、教练管理、课程排期、统计报表。做课设和毕设时,最忌讳一上来就堆功能,最后哪个都没做透。我建议把角色收敛成三个:普通用户(微信小程序端)、教练、管理员(管理后台端)。

用户端需要完成的是微信登录、浏览课程、按日期筛选、预约课程、取消预约、查看自己的预约记录。教练端的核心是维护可预约的课程信息,比如课程名称、上课时间、人数上限。管理员端要能管理用户、教练、课程、预约记录,并且能看到基本的预约统计。这三个角色,其实对应了三套页面和一组 RESTful 接口。类目不要贪多,把这几个模块做完整,文档和答辩就够撑起来了。

有些同学会额外加签到、评价、私教购买,这些可以作为扩展点写进“未来展望”,不建议纳入第一版开发。因为每加一个功能,数据库表、接口、小程序页面、测试用例都会成倍增加。等主体跑通了,再挑一两个扩展来做也不迟。

1.2 核心业务流:一次预约从开始到完成

这条链路是项目的灵魂,也应该是文档里最着重画出来的部分。在小程序端,用户打开页面后调用微信登录,后端根据 wx.login 返回的 code 换取 openid,完成注册或登录,然后签发一个 token 返回。前端拿着 token 去请求课程列表,选择日期后看到当天可预约的课程,点击预约按钮,后端校验课程状态、人数余量、是否已经预约过,全部通过后把预约记录写入数据库,并把该课程的已预约人数加一。

用户之后可以在“我的预约”里看到这一条记录,状态是“已预约”。如果临时有事,可以选择取消,后端将已预约人数减一,把预约状态改成“已取消”。管理员在后台可以看到所有课程和预约记录,也可以手动关闭某节课程的预约。这套流程串起来以后,系统的骨架就立住了。要注意的是,预约和取消这两个动作不是简单的 insert / delete,它们都涉及到人数增减和多用户同时操作的并发问题,后面我会专门展开。

1.3 功能清单与模块优先级

这里给一个可以直接抄作业的功能清单,按优先级排序。

模块用户端管理端优先级
登录认证微信登录、token 鉴权管理员账号密码登录P0
课程浏览按日期查看课程、查看余量课程增删改查、上下架P0
预约管理预约、取消、我的预约列表预约记录查询、状态筛选P0
用户管理查看个人资料会员列表、状态管理P1
统计看板无今日预约量、热门课程 TopNP1
扩展功能签到、评价、私教约课数据导出P2

优先级 P0 是第一版必须完成的,P1 看时间和精力,P2 完全可以放在文档里作为“系统改进方向”,不需要真的实现到代码里。这样功能边界清晰,开发节奏也容易控制。写完这一部分,需求文档和数据库设计就都有了依据。

2. 技术选型与关键方案说明

2.1 后端为什么选 SpringBoot + MyBatis-Plus

很多刚做毕设的同学会纠结:用 SSM 还是 SpringBoot?用 JPA 还是 MyBatis?我的建议是 SpringBoot 2.x + MyBatis-Plus,没有悬念。SpringBoot 内置了 Tomcat,省去配置一堆 XML 的麻烦,启动一个 main 方法就能跑起来。SpringBoot 3.x 虽然已经发布很久,但很多教程和第三方适配还停留在 2.x,课设阶段没必要给自己挖坑,稳定压倒一切。我用的是 SpringBoot 2.7,配 JDK 1.8,MySQL 5.7 或 8.0 都可以。

MyBatis-Plus 的价值在于单表 CRUD 基本不用写 SQL。选课程、插入预约记录、更新用户昵称这些操作,只要继承一个 BaseMapper,代码量直线下降。它还内置了分页插件,后台管理列表的分页查询可以直接用。对于课设来说,MyBatis-Plus 能让你把精力放到业务逻辑而不是重复的 SQL 上,而且答辩时说“使用了 MyBatis-Plus 简化数据访问层”也算一个技术亮点。唯一要注意的是,多表关联查询还是需要自己写 SQL,不要所有查询都靠 QueryWrapper 硬拼。

2.2 小程序端:原生开发还是用框架

小程序端我推荐直接用微信官方原生开发,不要引入 uni-app 或者 Taro。原因很简单:原生小程序本身就是基于页面、组件、路径路由的一套开发模式,代码结构清晰,官方调试工具成熟,遇到问题搜到的解决方案也最多。虽然 uni-app 能一套代码多端发布,但课设只需要一个微信小程序,完全没有必要增加编译链的复杂度。

至于 UI 组件库,Vant Weapp 可以用,但也不是必需。如果你对 CSS 不太熟,Vant 确实能快速做出漂亮的按钮和表单;但如果你希望项目足够“独立”,手写少量样式反而更可控,出问题也容易排查。我在这个项目里选择的是原生 + 少量自定义公共样式,核心页面控制在 8 个以内,开发体验其实很舒服。

2.3 为什么不用微信云开发,要自己搭后端

这是答辩时很常见的一个问题。云开发确实能省掉服务器和域名配置,数据库直接在小程序端读写,但“基于 SpringBoot 的健身房预约平台”这个题目已经限定了后端技术栈。自己搭后端的价值在于:一是能完整体现后端接口设计、权限控制、事务处理这些能力;二是方便将来扩展一个独立的管理后台;三是课设评分通常会看后端代码的深度,云开发很难写出多层业务逻辑,也经不起老师追问。

当然,自建后端的代价是必须要会处理跨域、部署、外部接口调用这些问题。微信小程序的 request 请求只能走 HTTPS 域名,本地调试时需要在开发者工具里勾选“不校验合法域名”。这些细节我会在部署部分详细说,提前有心理预期就不会慌。

2.4 数据库设计:核心表结构与字段含义

数据库是整个项目的地基。我设计了六张核心表:用户表、管理员表、教练表、课程表、预约表、公告表(可选)。其中 trainer 和 course 可以合并,但拆开会更符合业务直觉。重点看两张表:course 和 appointment。

课程表我采用“日期 + 时段”的粒度来存,也就是每一节具体的课,比如“2026-05-20 上午 10:00 的动感单车课”就是一条记录。字段包括 course_id、coach_id、course_name、course_date、start_time、end_time、max_count、booked_count、status。这里的 booked_count 是冗余字段,用来快速展示剩余名额,也方便在预约时做原子更新。status 表示“可预约/已满/已取消”。

预约表是业务核心,字段包括 appointment_id、course_id、user_id、appointment_time、status。status 用整型表示,1 为已预约,2 为已取消,3 为已完成。为了避免一个人重复预约同一节课,我在 course_id + user_id 上加了一个唯一索引;这个索引在并发控制里意义很大,后面会再次提到。用户表里需要存 openid 和 unionid,openid 用 varchar(64),nickname、avatar 可以允许为空。整体设计做到了第三范式够用,又故意保留 booked_count 这个冗余字段来提升查询性能,在文档里解释清楚就是得分点。

3. 后端核心接口实现细节

3.1 微信登录:从 code 到 openid 再到自定义 Token

小程序端通过 wx.login 拿到的 code 是一次性的临时凭证,后端需要拿着这个 code 去微信平台提供的接口换 openid 和 session_key。这里有几个容易出错的地方:第一,code 只能用一次,用完再换就会报 invalid code;第二,小程序的 appid 和 secret 属于后端配置,绝不能写在小程序代码里;第三,session_key 不需要自己存取,对课设来说我们只关心 openid。

后端实现大致是这样的。先定义一个 LoginController,接收前端传过来的 code,然后用 HttpClient 或 RestTemplate 请求微信接口,解析返回的 openid。拿到 openid 后去 user 表查记录,查不到就自动注册一个新用户。最后用 JWT 生成一个 token,把 userId 和 openid 放进去,返回给前端。注意这里要引入一个拦截器,除了登录接口和公开接口外,其他接口都必须校验请求头里的 Authorization。

下面是我习惯的代码骨架:

@RestController @RequestMapping("/api/user") public class UserController { @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { // 1. 用 code 请求微信接口,获得 openid String openid = wechatService.getOpenid(dto.getCode()); // 2. 查库或注册 User user = userService.findOrCreate(openid); // 3. 生成 token String token = jwtUtil.generateToken(user.getId()); return Result.success(token); } }

实现 getOpenid 时,通常会遇到网络请求返回 JSON 解析的问题。用 Spring 的 RestTemplate 就能搞定,但要注意把 appid 和 secret 放在 application.yml 里,不要写死在代码。答辩时老师可能会问“怎么保证用户身份安全”,你就答:JWT 无状态鉴权 + 拦截器统一校验,同时 openid 不直接返回给前端,只作为服务端标识,这样就安全了。

3.2 课程列表与日期筛选

课程列表接口是访问量最高的接口,设计成 GET /api/course/list?date=2026-05-20。后端根据日期查询当天 status 为可预约的课程,按开始时间排序,并且把剩余名额(max_count - booked_count)返回。可以用 MyBatis-Plus 的 LambdaQueryWrapper 来做条件查询,简单直接。

这里有一个小经验:course_date 最好用字符串类型存储,格式 yyyy-MM-dd,不要用 datetime。因为前端 date 组件传过来的就是字符串,后端处理起来不用做时区转换,模糊查询也方便。不要觉得存字符串不专业,很多真实项目在日期这类固定格式字段上也会用字符串,查询更快,代码更少。课程返回对象里,除了课程名称和教练姓名,还要带上剩余可预约人数,这样小程序首页可以直接展示,不用二次计算。

还需要处理一个状态逻辑:当前时间已经超过课程开始时间,就不能再预约了。这种判断放在数据库 SQL 里比较麻烦,我建议在接口查询时就过滤掉 course_date 小于当天日期的课程。对于当天但开始时间早于当前时间的,可以交给前端判断,也可以后端额外返回一个 is_expired 字段,前端根据这个字段禁用预约按钮。

3.3 预约与取消预约:如何防止“超卖”

这是全项目技术含量最高的地方,也是答辩提问的高频区。先说明需求:多个用户同时预约同一节只剩最后一个名额的课程,系统只能让一个人成功。如果用最简单的“先查余量,够再插入”的写法,极大概率在并发测试下出现两个人同时看到余量为 1,然后都插入成功,最终超卖。

解决思路是使用数据库的原子更新来保护余量。插入预约记录前,先执行这样一条 SQL:

int rows = courseService.update( new LambdaUpdateWrapper<Course>() .setSql("booked_count = booked_count + 1") .eq(Course::getId, courseId) .eq(Course::getStatus, 1) .lt(Course::getBookedCount, Course::getMaxCount) );

这行代码的意思是只有满足 booked_count 小于 max_count 时,才执行 +1 操作,并返回受影响行数。如果 rows 等于 0,说明已经满了或者课程状态不对,直接返回“预约失败”。如果 rows 等于 1,说明余量被成功扣减,然后再去插入预约记录。这两步放在同一个事务里,就能保证不会超卖。

取消预约是反过来,执行 update 时用 setSql("booked_count = booked_count - 1"),同时加一个条件 booked_count > 0,防止出现负数。然后修改预约记录的状态为已取消。还要注意重复取消的问题:更新预约记录时带上 status = 1 的条件,如果更新影响行数为 0,就说明这条记录已经被取消过,直接提示“该预约已取消”。

这段逻辑你完全可以直接写进 Service 类里,然后加上 @Transactional 注解。事务一旦抛出异常,数据库会自动回滚到之前的状态,不会出现“余量扣了但预约记录没生成”这种尴尬数据。

3.4 管理后台接口:统计与课程管理

管理后台的接口可以单独放在 /admin 路径下。管理员登录用账号密码,生成一个独立的 admin token。课程管理接口包括添加课程、编辑课程、删除或下架课程、分页查询课程列表。删除课程时要注意:如果已经有用户预约了,不应该物理删除,而是把 status 改成 2(已取消),这样预约过的用户端也能看到状态变化,数据更真实。

统计接口方面,最有用的就是今日预约数和热门课程排行。今日预约数可以用一条 group by 查出 appointment 表里 appointment_time 在当天的记录数。热门课程排行则可以用 group by course_id 加上 count 排序。也可以顺便查一下每个教练的预约量,方便后台做绩效。图表展示我推荐在小程序后台管理端用 ECharts,或者干脆用简单的柱状图原生组件渲染。后端只需要提供聚合好的数据列表,前端负责画图,责任单一,文档里也容易写。

4. 小程序端页面与交互实现

4.1 登录流程:静默登录与头像昵称的坑

微信小程序的登录坑特别多,尤其是从基础库 2.27.1 开始,官方收紧了 getUserProfile 获取头像昵称的能力。以往那种点击登录弹窗然后获取信息的方式已经废弃了。现在更推荐的做法是:页面加载后直接调 wx.login 获取 code,把 code 传给后端完成登录,后端返回 token。头像昵称可以在用户进入“个人中心”后,通过微信提供的“头像昵称填写能力”来维护,或者让用户手动上传头像和填写昵称。

实际开发时,很多同学会遇到“后端登录成功但前端拿不到用户信息”的情况,其实就是因为没有区分开“登录态”和“用户资料”。登录态只关心 openid,资料是用户自己补充的。我在这个项目里简单地让用户登录后默认显示“微信用户”,并提供一个编辑资料页面,用户自己设置昵称和头像,然后调用后端接口更新 user 表。这样既规避了接口限制,又不影响整体流程。

4.2 首页课程列表:日期选择与余量展示

首页是整个小程序最复杂的页面,因为要处理多个异步状态:加载课程列表、日期切换、下拉刷新。我的建议是页面结构拆成三块:顶部日期导航、课程列表、底部占位。日期导航用微信原生 picker 选择日期,也可以自己写一个横向滚动的连续日期条,后者更常见也更好看。选择日期后触发 setData 更新 currentDate,然后调用课程列表接口。

请求接口时需要带上 token。我封装了一个 request.js,统一拼接 baseUrl,统一在请求头里加 Authorization,也统一处理 401 跳转。小程序不能用 axios,只能用 wx.request,所以封装一个 Promise 风格的请求模块非常有必要。课程卡片上要展示课程名称、教练、时间、剩余名额,并显示预约按钮。剩余名额建议用 highlight 样式突出显示,如果余量为 0,按钮置灰。这里要注意不要用模板字符串拼 CSS class 的时候出错,状态判断最好是显式写清楚。

4.3 预约操作与防重复提交

当用户点击“预约”按钮时,前端要立刻进入 loading 状态,并且禁用按钮,防止用户连点造成重复请求。这个细节很多同学会忽略,但一旦后端没有做幂等控制,连续点了两次就可能出现两条预约记录。我之前就遇到过这种尴尬,后来前端加了 disabled,后端也做了唯一索引兜底,才彻底解决。

预约成功后的反馈很重要。不要只是弹一个 toast 就完事,最好在接口返回后回调 setData 把当前课程的余量减一,并且把按钮状态改成“已预约”。如果接口返回失败,也要把按钮恢复成可点击状态,并提示失败原因。小程序的 setData 是异步的,更新完数据后需要立刻刷新列表,建议在回调里重新拉取课程详情或整个列表,跟服务端保持最新状态一致。

4.4 我的预约列表与状态管理

“我的预约”页面需要按用户维度查询预约记录,并且展示课程时间、预约时间和状态。状态建议用不同的标签颜色区分:已预约是绿色,已取消是灰色,已完成是蓝色。可以把这个页面做成 tab 切换:全部、待上课、已结束。为了减少后端接口维度,通常前端传一个 status 参数,后端在 SQL 里做筛选。

待上课的预约要允许用户取消。取消时同样要弹确认框,确定后调用取消接口,成功后再刷新页面。这里有一个用户体验上的点:如果课程已经开始或者已经结束,就不能再取消,后端接口同样要做校验,不能只靠前端隐藏按钮。另外,取消接口返回后,之前首页对应课程的余量也会变化,所以从“我的预约”返回首页时,最好在 onShow 里重新刷新课程列表,避免数据不一致。

5. 部署运行与踩坑实录

5.1 本地联调完整步骤

这里按照一套干净环境来写,前提是你的电脑已经装了 JDK、MySQL、微信开发者工具。第一步,创建数据库,名字叫 gym_db,然后执行项目里提供的 gym.sql,它会自动建表并插入几条测试数据。第二步,打开后端项目,修改 application.yml,把数据库账号密码改成你自己的,同时填上小程序的 appid 和 secret。第三步,直接运行启动类,看到 Tomcat started 字样就代表后端起来了。

第四步,打开微信开发者工具,导入小程序前端目录,在 app.js 文件里把 baseUrl 改成 http://localhost:8080。如果你用的是真机预览,localhost 会失效,要把地址改为电脑局域网 IP,比如 http://192.168.x.x:8080。同时要勾选开发者工具右上角“详情”里的“不校验合法域名”,否则请求会被拦截。第五步,编译运行小程序,点登录,看后端控制台是否打印请求日志,然后正常走一遍预约流程。只要这五步走通,整套系统就算本地联调成功了。

5.2 高频报错与排查速查表

我把做这个项目过程中最常遇到的几个问题整理成一个速查表,方便你对照排查。

现象可能原因解决办法
登录时后端报“invalid code”wx.login 的 code 只能用一次,可能是重复发送重新调用 wx.login,不要使用缓存的 code
小程序请求 401请求头没有带 token,或 token 过期在 request.js 里统一添加 Authorization,拦截器里处理过期跳转
课程列表中文乱码数据库连接没有指定 UTF-8在 jdbc url 后面加 characterEncoding=utf8
后端启动报端口被占用8080 端口被其他程序占用改 application.yml 里的 server.port,或释放端口
预约永远提示“失败”课程 status 不是 1,或已满,或已经预约过检查数据库记录,确认唯一索引是否生效
小程序页面白屏baseUrl 配置错误,或后端未启动查看 console 报错,确认网络请求状态
找不到 Mapper 接口方法Mapper 接口和 XML 文件没有对应使用 MyBatis-Plus 时尽量用 baseMapper 自带方法,避免额外 XML

这里重点说一个容易踩的坑:由于预约接口加了唯一索引,当你测试时预约成功一次后,再手工去数据库把预约记录删除,但课程表的 booked_count 没有同步减下去,之后你再预约同一节课会一直提示“人数已满”。这种脏数据问题在开发阶段很常见。我的建议是,不要随便动数据库表数据,测试取消功能时一定要通过页面操作,保证前后端逻辑完整执行。如果真改了数据库,就用 SQL 把 booked_count 手动重置一下。

5.3 课程设计/毕业论文的文档写作建议

项目跑通只是第一步,文档在评分中的占比非常高。万字文档不是流水账,而是要有清晰的逻辑链。建议按照“摘要、需求分析、系统设计、数据库设计、系统实现、系统测试、总结”这个结构写,其中系统设计部分要重点画总体架构图和功能结构图。这些图不需要多么专业,用 Visio、ProcessOn 或者 draw.io 画清楚即可,注意图中每个模块都要和代码对应上。

在“系统实现”部分,不要所有接口平均用力,要挑出两到三个有亮点的模块详细写,比如微信登录流程、并发预约处理、统计报表。特别是并发预约处理,一定要写清楚为什么使用数据库的原子更新,留下测试过程数据,比如模拟两个用户同时预约同一节课的结果,这会成为你答辩时最有力的“技术证据”。测试部分除了功能测试,还可以加一点点压力测试的描述,用浏览器开发者工具模拟并发效果,哪怕只是粗略的记录,也比没有强很多。

文档里还可以加一节“遇到的问题与解决”,把开发过程中真实的坑写进去。不要觉得写问题会显得水平低,相反,有经验的老师最喜欢看这个,它说明你真的动手做了,不是下载别人的代码应付了事。

最后再分享一点个人体会

这个项目最容易被答辩老师追问的点,就是并发预约怎么处理。如果你能把“update 条件防超卖”讲清楚,再顺带说一下唯一索引兜底,基本就能和那些只会增删改查的项目拉开差距。我在实际写代码时还有一个习惯:所有前端请求都走了统一的 request 封装,所有后端接口都返回统一的 Result 结构,包括 code、message、data。虽然前期多花了十几分钟,但联调阶段几乎没为参数格式扯皮过,非常建议你也这样规范起来。这个小项目做完,你会发现 SpringBoot 接口设计、小程序生命周期、数据库事务都有了实感,后续再做其他系统,思路会顺很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询