SpringBoot使用MQTT订阅、发布、动态新增/移除连接/订阅

一、 导入依赖


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

二、Yml文件配置


mqtt:
  client-id-prefix: dev
  server-uris: tcp://192.168.1.16:1883
  username: admin
  password: admin123
  keep-alive-interval: 2
  message-mapper: com.panda.mqtt.component.support.MqttJsonMessageMapper #自定义消息转换器
  model-packages: com.panda.mqtt.model.toserver #
  sub-topics:
    - $SYS/brokers/+/clients/#  
    - iot_server/#               
    - iot_client/test123/#      

三、 Config文件配置

import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import com.panda.mqtt.component.support.BytesMessageConverter;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mapping.BytesMessageMapper;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.MqttMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;

@EnableIntegration
@Configuration
@ConfigurationProperties(prefix = "mqtt")
@IntegrationComponentScan(basePackages = "com.panda.mqtt")
public class MqttConfig {

    private String[] serverUris;
    private String username;
    private char[] password;
    private int keepAliveInterval;
    private String[] subTopics;
    private Class<? extends BytesMessageMapper> messageMapper;
    private String clientIdPrefix;
    private String modelPackages;
    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(serverUris);
        options.setUserName(username);
        options.setPassword(password);
        options.setKeepAliveInterval(keepAliveInterval);
        factory.setConnectionOptions(options);
        return factory;
    }

    @Bean
    public MqttMessageConverter bytesMessageConverter() throws NoSuchMethodException {
        BytesMessageMapper bytesMessageMapper = BeanUtils.instantiateClass(messageMapper.getConstructor(String.class), modelPackages);
        return new BytesMessageConverter(bytesMessageMapper);
    }

    @Bean
    @ServiceActivator(inputChannel = "mqttOutboundChannel")
    public MessageHandler mqttOutbound(MqttMessageConverter mqttMessageConverter) {
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientIdPrefix + "_outbound", mqttClientFactory());
        messageHandler.setConverter(mqttMessageConverter);
        messageHandler.setCompletionTimeout(5000);
        messageHandler.setAsync(true);
        return messageHandler;
    }

    /**
     * 在默认连接地址动态订阅主题
     * @param mqttMessageConverter 自定义转换器
     *  使用说明:
     * 注入对应类
     * @Autowired
     * private MqttPahoMessageDrivenChannelAdapter messageProducer.
     * 添加订阅:messageProducer.addTopic( topic:"/max/a",qos:2):
     * 移除订阅:messageProducer.removeTopic( topic:"/max/a"):
     * @return MqttPahoMessageDrivenChannelAdapter
     */
    @Bean
    public MqttPahoMessageDrivenChannelAdapter inbound(MqttMessageConverter mqttMessageConverter) {
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientIdPrefix + "_inbound_add", mqttClientFactory());
        adapter.setConverter(mqttMessageConverter);
        adapter.setOutputChannel(mqttInboundChannel());
        adapter.setQos(1);
        return adapter;
    }

    @Bean
    public MessageProducer mqttInbound(MqttMessageConverter mqttMessageConverter) {
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientIdPrefix + "_inbound", mqttClientFactory(), subTopics);
        adapter.setConverter(mqttMessageConverter);
        adapter.setOutputChannel(mqttInboundChannel());
        adapter.setCompletionTimeout(5000);
        adapter.setQos(1);
        return adapter;
    }

    @Bean
    public MessageChannel mqttOutboundChannel() {
        return new DirectChannel();
    }

    @Bean
    public MessageChannel mqttInboundChannel() {
        return new DirectChannel();
    }

    public String[] getServerUris() {
        return serverUris;
    }

    public void setServerUris(String[] serverUris) {
        this.serverUris = serverUris;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public char[] getPassword() {
        return password;
    }

    public void setPassword(char[] password) {
        this.password = password;
    }

    public int getKeepAliveInterval() {
        return keepAliveInterval;
    }

    public void setKeepAliveInterval(int keepAliveInterval) {
        this.keepAliveInterval = keepAliveInterval;
    }

    public String[] getSubTopics() {
        return subTopics;
    }

    public void setSubTopics(String[] subTopics) {
        this.subTopics = subTopics;
    }

    public Class<? extends BytesMessageMapper> getMessageMapper() {
        return messageMapper;
    }

    public void setMessageMapper(Class<? extends BytesMessageMapper> messageMapper) {
        this.messageMapper = messageMapper;
    }

    public String getClientIdPrefix() {
        return clientIdPrefix;
    }

    public void setClientIdPrefix(String clientIdPrefix) {
        this.clientIdPrefix = clientIdPrefix;
    }

    public String getModelPackages() {
        return modelPackages;
    }

    public void setModelPackages(String modelPackages) {
        this.modelPackages = modelPackages;
    }
}

