MQTT简单调用案例

74 阅读4分钟

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

www.redisant.cn/mqtt/downlo…

image.png