从0到1:基于java的健身房小程序开发笔记

0 阅读5分钟

背景调研

随着全民健身意识的提升,传统健身房的运营模式面临着信息传递滞后、预约流程繁琐、数据统计困难等挑战。本设计旨在开发一款基于微信小程序的健身房轻量化服务平台,实现线上线下服务闭环。系统采用前后端分离架构,前端基于微信小程序开发,后端采用Java SpringBoot框架,数据库选用MySQL。系统主要包含用户端和管理端两大模块:用户端提供栏目浏览、在线注册、私教/团课/场地预约、个人中心管理等功能;管理端提供内容维护、预约规则设置、扫码核销、数据导出及权限管理等能力。测试结果表明,该系统有效提升了会员健身体验,优化了场馆运营效率,实现了服务质量的量化管理

系统需求分析

在这里插入图片描述

用户端功能

  • 栏目浏览模块: 小店动态(活动、促销)、健身干货(技巧、教学)、饮食科普、荣誉资质。 支持详情查看、分享至朋友圈、关键词检索。
  • 用户注册模块: 微信一键登录,绑定基本信息(年龄、姓名、车辆情况等)。
  • 服务预约模块: 支持私教预约、团课预约(瑜伽、动感单车等)、场地预约。 实时展示可预约时段及剩余名额。 生成核销二维码,支持到店扫码核销。
  • 个人中心模块: 我的预约(查看状态、取消预约)、资料修改、浏览历史、我的收藏。

数据库设计

  • 用户表:存储用户ID、微信OpenID、姓名、年龄、联系方式等。
  • 栏目内容表:存储文章标题、内容、类型(动态/干货/饮食)、发布时间。
  • 预约项目表:存储项目名称、类型(私教/团课/场地)、可预约时段、最大人数。
  • 预约记录表:存储用户ID、项目ID、预约时间、状态(已预约/已完成/已取消/已核销)。
  • 管理员表:存储账号、密码、角色权限。
  • 预约流程设计:用户选择时段 -> 锁定名额 -> 生成订单 -> 到店出示二维码 -> 管理员扫码 -> 状态更新为“已核销”。 权限控制设计:基于角色的访问控制(RBAC),核销员仅拥有扫码权限,不可修改系统配置。
CREATE TABLE `sys_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `openid` varchar(64) NOT NULL COMMENT '微信OpenID',
  `username` varchar(50) DEFAULT NULL COMMENT '用户昵称',
  `phone` varchar(20) DEFAULT NULL COMMENT '联系电话',
  `age` int(3) DEFAULT NULL COMMENT '年龄',
  `gender` tinyint(1) DEFAULT 0 COMMENT '性别 0:未知 1:男 2:女',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
  `status` tinyint(1) DEFAULT 1 COMMENT '状态 1:正常 0:禁用',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_openid` (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息表';
CREATE TABLE `gym_project` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL COMMENT '项目名称',
  `type` varchar(20) NOT NULL COMMENT '类型:PRIVATE(私教), GROUP(团课), VENUE(场地)',
  `coach_name` varchar(50) DEFAULT NULL COMMENT '教练姓名(私教/团课用)',
  `max_capacity` int(11) NOT NULL DEFAULT 1 COMMENT '最大容纳人数/场次',
  `price` decimal(10,2) DEFAULT 0.00 COMMENT '价格',
  `description` text COMMENT '项目详情',
  `is_available` tinyint(1) DEFAULT 1 COMMENT '是否上架',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约项目表';
CREATE TABLE `gym_reservation` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(20) NOT NULL COMMENT '用户ID',
  `project_id` bigint(20) NOT NULL COMMENT '项目ID',
  `reserve_date` date NOT NULL COMMENT '预约日期',
  `reserve_time_slot` varchar(50) NOT NULL COMMENT '预约时段 如 14:00-15:00',
  `verify_code` varchar(32) NOT NULL COMMENT '核销码/二维码内容',
  `status` tinyint(2) NOT NULL DEFAULT 0 COMMENT '状态: 0-已预约 1-已核销 2-已取消 3-已过期',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `verify_time` datetime DEFAULT NULL COMMENT '核销时间',
  `verify_admin_id` bigint(20) DEFAULT NULL COMMENT '核销管理员ID',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约记录表';
CREATE TABLE `sys_admin` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) NOT NULL COMMENT '账号',
  `password` varchar(100) NOT NULL COMMENT '加密密码',
  `role` varchar(20) NOT NULL COMMENT '角色:SUPER_ADMIN, NORMAL_ADMIN, VERIFIER(核销员)',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';