四、消息回调


import com.panda.mqtt.commons.JsonUtils;
import com.panda.mqtt.component.model.Connected;
import com.panda.mqtt.component.model.Disconnected;
import com.panda.mqtt.component.model.MqttRequest;
import com.panda.mqtt.component.model.MqttResponse;
import com.panda.mqtt.model.toclient.SettingUpdate;
import com.panda.mqtt.model.toserver.CommonResponse;
import com.panda.mqtt.service.impl.MessageService;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Service;

/**
 * 默认连接回调处理
 */
@Service
public class MqttHandler implements MessageHandler {

    private static final Logger log = LoggerFactory.getLogger(MqttHandler.class);

    @Autowired
    private MessageService messageService;

    @ServiceActivator(inputChannel = "mqttInboundChannel")
    @Override
    public void handleMessage(Message<?> message) throws MessagingException {
        String topic = message.getHeaders().get(MqttHeaders.TOPIC, String.class);
        Object payload = message.getPayload();
        System.out.println("topic:" + topic + "\t" + "payload:" + payload + "\n");

       
        }
    }

动态连接回调


import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.stereotype.Service;

/**
 * 动态订阅主题回调实现
 */
@Service
public class SubHandler implements IMqttMessageListener {

    /**
     * 接收到消息
     * @param s 主题名称
     * @param mqttMessage 消息内容
     * @throws Exception 异常
     */
    @Override
    public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
        System.out.println("messageArrived: " + mqttMessage);
        System.out.println("s:" + s);
    }
}

五、 配置动态连接/断开以及动态订阅/移除订阅


import com.panda.mqtt.component.model.DynamicConnected;
import com.panda.mqtt.component.model.DynamicSubscribe;
import com.panda.mqtt.endpoint.SubHandler;
import com.panda.mqtt.service.impl.MqttService;
import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.stereotype.Service;

import java.util.concurrent.ConcurrentHashMap;

@Service
public class MqttServiceImpl implements MqttService {

    /**
     * 存放的已连接的设备
     */
    private final ConcurrentHashMap<String, IMqttClient> mqttClientMap = new ConcurrentHashMap<>();


    /**
     * 连接
     * @param connected 连接参数
     */
    @Override
    public void connect(DynamicConnected connected) {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(new String[]{getMqttUrl(connected.getIp(), connected.getPort())});
        options.setUserName(connected.getUsername());
        options.setPassword(connected.getPassword());
        options.setCleanSession(connected.getCleanSession());
        options.setKeepAliveInterval(connected.getKeepAliveInterval());
        String key = getKey(connected.getIp(), connected.getPort(), connected.getClientId(), connected.getUsername());
        mqttClientMap.computeIfAbsent(key,k->{
            try {
                MqttClient mqttClient = new MqttClient(getMqttUrl(connected.getIp(), connected.getPort()), connected.getClientId());
                mqttClient.connect(options);
                if (mqttClient.isConnected()) {
                    System.out.println("连接成功");
                    return mqttClient;
                }
                System.out.println(getMqttUrl(connected.getIp(), connected.getPort()));
                System.out.println("灭联系"+mqttClient.isConnected());
            } catch (MqttException e) {
                throw new RuntimeException(e);
            }
            return null;
        });
    }

