第十二章🚦SpringBoot-MQTT实现消息的发布-订阅

1,732 阅读4分钟

🧑‍🎓 个人主页:SilenceLamb

📖 本章内容:【MQTT实现消息的发布-订阅

image.png

🌳Gitee仓库地址:👉🏽MQTT实现消息的发布订阅


  • 实现MQTT协议需要客户端和服务器端通讯完成
  • 在通讯过程中, MQTT协议中有三种身份: 发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)
  • 其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者

在这里插入图片描述

🌳MQQT官方文档: 👉🏽SpringBoot整合MQQT


引入依赖

<!--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>

配置信息

  • 👉🏽可以不做任何更改
mqqt:
  username: michale
  password: michale # 密码
  hostUrl: tcp://broker.emqx.io:1883 # tcp://ip:端口
  clientId: clientId # 客户端id
  defaultTopic: topic # 订阅主题
  timeout: 1000 # 超时时间 (单位:秒)
  keepalive: 60 # 心跳 (单位:秒)
  enabled: false # 是否使能mqtt功能
  • 如果客户端订阅主题「topic/test/player1/#」,它会收到使用下列主题名发布的消息
「topic/test/player1」

「topic/test/player1/ranking」

「topic/test/player1/score/wimbledon」
  • 👉🏽读取配置信息
/**
 * @Author Michale
 * @CreateDate 2022/9/4
 * @Describe 读取MQQT配置信息
 */
@Data
@Component
@ConfigurationProperties(prefix = "mqqt")
public class MQQTProperties {

    @ApiModelProperty("用户名")
    private String username;

    @ApiModelProperty("密码")
    private String password;

    @ApiModelProperty("地址")
    private String hostUrl;

    @ApiModelProperty("客户端id")
    private String clientId;

    @ApiModelProperty("订阅主题")
    private String defaultTopic;

    @ApiModelProperty("超时时间")
    private int timeout;

    @ApiModelProperty("心跳")
    private int keepalive;

    @ApiModelProperty("MQQT开关")
    private boolean enabled;
}

MQQT配置类

  • 👉🏽 创建MqttPahoClientFactory
  • 👉🏽 设置MQTT Broker连接属性
    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setUserName(mqqtProperties.getUsername());
        options.setPassword(mqqtProperties.getPassword().toCharArray());
        options.setKeepAliveInterval(mqqtProperties.getKeepalive());
        options.setAutomaticReconnect(true);
        options.setConnectionTimeout(mqqtProperties.getTimeout());
        options.setMaxInflight(1000000);
        //多个服务器地址时处理
        options.setServerURIs(mqqtProperties.getHostUrl().split(","));
        factory.setConnectionOptions(options);
        return factory;
    }
  • 👉🏽 配置订阅的topic --支持通配符
    @Bean
    public MessageProducer inbound() {
        //clientId每个连接必须唯一,否则,两个相同的clientId相互挤掉线
        String serverIdStr = mqqtProperties.getClientId() + IdUtils.fastUUID();
        MqttPahoMessageDrivenChannelAdapter adapter =
                new MqttPahoMessageDrivenChannelAdapter(serverIdStr, mqttClientFactory(), mqqtProperties.getDefaultTopic());
        adapter.setConverter(new DefaultPahoMessageConverter());
        adapter.setCompletionTimeout(mqqtProperties.getTimeout());
        adapter.setQos(0);
        adapter.setOutputChannel(mqttInputChannel());
        return adapter;
    }
  • 👉🏽 发送通道配置默认主题
    @Bean
    @ServiceActivator(inputChannel = "mqttOutboundChannel")
    public MessageHandler mqttOutbound() {
        //clientId每个连接必须唯一,否则,两个相同的clientId相互挤掉线
        String clientIdStr = mqqtProperties.getClientId() + new SecureRandom().nextInt(10);
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientIdStr, mqttClientFactory());
        //async如果为true,则调用方不会阻塞。而是在发送消息时等待传递确认。默认值为false(发送将阻塞,直到确认发送)
        messageHandler.setAsync(true);
        messageHandler.setAsyncEvents(true);
        messageHandler.setDefaultTopic(mqqtProperties.getDefaultTopic());
        messageHandler.setDefaultQos(0);
        return messageHandler;
    }
  • 👉🏽 通过通道获取订阅的数据 (可不选)
    @Bean
    @ServiceActivator(inputChannel = "mqttInputChannel")
    public MessageHandler handler() {
        return new MessageHandler() {

            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                MqttDeliveryToken token = new MqttDeliveryToken();
                String payload = message.getPayload().toString();
                String topic = message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC).toString();
                //处理订阅到的所有的数据
                System.out.println(payload);
            }
        };
    }
  • 👉🏽创建两个通道
     /**
     *  发送通道
     */
    @Bean
    public MessageChannel mqttOutboundChannel() {
        return new DirectChannel();
    }

    /**
     *  接收通道
     */
    @Bean
    public MessageChannel mqttInputChannel() {
        return new DirectChannel();
    }

