前提
最近公司开发需要使用mqtt对接G7物流轨迹数据。记录一下自己使用docker部署搭建mqtt的整个核心流程和java代码。
本地安装 启动emqx
第一步 docker安装 emqx
docker pull emqx/emqx:latest
第二步 创建容器实例
docker run -d --name emqx --privileged=true -p 1883:1883 -p 8883:8883 -p 8083:8083 -p 8084:8084 -p 8081:8081 -p 18083:18083 emqx/emqx
启动之后登录mqtt控制台,配置完客户端认证和用户之后。
1.连接配置
2.订阅者配置(这里可以进行测试,左边发布消息之后右边就会接收到消息)
java实现
引入pom依赖
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
新增配置文件,如不需要对接G7则可自行定义url、账号、密码等配置数据
/**
* MQTT配置属性类
* 基于G7易流MQTT推送服务配置
*/
@ConfigurationProperties(prefix = "g7.mqtt")
@RefreshScope
@Configuration
@Data
@Slf4j
public class MqttProperties implements Serializable {
/**
* MQTT服务器地址 (V1: tcp://openapi.huoyunren.com:1883,
* V2: tcp://mqtt.openapi.huoyunren.com:8000)
*/
private String brokerUrl = "tcp://127.0.0.1:1883";
/**
* 客户端标识符,由G7易流系统分配
*/
private String clientId = "emqx_MDE0Nz";
/**
* 访问ID,对应MQTT连接的username,由G7易流系统分配
*/
private String accessId = "test";
/**
* 访问密钥,对应MQTT连接的password,由G7易流系统分配
*/
private String secret = "test";
/**
* 订阅主题,由G7易流系统分配
*/
private String topic = "testtopic/1";
/**
* 服务质量等级 (0: 最多一次, 1: 至少一次, 2: 恰好一次)
* G7易流推荐使用1或0,qos2消耗较大
*/
private int qos = 1;
/**
* 连接超时时间(秒)
*/
private int connectionTimeout = 30;
/**
* 自动重连间隔(秒)
*/
private int automaticReconnectInterval = 60;
/**
* 是否清除会话
* true: 清除会话,服务器发送topic的最新一条数据给客户端
* false: 保留会话,服务器将从上次访问的位置发送数据
*/
private boolean cleanSession = false;
/**
* 是否启用自动重连
*/
private boolean automaticReconnect = true;
/**
* 完成消息最大飞行窗口
*/
private int maxInflight = 100;
/**
* 是否实例化mqtt 0关闭1开启
*/
private Integer mqttEnabled = 1;
/**
* 共享消费组名称 区分不同环境
*/
private String groupName = "$share/ztc-group-dev/";
/**
* 创建MQTT客户端工厂
*/
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[] { brokerUrl });
options.setUserName(accessId);
options.setPassword(secret.toCharArray());
options.setCleanSession(cleanSession);
options.setAutomaticReconnect(automaticReconnect);
options.setConnectionTimeout(connectionTimeout);
options.setMaxInflight(maxInflight);
// 设置自动重连间隔
/* if (automaticReconnect) {
options.setAutomaticReconnectDelay(automaticReconnectInterval * 1000);
}
*/
// 设置遗嘱消息(可选)
// options.setWill("client/disconnected", "offline".getBytes(), 1, false);
factory.setConnectionOptions(options);
log.info("MQTT客户端工厂初始化完成,服务器地址: {}, 客户端ID: {}", brokerUrl, clientId);
return factory;
}
mqtt实例 实现类
/**
* MQTT消息接收服务实现类
* 基于G7易流MQTT推送服务
*/
@Slf4j
@Service
@RefreshScope
public class MqttServiceImpl implements MqttService {
@Autowired
private MqttProperties mqttProperties;
@Autowired
private MqttMessageProcessor messageProcessor;
private MqttClient mqttClient;
@Autowired
private MqttPahoClientFactory mqttClientFactory;
@PostConstruct
public void init() {
if (mqttProperties.getMqttEnabled() == 1) {
try {
// 创建MQTT客户端
mqttClient = new MqttClient(
mqttProperties.getBrokerUrl(),
mqttProperties.getClientId(),
new MemoryPersistence()
);
// 设置回调
mqttClient.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
log.error("MQTT连接断开: {}", cause.getMessage(), cause);
try {
//判断是否失败 失败则重连11
if (!mqttClient.isConnected()) {
connect();
}
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
log.info("收到MQTT消息,主题: {}, 消息ID: {}, QoS: {}",
topic, message.getId(), message.getQos());
// 解析消息内容
String payload = new String(message.getPayload(), "UTF-8");
log.debug("消息内容: {}", payload);
try {
// 这里可以添加消息处理逻辑
processMessage(topic, payload);
} catch (Exception e) {
log.error("处理MQTT消息失败: {}", e.getMessage(), e);
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
log.debug("消息发送完成: {}", token.getMessageId());
}
});
log.info("MQTT服务初始化完成,客户端ID: {}", mqttProperties.getClientId());
// 自动连接到MQTT服务器
connect();
} catch (Exception e) {
log.error("初始化MQTT服务失败: {}", e.getMessage(), e);
}
}
}
@Override
public boolean connect() {
try {
if (mqttClient == null) {
log.error("MQTT客户端未初始化");
return false;
}
if (mqttClient.isConnected()) {
log.info("MQTT客户端已连接");
return true;
}
log.info("正在连接MQTT服务器: {}", mqttProperties.getBrokerUrl());
mqttClient.connect(mqttClientFactory.getConnectionOptions());
// 连接成功后订阅主题
if (!mqttProperties.getTopic().isEmpty()) {
String[] split = mqttProperties.getTopic().split(",");
for (String topic : split) {
subscribe(mqttProperties.getGroupName() + topic, mqttProperties.getQos());
}
}
log.info("MQTT服务器连接成功");
return true;
} catch (Exception e) {
log.error("连接MQTT服务器失败: {}", e.getMessage(), e);
return false;
}
}
@Override
public void disconnect() {
try {
if (mqttClient != null && mqttClient.isConnected()) {
mqttClient.disconnect();
log.info("MQTT连接已断开");
}
} catch (Exception e) {
log.error("断开MQTT连接失败: {}", e.getMessage(), e);
}
}
@Override
public boolean subscribe(String topic, int qos) {
try {
if (mqttClient == null || !mqttClient.isConnected()) {
log.error("MQTT客户端未连接,无法订阅主题");
return false;
}
log.info("订阅MQTT主题: {}, QoS: {}", topic, qos);
mqttClient.subscribe(topic, qos);
return true;
} catch (Exception e) {
log.error("订阅MQTT主题失败: {}", e.getMessage(), e);
return false;
}
}
@Override
public void unsubscribe(String topic) {
try {
if (mqttClient == null || !mqttClient.isConnected()) {
log.error("MQTT客户端未连接,无法取消订阅主题");
return;
}
log.info("取消订阅MQTT主题: {}", topic);
mqttClient.unsubscribe(topic);
} catch (Exception e) {
log.error("取消订阅MQTT主题失败: {}", e.getMessage(), e);
}
}
@Override
public boolean publish(String topic, String message, int qos) {
try {
if (mqttClient == null || !mqttClient.isConnected()) {
log.error("MQTT客户端未连接,无法发布消息");
return false;
}
MqttMessage mqttMessage = new MqttMessage(message.getBytes("UTF-8"));
mqttMessage.setQos(qos);
log.info("发布MQTT消息,主题: {}, QoS: {}", topic, qos);
mqttClient.publish(topic, mqttMessage);
return true;
} catch (Exception e) {
log.error("发布MQTT消息失败: {}", e.getMessage(), e);
return false;
}
}
@Override
public boolean isConnected() {
return mqttClient != null && mqttClient.isConnected();
}
@PreDestroy
public void destroy() {
disconnect();
try {
if (mqttClient != null) {
mqttClient.close();
}
} catch (Exception e) {
log.error("关闭MQTT客户端失败: {}", e.getMessage(), e);
}
}
/**
* 处理接收到的MQTT消息
* @param topic 主题
* @param payload 消息内容
*/
private void processMessage(String topic, String payload) {
log.info("处理MQTT消息,主题: {}, 内容: {}", topic, payload);
// String[] supportedTopics = messageProcessor.getSupportedTopics();
String[] supportedTopics = mqttProperties.getTopic().split(",");
if (supportedTopics != null) {
for (String supportedTopic : supportedTopics) {
//判断是否是 配置的订阅主题才进行处理
if (topic.contains(supportedTopic) || topic.equals(supportedTopic)) {
try {
MqttMessageResponseVO result = messageProcessor.processMessage(topic, payload);
log.info("消息处理器处理结果: {}", result);
} catch (Exception e) {
log.error("消息处理器处理消息失败: {}", e.getMessage(), e);
}
break;
}
}
} else {
// 默认处理逻辑
try {
Object messageObj = JSON.parse(payload);
log.info("默认处理MQTT消息: {}", messageObj);
} catch (Exception e) {
log.error("解析MQTT消息失败: {}", e.getMessage(), e);
}
}
}
controller层
/**
* MQTT消息接收控制器
* 基于G7易流MQTT推送服务
*/
@Slf4j
@RestController
@RequestMapping("/mqtt")
@Tag(name = "MQTT消息接收", description = "MQTT消息接收服务API")
public class MqttController {
@Autowired
private MqttService mqttService;
/**
* 连接MQTT服务器
*/
@PostMapping("/connect")
@Operation(summary = "连接MQTT服务器", description = "连接到G7易流MQTT服务器")
public MqttMessageResponseVO connect() {
try {
boolean result = mqttService.connect();
if (result) {
return MqttMessageResponseVO.success("MQTT服务器连接成功");
} else {
return MqttMessageResponseVO.failure("MQTT服务器连接失败");
}
} catch (Exception e) {
log.error("连接MQTT服务器异常: {}", e.getMessage(), e);
return MqttMessageResponseVO.failure("连接MQTT服务器异常: " + e.getMessage());
}
}
/**
* 断开MQTT连接
*/
@PostMapping("/disconnect")
@Operation(summary = "断开MQTT连接", description = "断开与G7易流MQTT服务器的连接")
public MqttMessageResponseVO disconnect() {
try {
mqttService.disconnect();
return MqttMessageResponseVO.success("MQTT连接已断开");
} catch (Exception e) {
log.error("断开MQTT连接异常: {}", e.getMessage(), e);
return MqttMessageResponseVO.failure("断开MQTT连接异常: " + e.getMessage());
}
}
/**
* 订阅MQTT主题
*/
@PostMapping("/subscribe")
@Operation(summary = "订阅MQTT主题", description = "订阅指定的MQTT主题")
public MqttMessageResponseVO subscribe(
@Parameter(description = "主题名称", required = true) @RequestParam String topic,
@Parameter(description = "服务质量等级(0,1,2)") @RequestParam(defaultValue = "1") int qos) {
try {
boolean result = mqttService.subscribe(topic, qos);
if (result) {
return MqttMessageResponseVO.success("订阅主题成功: " + topic);
} else {
return MqttMessageResponseVO.failure("订阅主题失败: " + topic);
}
} catch (Exception e) {
log.error("订阅MQTT主题异常: {}", e.getMessage(), e);
return MqttMessageResponseVO.failure("订阅MQTT主题异常: " + e.getMessage());
}
}
/**
* 取消订阅MQTT主题
*/
@PostMapping("/unsubscribe")
@Operation(summary = "取消订阅MQTT主题", description = "取消订阅指定的MQTT主题")
public MqttMessageResponseVO unsubscribe(
@Parameter(description = "主题名称", required = true) @RequestParam String topic) {
try {
mqttService.unsubscribe(topic);
return MqttMessageResponseVO.success("取消订阅主题成功: " + topic);
} catch (Exception e) {
log.error("取消订阅MQTT主题异常: {}", e.getMessage(), e);
return MqttMessageResponseVO.failure("取消订阅MQTT主题异常: " + e.getMessage());
}
}
/**
* 发布MQTT消息
*/
@PostMapping("/publish")
@Operation(summary = "发布MQTT消息", description = "向指定主题发布消息")
public MqttMessageResponseVO publish(
@Parameter(description = "主题名称", required = true) @RequestParam String topic,
@Parameter(description = "消息内容", required = true) @RequestParam String message,
@Parameter(description = "服务质量等级(0,1,2)") @RequestParam(defaultValue = "1") int qos) {
try {
boolean result = mqttService.publish(topic, message, qos);
if (result) {
return MqttMessageResponseVO.success("发布消息成功");
} else {
return MqttMessageResponseVO.failure("发布消息失败");
}
} catch (Exception e) {
log.error("发布MQTT消息异常: {}", e.getMessage(), e);
return MqttMessageResponseVO.failure("发布MQTT消息异常: " + e.getMessage());
}
}
/**
* 获取MQTT连接状态
*/
@GetMapping("/status")
@Operation(summary = "获取MQTT连接状态", description = "查询当前MQTT连接状态")
public MqttMessageResponseVO getStatus() {
try {
boolean connected = mqttService.isConnected();
return MqttMessageResponseVO.success(connected ? "已连接" : "未连接");
} catch (Exception e) {
log.error("获取MQTT连接状态异常: {}", e.getMessage(), e);
return MqttMessageResponseVO.failure("获取MQTT连接状态异常: " + e.getMessage());
}
}
接口层
/**
* MQTT消息接收服务接口
* 基于G7易流MQTT推送服务
*/
public interface MqttService {
/**
* 连接MQTT服务器
* @return 连接是否成功
*/
boolean connect();
/**
* 断开MQTT连接
*/
void disconnect();
/**
* 订阅主题
* @param topic 主题名称
* @param qos 服务质量等级
* @return 订阅是否成功
*/
boolean subscribe(String topic, int qos);
/**
* 取消订阅主题
* @param topic 主题名称
*/
void unsubscribe(String topic);
/**
* 发布消息
* @param topic 主题名称
* @param message 消息内容
* @param qos 服务质量等级
* @return 发布是否成功
*/
boolean publish(String topic, String message, int qos);
/**
* 检查连接状态
* @return 是否已连接
*/
boolean isConnected();
}
如何实现共享消费(单实例单次消费)?
本地启动连接之后就会针对testtopic/1 topic有两个消费组,控制台发布一次消息之后,两个消费组都会消费消息。如何实现一个实例消费一次消息呢?就要使用mqtt的共享消费了($share/abc/t/1)。
实现以及结果
代码里我也是这样进行topic进行订阅,配置之后,就会轮询进行消费消息了。多次点击mqtt的控制台消息发布按钮就会发现,控制台的订阅者和我本地的订阅者共享订阅同一个topic的时候就会轮询进行消费了。
本地接口测试
也可以本地接口测试发送消息,具体可见controller 控制层的相关方法