后端核心代码

@Service
public class ReservationService {

    @Autowired
    private ReservationMapper reservationMapper;
    
    @Autowired
    private ProjectMapper projectMapper;

    /**
     * 用户发起预约
     * @param userId 用户ID
     * @param dto 预约参数包含项目ID、日期、时段
     * @return 预约结果
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<Void> createReservation(Long userId, ReservationDTO dto) {
        // 1. 查询项目信息
        GymProject project = projectMapper.selectById(dto.getProjectId());
        if (project == null || !project.getIsAvailable()) {
            return Result.fail("该项目暂未开放预约");
        }

        // 2. 检查该时段剩余名额 (并发场景下建议使用数据库乐观锁或Redis锁,此处演示基础逻辑)
        int bookedCount = reservationMapper.countByProjectAndSlot(
            dto.getProjectId(), dto.getReserveDate(), dto.getReserveTimeSlot(), 0);
        
        if (bookedCount >= project.getMaxCapacity()) {
            return Result.fail("该时段名额已满,请更换时间");
        }

        // 3. 生成唯一核销码 (UUID + 时间戳)
        String verifyCode = UUID.randomUUID().toString().replace("-", "");

        // 4. 插入预约记录
        GymReservation reservation = new GymReservation();
        reservation.setUserId(userId);
        reservation.setProjectId(dto.getProjectId());
        reservation.setReserveDate(dto.getReserveDate());
        reservation.setReserveTimeSlot(dto.getReserveTimeSlot());
        reservation.setVerifyCode(verifyCode);
        reservation.setStatus(0); // 0: 已预约
        
        reservationMapper.insert(reservation);

        return Result.success("预约成功,请前往个人中心查看核销码");
    }

    /**
     * 管理员扫码核销
     */
    @Transactional(rollbackFor = Exception.class)
    public Result<Void> verifyReservation(String verifyCode, Long adminId) {
        GymReservation reservation = reservationMapper.selectByVerifyCode(verifyCode);
        
        if (reservation == null) {
            return Result.fail("无效的核销码");
        }
        if (reservation.getStatus() != 0) {
            return Result.fail("该预约状态不可核销 (当前状态:" + reservation.getStatus() + ")");
        }

        // 更新状态为已核销,记录核销人和时间
        reservation.setStatus(1);
        reservation.setVerifyAdminId(adminId);
        reservation.setVerifyTime(new Date());
        
        reservationMapper.updateById(reservation);
        return Result.success("核销成功");
    }
}
@RestController
@RequestMapping("/api/admin/export")
public class ExportController {

    @Autowired
    private ReservationService reservationService;

    /**
     * 导出预约数据 Excel
     * @param startDate 开始日期
     * @param endDate 结束日期
     * @param response HttpServletResponse
     */
    @GetMapping("/reservations")
    public void exportReservations(@RequestParam String startDate, 
                                   @RequestParam String endDate, 
                                   HttpServletResponse response) throws IOException {
        // 设置响应头
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = "健身预约数据_" + System.currentTimeMillis() + ".xlsx";
        response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

        // 查询数据
        List<ReservationVO> data = reservationService.getReservationList(startDate, endDate);

        // 使用 EasyExcel 写入流
        EasyExcel.write(response.getOutputStream(), ReservationVO.class)
                 .sheet("预约明细")
                 .doWrite(data);
    }
}

UI设计

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

管理系统设计

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

git代码下载

点击下载