MQTT发送网关

  • 👉🏽 建议直接复制
/**
 * @Author Michale
 * @CreateDate 2022/9/16
 * @Describe 发送消息
 */
@Component
@MessagingGateway(defaultRequestChannel = MQTT_OUTBOUND_CHANNEL)
public interface MqqtSend {

    /**
     * 定义重载方法,用于消息发送
     *
     * @param payload 消息报文
     */
     void send(String payload);

    /**
     * 指定topic进行消息发送
     *
     * @param topic   主题
     * @param payload 消息报文
     */
     void send(@Header(MqttHeaders.TOPIC) String topic, String payload);

    /**
     * 指定topic和通道 进行消息发送
     *
     * @param topic   主题
     * @param qos     对消息处理的几种机制。
     *                0 表示的是订阅者没收到消息不会再次发送,消息会丢失。
     *                1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息。
     *                2 多了一次去重的动作,确保订阅者收到的消息有一次。
     * @param payload 消息报文
     */
     void send(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String payload);
}
  • 引入 import org.springframework.messaging.handler.annotation.Header;

接收到的消息

/**
 * @Author Michale
 * @CreateDate 2022/9/16
 * @Describe 接收到的数据
 */
@Slf4j
@Component
public class MqqtMessage implements MessageHandler {

    /**
     * Handle the given message.
     *
     * @param message the message to be handled
     * @throws MessagingException if the handler failed to process the message
     */
    @Override
    @ServiceActivator(inputChannel = MQTT_INPUT_CHANNEL)
    public void handleMessage(Message<?> message) throws MessagingException {
        String topic = message.getHeaders().get(MQTT_RECEIVED_TOPIC).toString();
        String payload = message.getPayload().toString();
        log.info("订阅的主题:{} 发送的内容:{}", topic, payload);
        checkTopic(topic, payload);
    }

    /**
     * 模拟硬件设备
     *
     * @param topic   订阅的主题
     * @param payload 消息报文
     */
    private void checkTopic(String topic, String payload) {
        if (StringUtils.inStringIgnoreCase(topic, "michale/furniture/led")) {
            extracted(payload, "客厅灯已打开", "客厅灯已关闭");
        } else if ((StringUtils.inStringIgnoreCase(topic, "michale/furniture/tv"))) {
            extracted(payload, "电视已打开", "电视已关闭");
        }
    }

    /**
     * 打印日志
     *
     * @param payload  消息报文
     * @param megTrue  正确信息
     * @param msgFalse 错误信息
     */
    private void extracted(String payload, String megTrue, String msgFalse) {
        if (StringUtils.inStringIgnoreCase(payload, "1")) {
            log.info(megTrue);
        } else {
            log.info(msgFalse);
        }
    }
}

演示MQQT

/**
 * @Author Michale
 * @CreateDate 2022/9/16
 * @Describe Mqqt消息体
 */
@Data
public class MqqtVo {

    @ApiModelProperty("订阅的主题")
    public   String topic ;

    @ApiModelProperty("发送的内容")
    public   String payload ;

}
	/**
     * 对接硬件设备
     *
     * @return
     */
    @Anonymous
    @PostMapping("/michale/led")
    public AjaxResult openLed(@RequestBody MqqtVo mqqtvo ) {
        mqqtSend.send(mqqtvo.getTopic(), mqqtvo.getPayload());
        return new AjaxResult().put("led_status", "open");
    }
  • 👉🏽发送请求
{
  "payload": "1",
  "topic": "michale/furniture/led"
}

在这里插入图片描述

  • 👉🏽MQQT客户端发送 在这里插入图片描述 在这里插入图片描述

扫码_搜索联合传播样式-标准色版.png

📢💨如果文章对你有帮助【关注👍点赞❤️收藏⭐】