SpringBoot 整合MQTT 消息推送

本文详细介绍了如何在Spring项目中集成MQTT,包括在pom.xml中添加依赖,配置yml文件,创建消息实体和MQTT配置类,以及生产者和消费者的实现。还展示了MQTT网关接口和消息处理工具的编写,提供了测试示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一: pom文件添加依赖

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

二: yml配置文件添加MQTT配置信息

###################### MQTT #################################
mqtt:
  # 服务器地址
  host: tcp://127.0.0.1:1883
  # ID唯一
  clientId: MQTT_WK
  # 主题 多个主题用逗号(,)分割 #表示这个主题下面所有,topic1,topic2,topic2/topic22/#(默认会取第一个主题)
  topics: topic1,topic2,topic2/topic22/#
  # 用户名
  username: admin
  # 密码
  password: 1q2w3e4r.
  # 连接超时
  timeout: 30
  # 心跳检测
  keepalive: 60
  # 对消息处理的几种机制。
  # 0 表示的是订阅者没收到消息不会再次发送,消息会丢失
  # 1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息
  # 2 多了一次去重的动作,确保订阅者收到的消息有一次
  qos: 1
  # false为建立持久会话
  cleanSession: false
  # 断开后重新连接
  automaticReconnect: true

三: 新建一个接收MQTT消息的实体用于接收参数(@RequestBody), 也可以直接参数字段(@RequestParam)这一步就不需要了

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @Description 消息实体对象
 * @Author WangKun
 * @Date 2023/4/19 14:59
 * @Version
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MQTTMessage implements Serializable {
    /**
     * MQTT主题
     */
    private String topic;

    /**
     * qos
     */
    private Integer qos = 1;

    /**
     * MQTT内容
     */
    private String content;

}

 四: 新建一个MQTT配置类(这可以改造两个配置类,一个生产者一个消费者,yml配置文件也是一个生产者一个消费者)

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


/**
 * @Description MQTT的配置类
 * @Author WangKun
 * @Date 2023/4/19 10:38
 * @Version
 */
@Component
@Data
public class MQTTConfig {

    /**
     * host 服务器地址配置
     */
    @Value("${mqtt.host}")
    private String[] host;

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

    /**
     * 主题
     */
    @Value("${mqtt.topics}")
    private String[] topics;

    /**
     * 用户名
     */
    @Value("${mqtt.username}")
    private String username;

    /**
     * 密码
     */
    @Value("${mqtt.password}")
    private String password;

    /**
     * 连接超时时长
     */
    @Value("${mqtt.timeout}")
    private Integer timeout;

    /**
     * keep Alive时间(心跳检测)
     */
    @Value("${mqtt.keepalive}")
    private Integer keepalive;

    /**
     * 遗嘱消息 QoS
     */
    @Value("${mqtt.qos}")
    private Integer qos;

    /**
     * false为建立持久会话
     */
    @Value("${mqtt.cleanSession}")
    private Boolean cleanSession;

    /**
     * 断开后重新连接
     */
    @Value("${mqtt.automaticReconnect}")
    private Boolean automaticReconnect;
}

五: 新建生产者与消费者

import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;

import javax.annotation.Resource;

/**
 * @Description MQTT生产端配置
 * @Author WangKun
 * @Date 2023/4/19 14:58
 * @Version
 */
@Configuration
public class MQTTProduceConfig {


    @Resource
    private MQTTConfig mqttConfig;

    // 客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息
    private static final byte[] WILL_DATA;

    static {
        WILL_DATA = "offline".getBytes();
    }

    /**
     * @param
     * @Description 出站直连通道
     * @Throws
     * @Return org.springframework.messaging.MessageChannel
     * @Date 2023-04-20 15:15:42
     * @Author WangKun
     */
    @Bean("mqttOut")
    public MessageChannel mqttOutBoundChannel() {
        return new DirectChannel();
    }


