1、获取Spring对象上下文
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* @author by yihang
* @Date 2022/11/24 8:37
* @Description
*/
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 通过name获取 Bean.
*
* @param name
* @return
*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
/**
* 通过class获取Bean.
*
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name及Clazz返回指定的Bean
*
* @param name
* @param clazz
* @param <T>
* @return
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
/**
* 获取applicationContext
*
* @return
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}
2、定义mqtt回调处理
import cn.hutool.core.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class MQTTCallBack implements MqttCallback {
private MQTTCustomClient customClient;
public MQTTCallBack(MQTTCustomClient customClient) {
this.customClient = customClient;
}
// /**
// * 丢失连接,可在这里做重连
// * 只会调用一次
// *
// * @param throwable
// */
// @Override
// public void connectionLost(Throwable throwable) {
// log.error("MQTT连接断开,5秒之后尝试重连: {}", throwable.getMessage());
// long reconnectTimes = 1;
// while (true) {
// try {
// if (customClient.isConnected()) {
// //判断已经重新连接成功 需要重新订阅主题 可以在这个if里面订阅主题 或者 connectComplete(方法里面) 看你们自己选择
// log.warn("MQTT已重新连接 重新订阅成功……");
// return;
// }
// reconnectTimes += 1;
// log.warn("MQTT reconnect times = {} try again... MQTT重新连接时间 {}", reconnectTimes, reconnectTimes);
// customClient.reConnect();
// } catch (Exception e) {
// log.error("MQTT连接异常,异常信息:{}", e.getMessage());
// e.printStackTrace();
// }
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e1) {
// e1.printStackTrace();
// }
// }
// }
/**
* 丢失连接,可在这里做重连
* 只会调用一次
*
* @param throwable
*/
@Override
public void connectionLost(Throwable throwable) {
log.error("MQTT连接断开,尝试重连: {}", throwable.getMessage());
if (null == customClient || !customClient.isConnected()) {
customClient.reConnect();
}
}
/**
* @param topic
* @param mqttMessage
* @throws Exception subscribe后得到的消息会执行到这里面
*/
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
log.info("接收MQTT消息主题 : {},消息内容 : {}", topic, new String(mqttMessage.getPayload()));
try {
if (topic.equals("Visitor/attendance/record")) {
String msgPayload = new String(mqttMessage.getPayload(), CharsetUtil.UTF_8);
log.info("已获取到订阅数据:{}", topic);
} else {
log.info("非订阅主题!-> 消息主题:{}", topic);
}
} catch (Exception e) {
log.info("订阅消息主题执行异常:{}", e.getMessage());
e.printStackTrace();
}
}
/**
* 连接成功后的回调 可以在这个方法执行 订阅主题 生成Bean的 MqttConfiguration方法中订阅主题 出现bug
* 重新连接后 主题也需要再次订阅 将重新订阅主题放在连接成功后的回调 比较合理
*
* @param reconnect
* @param serverURI
*/
// @Override
// public void connectComplete(boolean reconnect, String serverURI) {
// log.info("MQTT 连接成功,连接方式:{}", reconnect ? "重连" : "直连");
// //订阅主题
//// mqttConfiguration.subscribe(mqttConfiguration.topic1, 1);
//// mqttConfiguration.subscribe(mqttConfiguration.topic2, 1);
//// mqttConfiguration.subscribe(mqttConfiguration.topic3, 1);
//// mqttConfiguration.subscribe(mqttConfiguration.topic4, 1);
// try {
// client.subscribe(serverURI, 1);
// } catch (MqttException e) {
// e.printStackTrace();
// }
// }
/**
* 消息到达后
* subscribe后,执行的回调函数
*
* @param s
* @param mqttMessage
* @throws Exception
*/
/**
* publish后,配送完成后回调的方法
*
* @param iMqttDeliveryToken
*/
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.info("MQTT消息配送已完成,配送结果:{}", iMqttDeliveryToken.isComplete());
MqttAsyncClient client = (MqttAsyncClient) iMqttDeliveryToken.getClient();
log.info("MQTT消息发布成功,客户端ID:{} ", client.getClientId());
}
}
3、mqtt配置类
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
@Slf4j
@Data
@Configuration
public class MQTTConfiguration {
@Value("${mqtt.host}")
private String hostUrl;
@Value("${mqtt.username}")
private String username;
@Value("${mqtt.password}")
private String password;
@Value("${mqtt.clientId}")
private String clientId;
@Value("${mqtt.timeout}")
private int timeOut;
@Value("${mqtt.keepalive}")
private int keepAlive;
@Value("${mqtt.topic}")
String topic;
/**
* 客户端对象
*/
@Resource
private MQTTCustomClient mqttCustomClient;
@PostConstruct
public MQTTCustomClient getMqttCustomClient() {
try {
mqttCustomClient.connect(hostUrl, clientId, username, password, timeOut, keepAlive);
} catch (MqttException e) {
log.error("MQTT连接异常,异常信息:{}", e.getMessage());
e.printStackTrace();
}
String mqtt_topic[] = topic.split(",");
for(int i = 0; i < mqtt_topic.length; i++){
mqttCustomClient.subscribe(mqtt_topic[i], 2);//订阅主题
}
return mqttCustomClient;
}
}
4、定义Controller控制器推送mqtt测试消息
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.idkj.access.common.exception.R;
import lombok.extern.slf4j.Slf4j;
import oracle.jdbc.proxy.annotation.Post;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
/**
* @author by yihang
* @Date 2022/11/23 17:41
* @Description
*/
@Slf4j
@RestController
@RequestMapping("/mqtt")
public class MQTTController {
@Autowired
private MQTTCustomClient customClient;
@Value("${mqtt.host}")
private String hostUrl;
@Value("${mqtt.username}")
private String username;
@Value("${mqtt.password}")
private String password;
@Value("${mqtt.clientId}")
private String clientId;
@Value("${mqtt.timeout}")
private int timeOut;
@Value("${mqtt.keepalive}")
private int keepAlive;
@Value("${mqtt.topic}")
String topic;
/**
* MQTT消息推送
* @param jo
* @return
*/
@PostMapping("/sendMessage")
public R sendMessage(@RequestBody JSONObject jo){
try {
jo.put("publishTime", DateUtil.formatDateTime(new Date()));
//发布消息 topic 是你要发送到那个通道里面的主题 比如我要发送到topic2主题消息
boolean publish = customClient.publish(JSON.toJSONString(jo), topic, 2, false);
if (publish) {
return R.ok("发送成功");
}
return R.error("发送失败");
} catch (Exception e) {
log.error("MQTT消息推送异常:{}", e.getMessage());
e.printStackTrace();
return R.error(e.getMessage());
}
}
/**
* 连接MQTT服务器
* @return
*/
@PostMapping("/connect")
public String connect(@RequestBody JSONObject jo){
String clientId = jo.getString("clientId");
try {
boolean connect = customClient.connect(hostUrl, clientId, username, password, timeOut, keepAlive);
if (connect) {
return clientId + "成功连接MQTT服务器";
}
} catch (MqttException e) {
e.printStackTrace();
}
return clientId + "连接MQTT服务器失败";
}
/**
* 订阅MQTT服务器
* @return
*/
@PostMapping("/subscribe")
public String subscribe(@RequestBody JSONObject jo){
boolean subscribe = customClient.subscribe(jo.getString("topic"), 2);
if (subscribe) {
return jo.getString("topic") + "订阅成功!";
}
return jo.getString("topic") + "订阅失败!";
}
/**
* 断开MQTT订阅
* @return
*/
@GetMapping("/unSubscribe")
public String unSubscribe(@RequestBody JSONObject jo){
boolean unSubscribe = customClient.unSubscribe(jo.getString("topic"));
if (unSubscribe) {
return jo.getString("topic") + "断开MQTT订阅成功!";
}
return jo.getString("topic") + "断开MQTT订阅失败!";
}
}
5、定义mqtt客户端
import com.alibaba.fastjson.JSON;
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 javax.annotation.PreDestroy;
@Slf4j
@Component
public class MQTTCustomClient {
private static MqttClient client;
private static MqttClient getClient() {
return client;
}
private static void setClient(MqttClient client) {
MQTTCustomClient.client = client;
}
/**
* 连接mqtt服务端,得到MqttClient连接对象
*/
public boolean connect(String host, String clientID, String username, String password, int timeout, int keepalive) throws MqttException {
MqttClient client;
try {
client = new MqttClient(host, clientID, new MemoryPersistence());
MqttConnectOptions mqttConnectOptions = setMqttConnectOptions(username, password, timeout, keepalive);
MQTTCustomClient.setClient(client);
client.setCallback(new MQTTCallBack(MQTTCustomClient.this));
client.connect(mqttConnectOptions);
return true;
} catch (Exception e) {
log.error("连接MQTT服务端异常:{}", e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* 设置mqtt连接参数
*
* @param username
* @param password
* @param timeout
* @param keepalive
* @return
*/
public MqttConnectOptions setMqttConnectOptions(String username, String password, int timeout, int keepalive) {
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(username);
options.setPassword(password.toCharArray());
options.setConnectionTimeout(timeout);
options.setKeepAliveInterval(keepalive);
options.setCleanSession(true);
// 是否开启自动重连
options.setAutomaticReconnect(false);
return options;
}
/**
* 断开连接
*/
@PreDestroy
public void disConnect() {
try {
MQTTCustomClient.getClient().disconnect();
} catch (MqttException e) {
log.error("断开连接产生异常,异常信息{}", e.getMessage());
e.printStackTrace();
}
}
/**
* 重连
*/
public void reConnect() {
try {
MQTTCustomClient.getClient().reconnect();
log.info("重新连接成功");
} catch (MqttException e) {
log.error("重连失败,失败原因: {}", e.getMessage());
}
}
/**
* 发布消息
*
* @param pushMessage
* @param topic
* @param qos 默认qos为0,非持久化
* @param retained:留存
*/
public boolean publish(String pushMessage, String topic, int qos, boolean retained) {
MqttMessage message = new MqttMessage();
message.setQos(qos);
message.setRetained(retained);
message.setPayload(pushMessage.getBytes());
MqttTopic mTopic = MQTTCustomClient.getClient().getTopic(topic);
if (null == mTopic) {
log.error("订阅主题不存在:{}", topic);
}
log.info("即将推送主题:{}", mTopic.getName());
MqttDeliveryToken token;
synchronized (this) {//注意:这里一定要同步,否则,在多线程publish的情况下,线程会发生死锁
try {
token = mTopic.publish(message);
token.waitForCompletion(1000L);
return true;
} catch (MqttPersistenceException e) {
log.error("MQTT发生持久性异常:{}", e.getMessage());
e.printStackTrace();
return false;
} catch (MqttException e) {
log.error("MQTT发布消息异常:{}", e.getMessage());
e.printStackTrace();
return false;
}
}
}
/**
* 订阅
*
* @param topic
* @param qos
*/
public boolean subscribe(String topic, int qos) {
log.info("开始订阅主题:{}", topic);
try {
MQTTCustomClient.getClient().subscribe(topic, qos);
return true;
} catch (MqttException e) {
log.error("订阅主题失败,错误报文:{},订阅主题:{},qos:{}", e.getMessage(), topic, qos);
e.printStackTrace();
return false;
}
}
public boolean isConnected() {
return MQTTCustomClient.getClient().isConnected();
}
/**
* 取消订阅主题
*
* @param topic 主题名称
*/
public boolean unSubscribe(String topic) {
MqttClient mqttClient = MQTTCustomClient.getClient();
if (mqttClient != null && mqttClient.isConnected()) {
try {
MQTTCustomClient.getClient().unsubscribe(topic);
return true;
} catch (MqttException e) {
e.printStackTrace();
return false;
}
}
log.info("取消订阅失败: {}", topic);
return false;
}
}
6、MQTT调用工具推荐
- redisant.cn