java实现mqtt链接并控制门锁设备

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。

该方案可扩展至其他物联网设备(如水电表、传感器),只需适配不同的指令协议。本文档可作为后续开发同类项目的参考模板。

相关推荐
codeejun5 小时前
每日一Go-53、Go微服务--限流与降级
开发语言·微服务·golang
xier_ran5 小时前
【C++】static 关键字与 const 关键字的作用
java·数据库·microsoft
阿里嘎多学长5 小时前
2026-04-17 GitHub 热点项目精选
开发语言·程序员·github·代码托管
Wadli5 小时前
集群C++聊天服务器
服务器·开发语言·c++
凭君语未可5 小时前
为什么需要代理?从一个基础问题理解 JDK 静态代理
java·开发语言
luoqice5 小时前
利用flv库读取flv文件时长c程序
c语言·开发语言
NotFound4865 小时前
Go语言中的图形界面开发实战解析:从GUI到WebAssembly
开发语言·golang·wasm
Makoto_Kimur5 小时前
Agent 面试速成清单
java·agent
Rust研习社5 小时前
Rust Default 特征详解:轻松实现类型默认值
开发语言·后端·rust