无人自助健身平台搭建:从架构设计到设备联动的完整实战
无人自助健身平台搭建的核心,是把"用户扫码---校验权益---开门/开闸---计时计次---结束结算---数据回传"这条链路做成自动化闭环。技术上通常采用 Spring Boot + MyBatis Plus + MySQL 作为后端服务,用户端用 UniApp(Vue 语法)适配 H5、APP 与小程序,管理端用 Vue + ElementUI 实现门店、设备、会员、订单的可视化运营。下面按架构、数据模型、设备联动、部署联调四个部分展开,代码与步骤可直接参考落地。
一、整体架构与技术选型:为什么是 Spr
Spring Boot + UniApp 组合
无人自助健身场景有三个硬性要求:多端一致、设备可控、状态可追踪。因此架构上建议拆成四层:
- 用户端:UniApp 一套代码编译到小程序、H5、APP,负责扫码、预约、查看订单、开门。
- 管理端:Vue + ElementUI,负责门店管理、设备管理、会员卡配置、订单流水、教练排班。
- 后端服务:Spring Boot + MyBatis Plus + MySQL,按模块拆分为用户服务、订单服务、设备服务、支付回调服务。Redis 用于缓存权益、分布式锁与设备在线状态。
设备层:智能门禁、闸机、储物柜、跑步机等通过 MQTT 或 HTTP 与后端通信,形成软硬件一体化管理。
技术选型上,Spring Boot 的生态适合快速接入支付回调、定时任务与 WebSocket;MyBatis Plus 能减少单表 CRUD 的样板代码;MySQL 保证订单与流水的强一致。UniApp 的价值在于一套代码覆盖多端,避免为小程序和 APP 分别维护两套逻辑。
一个典型的权益校验接口可以写成这样:
java
@RestController
@RequestMapping("/api/access")
public
class AccessController {
@Autowired
private MemberCardService memberCardService;
@Autowired
private DeviceCommandService deviceCommandService;
@PostMapping("/open")
public Result openDoor(@RequestBody OpenDoorDTO dto) {
// 1. 校验用户是否有可用权益
MemberCard card = memberCardService.getValidCard(dto.getUserId(), dto.getStoreId());
if (card == null) {
return Result.fail("暂无可用权益");
}
// 2. 校验设备是否在线
if (!deviceCommandService.isOnline(dto.getDeviceId())) {
return Result.
fail("设备离线,请稍后重试");
}
// 3. 下发开门指令,写入开门记录
String commandId = deviceCommandService.sendOpenCommand(dto.getDeviceId(), dto.getUserId());
return Result.ok(commandId);
}
}
注意:开门接口必须做幂等,避免用户连续点击导致重复扣次。可用 Redis 对 userId + deviceId 加短时锁。
二、
核心数据模型与订单状态机
无人自助健身平台的数据模型不需要一开始就大而全,但以下几张表建议提前设计:用户表、门店表、设备表、会员卡/次卡表、订单表、开门记录表、教练表、预约表。
设备表要保留 device_sn、device_type、online_status、last_heartbeat 字段,便于判断在线状态。订单表要保留 order_no、user_id、device_id、start_time、end_time、status、amount_type。开门记录表独立存储,方便排查"扣了次数但门没开"
的争议。
订单状态机建议固定为:CREATED → USING → FINISHED → SETTLED,异常分支为 CANCELLED。状态流转必须由服务端控制,禁止客户端直接修改。
sql
CREATE TABLE `access_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(64) NOT NULL,
`user_id` bigint NOT NULL,
`device_id` bigint NOT NULL,
`status`
varchar(20) NOT NULL DEFAULT 'CREATED',
`start_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_status` (`user_id`, `stat
us`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
状态流转时建议用乐观锁或 update ... where status = ? 的方式,避免并发下重复结算。
三、设备联动:从扫码到开门的关键链路
设备联动是无人自助健身平台搭建中容易出问题的环节。常见做法是后端通过 MQTT 向设备下发指令,设备执行后回调后端确认。完整链路如下:
- 用户在小程序扫码,拿到
device_sn。 - 用户端调用
/api/access/open,后端校验权益与设备在线状态。 - 后端生成
commandId,通过 MQTT 发布到主题device/{sn}/cmd,消息体包含指令类型与过期时间。 - 设备收到指令后开门,并向
device/{sn}/ack回传执行结果。 - 后端根据
commandId更新开门记录,同时把订单状态改为USING。 - 用户结束锻炼,设备或用户端触发结束,后端计算时长/次数,进入
FINISHED并结算。
MQTT 消息建议带 expireAt,设备侧判断过期后不再执行,防止网络延迟导致"人走了门才开"。同时后端要记录指令下发日志,方便对账。
如果设备只支持
HTTP,可以用轮询或长连接替代,但实时性会下降。对于门禁与闸机,建议优先选 MQTT,弱网环境下也能通过 QoS 保证消息可达。
四、部署与联调:让整套系统跑起来
部署阶段建议用 Docker Compose 编排 MySQL、Redis、后端服务与 Nginx。前端用户端编译出小程序包和 H5 静态资源,管理端编译出 dist 目录,由 Nginx 统一托管。
yaml
version: "3"
services:
mysql:
image: mysql:8.0
environment:
MYSQL
_ROOT_PASSWORD: your_password
MYSQL_DATABASE: fitness
volumes:
- ./mysql-data:/var/lib/mysql
redis:
image: redis:7
backend:
build: ./backend
depends_on:
- mysql
- redis
ports:
- "8080:8080"
联调顺序建议:先跑通用户注册登录 → 会员卡配置 → 设备注册
与心跳 → 扫码开门 → 订单生成与结束 → 管理端查看流水。小程序需要配置 HTTPS 域名与业务域名,H5 需要处理跨域。设备侧建议先用手写 MQTT 客户端模拟,确认协议后再接入真实硬件。
常见坑包括:设备心跳间隔过长导致误判离线;支付回调未做签名校验;订单结算与开门记录不在同一事务;MySQL 时间与设备时间不同步。建议统一使用服务端时间,并在关键表加索引。
FAQ
Q1:无人自助健身平台搭建一定要用 UniApp 吗?
不是必须,但 UniApp 能用一套 Vue 语法覆盖小程序、H5 和 APP,减少多端维护成本。如果只做
小程序,原生开发也可以。
Q2:设备联动一定要用 MQTT 吗?
不是。设备支持 HTTP 时也可以用 HTTP 回调,但 MQTT 在弱网和实时性上更有优势,适合门禁、闸机这类需要快速响应的场景。
Q3:如何避免用户重复扣次?
在开门接口对 userId + deviceId 加 Redis 短锁,并对订单号做索引;状态流转使用条件更新,确保同一订单只能结算一次。
Q4:管理端需要哪些基础模块?
至少包含门店管理、设备管理、会员卡管理、订单流水、教练与排班、数据看板。先保证设备在线状态和订单可追溯,再扩展营销功能。
Q5:部署时容易忽略什么?
容易忽略的是设备时间同步与回调幂等。建议所有服务统一 NTP,回调接口按 commandId 去重,避免重复开门或重复结算。