SpringBoot整合MQTT:后端订阅设备数据、解析传感器报文

SpringBoot整合MQTT:后端订阅设备数据、解析传感器报文

作者:黒漂技术佬

前面几篇文章都是在讲 MQTT 协议本身和硬件端的事情。现在该轮到后端程序员出场了------设备把数据发到了 MQTT Broker,咱们的后端服务怎么把它接住?

这篇就手把手带你用 SpringBoot 整合 MQTT,实现订阅传感器数据、解析报文、存入库的全流程。

一、方案选型:用哪个 MQTT 客户端库?

Java 生态里搞 MQTT 主要有两个选项:

方案 说明 推荐度
org.eclipse.paho.client.mqttv3 Eclipse Paho 原生客户端,功能完整但偏底层 ⭐⭐⭐
spring-integration-mqtt Spring 官方集成方案,基于 Paho 封装,与 Spring 生态无缝对接 ⭐⭐⭐⭐⭐

强烈推荐 spring-integration-mqtt 理由很简单:你已经在用 SpringBoot 了,何必再从底层写一堆连接管理、线程池、异常处理的代码?Spring Integration 把 MQTT 客户端包装成了 Spring 的 MessageChannelMessageHandler,和你写 Controller 一个味儿。

二、引入依赖和配置

2.1 pom.xml

xml 复制代码
<!-- MQTT 核心依赖 -->
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mqtt</artifactId>
</dependency>

<!-- JSON 处理 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

SpringBoot 的 spring-boot-starter-integration 会自动拉取 Integration 核心,所以不需要额外引入。

2.2 application.yml

yaml 复制代码
mqtt:
  broker-url: tcp://192.168.1.100:1883
  client-id: ${spring.application.name}-${random.value}
  username: admin
  password: admin123
  # 订阅的Topic列表
  topics:
    - agriculture/+/+/sensor/#
  # QoS级别
  qos: 1
  # 超时和心跳配置
  completion-timeout: 3000
  keep-alive-interval: 60
  # 是否异步发送
  async: true

client-id 里加了随机值是为了支持多实例部署------两个相同 client-id 的连接会互相踢下线,你肯定不想这样。

三、MQTT 配置类

下面是一个可直接用于生产的 MQTT 配置类(40行左右):

java 复制代码
@Configuration
@IntegrationComponentScan
public class MqttConfig {

    @Value("${mqtt.broker-url}")
    private String brokerUrl;

    @Value("${mqtt.client-id}")
    private String clientId;

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

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

    @Value("${mqtt.completion-timeout}")
    private int completionTimeout;

    @Value("${mqtt.keep-alive-interval}")
    private int keepAliveInterval;

    @Value("#{'${mqtt.topics}'.split(',')}")
    private List<String> topics;

    @Value("${mqtt.qos}")
    private int qos;

    // ① 连接配置
    @Bean
    public MqttConnectOptions mqttConnectOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(new String[]{brokerUrl});
        options.setUserName(username);
        options.setPassword(password.toCharArray());
        options.setCleanSession(false);      // 持久会话
        options.setAutomaticReconnect(true); // 自动重连
        options.setKeepAliveInterval(keepAliveInterval);
        options.setConnectionTimeout(10);
        return options;
    }

    // ② 客户端工厂
    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        factory.setConnectionOptions(mqttConnectOptions());
        return factory;
    }

    // ③ 入站通道(MQTT Broker → 应用)
    @Bean
    public MessageChannel mqttInputChannel() {
        return new DirectChannel();
    }

    // ④ 入站适配器(订阅Topic)
    @Bean
    public MessageProducer inbound() {
        MqttPahoMessageDrivenChannelAdapter adapter =
            new MqttPahoMessageDrivenChannelAdapter(
                clientId, mqttClientFactory(),
                topics.toArray(new String[0]));
        adapter.setCompletionTimeout(completionTimeout);
        adapter.setConverter(new DefaultPahoMessageConverter());
        adapter.setQos(qos);
        adapter.setOutputChannel(mqttInputChannel());
        return adapter;
    }
}