    /**
     * @param
     * @Description 创建MqttPahoClientFactory 设置MQTT的broker的连接属性
     * @Throws
     * @Return org.springframework.integration.mqtt.core.MqttPahoClientFactory
     * @Date 2023-04-20 15:15:52
     * @Author WangKun
     */
    @Bean
    public MqttPahoClientFactory outClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        String[] hosts = mqttConfig.getHost();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(hosts);
        options.setUserName(mqttConfig.getUsername());
        options.setPassword(mqttConfig.getPassword().toCharArray());
        options.setConnectionTimeout(mqttConfig.getTimeout());
        options.setKeepAliveInterval(mqttConfig.getKeepalive());
        options.setCleanSession(mqttConfig.getCleanSession());
        options.setAutomaticReconnect(mqttConfig.getAutomaticReconnect());
        options.setWill("willTopic", WILL_DATA, 2, false);
        factory.setConnectionOptions(options);
        return factory;
    }

    /**
     * @param
     * @Description 出站
     * @Throws
     * @Return org.springframework.messaging.MessageHandler
     * @Date 2023-04-20 15:16:05
     * @Author WangKun
     */
    @Bean
    @ServiceActivator(inputChannel = "mqttOut")
    public MessageHandler mqttOutbound() {
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(mqttConfig.getClientId() + "_producer", outClientFactory());
        //如果设置成true,即异步,发送消息时将不会阻塞。
        messageHandler.setAsync(true);
        //设置默认QoS
        messageHandler.setDefaultQos(mqttConfig.getQos());
        // 设置默认主题,取第一个
        messageHandler.setDefaultTopic(mqttConfig.getTopics()[0]);
        DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter();
        //发送默认按字节类型发送消息
//        defaultPahoMessageConverter.setPayloadAsBytes(true);
        messageHandler.setConverter(defaultPahoMessageConverter);
        return messageHandler;
    }
}
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import javax.annotation.Resource;

/**
 * @Description MQTT 消费端的配置
 * @Author WangKun
 * @Date 2023/4/19 14:56
 * @Version
 */
@Configuration
@Slf4j
public class MQTTConsumeConfig {

    @Resource
    private MQTTConfig mqttConfig;

    @Resource
    private MQTTMessageHandler mqttMessageHandler;

    // 客户端与服务器之间的连接意外中断,服务器将发布客户端的“遗嘱”消息
    private static final byte[] WILL_DATA;

    static {
        WILL_DATA = "offline".getBytes();
    }

    /**
     * @param
     * @Description 入站直连通道
     * @Throws
     * @Return org.springframework.messaging.MessageChannel
     * @Date 2023-04-20 15:10:25
     * @Author WangKun
     */
    @Bean("mqttInput")
    public MessageChannel mqttInputChannel() {
        return new DirectChannel();
    }