    /**
     * 断开连接
     * @param connected 断开参数
     */
    @Override
    public void disconnect(DynamicConnected connected) {
        String key = getKey(connected.getIp(), connected.getPort(), connected.getClientId(), connected.getUsername());
        if (!key.isEmpty()) {
            IMqttClient iMqttClient = mqttClientMap.get(key);
            if (iMqttClient != null && iMqttClient.isConnected()) {
                try {
                    iMqttClient.disconnect();
                } catch (MqttException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 订阅主题
     * @param subscribe 订阅参数
     */
    @Override
    public void subscribe(DynamicSubscribe subscribe) {
        String key = getKey(subscribe.getIp(), subscribe.getPort(), subscribe.getClientId(), subscribe.getUsername());
        if (!key.isEmpty()) {
            IMqttClient iMqttClient = mqttClientMap.get(key);
            if (iMqttClient != null && iMqttClient.isConnected()) {
                try {
                    iMqttClient.subscribe(subscribe.getTopic(), subscribe.getQos(),new SubHandler());
                } catch (MqttException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 移除订阅
     * @param subscribe 移除参数
     */
    @Override
    public void unsubscribe(DynamicSubscribe subscribe) {
        String key = getKey(subscribe.getIp(), subscribe.getPort(), subscribe.getClientId(), subscribe.getUsername());
        if (!key.isEmpty()) {
            IMqttClient iMqttClient = mqttClientMap.get(key);
            if (iMqttClient != null && iMqttClient.isConnected()) {
                try {
                    iMqttClient.unsubscribe(subscribe.getTopic());
                } catch (MqttException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }


    /**
     * 获取MQTT连接地址
     * @param ip IP
     * @param port 端口
     * @return 地址
     */
    private String getMqttUrl(String ip, Integer port) {
        return "tcp://"+ip+":"+port;
    }


    /**
     * 获取存储的Key
     * @param ip IP
     * @param port 端口
     * @param clientId 唯一ID
     * @param username 用户名
     * @return Key
     */
    private String getKey(String ip, Integer port, String clientId,String username) {
        return ip+"_"+port+"_"+clientId+"_"+username;
    }
}

六、 配置默认连接发送


import com.panda.mqtt.component.annotations.Topic;
import com.panda.mqtt.component.model.MqttRequest;
import com.panda.mqtt.component.model.MqttResponse;
import com.panda.mqtt.endpoint.MqttProducer;
import com.panda.mqtt.service.impl.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;


@Service
public class MessageServiceImpl implements MessageService {

    private static final Mono<MqttResponse> Rejected = Mono.error(new RejectedExecutionException("客户端暂未响应,请勿重复发送"));

    private Map<String, MonoSink<MqttResponse>> topicSubscribers = new ConcurrentHashMap<>();

    @Autowired
    private MqttProducer mqttProducer;

    @Override
    public void notify(String deviceId, Object payload) {
        Topic annotation = payload.getClass().getAnnotation(Topic.class);
        String value = annotation.value();
        String topic = value.replace("{deviceId}", deviceId);
        mqttProducer.sendTo(topic, payload);
    }

    @Override
    public Mono<MqttResponse> request(String deviceId, MqttRequest payload) {
        Class<? extends MqttRequest> clazz = payload.getClass();
        Topic annotation = clazz.getAnnotation(Topic.class);
        String value = annotation.value();
        String topic = value.replace("{deviceId}", deviceId);

        long messageId = payload.getMessageId();
        if (messageId == 0L)
            payload.setMessageId(messageId = System.currentTimeMillis());

        String key = getKey(deviceId, messageId);
        Mono<MqttResponse> receive = subscribe(key);
        if (receive == null)
            return Rejected;

        return Mono.create(sink -> {
            mqttProducer.sendTo(topic, payload);
            sink.success();
        }).then(receive).doFinally(signal -> unSubscribe(key));
    }

    @Override
    public boolean response(Message<MqttResponse> message) {
        MqttResponse payload = message.getPayload();
        MonoSink<MqttResponse> sink = topicSubscribers.get(getKey(payload.getDeviceId(), payload.getMessageId()));
        if (sink != null) {
            sink.success(payload);
            return true;
        }
        return false;
    }

    private String getKey(String deviceId, long messageId) {
        return deviceId + "/" + messageId;
    }

    private Mono<MqttResponse> subscribe(String key) {
        if (!topicSubscribers.containsKey(key)) {
            return Mono.create(sink -> topicSubscribers.put(key, sink));
        }
        return null;
    }

    private void unSubscribe(String key) {
        topicSubscribers.remove(key);
    }
}

七、测试


@RestController
@RequestMapping(path = "mqtt", method = {RequestMethod.GET})
public class MqttController {

    @Autowired
    private MessageService messageService;

    @RequestMapping(path = "send")
    public Mono<MqttResponse> sendMessage(@RequestParam(defaultValue = "test123") String deviceId,
                                          SettingUpdate settingUpdate) {
        Mono<MqttResponse> result = messageService.request(deviceId, settingUpdate);

        //TODO 模拟客户端回复消息
        Mono.create(monoSink -> {
            CommonResponse commonResponse = new CommonResponse(settingUpdate.getMessageId(), deviceId, (byte) 9);
            messageService.notify(deviceId, commonResponse);
        }).delaySubscription(Duration.ofMillis(10L)).subscribe();

        return result.timeout(Duration.ofSeconds(10L));
    }

    @RequestMapping(path = "send/notice")
    public void sendNotice(@RequestParam(defaultValue = "test123") String deviceId,
                           Location location) {


        messageService.notify(deviceId, location);
        System.out.println("发送成功-----info");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

以下是动态添加部分


@RestController
@RequestMapping(path = "dy", method = {RequestMethod.GET})
public class DyController {

    @Autowired
    private MqttService mqttService;


    /**
     * 连接
     */
    @GetMapping("/con")
    public void connect(){
        DynamicConnected dynamicConnected = new DynamicConnected();
        dynamicConnected.setIp("IP");
        dynamicConnected.setPort(1883);
        dynamicConnected.setUsername("admin");
        dynamicConnected.setPassword("public");
        dynamicConnected.setKeepAliveInterval(2);
        dynamicConnected.setClientId("cliendTd");
        dynamicConnected.setCleanSession(true);
        mqttService.connect(dynamicConnected);
    };

    /**
     * 断开连接
     */
    @GetMapping("/dis")
    public void disconnect(){
        DynamicConnected dynamicConnected = new DynamicConnected();
        dynamicConnected.setIp("IP");
        dynamicConnected.setPort(1883);
        dynamicConnected.setUsername("admin");
        dynamicConnected.setPassword("public");
        dynamicConnected.setKeepAliveInterval(2);
        dynamicConnected.setClientId("cliendTd");
        dynamicConnected.setCleanSession(true);
        mqttService.disconnect(dynamicConnected);
    };

    /**
     * 订阅主题
     */
    @GetMapping("/sub")
    public void subscribe(){
        DynamicSubscribe dynamicSubscribe = new DynamicSubscribe();
        dynamicSubscribe.setIp("IP");
        dynamicSubscribe.setPort(1883);
        dynamicSubscribe.setUsername("admin");
        dynamicSubscribe.setPassword("public");
        dynamicSubscribe.setKeepAliveInterval(2);
        dynamicSubscribe.setClientId("cliendTd");
        dynamicSubscribe.setCleanSession(true);
        dynamicSubscribe.setTopic("/max/a");
        dynamicSubscribe.setQos(2);
        mqttService.subscribe(dynamicSubscribe);
    };

    /**
     * 移除订阅
     */
    @GetMapping("/unsub")
    public void unsubscribe(){
        DynamicSubscribe dynamicSubscribe = new DynamicSubscribe();
        dynamicSubscribe.setIp("IP");
        dynamicSubscribe.setPort(1883);
        dynamicSubscribe.setUsername("admin");
        dynamicSubscribe.setPassword("public");
        dynamicSubscribe.setKeepAliveInterval(2);
        dynamicSubscribe.setClientId("cliendTd");
        dynamicSubscribe.setCleanSession(true);
        dynamicSubscribe.setTopic("/max/a");
        dynamicSubscribe.setQos(2);
        mqttService.unsubscribe(dynamicSubscribe);
    };

到此就结束了,非常的好用,完美测试完成!!!

如需完整源码,请留言!!!

Spring Boot 是一个用于快速构建 Spring 应用程序的框架,而 MQTT 是一种轻量级的消息传递协议。将 Spring BootMQTT 集成可以让我们更加容易地创建一个可靠的、实时的通信系统。以下是如何将 Spring Boot 集成 MQTT 发布/订阅的步骤: 1. 引入 MQTT 相关依赖:在 pom.xml 文件中添加以下依赖: <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> <version>1.1.3.RELEASE</version> </dependency> 2. 配置 MQTT:在 application.properties 文件中添加以下配置: # MQTT 配置 mqtt.client.id=spring-boot-mqtt mqtt.username=username mqtt.password=password mqtt.url=tcp://localhost:1883 3. 编写发布代码:使用 Spring Integration 的 PublishSubscribeChannel 和 MqttPahoMessageHandler 来向 MQTT 发布消息。 @Autowired private MessageChannel mqttOutboundChannel; public void sendToMqtt(String message) { mqttOutboundChannel.send(MessageBuilder.withPayload(message).build()); } 4. 编写订阅代码:使用 Spring Integration 的 MqttPahoMessageDrivenChannelAdapter 和 MessageHandler 来实现订阅。 @Bean public MessageChannel mqttInputChannel() { return new DirectChannel(); } @Bean public MqttPahoMessageDrivenChannelAdapter mqttInbound() { String clientId = MqttAsyncClient.generateClientId(); MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(mqtt.url, clientId, mqtt.topic); adapter.setCompletionTimeout(5000); adapter.setConverter(new DefaultPahoMessageConverter()); adapter.setQos(1); adapter.setOutputChannel(mqttInputChannel()); return adapter; } @Bean @ServiceActivator(inputChannel = "mqttInputChannel") public MessageHandler handler() { return message -> { String payload = message.getPayload().toString(); System.out.println("MQTT Received: " + payload); }; } 通过以上步骤,我们可以轻松地集成 MQTT 发布/订阅功能。注意,在实际的应用程序中,我们需要为 MQTT 客户端定义一个独特的客户端 ID,并在订阅消息时指定选择的 MQTT 主题。这可以确保不同的客户端能够接收到正确的消息。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

rabbit_pro

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值