Java后端基于MQTT的智能门锁远程控制实现技术文档
一、背景与目标
在智慧园区/智慧停车系统中,需要实现对门禁设备(门锁)的远程管理,包括:
远程修改门锁管理员密码及密码授权管理
远程开门/关门、设置常开模式
读取门锁事件记录(刷卡、密码开门、门磁变化等)
实时接收门锁状态与心跳,保证设备在线
本文档基于某门锁厂商提供的MQTT通信协议,使用Java(Spring Boot + Spring Integration MQTT)实现了后端与MQTT Broker的交互,完成上述功能。
二、整体架构
text
┌─────────────┐ MQTT (TCP) ┌─────────────┐ MQTT ┌─────────┐
│ 后端服务 │ ◄────────────► │ MQTT Broker │ ◄───────► │ 网关 │
│ (SpringBoot)│ │ (EMQX等) │ │(clientId)│
└─────────────┘ └─────────────┘ └────┬────┘
│
┌────▼────┐
│ 门锁 │
└─────────┘
MQTT Broker:提供消息中转服务()
网关:每个物理区域/门禁控制器有一个唯一ID(clientId),负责与门锁通信并转发MQTT指令
门锁:受网关控制,每个门锁也有唯一ID(lockId)
后端:作为MQTT客户端,向指定主题发布指令,并订阅响应主题,实现一问一答。
三、通信协议关键规范
根据厂商文档,协议核心约定如下:
方向 主题格式 说明
下行(平台 → 网关) smartHouse/{clientId} 平台发送指令给指定网关
上行(网关 → 平台) device/{clientId} 网关上报心跳、响应指令结果
重要原则:
采用一问一答方式,保证上一条指令收到回复后再发新指令。
网关定时发送心跳(初始6秒,稳定后1分钟),服务器必须回复心跳(包含正确时间戳),否则网关会断开连接。
四、技术实现选型
MQTT客户端库:org.eclipse.paho.client.mqttv3(底层)
Spring Integration MQTT:提供声明式适配器,简化连接、发布/订阅配置
异步处理:使用 CompletableFuture + ConcurrentHashMap 实现请求-响应匹配
JSON处理:Fastjson 解析指令负载
五、核心实现步骤
5.1 Maven依赖
xml
org.springframework.integration
spring-integration-mqtt
org.eclipse.paho
org.eclipse.paho.client.mqttv3
1.2.5
com.alibaba
fastjson
2.0.32
5.2 配置类 MqttConfig
java
@Configuration
@EnableIntegration
public class MqttConfig {
@Value("mqtt.broker−url")privateStringbrokerUrl;@Value("{mqtt.broker-url}") private String brokerUrl; @Value("mqtt.broker−url")privateStringbrokerUrl;@Value("{mqtt.username}")
private String username;
@Value("mqtt.password")privateStringpassword;@Value("{mqtt.password}") private String password; @Value("mqtt.password")privateStringpassword;@Value("{mqtt.client-id}")
private String clientId;
@Bean
public MqttPahoClientFactory mqttClientFactory() { ... }
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() { ... }
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
@Bean
public MessageProducer mqttInbound() {
// 订阅所有网关的上行消息:device/+
MqttPahoMessageDrivenChannelAdapter adapter = ...;
adapter.setOutputChannel(mqttInputChannel());
return adapter;
}
}
关键点:
mqttOutboundChannel 用于发布消息
mqttInbound 订阅 device/+,接收所有网关的心跳、响应
心跳处理必须在业务层实现,否则网关会超时断开。
5.3 消息网关接口 MqttMessageGateway
java
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttMessageGateway {
void sendToMqtt(String payload);
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
}
Spring Integration 会动态代理此接口,实现消息发送。
5.4 消息处理器 MqttMessageService
核心职责:
接收 mqttInputChannel 的消息
区分指令类型:heartbeat、authPassword、configLock、readRecord
对于心跳,构造响应并发送到 smartHouse/{clientId}
对于业务响应,根据 id 字段匹配 CompletableFuture 并完成
java
@ServiceActivator(inputChannel = "mqttInputChannel")
public void handleIncomingMessage(Message<?> message) {
String payload = message.getPayload().toString();
String topic = (String) message.getHeaders().get("mqtt_receivedTopic");
JSONObject json = JSONObject.parseObject(payload);
String cmd = json.getString("cmd");
String id = json.getString("id");
if ("heartbeat".equals(cmd)) {
// 回复心跳
String replyTopic = "smartHouse/" + json.getString("clientId");
JSONObject resp = new JSONObject();
resp.put("cmd", "heartbeat");
resp.put("clientId", json.getString("clientId"));
resp.put("timestamp", System.currentTimeMillis() / 1000);
resp.put("id", id);
mqttMessageGateway.sendToMqtt(replyTopic, resp.toJSONString());
} else {
// 业务响应:根据id找到对应的Future并complete
CompletableFuture future = responseFutures.remove(id);
if (future != null) future.complete(json.getString("result"));
}
}
5.5 业务服务实现(以修改密码为例)
java
public String modifyLockPassword(Long lockId, String newPassword, String oldPassword, boolean clearAllFirst) {
// 1. 查询门锁信息,获取gatewayId, lockDeviceId
AcEquipLockDO lock = acEquipLockMapper.selectById(lockId);
String gatewayId = lock.getAcLockGateway();
String lockDeviceId = lock.getAcLockCode();
String commandTopic = "smartHouse/" + gatewayId;
// 2. 构造密码授权字符串(40字符/组)
String passwords = LockPasswordUtil.buildAddAuthorization(newPassword);
if (oldPassword != null) passwords += LockPasswordUtil.buildDeleteAuthorization(oldPassword);
// 3. 构造指令
String requestId = String.valueOf(System.currentTimeMillis());
JSONObject command = new JSONObject();
command.put("cmd", "authPassword");
command.put("clientId", gatewayId);
command.put("lockId", lockDeviceId);
command.put("clearAllPassword", clearAllFirst ? 1 : 0);
command.put("passwords", passwords);
command.put("id", requestId);
// 4. 注册Future
CompletableFuture<String> future = new CompletableFuture<>();
mqttMessageService.registerFuture(requestId, future);
// 5. 发送指令
mqttMessageGateway.sendToMqtt(commandTopic, command.toJSONString());
// 6. 等待响应(超时10秒)
String result = future.get(10, TimeUnit.SECONDS);
if ("ok".equals(result)) {
// 更新数据库中的管理员密码
lock.setAcAdminPassword(newPassword);
acEquipLockMapper.updateById(lock);
return "success";
} else {
throw new ServiceException("密码修改失败:" + result);
}
}
5.6 密码授权字符串生成规则(根据协议)
每个授权固定40字符:
位置 长度 含义 添加示例
1-2 2 01=添加,00=删除 01
3-18 16 前10位0 + 6位密码 0000000000123456
19-20 2 有效次数(FF=无限) FF
21-30 10 生效开始时间(全FF=立即) FFFFFFFFFF
31-40 10 生效结束时间(全FF=永不过期) FFFFFFFFFF
java
public static String buildAddAuthorization(String password) {
return "01" + "0000000000" + password + "FF" + "FFFFFFFFFF" + "FFFFFFFFFF";
}
public static String buildDeleteAuthorization(String password) {
return "00" + "0000000000" + password + "FF" + "FFFFFFFFFF" + "0000000000";
}
5.7 远程开门/关门指令 (configLock)
类似上述流程,指令 cmd 为 configLock,携带 action(open/close)、holdOpen、delayTime 等可选参数。实现时复用相同的异步框架。
5.8 读取门锁记录 (readRecord)
协议采用三次握手分页读取:
服务器发送 readRecord 请求
网关返回一批记录(含总条数、本次条数、记录字符串)
服务器回复 more 或 ok 确认,告知是否继续
实现时使用 ReadRecordSession 保存中间状态,CompletableFuture<List> 等待所有批次完成。
六、关键技术点解析
6.1 异步请求-响应匹配
使用 ConcurrentHashMap<String, CompletableFuture> 存储请求ID与Future的映射。当收到响应时,根据ID取出Future并complete(),业务线程通过future.get()同步等待结果。这实现了同步编码、异步MQTT通信的效果。
6.2 心跳保活
网关心跳要求服务器必须在3秒内回复,且时间戳必须准确。后端通过 MqttMessageService 专门处理 cmd=heartbeat 的消息,构造包含当前秒级时间戳的响应,发送到 smartHouse/{clientId}。若不处理,网关会断开连接,导致后续指令失败。
6.3 动态主题
所有指令主题都需要根据数据库中的 gatewayId 动态拼接为 smartHouse/{clientId},不能硬编码。同样订阅主题使用通配符 device/+ 可接收所有网关的上行消息。
6.4 会话管理(分页)
对于 readRecord,需要维护会话对象,记录已接收条数、总条数,并在每次响应后决定回复 more 还是 ok。使用 ConcurrentHashMap<String, ReadRecordSession> 存储会话,确保多个门锁同时读取时互不干扰。
七、遇到的问题与解决方案
7.1 主题不匹配导致超时
现象:发送指令后无响应,超时。
原因:代码中使用了自定义主题 gateway/command,而协议要求 smartHouse/{clientId}。
解决:根据规范修改主题,并动态拼接。
7.2 心跳未处理导致连接断开
现象:MQTT连接频繁重连,日志显示 Lost connection: MqttException; retrying...。
原因:未对网关心跳作出响应。
解决:在消息处理器中增加心跳处理逻辑,回复心跳包。
7.3 IDEA 报 "No beans of 'MqttMessageGateway' type found"
现象:编辑时红色波浪线,但运行正常。
原因:IDEA静态检查无法识别 @MessagingGateway 动态生成的代理Bean。
解决:忽略警告或使用 @SuppressWarnings。
7.4 密码授权字符串长度错误
现象:网关返回 fail。
原因:字符串长度不是40的倍数,或删除授权中结束时间格式错误。
解决:严格按照文档示例生成40字符授权串。
7.5 并发请求的Future匹配
问题:多个请求同时发送,响应回来时如何正确匹配?
解决:每个请求生成唯一ID(时间戳+随机数),存入 ConcurrentHashMap,响应时根据ID取出Future并完成。使用 ConcurrentHashMap 保证线程安全。
八、总结
通过本项目,我们实现了Java后端基于MQTT协议对智能门锁的远程管理,涵盖:
密码授权管理(添加/删除密码)
远程控制(开门/关门、设置常开)
记录读取(事件记录主动拉取)
心跳保活(保持设备在线)
核心技术栈:Spring Boot + Spring Integration MQTT + Eclipse Paho + CompletableFuture。
该方案可扩展至其他物联网设备(如水电表、传感器),只需适配不同的指令协议。本文档可作为后续开发同类项目的参考模板。