Spring boot 集成EMQ

最近项目中使用了EMQ,将使用情况记录总结下。

1、pom配置,引入相关jar

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mqtt</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-stream</artifactId>
</dependency>

2、MQTT服务器信息配置

配置MQTT服务器基本信息,在springBoot配置文件application.properties中配置,添加如下:

config:
  mqtt:
    clientId: client002
    host: tcp://1.10.2.4:1883
    password: xxx@xxx
    timeout: 2000
    username: system
    #默认订阅的topic $share/{group}/固定写法 相同group同一消息只会消费一次
    topic-list:
      - $share/aaa/data/+/upload
      - $share/aaa/data/+/topic2
      - $share/aa/data/+/topic3
  data-save:
    enable: true
  #线程池相关配置
  pool:
    single:
      poolSize: 200
      coreSize: 1
      keepAliveSeconds: 0
      maxSize: 1
      namePrefix: single-pool-
      queueCapacity: 2000

3、创建MQTT相关配置类

import com.xx.MqttConfigProperties;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.UUID;

@Component
@Slf4j
public class MqttPushClient {

    private static MqttClient client;
    @Autowired
    private PushCallback pushCallback;

    @Autowired
    private MqttConfigProperties mqttConfigProperties;

    public static MqttClient getClient() {
        return client;
    }

    /**
     * 发布,默认qos为0,非持久化
     */
    public static void publish(String topic, String pushMessage) {
        publish(0, false, topic, pushMessage);
    }

