SpringBoot:Springboot整合Mqtt并处理问题

搭建mqtt服务

Docker搭建MQTT服务:https://www.cnblogs.com/nhdlb/p/17960641

项目结构

这是我的项目结构,主要有两个模块 base-modules(业务模块)、base-utils(工具模块) 组成,其中base-mqtt服务为工具模块,用于提供给其他业务模块引用依赖的。

base-mqtt模块

pom.xml

这里我的Springboot版本为2.7.17

    <dependencies>
        <!--  lombok依赖  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--mqtt相关依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-integration</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-stream</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.integration</groupId>
            <artifactId>spring-integration-mqtt</artifactId>
        </dependency>
    </dependencies>

MqttConfig 配置类

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.springframework.beans.factory.annotation.Value;
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.core.MessageProducer;
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.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;

/**
 * @Author: 
 * @Date: 2024/1/24 11:00
 * @Description:
 */
@Data
@Slf4j
@Configuration
@IntegrationComponentScan
public class MqttConfig {

    /**
     * 订阅的bean名称
     */
    public static final String CHANNEL_NAME_IN = "mqttInboundChannel";
    /**
     * 发布的bean名称
     */
    public static final String CHANNEL_NAME_OUT = "mqttOutboundChannel";

    private final String subStrP = "-provider";
    private final String subStrC = "-consumer";

    @Value("${spring.mqtt.username}")
    private String username;

    @Value("${spring.mqtt.password}")
    private String password;

    @Value("${spring.mqtt.url}")
    private String url;

    @Value("${spring.mqtt.clientId}")
    private String clientId;

    @Value("${spring.mqtt.topic}")
    private String topics;

    /**
     * MQTT连接器选项
     *
     * @return {@link org.eclipse.paho.client.mqttv3.MqttConnectOptions}
     */
    @Bean
    public MqttConnectOptions getMqttConnectOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,
        // 这里设置为true表示每次连接到服务器都以新的身份连接
        options.setCleanSession(true);
        // 设置连接的用户名
        options.setUserName(username);
        // 设置连接的密码
        options.setPassword(password.toCharArray());
        options.setServerURIs(url.split(","));
        // 设置超时时间 单位为秒
        options.setConnectionTimeout(100);
        // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送心跳判断客户端是否在线,但这个方法并没有重连的机制
        options.setKeepAliveInterval(20);
        //设置遗嘱消息的话题,若客户端和服务器之间的连接意外断开,服务器将发布客户端的遗嘱信息
        options.setWill("willTopic", (clientId + "与服务器断开连接").getBytes(), 0, false);
        return options;
    }

    /**
     * MQTT管道适配器
     * @param factory
     * @return
     */
//    @Bean
//    public MqttPahoMessageDrivenChannelAdapter adapter(MqttPahoClientFactory factory){
//        return new MqttPahoMessageDrivenChannelAdapter(clientId, factory, topics.split(","));
//    }

    /**
     * MQTT客户端
     *
     * @return {@link MqttPahoClientFactory}
     */
    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        factory.setConnectionOptions(getMqttConnectOptions());
        return factory;
    }

    /**
     * MQTT信息通道(生产者)
     *
     * @return {@link org.springframework.messaging.MessageChannel}
     */
    @Bean(name = CHANNEL_NAME_OUT)
    public MessageChannel mqttOutboundChannel() {
        return new DirectChannel();
    }

    /**
     * MQTT信息通道(消费者)
     *
     * @return {@link org.springframework.messaging.MessageChannel}
     */
    @Bean(name = CHANNEL_NAME_IN)
    public MessageChannel mqttInboundChannel() {
        return new DirectChannel();
    }

    /**
     * MQTT消息处理器(生产者)
     *
     * @return {@link org.springframework.messaging.MessageHandler}
     */
    @Bean
    @ServiceActivator(inputChannel = CHANNEL_NAME_OUT)
    public MessageHandler mqttOutbound() {
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(
                clientId+subStrP,
                mqttClientFactory());
        messageHandler.setAsync(true);
        messageHandler.setDefaultTopic(topics.split(",")[0]);
        messageHandler.setDefaultRetained(false);
        return messageHandler;
    }

    /**
     * MQTT消息订阅绑定(消费者)
     *
     * @return {@link org.springframework.integration.core.MessageProducer}
     */
    @Bean
    public MessageProducer inbound() {
        // 可以同时消费(订阅)多个Topic
        MqttPahoMessageDrivenChannelAdapter adapter =
                new MqttPahoMessageDrivenChannelAdapter(
                        clientId+subStrC, mqttClientFactory(),
                        topics.split(","));
        adapter.setCompletionTimeout(5000);
        adapter.setConverter(new DefaultPahoMessageConverter());
        adapter.setQos(0);
        // 设置订阅通道
        adapter.setOutputChannel(mqttInboundChannel());
        return adapter;
    }

}

IMqttSender 消息发布类

import com.higentec.mqtt.config.MqttConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;

/**
 * MQTT生产者消息发送接口
 * <p>MessagingGateway要指定生产者的通道名称</p>
 * @author
 */
@Configuration
// 启动Springboot时这里会有问题,原因为@ComponentScan扫描不到这个注解!!!
@MessagingGateway(defaultRequestChannel = MqttConfig.CHANNEL_NAME_OUT)
public interface IMqttSender {

    /**
     * 发送信息到MQTT服务器
     *
     * @param data 发送的文本
     */
    void sendToMqtt(String data);