    /**
     * @param
     * @Description 创建MqttPahoClientFactory 设置MQTT的broker的连接属性
     * @Throws
     * @Return org.springframework.integration.mqtt.core.MqttPahoClientFactory
     * @Date 2023-04-20 15:11:33
     * @Author WangKun
     */
    @Bean
    public MqttPahoClientFactory inClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(mqttConfig.getHost());
        options.setUserName(mqttConfig.getUsername());
        options.setPassword(mqttConfig.getPassword().toCharArray());
        options.setConnectionTimeout(mqttConfig.getTimeout());
        options.setKeepAliveInterval(mqttConfig.getKeepalive());
        options.setCleanSession(mqttConfig.getCleanSession());
        options.setAutomaticReconnect(mqttConfig.getAutomaticReconnect());
        options.setWill("willTopic", WILL_DATA, 2, false);
        factory.setConnectionOptions(options);
        return factory;
    }


    /**
     * @param
     * @Description 入站
     * @Throws
     * @Return org.springframework.integration.core.MessageProducer
     * @Date 2023-04-20 15:12:06
     * @Author WangKun
     */
    @Bean
    public MessageProducer producer() {
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(mqttConfig.getClientId() + "_customer", inClientFactory(), mqttConfig.getTopics());
        adapter.setCompletionTimeout(5000);
        DefaultPahoMessageConverter defaultPahoMessageConverter = new DefaultPahoMessageConverter();
        // 按字节接收消息
        // defaultPahoMessageConverter.setPayloadAsBytes(true);
        adapter.setConverter(defaultPahoMessageConverter);
        adapter.setQos(mqttConfig.getQos());
        adapter.setOutputChannel(mqttInputChannel());

        return adapter;
    }

    /**
     * @param
     * @Description 通过通道获取数据
     * ServiceActivator注解表明:当前方法用于处理MQTT消息,inputChannel参数指定了用于消费消息的channel。
     * tips:
     * 异步处理
     * @Throws
     * @Return org.springframework.messaging.MessageHandler
     * @Date 2023-04-20 15:12:55
     * @Author WangKun
     */
    @Bean
    @ServiceActivator(inputChannel = "mqttInput")
    public MessageHandler handler() {
        return this.mqttMessageHandler;
    }
}

六: 重写MQTT网关接口

import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;

/**
 * @Description 网关接口MqttGateway
 * @Author WangKun
 * @Date 2023/4/19 15:01
 * @Version
 */
@MessagingGateway(defaultRequestChannel = "mqttOut")
public interface MQTTGateway {

    /**
     * @param message 消息
     * @Description 定义重载方法,用于消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:53:22
     * @Author WangKun
     */
    void sendToMqtt(String message);

    /**
     * @param topic   主题
     * @param message 消息
     * @Description 指定topic进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:03
     * @Author WangKun
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String message);

    /**
     * @param topic   主题
     * @param qos     qos
     * @param message 消息
     * @Description 指定topic和qos进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:33
     * @Author WangKun
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, String message);

    /**
     * @param topic   主题
     * @param qos     qos
     * @param message 消息(字节数字类型)
     * @Description 指定topic和qos进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:56
     * @Author WangKun
     */
    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, @Header(MqttHeaders.QOS) int qos, byte[] message);
}

七: 消费端消息拦截提取

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.Component;

/**
 * @Description 消费端消息处理工具(消费端消息处理)
 * @Author WangKun
 * @Date 2023/4/20 15:31
 * @Version
 */

@AllArgsConstructor
@Component
@Slf4j
public class MQTTMessageHandler implements MessageHandler {


    /**
     * @Description 消息处理(在这实现接收消息处理业务, 资源注入不进来,可以手动注入)
      * @param message
     * @Throws
     * @Return void
     * @Date 2023-04-20 15:38:22
     * @Author WangKun
     */
    @Override
    public void handleMessage(Message<?> message) throws MessagingException {
        log.info("收到的完整消息为--->{}", message);
        log.info("----------------------");
        log.info("message:" + message.getPayload());
        log.info("Id:" + message.getHeaders().getId());
        log.info("receivedQos:" + message.getHeaders().get(MqttHeaders.RECEIVED_QOS));
        String topic = (String) message.getHeaders().get(MqttHeaders.RECEIVED_TOPIC);
        log.info("topic:" + topic);
        log.info("----------------------");
    }
}

八: 为了方便使用,再封装发送工具


import org.springframework.stereotype.Component;


/**
 * @Description MQTT 消息发送工具类
 * @Author WangKun
 * @Date 2023/4/20 16:13
 * @Version
 */
@Component
public class MQTTUtils {

    /**
     * 资源手动注入
     */
    private static final MQTTGateway mqttGateway = SpringUtils.getBean(MQTTGateway.class);

    /**
     * @param message 消息
     * @Description 定义重载方法,用于消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:53:22
     * @Author WangKun
     */
    public static void send(String message) {
        mqttGateway.sendToMqtt(message);
    }