    /**
     * 发布
     */
    public static void publish(int qos, boolean retained, String topic, String pushMessage) {
        MqttMessage message = new MqttMessage();
        message.setQos(qos);
        message.setRetained(retained);
        message.setPayload(pushMessage.getBytes());
        MqttTopic mTopic = MqttPushClient.getClient().getTopic(topic);
        if (null == mTopic) {
            log.error("topic not exist");
            throw new RuntimeException("topic not exist");
        }
        MqttDeliveryToken token;
        try {
            token = mTopic.publish(message);
            token.waitForCompletion();
        } catch (MqttException e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 订阅某个主题,qos默认为0
     */
    public static void subscribe(String topic) {
        subscribe(topic, 0);
    }

    /**
     * 订阅某个主题
     */
    public static void subscribe(String topic, int qos) {
        try {
            log.info("开始订阅topic:{},qos:{}", topic, qos);
            MqttPushClient.getClient().subscribe(topic, qos);
            log.info("{},订阅成功,qos:{}", topic, qos);
        } catch (MqttException e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 设置mqtt连接参数
     */
    public MqttConnectOptions setMqttConnectOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName(mqttConfigProperties.getUsername());
        options.setPassword(mqttConfigProperties.getPassword().toCharArray());
        options.setConnectionTimeout(mqttConfigProperties.getTimeout());
        options.setKeepAliveInterval(mqttConfigProperties.getKeepalive());
        options.setCleanSession(false);
        return options;
    }

    /**
     * 连接emq服务器
     */
    public void connect() {
        try {
            if (client == null) {
                //clientId重复会导致服务启动失败
                client = new MqttClient(mqttConfigProperties.getHost(), mqttConfigProperties.getClientId() + UUID.randomUUID(), new MemoryPersistence());
                client.setCallback(pushCallback);
            }
            MqttConnectOptions mqttConnectOptions = setMqttConnectOptions();

            if (client.isConnected()) {
                client.disconnect();
            }
            client.connect(mqttConnectOptions);

        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    @Bean
    public MqttPushClient getMqttPushClient() {
        this.connect();
        return this;
    }

}

4、读取配置信息

Spring boot 读取自定义properties配置文件,同时初始化配置类。

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.List;

@Configuration
@ConfigurationProperties("config.mqtt")
@Data
@Slf4j
public class MqttConfigProperties {

    private String host;
    private String clientId;
    private String username;
    private String password;
    private List<String> topicList;
    private int timeout;
    private int keepalive;

    public void setTopicList(List<String> topicList) {
        this.topicList = topicList;
    }

    /**
     * @return 根据配置文件选举emq服务器
     */
    public String getHost() {
        String machineIp = "";
        try {
            machineIp = getIpAdd();
            log.info("【emq注册】 获取到本机ip:{}", machineIp);
        } catch (UnknownHostException | SocketException e) {
            log.error("【emq注册】本机ip获取失败:{},{}", e.getMessage(), e);
        }
        if (host.contains("|")) {
            log.info("【emq注册】开始匹配 获取到配置为:{}", host);
            String[] pieceDataArray = host.split("\\|");
            for (String pieceData : pieceDataArray) {
                String ip = pieceData.split(",")[0];
                String emqIp = pieceData.split(",")[1];
                if (ip.equals(machineIp)) {
                    log.info("【emq注册】匹配成功,开始注册,目标emqIp:{}", emqIp);
                    return emqIp;
                }
            }
        }
        log.warn("【emq注册】配置文件未更新或格式错误,直接返回配置");
        return host;
    }

    public String getIpAdd() throws SocketException, UnknownHostException {
        String ip = "";
        for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
            NetworkInterface intf = en.nextElement();
            String name = intf.getName();
            if (!name.contains("docker") && !name.contains("lo")) {
                for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
                    //获得IP
                    InetAddress inetAddress = enumIpAddr.nextElement();
                    if (!inetAddress.isLoopbackAddress()) {
                        String ipaddress = inetAddress.getHostAddress();
                        if (!ipaddress.contains("::") && !ipaddress.contains("0:0:") && !ipaddress.contains("fe80")) {
                            log.info("【获取ip】:{}", ipaddress);
                            if (!"127.0.0.1".equals(ip)) {
                                ip = ipaddress;
                            }
                        }
                    }
                }
            }
        }
        return ip;
    }
}

5、消费监听类

在messageArrived方法中会收到mqtt中的数据,需要处理的话就在当前方法中操作


import com.alibaba.fastjson.JSONObject;
import com.enoesis.dgs.config.mqtt.MqttMessageHandler;
import com.enoesis.dgs.config.threadPool.SinglePoolProperties;
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.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

@Component
@Slf4j
public class PushCallback implements MqttCallback {

    @Autowired
    private MqttPushClient mqttPushClient;

    @Autowired
    private MqttMessageHandler mqttMessageHandler;

    @Autowired
    private  Map<String, ThreadPoolExecutor> singlePoolMap;

    @Autowired
    private  SinglePoolProperties singlePoolProperties;

    @Override
    public void connectionLost(Throwable cause) {
        // 连接丢失后,一般在这里面进行重连
        log.error("连接断开,尝试重连..");
        long reconnectTimes = 1;
        while (true) {
            try {
                if (MqttPushClient.getClient().isConnected()) {
                    log.info("mqtt重连成功");
                    return;
                }
                log.info("mqtt重连次数 = {} 再试一次...", reconnectTimes++);
                mqttPushClient.connect();
            } catch (Exception e) {
                log.error("mqtt重连失败:{}{}", e.getMessage(), e);
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
        }
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token) {
        log.info("deliveryComplete: " + token.isComplete());
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        //增加异常捕获,防止rocketMq异常导致mqtt重新连接
        try {
            String[] topicSplit = topic.split("/");
            String gatewayId = topicSplit[1];
            String messageType = topicSplit[2];
            JSONObject jsonObject = buildPayload(message.getPayload(), gatewayId);
            String deviceId = jsonObject.getString("device_id");
            ThreadPoolExecutor threadPoolExecutor = switchPoolExecutor(deviceId);
            if (StringUtils.isBlank(deviceId)) {
                log.error("分区键 device_id 不存在,message:{}", message);
                throw new RuntimeException("分区键 device_id 不存在");
            }
            threadPoolExecutor.execute(() ->mqttMessageHandler.pushToRocket(messageType, jsonObject));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        // subscribe后得到的消息会执行到这里面
        log.info("接收消息主题{},qos:{},payload:{} ", topic, message.getQos(), new String(message.getPayload()));
    }
    
    public ThreadPoolExecutor switchPoolExecutor(String deviceId) {
        int h;
        int i = (Math.abs(h = deviceId.hashCode()) ^ (h >>> 16)) % singlePoolProperties.getPoolSize();
        return singlePoolMap.get(singlePoolProperties.getSinglePoolName(i));
    }

    /**
     * 构造rocketMQ的payload
     *
     * @param mqttPayload mqttPayload
     * @param gatewayId   网关id
     * @return 用于推送rocketMQ的payload
     */
    private JSONObject buildPayload(byte[] mqttPayload, String gatewayId) {
        String message = new String(mqttPayload);
        JSONObject jsonObject = JSONObject.parseObject(message);
        if (jsonObject.getString("client_id") == null) {
            log.warn("消息中没有client_id,自动补充。消息:{}", message);
            jsonObject.put("client_id", gatewayId);
        }
        return jsonObject;
    }
}

6、订阅

import com.x.MqttConfigProperties;
import lombok.AllArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.List;


@Component
@AllArgsConstructor
public class Subscribe implements ApplicationRunner {

    private final MqttConfigProperties mqttConfigProperties;

    /**
     * 设置默认订阅的topic
     */
    @Override
    public void run(ApplicationArguments args) {
        List<String> topicList = mqttConfigProperties.getTopicList();
        topicList.forEach(MqttPushClient::subscribe);
    }
}

7.controller样例


@Controller
public class MqttController {
    @Autowired
    private MqttPushClient mqttPushClient;
    @RequestMapping("/hello")
    @ResponseBody
    public String sendHello(){
        String kdTopic = "topic1";
        mqttPushClient.publish(0, false, kdTopic, "15345715326");
        mqttPushClient.subscribe(kdTopic);
        return "123";
    }
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值