    /**
     * 发送信息到MQTT服务器
     *
     * @param topic 主题
     * @param message 消息主体
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
                    String message);

    /**
     * 发送信息到MQTT服务器
     *
     * @param topic 主题
     * @param qos 对消息处理的几种机制。
     *  0 表示的是订阅者没收到消息不会再次发送,消息会丢失。
     *  1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息。
     *  2 多了一次去重的动作,确保订阅者收到的消息有一次。
     * @param message 消息主体
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
                    @Header(MqttHeaders.QOS) int qos,
                    String message);
}

重点

启动Springboot时这里会有问题,原因:@ComponentScan扫描不到@MessagingGateway注解!!!

@MessagingGateway的源码,可以看到这样一些注释!!

大概意思是: @MessagingGateway注解需要配合 @Configuration注解 使用,并通过 @IntegrationComponentScan注解扫描才能识别到;而@ComponentScan注解无法扫描到;@IntegrationComponentScan注解用于扫描特定于Spring Integration的组件,它们都带有Spring Integration 4.0。

解决办法

在业务模块的启动类加上@IntegrationComponentScan注解

/**
 * 设备管理模块
 *
 */
@SpringBootApplication
@EnableFeignClients(basePackages = "com.higentec.feign")
@ComponentScan({"com.higentec"})
// 用于扫描MQTT接口类
@IntegrationComponentScan({"com.higentec.mqtt"})
public class DeviceApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(DeviceApplication.class);
        application.setApplicationStartup(new BufferingApplicationStartup(2048));
        application.run(args);
        System.out.println("= = = = >>>>>> 设备管理模块启动成功 ********** ");
    }
}

base-device模块

pom.xml

    <dependencies>
        <!-- 引入mqtt工具模块 -->
        <dependency>
            <groupId>com.higentec</groupId>
            <artifactId>base-mqtt</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
     </dependencies>

application.yml

spring:
  #MQTT配置信息
  mqtt:
    #MQTT服务端地址,端口默认为1883,如果有多个,用逗号隔开,如tcp://127.0.0.1:1883,tcp://192.168.60.133:1883
    url: tcp://127.0.0.1:1883
    #用户名
    username: admin
    #密码
    password: admin
    #客户端id(不能重复)
    clientId: ${spring.application.name}
    #MQTT默认的消息推送主题,实际可在调用接口时指定
    topic: test,weigh

MqttEventListener 消息监听类

import com.alibaba.fastjson2.JSONObject;
import com.higentec.device.enums.DeviceType;
import com.higentec.mqtt.config.MqttConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.messaging.MessageHandler;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class MqttEventListener {

    // 自定义的业务service
    @Autowired
    private MyDeviceService deviceService;

    /**
     * MQTT消息处理器(消费者)
     * @return {@link org.springframework.messaging.MessageHandler}
     */
    @Bean
    @Async
    @ServiceActivator(inputChannel = MqttConfig.CHANNEL_NAME_IN)
    public MessageHandler handler() {
        return message -> {
            String topic = message.getHeaders().get("mqtt_receivedTopic").toString();
            String source = message.getPayload().toString();
            issue(topic, source);
        };
    }

    /**
     * 方法描述: 消息分发
     *
     * @param topic
     * @param source
     */
    public void issue(String topic, String source) {
        DeviceType deviceType = DeviceType.parseDeviceType(topic);
        if (deviceType != null) {
            switch (deviceType) {
                // 称重设备
                case WEIGH:
                    WeighEntity weigh = JSONObject.parseObject(source, WeighEntity.class);
                    deviceService.insertWeigh(weigh);
                    System.out.println("称重设备 " + weigh.toJSONString());
                    break;
                default:
                    throw new IllegalStateException("Unexpected value: " + topic);
            }
        }
    }

}

DeviceType 枚举类

import java.util.HashMap;
import java.util.Map;

public enum DeviceType {

    WEIGH("weigh", "称重设备"),
    ;

    private final String topic;

    private final String desc;

    DeviceType(String topic, String desc) {
        this.topic = topic;
        this.desc = desc;
        InnerClass.commandMap.put(topic, this);
    }

    private static class InnerClass {
        static Map<String, DeviceType> commandMap = new HashMap<>();
    }

    public static DeviceType parseDeviceType(String topic) {
        return InnerClass.commandMap.get(topic);
    }

    String getTopic() {
        return topic;
    }

    String getDesc() {
        return desc;
    }
}

启动类

/**
 * 设备管理模块
 *
 */
@SpringBootApplication
@EnableFeignClients(basePackages = "com.higentec.feign")
@ComponentScan({"com.higentec"})
// 用于扫描MQTT接口类
@IntegrationComponentScan({"com.higentec.mqtt"})
public class DeviceApplication {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(DeviceApplication.class);
        application.setApplicationStartup(new BufferingApplicationStartup(2048));
        application.run(args);
        System.out.println("= = = = >>>>>> 设备管理模块启动成功 ********** ");
    }
}

接口类

/**
 * 接口类Controller
 */
@Tag(name = "设备信息")
@RestController
@RequestMapping("/device/base")
public class DeviceBaseInfoController extends BaseController {
    
    // 引入消息发布工具类
    @Resource
    private IMqttSender mqttSender;

    /**
     * 测试mqtt发送消息
     */
    @Operation(summary = "测试mqtt发送消息")
    @GetMapping(value = "/mqtt")
    public void getInfo(@RequestParam("topic") String topic, @RequestParam("message") String message) {
        mqttSender.sendToMqtt(topic,message);
    }
}

 

 文章整合至:https://blog.csdn.net/kumubajie/article/details/122939003https://www.pianshen.com/article/90891585920/https://blog.csdn.net/qq_40674081/article/details/106815495https://www.cnblogs.com/xct5622/p/15094017.html

posted @ 2024-01-25 16:51  怒吼的萝卜  阅读(662)  评论(0编辑  收藏  举报