欢迎光临
我们一直在努力

24小时无人健身房系统开发落地方案:门禁、计费与IoT联动的Java实战

24小时无人健身房系统开发落地方案:门禁、计费与IoT联动的Java实战

“无人”不等于“没人管”,而是把前台、核销、计费、巡场全部转成系统闭环。24小时自助健身房真正的技术重心,不在小程序页面多好看,而在三件事:门禁能不能可靠开、计时计费准不准、断网异常会不会资损。本文基于 Spring Boot + MQTT 落地一套可生产的架构方案。

一、系统定位与分层架构

无人健身房是“交易面 + 控制面 + 感知面”的三平面系统:

  • 交易面:购卡、次卡、月卡、订单、结算(强一致)
  • 控制面:闸机、门禁、灯控、空调、储物柜(高可用)
  • 感知面:AI摄像头、人数统计、器械占用、SOS告警(最终一致)

技术分层:

小程序/H5 ──► Gateway(JWT限流)
│
┌───────┴────────┐
业务服务层(Spring Boot) IoT接入层(MQTT/EMQX)
会员/订单/计费/权限 设备网关/边缘计算
│ │
MySQL + Redis 门禁/闸机/灯控/传感器

技术栈:Spring Boot + MyBatis-Plus + MySQL 5.7(分表)+ Redis 7 + RabbitMQ + EMQX(MQTT Broker)。

二、核心业务模型

精简版核心表:

— 会员卡(次卡/时段卡/月卡)
CREATE TABLE member_card (
id BIGINT PRIMARY KEY,
user_id BIGINT,
card_type TINYINT COMMENT '1次卡 2时段卡 3月卡 4储值',
remain_times INT DEFAULT 0,
start_time DATETIME,
expire_time DATETIME,
freeze TINYINT DEFAULT 0,
version INT DEFAULT 0
);

— 健身会话订单
CREATE TABLE gym_session (
id BIGINT PRIMARY KEY,
order_no VARCHAR(32) UNIQUE,
user_id BIGINT,
store_id BIGINT,
enter_time DATETIME,
exit_time DATETIME,
duration_min INT,
amount DECIMAL(10,2),
status TINYINT COMMENT '0待入场 1在场 2已离场 3已结算 4异常'
);

人脸数据只存特征哈希,不存原图,符合个人信息保护底线。

三、门禁闭环:业务校验 + 一次性凭证 + 设备回执