    /**
     * @param topic   主题
     * @param message 消息
     * @Description 指定topic进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:03
     * @Author WangKun
     */
    public static void send(String topic, String message) {
        mqttGateway.sendToMqtt(topic, message);
    }

    /**
     * @param topic   主题
     * @param qos     qos
     * @param message 消息
     * @Description 指定topic和qos进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:33
     * @Author WangKun
     */
    public static void send(String topic, int qos, String message) {
        mqttGateway.sendToMqtt(topic, qos, message);
    }

    /**
     * @param topic   主题
     * @param qos     qos
     * @param message 消息(字节数字类型)
     * @Description 指定topic和qos进行消息发送
     * @Throws
     * @Return void
     * @Date 2023-04-20 16:54:56
     * @Author WangKun
     */
    public static void send(String topic, int qos, byte[] message) {
        mqttGateway.sendToMqtt(topic, qos, message);
    }

}

九: 测试类

/**
 * @Description TODO
 * @Author WangKun
 * @Date 2023/4/20 16:58
 * @Version
 */
@RestController
public class MQTTController {

    @PostMapping("/send")
    public String send(@RequestBody MQTTMessage message) {
        // 发送消息到指定主题
//        mqttGateWay.sendToMqtt(myMessage.getTopic(), 1, myMessage.getContent());
        MQTTUtils.send(message.getTopic(), message.getQos(), message.getContent());
        return "send topic: " + message.getTopic() + ", message : " + message.getContent();
    }

}

整体代码结构

 测试结果:

EMQX服务监控:

 Apifox: 

MQTTX订阅该主题同时也收到消息

 MQTTX发送消息到该主题

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @Description spring工具类 方便在非spring管理环境中获取bean
 * @Author WangKun
 * @Date 2023/4/19 15:01
 * @Version
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware {
    // Spring应用上下文环境
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    /**
     * @param name
     * @Description 获取bean对象
     * @Throws
     * @Return T 注册的bean的实例
     * @Date 2023-04-20 16:50:47
     * @Author WangKun
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }

    /**
     * @param clz
     * @Description 获取类型为requiredType的对象
     * @Throws
     * @Return T
     * @Date 2023-04-20 16:51:18
     * @Author WangKun
     */
    public static <T> T getBean(Class<T> clz) throws BeansException {
        return (T) beanFactory.getBean(clz);
    }

    /**
     * @param name
     * @Description 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     * @Throws
     * @Return boolean
     * @Date 2023-04-20 16:51:26
     * @Author WangKun
     */
    public static boolean containsBean(String name) {
        return beanFactory.containsBean(name);
    }

    /**
     * @param name
     * @Description 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     * @Throws
     * @Return boolean
     * @Date 2023-04-20 16:51:39
     * @Author WangKun
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @Description 注册对象的类型
     * @Throws
     * @Return java.lang.Class<?>
     * @Date 2023-04-20 16:51:58
     * @Author WangKun
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getType(name);
    }

    /**
     * @param name
     * @Description 如果给定的bean名字在bean定义中有别名,则返回这些别名
     * @Throws
     * @Return java.lang.String[]
     * @Date 2023-04-20 16:52:08
     * @Author WangKun
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getAliases(name);
    }

    /**
     * @param invoker
     * @Description 获取aop代理对象
     * @Throws
     * @Return T
     * @Date 2023-04-20 16:52:17
     * @Author WangKun
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker) {
        return (T) AopContext.currentProxy();
    }

    /**
     * @Description 获取配置文件值
      * @param key
     * @Throws
     * @Return java.lang.String
     * @Date 2023-04-25 11:30:50
     * @Author WangKun
     */
    public static String getRequiredProperty(String key) {
        return applicationContext.getEnvironment().getRequiredProperty(key);
    }
}