来逐段解读一下:

  • MqttConnectOptions :就像你上网时的连接设置。setAutomaticReconnect(true) 告诉 Paho:「断了就自己连回来,别烦我」。
  • MqttPahoClientFactory:工厂模式,负责生产 MQTT 客户端实例。Spring Integration 内部会用它来创建连接。
  • DirectChannel:Spring Integration 的消息通道,简单理解就是一个「管道」,消息从这里流进来。
  • MqttPahoMessageDrivenChannelAdapter :入站适配器,负责订阅 Topic,把收到的消息灌入 mqttInputChanneltopics.toArray(new String[0]) 支持多 Topic 订阅,比如同时订阅温湿度 Topic 和光照 Topic。

四、消息接收处理器

配置写好了,现在接收消息:

java 复制代码
@Component
public class SensorDataHandler {

    @ServiceActivator(inputChannel = "mqttInputChannel")
    public void handleMessage(Message<?> message) {
        // 获取 Topic
        String topic = (String) message.getHeaders()
            .get(MqttHeaders.RECEIVED_TOPIC);
        
        // 获取 Payload(消息体)
        String payload = (String) message.getPayload();
        
        System.out.printf("[收到消息] Topic: %s%n", topic);
        System.out.printf("[消息内容] %s%n", payload);
        
        // 解析JSON
        try {
            SensorData data = parseSensorData(topic, payload);
            processSensorData(data);
        } catch (Exception e) {
            System.err.println("消息解析失败: " + e.getMessage());
        }
    }

    /**
     * 根据Topic路由到不同的解析逻辑
     */
    private SensorData parseSensorData(String topic, String payload) 
            throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        
        if (topic.contains("/temperature")) {
            return mapper.readValue(payload, TemperatureData.class);
        } else if (topic.contains("/humidity")) {
            return mapper.readValue(payload, HumidityData.class);
        } else if (topic.contains("/light")) {
            return mapper.readValue(payload, LightData.class);
        }
        throw new IllegalArgumentException("Unknown topic: " + topic);
    }

    private void processSensorData(SensorData data) {
        // 1. 数据校验
        if (!data.isValid()) {
            log.warn("数据异常,已丢弃: {}", data);
            return;
        }
        // 2. 业务处理(存库、告警、转发等)
        sensorDataService.save(data);
        
        // 3. 实时推送(WebSocket通知前端大屏)
        webSocketService.push(data);
    }
}

@ServiceActivator(inputChannel = "mqttInputChannel") 这行是核心。它告诉 Spring:「MQTT 来的消息从 mqttInputChannel 这个管道流过来,交给 handleMessage 方法处理」。

Spring Integration 的 Message<?> 对象封装了消息的 Header(元数据)和 Payload(消息体)。从 Header 里可以拿到 Topic、QoS、是否 Retained 等信息。

五、消息发布:后端向设备发指令

光收不发怎么行?咱们还得给设备下发控制指令(开风机、关水泵之类的):

java 复制代码
@Service
public class MqttCommandService {

    @Autowired
    private MqttPahoClientFactory mqttClientFactory;

    @Value("${mqtt.client-id}-outbound")
    private String outboundClientId;

    /**
     * 发送控制指令到指定设备
     */
    public void sendCommand(String deviceId, String command, Map<String, Object> params) {
        String topic = String.format("agriculture/%s/command", deviceId);
        
        CommandMessage msg = new CommandMessage();
        msg.setCommand(command);
        msg.setParams(params);
        msg.setTimestamp(System.currentTimeMillis());
        
        String payload = new ObjectMapper().writeValueAsString(msg);
        
        // 创建出站处理器
        MqttPahoMessageHandler handler = new MqttPahoMessageHandler(
            outboundClientId, mqttClientFactory);
        handler.setDefaultTopic(topic);
        handler.setDefaultQos(1); // 至少一次送达
        
        // 发送
        handler.handleMessage(
            MessageBuilder.withPayload(payload).build());
    }
}

注意出站适配器的 clientId 和入站的不能一样,否则会冲突。这里加了 -outbound 后缀来区分。

六、连接异常处理

农业生产环境不如机房稳定,MQTT 连接偶尔会断开。我们需要感知并处理这种状况:

java 复制代码
@Bean
public MqttPahoClientFactory mqttClientFactory() {
    DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
    MqttConnectOptions options = mqttConnectOptions();
    
    // 方式一:Paho 自带的自动重连(推荐)
    options.setAutomaticReconnect(true);
    // 重连间隔从 1 秒开始,最大 30 秒
    options.setMaxReconnectDelay(30000);
    
    factory.setConnectionOptions(options);
    return factory;
}

/**
 * 方式二:自定义回调监听连接状态(更灵活)
 */
@Component
public class MqttConnectionListener implements MqttCallbackExtended {

    @Override
    public void connectComplete(boolean reconnect, String serverURI) {
        if (reconnect) {
            // 重连成功,处理离线期间积压的业务
            log.info("MQTT 重连成功: {}", serverURI);
            sensorService.syncOfflineData();
        } else {
            log.info("MQTT 首次连接成功: {}", serverURI);
        }
    }

    @Override
    public void connectionLost(Throwable cause) {
        log.error("MQTT 连接断开: {}", cause.getMessage());
        // 可以在这里触发告警通知
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        // 这个回调由 Paho 原生 API 触发
        // 用 Spring Integration 的话,消息走 @ServiceActivator,这里不需要处理
    }
}

七、多 Topic 订阅与消息分发

智慧农业场景下,后端通常要同时订阅十几个 Topic。如果全堆在一个 Handler 里,代码必然变成一锅粥。这时候策略模式就派上用场了:

java 复制代码
// 定义处理器接口
public interface TopicHandler {
    boolean supports(String topic);
    void handle(String topic, String payload);
}

// 温度处理器
@Component
public class TemperatureHandler implements TopicHandler {
    public boolean supports(String topic) {
        return topic.contains("/temperature");
    }
    public void handle(String topic, String payload) {
        // 温度相关处理
    }
}

// 湿度处理器
@Component
public class HumidityHandler implements TopicHandler {
    public boolean supports(String topic) {
        return topic.contains("/humidity");
    }
    public void handle(String topic, String payload) {
        // 湿度相关处理
    }
}

// 统一分发器
@Component
public class MessageDispatcher {

    private final List<TopicHandler> handlers;

    public MessageDispatcher(List<TopicHandler> handlers) {
        this.handlers = handlers;
    }

    public void dispatch(String topic, String payload) {
        for (TopicHandler handler : handlers) {
            if (handler.supports(topic)) {
                handler.handle(topic, payload);
                return;
            }
        }
        log.warn("未找到匹配的处理器: {}", topic);
    }
}

Spring 会自动扫描所有实现了 TopicHandler 的 Bean,注入到 MessageDispatcher。后续新增传感器类型只需新增一个 Handler 类,完全符合开闭原则------对扩展开放,对修改关闭

总结

SpringBoot 整合 MQTT 的关键步骤就三步:配置连接参数 → 定义消息通道 → 绑定处理器。Spring Integration 帮你屏蔽了连接管理、线程调度、异常重试这些脏活累活,你就可以专心写业务逻辑。

记住几个容易踩的坑:

  1. 多实例部署时 clientId 必须唯一
  2. 入站和出站不能用同一个 clientId
  3. setCleanSession(false) 配合 setAutomaticReconnect(true) 才是弱网环境的正确打开方式
相关推荐
不加辣椒15 分钟前
第 4 章 什么是 Harness Engineering
人工智能
桃西西呀15 分钟前
上下文窗口都卷到 100 万了,大模型为什么还在为"位置"发愁?
人工智能·llm·ai编程
然我24 分钟前
模型不是 Agent:从零实现一个最小 Agent Loop
前端·人工智能·agent
黄油面包34 分钟前
Codex 额度三天见底后,我重新做了一周预算
前端·人工智能
AI效率君39 分钟前
Deer‑Flow 2.0 + Go‑MCP‑Server(add加法工具)保姆级完整教程
人工智能·agent
阿拉斯攀登39 分钟前
SpringCloud微服务MQTT架构:设备消息统一接入、业务分发设计
人工智能
Csvn1 小时前
第 12 章 并行化 Parallelization
人工智能·aigc·agent
大刚测试开发实战1 小时前
我在WorkBuddy上打通了TestHub,全程用AI来跑测试!
人工智能