=============== 发布端================
发布端连接得到sampleClient实例
package com.chalk.mqtt.publish;
import com.chalk.common.constant.MqttConstants;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author gluoh
* @Description 发布客户端连接
* @date 2021/02/26 11:30
*/
@Component
public class PublishConnect {
@Value("${mq.broker}")
private String broker;
public MqttClient connectMqttClient(MqttPublishDto mqttPublishDto, MqttConnectOptions mqttConnectOptions) throws MqttException {
// 内存存储
MemoryPersistence persistence = new MemoryPersistence();
// 创建客户端
MqttClient sampleClient = new MqttClient(broker, MqttConstants.DEVICE_NUM_FLAG+mqttPublishDto.getDeviceNum(), persistence);
// 在重新启动和重新连接时记住状态
mqttConnectOptions.setCleanSession(false);
// 设置回调
sampleClient.setCallback(new PublishCallback());
// 建立连接
sampleClient.connect(mqttConnectOptions);
return sampleClient;
}
}
发布主题的回调类
package com.chalk.mqtt.publish;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
/**
* @author gluoh
* @Description 发布主题的回调类
* @date 2021/02/26 10:46
*/
public class PublishCallback implements MqttCallback {
/**
* 在断开连接时调用
* @param cause
*/
@Override
public void connectionLost(Throwable cause) {
System.out.println("连接断开,可以做重连");
}
/**
* 接收已经预订的发布
* @param topic
* @param message
* @throws Exception
*/
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
// subscribe后得到的消息会执行到这里面
System.out.println("Server 接收消息主题 : " + topic);
System.out.println("Server 接收消息Qos : " + message.getQos());
System.out.println("Server 接收消息内容 : " + new String(message.getPayload()));
}
/**
* 接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。
* 由 MqttClient.connect 激活此回调。
* @param token
*/
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("deliveryComplete---------" + token.isComplete());
}
}
通用发布端DTO
package com.chalk.mqtt.publish;
import lombok.Data;
/**
* @author gluoh
* @Description
* @date 2021/02/26 18:21
*/
@Data
public class MqttPublishDto {
// MQTT Client部分
/** MQTT服务器地址 */
private String broker;
/** 主题 */
private String topic;
/** 消息内容 */
private String message;
/** 账号 */
private String userName;
/** 密码 */
private String password;
/** 客户端ID */
private String clientId;
/** 信号质量 */
private Integer qos;
/*-------------以下部分根据自己业务处理---------------*/
// 平台设备部分
/** 设备号 */
private String deviceNum;
/** 消息版本号 */
private Integer version;
/** 消息名称 */
private String name;
/** 消息值 */
private String value;
// 业务部分
/** 订单ID */
private String orderId;
}
发布接口
package com.chalk.mqtt.publish.service;
import com.chalk.common.util.Result;
import com.chalk.mqtt.publish.MqttPublishDto;
/**
* @author gluoh
* @Description
* @date 2021/02/26 9:50
*/
public interface MqttPublishService {
/**
* 项目中发布主题
* @param mqttPublishDto 发布主题DTO
* @return
*/
Result publish(MqttPublishDto mqttPublishDto);
/**
* 原始发布主题
* @param mqttPublishDto 发布主题DTO
* @return
*/
Result publishOrigin(MqttPublishDto mqttPublishDto);
}
发布接口实现
package com.chalk.mqtt.publish.service.impl;
import com.chalk.common.constant.MqttConstants;
import com.chalk.common.util.RedisUtil;
import com.chalk.common.util.Result;
import com.chalk.dao.CarWashProjectDao;
import com.chalk.dao.OrderDao;
import com.chalk.model.CarWashProject;
import com.chalk.model.Order;
import com.chalk.mqtt.publish.MqttPublishDto;
import com.chalk.mqtt.publish.PublishConnect;
import com.chalk.mqtt.publish.service.MqttPublishService;
import com.chalk.mqtt.util.MqttUtil;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Objects;
/**
* @author gluoh
* @Description 发布主题
* @date 2032/02/26 10:49
*/
@Service
public class MqttPublishServiceImpl implements MqttPublishService {
private static final Logger LOGGER = LoggerFactory.getLogger(MqttPublishServiceImpl.class);
@Autowired
private PublishConnect publishConnect;
@Autowired
private OrderDao orderDao;
@Autowired
private CarWashProjectDao carWashProjectDao;
@Autowired
private RedisUtil redisUtil;
/**
* 发布主题
* @param mqttPublishDto 发布主题DTO
* @return
*/
@Override
public Result publish(MqttPublishDto mqttPublishDto) {
LOGGER.info("进入【MqttServiceImpl】的[publish]方法,入参:{}",mqttPublishDto);
try {
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
// 设置连接的用户名
mqttConnectOptions.setUserName(mqttPublishDto.getDeviceNum());
mqttConnectOptions.setPassword(mqttPublishDto.getName().toCharArray());
MqttClient sampleClient = publishConnect.connectMqttClient(mqttPublishDto, mqttConnectOptions);
// 创建消息
String messageStr = "{\"Version\":"+mqttPublishDto.getVersion()+",\n" +
"\"Data\":[\n" +
"{\"name\":\""+mqttPublishDto.getName()+"\",\"value\":\""+mqttPublishDto.getValue()+"\"}\n" +
"]}";
LOGGER.info("【MQTT】发布的消息:{}", messageStr);
MqttMessage message = new MqttMessage(messageStr.getBytes());
// 设置消息的服务质量
message.setQos(MqttConstants.QOS);
String topic = MqttConstants.WRITE_DATA_TOPIC.replace(MqttConstants.DEVICE_NUM_FLAG, mqttPublishDto.getDeviceNum());
LOGGER.info("【MQTT_TOPIC】:{}",topic);
sampleClient.publish(topic, message);
// 让盒子立即发布监控点主题
String pubNowTopic = MqttConstants.DATA_PUB_NOW_TOPIC.replace(MqttConstants.DEVICE_NUM_FLAG, mqttPublishDto.getDeviceNum());
sampleClient.publish(pubNowTopic, message);
// 断开连接
sampleClient.disconnect();
// 关闭客户端
sampleClient.close();
LOGGER.info("【MQTT】发布的消息<成功>");
return Result.success("消息发布成功!");
} catch (MqttException me) {
LOGGER.error("reasonCode:{},msg:{},loc:{},cause:{}",me.getReasonCode(),me.getMessage(),me.getLocalizedMessage(),me.getCause());
me.printStackTrace();
return Result.fail("消息发布失败,失败原因:{}", me.getMessage());
}
}
/**
* 原始发布主题
* @param mqttPublishDto 发布主题DTO
* @return
*/
@Override
public Result publishOrigin(MqttPublishDto mqttPublishDto) {
LOGGER.info("进入【MqttServiceImpl】的[publishOrigin]方法,入参:{}",mqttPublishDto);
try {
// 内存存储
MemoryPersistence persistence = new MemoryPersistence();
// 创建客户端
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setUserName(mqttPublishDto.getUserName());
mqttConnectOptions.setPassword(mqttPublishDto.getPassword().toCharArray());
MqttClient sampleClient = publishConnect.connectMqttClient(mqttPublishDto, mqttConnectOptions);
// 创建消息
LOGGER.info("【MQTT】发布的消息:{}", mqttPublishDto.getMessage());
MqttMessage message = new MqttMessage(mqttPublishDto.getMessage().getBytes());
// 设置消息的服务质量
Integer qos = mqttPublishDto.getQos() == null ? 1: mqttPublishDto.getQos();
message.setQos(qos);
LOGGER.info("【MQTT_TOPIC】:{}",mqttPublishDto.getTopic());
sampleClient.publish(mqttPublishDto.getTopic(), message);
// 断开连接
sampleClient.disconnect();
// 关闭客户端
sampleClient.close();
LOGGER.info("【MQTT】发布的消息<成功>");
return Result.success("消息发布成功!");
} catch (MqttException me) {
LOGGER.error("reasonCode:{},msg:{},loc:{},cause:{}",me.getReasonCode(),me.getMessage(),me.getLocalizedMessage(),me.getCause());
me.printStackTrace();
return Result.fail("消息发布失败,失败原因:{}", me.getMessage());
}
}
}
=============== 订阅端================
通用订阅端DTO
package com.chalk.mqtt.subscribe;
import lombok.Data;
/**
* @author gluoh
* @Description
* @date 2021/02/26 10:47
*/
@Data
public class MqttSubscribeDto {
/** MQTT服务器地址 */
private String broker;
/** 主题 */
private String topic;
/** 账号 */
private String userName;
/** 密码 */
private String password;
/** 客户端ID */
private String clientId;
/** 是否清除session*/
private boolean cleanSession;
/** 设置连接超时时间 单位秒*/
private Integer connectionTimeOut;
/** 设置会话心跳时间 单位秒*/
private Integer keepAlive;
/** 信号质量 */
private Integer qos;
}
订阅回调类
package com.chalk.mqtt.subscribe;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.chalk.common.constant.CommonConstants;
import com.chalk.common.constant.MqttConstants;
import com.chalk.common.constant.StatusConstants;
import com.chalk.common.util.RedisUtil;
import com.chalk.common.util.Result;
import com.chalk.common.util.ResultCode;
import com.chalk.common.util.SpringContextUtil;
import com.chalk.dao.OrderDao;
import com.chalk.model.Order;
import com.chalk.mqtt.publish.MqttPublishDto;
import com.chalk.mqtt.publish.service.impl.MqttPublishServiceImpl;
import com.chalk.mqtt.util.MqttUtil;
import com.chalk.service.OrderService;
import com.chalk.websocket.server.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Objects;
/**
* @author gluoh
* @Description 订阅回调类
* @date 2021/02/26 11:44
*/
@Slf4j
@Component
public class SubscribeCallback implements MqttCallback {
private MqttSubscribeDto mqttSubscribeDto;
@Autowired
private SubscribeConnect subscribeConnect;
public SubscribeCallback() {
}
public SubscribeCallback(MqttSubscribeDto mqttSubscribeDto) {
this.mqttSubscribeDto = mqttSubscribeDto;
}
@Override
public void connectionLost(Throwable cause) {
log.info("Subscribe connectLost");
subscribeConnect.getConn(mqttSubscribeDto);
}
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) {
String content = new String(mqttMessage.getPayload());
log.info("接收消息主题:{},接收消息Qos:{},接收消息内容:{}",topic,mqttMessage.getQos(),content);
try {
// 取设备号
String deviceNum = topic.split("/")[3];
// 保存到redis
RedisUtil redisUtil = SpringContextUtil.getBean(RedisUtil.class);
redisUtil.set(MqttConstants.GERMANY_MONITOR_TOPIC_REDIS + deviceNum, content, MqttConstants.GERMANY_MONITOR_TOPIC_EXPIRED);
// 发送给前端(手动抬杆业务)
WebSocketServer webSocketServer = SpringContextUtil.getBean(WebSocketServer.class);
webSocketServer.sendMessage(JSONUtil.parseFromMap(Result.success(ResultCode.WEBSOCKET_SUCCESS, content)).toString(),deviceNum.trim());
}catch (Exception e){
e.printStackTrace();
log.info("接收消息主题:{},errorMessage:{}",topic,e.getMessage());
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.info("deliveryComplete:{}",iMqttDeliveryToken.isComplete());
}
}
订阅客户端连接
package com.chalk.mqtt.subscribe;
import com.chalk.common.util.IdGenerator;
import com.chalk.mqtt.ServiceInfoUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author gluoh
* @Description 订阅客户端连接
* @date 2021/02/26 11:32
*/
@Slf4j
@Data
@Component
public class SubscribeConnect {
@Value("${mq.broker}")
private String broker;
@Autowired
private ServiceInfoUtil serviceInfoUtil;
private MqttClient mqttClient;
private MqttConnectOptions mqttConnectOptions;
/**
* 连接emq服务器
*/
public MqttClient getConn(MqttSubscribeDto subscribeDto) {
try {
// host为主机名,clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
String clientId = IdGenerator.getIdStr() + "["+serviceInfoUtil.getPort() + "]";
mqttClient = new MqttClient(broker, clientId, new MemoryPersistence());
// MQTT的连接设置
mqttConnectOptions = new MqttConnectOptions();
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
mqttConnectOptions.setCleanSession(true);
// 设置连接的用户名
mqttConnectOptions.setUserName(subscribeDto.getUserName());
// 设置连接的密码
mqttConnectOptions.setPassword(subscribeDto.getPassword().toCharArray());
// 设置超时时间 单位为秒
mqttConnectOptions.setConnectionTimeout(10);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*keepAliveInterval秒的时间向客户端发送个消息判断客户端是否在线,
// 但这个方法并没有重连的机制
mqttConnectOptions.setKeepAliveInterval(5);
// mqttConnectOptions.setServerURIs(); 配置多个服务器列表,一个挂掉会自动切换到其他正常的服务器,挂掉的服务器正常后,客户端会再次切换回来
mqttClient.setCallback(new SubscribeCallback(subscribeDto));
IMqttToken iMqttToken = mqttClient.connectWithResult(mqttConnectOptions);
log.info("连接服务器成功,IMqttToken:{}",iMqttToken);
} catch (MqttException e) {
log.info("订阅连接MQTT服务器失败,失败原因:{}", e.getMessage());
}
return mqttClient;
}
}
实现ApplicationRunner 接口 重写run方法springboot启动后启动mqtt订阅
package com.chalk.mqtt;
import com.chalk.mqtt.subscribe.MqttSubscribeDto;
import com.chalk.mqtt.subscribe.service.MqttWrapperService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* @author gluoh
* @Description MQTT订阅启动
* @date 2021/02/26 16:34
*/
@Slf4j
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Value("${mq.flexem-topic}")
private String topic;
@Value("${mq.username}")
private String username;
@Value("${mq.password}")
private String password;
@Autowired
private MqttWrapperService mqttWrapperService;
//String TOPIC = "Topic/flexem/fbox/+/system/MonitorData";
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
log.info(" MQTT Subscribe Server 开始...");
MqttSubscribeDto mqttSubscribeDto = new MqttSubscribeDto();
mqttSubscribeDto.setUserName(username);
mqttSubscribeDto.setPassword(password);
mqttSubscribeDto.setTopic(topic);
mqttWrapperService.subscribe(mqttSubscribeDto);
}
}
获取服务端口号
package com.chalk.mqtt;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.context.WebServerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* @author gluoh
* @Description 监听WebServerInitializedEvent事件,用来获取Servlet容器初始化的端口
* @date 2021/02/26 12:24
*/
@Slf4j
@Component
public class ServiceInfoUtil implements ApplicationListener<WebServerInitializedEvent> {
private static WebServerInitializedEvent event;
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
ServiceInfoUtil.event = event;
}
public int getPort() {
return event.getWebServer().getPort();
}
}