<think>我们使用以下技术栈: - Spring Boot:作为后端框架 - MyBatis:用于数据库操作 - MQTT:用于从KepServer接收实时数据 - KepServer:作为OPC UA服务器,与西门子PLC通信,并将数据通过MQTT发布 - MySQL:存储数据 - Kafka:可选,用于大规模数据缓冲(根据需求,这里用户要求使用Kafka,所以我们将数据先通过MQTT接收,再发送到Kafka,然后由消费者存入MySQL) 步骤概述: 1. 配置KepServer,使其通过MQTT发布西门子PLC的数据。 2. 创建Spring Boot项目,集成MQTT客户端,订阅KepServer发布的主题,接收数据。 3. 将接收到的数据发送到Kafka(可选,但用户要求,所以加入)。 4. 创建Kafka消费者,将数据解析并存储到MySQL数据库,使用MyBatis进行数据操作。 5. 配置MySQL数据库。 详细步骤: ### 1. 配置KepServer发布MQTT数据 - 在KepServer中,配置一个MQTT的客户端通道,设置MQTT代理(broker)的地址(比如本地的mosquitto或者EMQX等)。 - 在KepServer中创建标签(Tag)对应PLC的数据点,并设置发布到MQTT的主题(Topic)和格式(例如JSON)。 ### 2. 创建Spring Boot项目 使用Spring Initializr(https://start.spring.io/)创建项目,选择依赖: - Spring Web - MyBatis Framework - MySQL Driver - Spring for Apache Kafka - Spring Integration (用于MQTT) 或者手动添加依赖(Maven): ```xml <dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!-- MySQL Driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- Spring Kafka --> <dependency> <groupId>org.springframework.kafka</groupId> <artifactId>spring-kafka</artifactId> </dependency> <!-- Spring Integration MQTT --> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> </dependency> <!-- Lombok(可选,简化代码) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> ``` ### 3. 配置MQTT订阅 创建MQTT配置类,订阅KepServer发布的主题。 ```java import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.support.DefaultPahoMessageConverter; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHandler; @Configuration public class MqttConfig { private static final String MQTT_BROKER_URL = "tcp://localhost:1883"; private static final String CLIENT_ID = "spring-boot-client"; private static final String TOPIC = "kepserver/data"; @Bean public MqttPahoClientFactory mqttClientFactory() { DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory(); MqttConnectOptions options = new MqttConnectOptions(); options.setServerURIs(new String[]{MQTT_BROKER_URL}); // 如果有用户名和密码 // options.setUserName("username"); // options.setPassword("password".toCharArray()); factory.setConnectionOptions(options); return factory; } @Bean public MessageChannel mqttInputChannel() { return new DirectChannel(); } @Bean public MessageProducer inbound() { MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(CLIENT_ID, mqttClientFactory(), 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 -> { // 这里接收到消息,将消息发送到Kafka String payload = message.getPayload().toString(); // 假设我们有一个Kafka生产者服务 kafkaProducerService.sendMessage(payload); }; } } ``` ### 4. 创建Kafka生产者服务 创建一个Kafka生产者服务,用于将MQTT接收到的消息发送到Kafka主题。 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.stereotype.Service; @Service public class KafkaProducerService { private static final String TOPIC = "plc_data_topic"; @Autowired private KafkaTemplate<String, String> kafkaTemplate; public void sendMessage(String message) { kafkaTemplate.send(TOPIC, message); } } ``` 配置Kafka(application.properties): ```properties # Kafka配置 spring.kafka.bootstrap-servers=localhost:9092 spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer spring.kafka.producer.value-serializer=org.apache.kafka.common.serialization.StringSerializer ``` ### 5. 创建Kafka消费者,存储到MySQL 创建Kafka消费者,监听Kafka主题,将消息解析后存储到数据库。 首先,创建实体类(根据PLC数据结构定义): ```java import lombok.Data; import java.util.Date; @Data public class PlcData { private Long id; private String tagName; private Double value; private Date timestamp; // 其他字段... } ``` 创建MyBatis Mapper接口: ```java import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; @Mapper public interface PlcDataMapper { @Insert("INSERT INTO plc_data (tag_name, value, timestamp) VALUES (#{tagName}, #{value}, #{timestamp})") void insert(PlcData plcData); } ``` 创建Kafka消费者服务: ```java import org.springframework.kafka.annotation.KafkaListener; import org.springframework.stereotype.Service; import com.fasterxml.jackson.databind.ObjectMapper; @Service public class KafkaConsumerService { @Autowired private PlcDataMapper plcDataMapper; @KafkaListener(topics = "plc_data_topic", groupId = "group_id") public void consume(String message) { try { ObjectMapper objectMapper = new ObjectMapper(); PlcData plcData = objectMapper.readValue(message, PlcData.class); plcData.setTimestamp(new Date()); // 如果消息中没有时间戳,则用当前时间 plcDataMapper.insert(plcData); } catch (Exception e) { e.printStackTrace(); } } } ``` ### 6. 配置MySQL 在application.properties中配置MySQL: ```properties spring.datasource.url=jdbc:mysql://localhost:3306/plc_data?useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver mybatis.mapper-locations=classpath:mapper/*.xml ``` 创建数据库表: ```sql CREATE TABLE plc_data ( id BIGINT AUTO_INCREMENT PRIMARY KEY, tag_name VARCHAR(255) NOT NULL, value DOUBLE NOT NULL, timestamp DATETIME NOT NULL ); ``` ### 7. 运行与测试 1. 启动KepServer,确保PLC连接正常,并且MQTT发布正常。 2. 启动Kafka和Zookeeper(如果Kafka还未启动)。 3. 启动MySQL数据库。 4. 启动Spring Boot应用。 此时,数据流为:PLC -> KepServer -> MQTT Broker -> Spring Boot MQTT订阅 -> Kafka生产者 -> Kafka -> Kafka消费者 -> MySQL。 ### 注意事项 - 确保所有中间件(MQTT Broker, Kafka, MySQL)已正确安装和配置。 - 根据实际PLC数据结构调整实体类和JSON解析。 - 考虑异常处理、数据持久化、消费者幂等性等问题。 ### 性能优化建议 - 对于高频数据,考虑批量插入数据库。 - 调整Kafka的分区和消费者数量。 - 使用连接池配置数据库连接。 §§相关问题§§ 1. 如何配置KepServer以通过MQTT发布PLC数据? 2. Spring Boot集成MQTT时如何保证消息不丢失? 3. 如何优化Kafka消费者写入数据库的性能? 4. 在实时数据采集中,如何确保数据的一致性和可靠性? 5. 如何监控这个数据采集系统的运行状态?</think>### 西门子PLC数据实时采集存储方案(SpringBoot+MyBatis+MQTT+KepServer+MySQL+Kafka) #### 1. 整体架构流程 ```mermaid graph LR A[西门子PLC] -->|工业协议| B(KepServer OPC UA) B -->|MQTT发布| C(MQTT Broker) C -->|订阅| D(SpringBoot MQTT Client) D -->|写入| E(Kafka) E -->|消费| F(SpringBoot Kafka Consumer) F -->|MyBatis持久化| G(MySQL) ``` #### 2. 详细实现步骤 **步骤1:配置KepServer数据源** 1. 安装KEPServerEX 6+[^1] 2. 创建通道:`Channel > 右键添加通道 > 选择Siemens TCP/IP` 3. 配置设备:`Device > 设置PLC IP地址和机架/插槽号` 4. 创建标签:`Tag > 添加需要采集的PLC变量地址(如DB1.DBD0)` 5. 启用MQTT传输:`项目树 > 配置 > 启用MQTT引擎 > 设置Broker地址` **步骤2:SpringBoot集成MQTT客户端** ```java // MQTT配置类 @Configuration public class MqttConfig { @Value("${mqtt.broker}") private String brokerUrl; @Bean public MqttPahoClientFactory mqttFactory() { DefaultMqttPahoClientFactory factory = new DefaultMttPahoClientFactory(); MqttConnectOptions options = new MqttConnectOptions(); options.setServerURIs(new String[]{brokerUrl}); factory.setConnectionOptions(options); return factory; } @Bean public MessageChannel mqttInputChannel() { return new DirectChannel(); } @Bean public MessageProducer inbound() { MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter("clientId", mqttFactory(), "kepserver/data/#"); adapter.setConverter(new DefaultPahoMessageConverter()); adapter.setQos(1); adapter.setOutputChannel(mqttInputChannel()); return adapter; } } ``` **步骤3:MQTT消息处理(含Kafka写入)** ```java @Service public class MqttService { @Autowired private KafkaTemplate<String, String> kafkaTemplate; @ServiceActivator(inputChannel = "mqttInputChannel") public void handleMessage(Message<?> message) { String payload = message.getPayload().toString(); // 示例payload: { "tag": "DB1.DBD0", "value": 25.6, "timestamp": 1625097600 } kafkaTemplate.send("plc_raw_data", payload); // 写入Kafka原始数据 } } ``` **步骤4:Kafka消费者处理(MyBatis持久化)** ```java @KafkaListener(topics = "plc_raw_data", groupId = "plc-group") public void consume(String message) { PlcData data = parseData(message); // JSON解析 plcDataMapper.insert(data); // MyBatis写入 } // MyBatis Mapper接口 @Mapper public interface PlcDataMapper { @Insert("INSERT INTO plc_data(tag_name, value, timestamp) VALUES(#{tagName}, #{value}, #{timestamp})") void insert(PlcData data); } ``` **步骤5:MySQL表结构设计** ```sql CREATE TABLE plc_data ( id BIGINT AUTO_INCREMENT PRIMARY KEY, tag_name VARCHAR(50) NOT NULL COMMENT 'PLC标签地址', value DOUBLE NOT NULL COMMENT '采集数值', timestamp DATETIME NOT NULL COMMENT '采集时间戳', created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` **步骤6:关键配置文件(application.yml)** ```yaml mqtt: broker: tcp://192.168.1.100:1883 kafka: bootstrap-servers: localhost:9092 producer: key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.apache.kafka.common.serialization.StringSerializer spring: datasource: url: jdbc:mysql://localhost:3306/plc_db username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver ``` #### 3. 性能优化要点 1. **Kafka分区策略**:按PLC设备ID分区,保证数据顺序性 ```java kafkaTemplate.send("plc_raw_data", deviceId, payload); ``` 2. **批量写入**:配置MyBatis批量插入 ```xml <insert id="batchInsert" parameterType="list"> INSERT INTO plc_data(...) VALUES <foreach collection="list" item="item" separator=","> (#{item.tagName},...) </foreach> </insert> ``` 3. **连接池配置**:使用HikariCP提升数据库性能 ```yaml spring: datasource: hikari: maximum-pool-size: 20 connection-timeout: 30000 ``` #### 4. 异常处理机制 - MQTT断线重连 ```java options.setAutomaticReconnect(true); // 自动重连 ``` - Kafka消费幂等性 ```java @KafkaListener(idempotent = true) // 防止重复消费 ``` - 数据库写入重试 ```java @Retryable(value = SQLException.class, maxAttempts = 3) ``` #### 5. 验证测试流程 1. 使用MQTT.fx模拟KepServer发布数据 2. 通过Kafka Tool查看消息队列状态 3. 监控MySQL写入性能: ```sql SHOW GLOBAL STATUS LIKE 'Innodb_rows_inserted'; ``` > **注意**:实际部署时需开启防火墙端口(1883 MQTT/9092 Kafka/3306 MySQL)[^2]
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值