业务场景
硬件采集的数据传入MQTT(这边MQTT的服务器用的是EMQX,有兴趣的可以自己去了解一下),JAVA通过代码连接MQTT服务器,对数据进行处理
新建SpringBoot项目,pom文件中直接引入下面MQTT的依赖
org.springframework.integration
spring-integration-stream
org.springframework.boot
spring-boot-starter-integration
org.springframework.integration
spring-integration-mqtt
MQTT工具类
MqttServerData
因为是多连接,所以说MQTT的配置需要存放在数据库中,需要新增MQTT基础表MqttServerData,我这边用的是Spring Data JPA,大家可以用自己的ORM框架生成这些基础表的信息
package com.huibo.xx.xx.entity;
import com.huibo.core.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.Entity;
/**
* @author :cola
* @description: Mqtt配置表
* @date :2023/7/18 8:51
*/
@Data
@Entity
@EqualsAndHashCode(callSuper = true)
public class MqttServerData extends BaseEntity {
private String serverHost;
private String serverPort;
private String userName;
private String password;
private String clientId;
/**
* mqtt订阅的主体,以“,”隔开
*/
private String clientTopic;
/**
* 客户端每次重连是否清除session
*/
private String clientCleanSession;
private String remark;
private Boolean enableFlag;
}
MqttServerDataRepository
package com.xx.xx.mqtt.repository;
import com.xx.xx.mqtt.entity.MqttServerData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface MqttServerDataRepository extends JpaRepository, JpaSpecificationExecutor
MqttServerDataService
package com.xx.xx.mqtt.service;
import com.xx.xx.entity.Device;
import com.xx.xx.mqtt.controller.dto.MqttServerDataAddDto;
import com.xx.xx.mqtt.controller.dto.MqttServerDataQueryDto;
import com.xx.xx.mqtt.controller.dto.MqttServerDataUpdateDto;
import com.xx.xx.mqtt.entity.MqttServerData;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
public interface MqttServerDataService {
MqttServerData add(MqttServerDataAddDto addDto);
boolean update(MqttServerDataUpdateDto updateDto) throws MqttException;
MqttServerData findById(Long id);
boolean delete(Long id) throws MqttException;
List findByList(MqttServerDataQueryDto queryDto);
Page findByPage(MqttServerDataQueryDto queryDto, Pageable page);
/**
* 初始化MQTT连接
*
* @param response 响应体
*/
void initMqttServer(MqttServerData mqttServerData) throws MqttException;
}
MqttServerDataServiceImpl
/**
* @author cola
* @date 2023/7/13 16:23
*/
@Service
@Slf4j
public class MqttServerDataServiceImpl implements MqttServerDataService {
@Autowired
private MqttServerDataRepository mqttServerDataRepository;
@Autowired
private ExcelHandler excelHandler;
@Override
public MqttServerData add(MqttServerDataAddDto addDto) {
MqttServerData mqttServerData = mqttServerDataRepository.save(from(addDto));
if (mqttServerData.getEnableFlag()) {
this.initMqttServer(mqttServerData);
}
return mqttServerData;
}
@Override
@Transactional
public boolean update(MqttServerDataUpdateDto updateDto) throws MqttException {
MqttServerData entity = findById(updateDto.getId());
assemble(entity, updateDto);
if (!entity.getEnableFlag() && updateDto.getEnableFlag()) {
if (!MqttClientListener.mqttClients.containsKey(entity.getId().toString())) {
this.initMqttServer(entity);
} else {
}
}
if (entity.getEnableFlag() && !updateDto.getEnableFlag()) {
MqttClientConnect mqttClientConnect = MqttClientListener.mqttClients.get(entity.getId().toString());
mqttClientConnect.close();
}
return true;
}
@Override
public MqttServerData findById(Long id) {
return mqttServerDataRepository.findById(id).orElseThrow(() -> new NoEntityFoundException("No record found by id = " + id));
}
@Override
@Transactional
public boolean delete(Long id) throws MqttException {
MqttServerData entity = findById(id);
entity.setDeleted(true);
MqttClientConnect mqttClientConnect = MqttClientListener.mqttClients.get(entity.getId().toString());
if (Objects.nonNull(mqttClientConnect)) {
mqttClientConnect.close();
}
return true;
}
@Override
public List findByList(MqttServerDataQueryDto queryDto) {
return mqttServerDataRepository.findAll(getSpecification(queryDto));
}
@Override
public Page findByPage(MqttServerDataQueryDto queryDto, Pageable page) {
return mqttServerDataRepository.findAll(getSpecification(queryDto), page);
}
@Override
public void initMqttServer(MqttServerData mqttServerData) {
try {
MqttClientConnect mqttClientConnect = new MqttClientConnect();
mqttClientConnect.setMqttClientId(UUID.randomUUID().toString());
String host = "tcp://" + mqttServerData.getServerHost() + ":" + mqttServerData.getServerPort();
mqttClientConnect.setMqttClient(host, UUID.randomUUID().toString(), mqttServerData.getUserName(), mqttServerData.getPassword(), new MqttClientCallback(String.valueOf(mqttServerData.getId())));
//订阅CPUId
mqttClientConnect.sub(String.format(DEVICE_DOWN_TOPIC, ServerUtil.getCPUSerial()));
MqttClientListener.mqttClients.put(String.valueOf(mqttServerData.getId()), mqttClientConnect);
log.info("{}--连接成功!!订阅主题--{}", mqttServerData.getId(), mqttServerData.getClientTopic());
} catch (MqttException e) {
e.printStackTrace();
}
}
@Override
public void addSubTopicForDevice(Device device) {
MqttClientListener.mqttClients.forEach((key, value) -> {
try {
value.sub(String.format(DEVICE_DOWN_TOPIC, device.getName()));
} catch (MqttException e) {
e.printStackTrace();
}
});
}
private MqttServerData from(MqttServerDataAddDto dto) {
MqttServerData entity = new MqttServerData();
BeanUtils.copyProperties(dto, entity);
entity.setClientTopic("/topic/default");
return entity;
}
private void assemble(MqttServerData entity, MqttServerDataUpdateDto updateDto) {
BeanUtils.copyProperties(updateDto, entity);
}
private Specification getSpecification(MqttServerDataQueryDto queryDto) {
return (root, query, builder) -> {
List predicates = Lists.newArrayList();
if (!StringUtils.isEmpty(queryDto.getServerHost())) {
predicates.add(builder.like(root.get("serverHost"), "%".concat(queryDto.getServerHost()).concat("%")));
}
if (Objects.nonNull(queryDto.getEnableFlag())) {
predicates.add(builder.isTrue(root.get("enableFlag")));
}
predicates.add(builder.isFalse(root.get("isDeleted")));
return builder.and(predicates.toArray(new Predicate[0]));
};
}
}
MqttClientListener
项目启动,监听主题
package com.huibo.acquisition.mqtt.listener;
import com.xx.xx.mqtt.config.MqttClientConnect;
import com.xx.xx.acquisition.mqtt.controller.dto.MqttServerDataQueryDto;
import com.xx.xx.acquisition.mqtt.entity.MqttServerData;
import com.xx.xx.acquisition.mqtt.service.MqttServerDataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* @author :cola
* @description: 项目启动 监听主题
* @date :2023/7/18 10:53
*/
@Slf4j
@Component
public class MqttClientListener implements ApplicationListener {
@Autowired
private MqttServerDataService mqttServerDataService;
/**
* 客户端
*/
public static ConcurrentHashMap mqttClients = new ConcurrentHashMap();
private final AtomicBoolean isInit = new AtomicBoolean(false);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//防止重复触发
if (!isInit.compareAndSet(false, true)) {
return;
}
//TODO 需要传入启用参数
List mqttServers = mqttServerDataService.findByList(new MqttServerDataQueryDto() {{
setEnableFlag(Boolean.TRUE);
}});
try {
if (!CollectionUtils.isEmpty(mqttServers)) {
for (MqttServerData mqttServerData : mqttServers) {
mqttServerDataService.initMqttServer(mqttServerData);
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
MqttClientConnect
package com.xx.xx.mqtt.config;
import com.huibo.acquisition.mqtt.callback.MqttClientCallback;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.stereotype.Component;
import java.io.UnsupportedEncodingException;
/**
* @author :cola
* @description: Mqtt
* @date :2023/7/18 10:33
*/
@Slf4j
@Component
public class MqttClientConnect {
private MqttClient mqttClient;
/**
* 系统的mqtt客户端id
*/
private String mqttClientId;
/**
* 客户端connect连接mqtt服务器
*
* @param userName 用户名
* @param passWord 密码
* @param mqttCallback 回调函数
**/
public void setMqttClient(String host, String clientId, String userName, String passWord, MqttCallback mqttCallback) throws MqttException {
// 连接设置
MqttConnectOptions options = mqttConnectOptions(host, clientId, userName, passWord);
if (mqttCallback == null) {
mqttClient.setCallback(new MqttClientCallback(mqttClientId));
} else {
mqttClient.setCallback(mqttCallback);
}
mqttClient.connect(options);
}
/**
* MQTT连接参数设置
*/
private MqttConnectOptions mqttConnectOptions(String host, String clientId, String userName, String passWord) throws MqttException {
// 创建 MQTT 客户端对象
mqttClient = new MqttClient(host, clientId, new MemoryPersistence());
// 连接设置
MqttConnectOptions options = new MqttConnectOptions();
// 设置连接用户名
options.setUserName(userName);
// 设置连接密码
options.setPassword(passWord.toCharArray());
// 设置超时时间,单位为秒
options.setConnectionTimeout(30);
// 设置心跳时间 单位为秒,表示服务器每 30 秒向客户端发送心跳判断客户端是否在线
options.setKeepAliveInterval(30);
// 设置遗嘱消息的话题,若客户端和服务器之间的连接意外断开,服务器将发布客户端的遗嘱信息
options.setWill("willTopic", (mqttClientId + "与服务器断开连接").getBytes(), 0, false);
// 断线自动重连
options.setAutomaticReconnect(true);
// 是否清空 session,设置为 false 表示服务器会保留客户端的连接记录,客户端重连之后能获取到服务器在客户端断开连接期间推送的消息
// 设置为 true 表示每次连接到服务端都是以新的身份
options.setCleanSession(true);
return options;
}
/**
* 关闭MQTT连接
*/
public void close() throws MqttException {
mqttClient.close();
mqttClient.disconnect();
}
/**
* 向某个主题发布消息 默认qos:1
*
* @param topic:发布的主题
* @param msg:发布的消息
*/
public void pub(String topic, String msg) throws MqttException {
MqttMessage mqttMessage = new MqttMessage();
//mqttMessage.setQos(2);
try {
mqttMessage.setPayload(msg.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage());
}
MqttTopic mqttTopic = mqttClient.getTopic(topic);
MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
}
/**
* 向某个主题发布消息
*
* @param topic: 发布的主题
* @param msg: 发布的消息
* @param qos: 消息质量 Qos:0、1、2
*/
public void pub(String topic, String msg, int qos) throws MqttException {
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setQos(qos);
mqttMessage.setPayload(msg.getBytes());
MqttTopic mqttTopic = mqttClient.getTopic(topic);
MqttDeliveryToken token = mqttTopic.publish(mqttMessage);
token.waitForCompletion();
}
/**
* 订阅多个主题 ,此方法默认的的Qos等级为:1
*
* @param topic 主题
*/
public void sub(String[] topic) throws MqttException {
mqttClient.subscribe(topic);
}
/**
* 订阅一个个主题 ,此方法默认的的Qos等级为:1
*
* @param topic 主题
*/
public void sub(String topic) throws MqttException {
mqttClient.subscribe(topic);
}
/**
* 订阅某一个主题,可携带Qos
*
* @param topic 所要订阅的主题
* @param qos 消息质量:0、1、2
*/
public void sub(String topic, int qos) throws MqttException {
mqttClient.subscribe(topic, qos);
}
public String getMqttClientId() {
return mqttClientId;
}
public void setMqttClientId(String mqttClientId) {
this.mqttClientId = mqttClientId;
}
}
MqttClientCallback
Mqtt的回调
package com.huibo.acquisition.mqtt.callback;
import com.huibo.acquisition.mqtt.listener.MqttClientListener;
import com.huibo.acquisition.service.DeviceService;
import com.huibo.acquisition.utils.ApplicationContextUtil;
import com.huibo.acquisition.utils.ServerUtil;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import java.nio.charset.StandardCharsets;
import static com.huibo.acquisition.constant.GlobalConstant.DEVICE_DOWN_TOPIC;
/**
* @author :cola
* @description:Mqtt的回调
* @date :2023/7/18 10:43
*/
@Slf4j
public class MqttClientCallback implements MqttCallbackExtended {
/**
* 系统的mqtt客户端id
*/
private String mqttClientId;
public MqttClientCallback(String mqttClientId) {
this.mqttClientId = mqttClientId;
}
/**
* TODO subscribe订阅后得到的消息会执行到这里
*/
@Override
public void messageArrived(String topic, MqttMessage message) {
String data = new String(message.getPayload(), StandardCharsets.UTF_8);
System.out.printf("接收消息主题: %s%n", topic);
System.out.printf("接收消息Qos: %d%n", message.getQos());
// message.toString() 也可以拿到消息内容
System.out.printf("接收消息内容: %s%n", data);
System.out.printf("接收消息retained: %b%n", message.isRetained());
//处理消息
ApplicationContextUtil.getBean(DeviceService.class).handleMqttMessage(data);
}
/**
* MQTT 断开连接会执行此方法
*/
@Override
public void connectionLost(Throwable cause) {
log.info("断开了MQTT连接 :{}", cause.getMessage());
}
/**
* publish发布成功后会执行到这里
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
log.info("发布消息成功");
}
@Override
public void connectComplete(boolean reconnect, String serverURI) {
MqttClientListener.mqttClients.forEach((k, v) -> {
try {
log.info("Mqtt重连成功:" + v.getMqttClientId() + " 已连接");
v.sub(String.format(DEVICE_DOWN_TOPIC, ServerUtil.getCPUSerial()));
} catch (MqttException e) {
e.printStackTrace();
}
});
}
}