扫码/刷脸入场不能“前端验一下就开门”,必须走三层:

  • 后端校验卡状态、有效期、门店权限
  • 生成一次性开门凭证(Redis 60s 过期)
  • MQTT 下发指令,设备执行后回执 ack
  • public Result requestEntry(Long userId, Long storeId) {
    MemberCard card = cardMapper.selectValid(userId, storeId);
    if (card == null || card.getFreeze() == 1)
    return Result.fail("无有效入场权限");
    if (card.getCardType() == 1 && card.getRemainTimes() <= 0)
    return Result.fail("次卡余额不足");

    // 一次性凭证
    String ticket = UUID.randomUUID().toString();
    redisTemplate.opsForValue().set("entry:ticket:" + ticket,
    userId + ":" + storeId, 60, TimeUnit.SECONDS);

    // 下发MQTT(带commandId防重复)
    String cmd = buildCmd("open_door", ticket);
    mqttGateway.publish("gym/device/" + storeId + "/cmd", cmd);

    return Result.success("正在开门");
    }

    MQTT 网关发指令(带唯一 commandId 匹配回执):

    public void publish(String topic, String payload) {
    MqttMessage msg = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8));
    msg.setQos(1); // 至少一次 + 业务幂等
    mqttTemplate.publish(topic, msg);
    }

    设备回执监听:

    @MqttSubscribe(topic = "gym/device/+/event")
    public void onAck(MqttMessage msg) {
    EntryEvent event = JSON.parseObject(msg.toString(), EntryEvent.class);
    if ("door_opened".equals(event.getAction())) {
    // 开门成功 → 创建在场会话
    sessionService.createSession(event.getTicket());
    cardService.deductOnce(event.getTicket());
    }
    }

    断网时边缘网关缓存权限快照,本地核验开门,恢复后补传事件。

    四、订单状态机:消灭“幽灵计费”

    最经典的坑:用户进场后没刷闸机离开,订单一直 在场 持续扣钱。

    状态机收敛:

    public enum SessionState {
    WAIT_ENTER, IN_GYM, LEFT, SETTLED, ABNORMAL;

    public boolean canTransit(SessionState next) {
    return switch (this) {
    case WAIT_ENTER -> next == IN_GYM || ABNORMAL;
    case IN_GYM -> next == LEFT || ABNORMAL;
    case LEFT -> next == SETTLED;
    default -> false;
    };
    }
    }

    离场逻辑:

    @Transactional
    public void leave(Long sessionId) {
    GymSession s = sessionMapper.selectForUpdate(sessionId);
    if (!s.getStatus().canTransit(LEFT))
    throw new BizException("状态不允许离场");
    s.setExitTime(LocalDateTime.now());
    s.setDurationMin((int) Duration.between(s.getEnterTime(), s.getExitTime()).toMinutes());
    s.setStatus(LEFT);
    sessionMapper.updateById(s);
    // 投消息进结算队列
    mqSender.sendSettleEvent(s.getOrderNo());
    }

    夜间巡检任务:结合门磁 + 摄像头人数,发现“订单在场但场内无人”自动置 ABNORMAL 待人工复核。

    五、分时计费引擎:策略模式 + 服务端计时

    计费绝不能用前端时间。服务端以 UTC 时间为唯一基准,规则可配置:

    public interface FeeStrategy {
    BigDecimal calc(SessionContext ctx);
    }

    @Component
    public class TimeSlotStrategy implements FeeStrategy {
    @Override
    public BigDecimal calc(SessionContext ctx) {
    // 从配置中心读时段单价:早鸟/高峰/平峰
    int minutes = ctx.getDurationMin();
    BigDecimal rate = ruleMapper.rateOf(ctx.getStoreId(), ctx.enterLocalTime());
    return rate.multiply(BigDecimal.valueOf(minutes))
    .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
    }
    }

    支持叠加规则:

    • 基础按时长
    • 高峰时段附加费
    • 月卡免计费(直接跳过扣款)
    • 封顶价(单日最多扣 39 元)

    结算服务消费 MQ:

    @RabbitListener(queues = "gym.settle")
    public void settle(SettleEvent e) {
    String idem = "settle:done:" + e.getOrderNo();
    if (Boolean.TRUE.equals(redisTemplate.hasKey(idem))) return;

    BigDecimal fee = feeEngine.calc(e);
    accountService.charge(e.getUserId(), fee);
    sessionMapper.markSettled(e.getOrderNo(), fee);

    redisTemplate.opsForValue().set(idem, "1", 7, TimeUnit.DAYS);
    }

    钱相关全用 BigDecimal,禁用 double。

    六、设备联动:人进灯亮,人走断电

    不是每个设备都实时同步,控制面走 MQTT 异步:

    • 入场成功 → 发 light:on / ac:on
    • 最后一人离场 → 延时 5 分钟关灯关空调
    • 门磁异常/烟雾/SOS → 高级告警,声光+推送运维

    public void onLastPersonLeft(Long storeId) {
    mqttGateway.publish("gym/device/" + storeId + "/cmd",
    "{\\"action\\":\\"light_off\\",\\"delay\\":300}");
    }

    心跳保活:

    @MqttSubscribe(topic = "gym/device/+/heartbeat")
    public void heartbeat(DeviceHeart h) {
    redisTemplate.opsForValue().set("dev:online:" + h.getSn(),
    "1", 180, TimeUnit.SECONDS);
    deviceStatusMapper.upsert(h);
    }

    超过 3 次心跳丢失 → 设备离线告警,运营大屏标红。

    七、防超卖与并发细节

    次卡扣减必须乐观锁,防止并发重复消次:

    <update id="deductOnce">
    UPDATE member_card
    SET remain_times = remain_times – 1,
    version = version + 1
    WHERE id = #{id}
    AND remain_times > 0
    AND version = #{version}
    </update>

    扣减失败抛异常,回滚会话创建,避免“门开了但没扣次”。

    开门接口限流:

    // 单用户 1s 最多 3 次开门请求
    if (redis.incr("rate:entry:" + userId) > 3) return Result.fail("操作频繁");

    八、安全与容灾底线

    • AB门防尾随:A门开→确认单人→B门开,两道互锁
    • 双因子:动态码 + 人脸活体,防截屏代刷
    • 断网逃生:门禁本地缓存 7 天有效会员,4G 备份链路
    • 审计日志:每次开门/计费/异常都落 trace 表,财务可对账
    • 隐私:人脸特征哈希化,原始视频本地加密,云端只拿事件

    九、小程序端关键页面

    用户端:

    • 附近门店(实时在场人数)
    • 购卡/次卡/月卡
    • 一键开门(码+人脸)
    • 当前会话计时与预估费用
    • 历史账单、异常申诉

    运营端:

    • 门店设备状态大屏
    • 异常订单复核台
    • 告警中心(门磁/离线/SOS)
    • 分时段营收报表

    十、落地节奏

  • MVP:购卡 → 扫码开门 → 在场计时 → 离场结算(先不做AI摄像头)
  • 增强:人脸闸机、AB门、灯控联动、异常巡检
  • 规模化:多门店分库分表、边缘网关、规则引擎可视化、私教/团课扩展
  • 别一上来就接支付分账和AI行为分析。先把“开门可靠、计时准确、异常可追”三件事做硬,再谈智能化。


    24小时无人健身房的系统价值,是把“信任”写进代码里:用户敢刷脸进,商家不怕跑单,半夜断网也能开门、恢复后能对账。状态机锁流程、MQTT 解耦硬件、服务端单一计时基准,这三根柱子立住,产品才真的“无人可跑”。

    赞(0)
    未经允许不得转载:171主机测评 » 24小时无人健身房系统开发落地方案:门禁、计费与IoT联动的Java实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址