随着移动互联网的普及和全民健身意识的提升,传统健身房的运营模式面临着数字化转型的需求。本文设计并实现了一款基于微信小程序和Spring Boot后端架构的健身房服务小程序。该系统旨在为会员提供便捷的课程预约、场地预订、资讯浏览及个人信息管理服务,同时为健身房管理者提供高效的内容维护、预约核销、用户管理及数据统计功能。系统采用前后端分离架构,前端使用微信小程序原生开发,后端基于Java Spring Boot框架,数据库选用MySQL 8.0+。测试结果表明,该系统有效提升了健身房的运营效率和会员的服务体验,实现了线上线下服务的闭环管理。
系统需求分析
栏目浏览模块
小店动态:查看门店活动、促销套餐、课程更新等信息。
健身干货:浏览训练技巧、动作教学、增肌减脂等专业知识。
饮食科普:获取健身餐搭配、营养补充建议。
荣誉资质:查看场馆资质、教练团队及荣誉评价。
交互功能:支持详情查看、微信分享、关键词检索。
用户注册模块
首次使用需填写年龄、姓名、车辆情况等基本信息。
绑定微信账号,实现“一键登录”。
服务预约模块
预约类型:私教预约、团课预约(瑜伽、动感单车等)、场地预约(拳击台、拉伸区)。
预约展示:直观显示可预约时段及剩余名额。
到店核销:生成核销二维码,管理员扫码确认到店。
个人中心模块
我的预约:查看所有预约记录(已完成/进行中/已取消),支持取消操作。
资料修改:更新联系方式、健身目标等个人信息。
浏览历史:记录并回顾浏览过的内容。
我的收藏:收藏感兴趣的课程或文章。
总体设计
本系统采用前后端分离的开发模式,具体技术栈如下:
前端:微信小程序(Native开发),使用微信开发者工具。
后端:Java Spring Boot框架,提供RESTful API接口。
数据库:MySQL 8.0+,存储业务数据。
开发工具:
IDE:IntelliJ IDEA (2023.2.1+)
数据库管理:Navicat 16+
小程序开发:微信开发者工具
系统架构设计
表现层:微信小程序负责页面渲染和用户交互,后台管理端嵌入小程序中,通过权限控制访问。
业务逻辑层:Spring Boot处理核心业务逻辑,如预约规则校验、核销状态更新、数据统计等。
数据访问层:通过MyBatis框架操作MySQL数据库,实现数据的持久化存储。
数据库设计
字段名
类型
长度
约束
说明
id
BIGINT
20
PK, AI
主键ID
openid
VARCHAR
64
NOT NULL, UNIQUE
微信OpenID
name
VARCHAR
50
姓名
age
INT
3
年龄
phone
VARCHAR
20
联系电话
vehicle_info
VARCHAR
100
车辆情况
fitness_goal
VARCHAR
255
健身目标
create_time
DATETIME
注册时间
status
TINYINT
1
DEFAULT 1
状态(1正常 0禁用)
字段名
类型
长度
约束
说明
:—
:—
:—
:—
:—
id
BIGINT
20
PK, AI
主键ID
name
VARCHAR
100
NOT NULL
项目名称
type
TINYINT
1
NOT NULL
类型(1私教 2团课 3场地)
description
TEXT
项目描述
max_capacity
INT
最大容纳人数
sort_order
INT
DEFAULT 0
排序权重
is_available
TINYINT
1
DEFAULT 1
是否可用
rule_desc
VARCHAR
500
预约规则说明
字段名
类型
长度
约束
说明
id
BIGINT
20
PK, AI
主键ID
user_id
BIGINT
20
FK
用户ID
project_id
BIGINT
20
FK
项目ID
reserve_date
DATE
NOT NULL
预约日期
time_slot
VARCHAR
50
NOT NULL
预约时段
verify_code
VARCHAR
64
UNIQUE
核销二维码字符串
status
TINYINT
1
DEFAULT 0
状态(0待核销 1已完成 2已取消)
create_time
DATETIME
创建时间
verify_time
DATETIME
核销时间
字段名
类型
长度
约束
说明
id
BIGINT
20
PK, AI
主键ID
category
TINYINT
1
NOT NULL
分类(1动态 2干货 3饮食 4荣誉)
title
VARCHAR
200
NOT NULL
标题
content
LONGTEXT
正文内容(支持HTML)
cover_image
VARCHAR
255
封面图URL
view_count
INT
DEFAULT 0
浏览量
publish_time
DATETIME
发布时间
字段名
类型
长度
约束
说明
id
BIGINT
20
PK, AI
主键ID
username
VARCHAR
50
NOT NULL, UNIQUE
用户名
password
VARCHAR
100
NOT NULL
加密密码
role
TINYINT
1
NOT NULL
角色(1超级管理员 2普通管理员 3核销员)
status
TINYINT
1
DEFAULT 1
状态
last_login
DATETIME
最后登录时间
核心代码
@RestController
@RequestMapping("/api/reservation")
public class ReservationController {
@Autowired
private ReservationService reservationService;
@PostMapping("/create")
public Result create(@RequestBody ReservationDTO dto, HttpServletRequest request) {
// 从Session或Token中获取当前用户ID
Long userId = UserContext.getCurrentUserId(request);
return reservationService.createReservation(userId, dto.getProjectId(), dto.getDate(), dto.getTimeSlot());
}
@PostMapping("/verify")
public Result verify(@RequestParam String code, HttpServletRequest request) {
// 获取当前管理员ID
Long adminId = AdminContext.getCurrentAdminId(request);
return reservationService.verifyReservation(code, adminId);
}
}
@Service
public class ReservationService {
@Autowired
private ReservationMapper reservationMapper;
@Autowired
private ProjectMapper projectMapper;
/**
* 创建预约
*/
@Transactional(rollbackFor = Exception.class)
public Result createReservation(Long userId, Long projectId, String date, String timeSlot) {
// 1. 检查项目是否存在及可用
Project project = projectMapper.selectById(projectId);
if (project == null || project.getIsAvailable() == 0) {
return Result.error("该项目不可预约");
}
// 2. 检查该时段剩余名额
int bookedCount = reservationMapper.countByProjectAndSlot(projectId, date, timeSlot);
if (bookedCount >= project.getMaxCapacity()) {
return Result.error("该时段名额已满");
}
// 3. 生成核销码
String verifyCode = UUID.randomUUID().toString().replace("-", "");
// 4. 插入预约记录
Reservation reservation = new Reservation();
reservation.setUserId(userId);
reservation.setProjectId(projectId);
reservation.setReserveDate(DateUtil.parse(date));
reservation.setTimeSlot(timeSlot);
reservation.setVerifyCode(verifyCode);
reservation.setStatus(0); // 待核销
reservationMapper.insert(reservation);
return Result.success("预约成功", verifyCode);
}
/**
* 扫码核销
*/
public Result verifyReservation(String verifyCode, Long adminId) {
Reservation reservation = reservationMapper.selectByVerifyCode(verifyCode);
if (reservation == null) {
return Result.error("无效的核销码");
}
if (reservation.getStatus() != 0) {
return Result.error("该预约已" + (reservation.getStatus() == 1 ? "完成" : "取消"));
}
// 更新状态为已完成
reservation.setStatus(1);
reservation.setVerifyTime(new Date());
reservationMapper.updateById(reservation);
return Result.success("核销成功");
}
}