MQTT 配置、依赖与使用说明

概述

本项目实现了基于Spring Integration MQTT的设备数据采集功能,用于接收地热监测设备通过MQTT协议发送的实时数据。系统支持微功耗设备和市电设备两种类型的数据接入,并将数据存储到PostgreSQL数据库中。

依赖配置

Maven 依赖

/pom.xml 中添加以下依赖:

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

该依赖包含了Eclipse Paho MQTT客户端和Spring Integration MQTT的支持。

配置说明

application-dev.yml 配置项

yaml 复制代码
spring:
  # mqtt 的相关配置服务
  mqtt:
    username: admin                    # MQTT服务器用户名
    password: xxx                    # MQTT服务器密码
    # 指定MQTT服务器的URI,包括协议类型和服务器地址及端口
    server-uri: tcp://127.0.0.1:1883        # MQTT服务器地址
    # 客户端ID,用于在MQTT服务器上唯一标识这个客户端
    client-id: consumerClient-paho          # 客户端唯一标识
    # 配置需要订阅的主题
    topic:
      # 用于订阅"boat"主题 - 现在没有,用于测试
      dr_data: dr_data
      # 用于订阅"collector"主题
      collector: collector
      # 用于订阅 "DZSDCQETZ/p867297065936977" 主题
      DZSDCQETZ/s867297065936981: DZSDCQETZ/s867297065936981
      # 使用通配符订阅所有以"sensor"开头的主题
      sensor: '+/sensor'
    # 指定默认的主题,当没有指定具体主题时,客户端将订阅这个主题
    default-topic: command                  # 默认主题

配置项说明

配置项 说明 示例值
username MQTT服务器认证用户名 superadmin
password MQTT服务器认证密码 drzyjg@131
server-uri MQTT服务器连接地址 tcp://127.0.0.1:1883
client-id 客户端唯一标识符 consumerClient-paho
topic 需要订阅的主题列表 多个主题配置
default-topic 默认发布消息的主题 command

核心组件

1. MqttConfig - MQTT配置类

位于: /mqtt/config/MqttConfig.java

该类负责配置MQTT客户端的连接参数、消息收发通道以及消息处理器。

主要功能
  • MQTT客户端工厂配置: 创建并配置MqttPahoClientFactory
  • 消息输入通道: 创建DirectChannel用于接收MQTT消息
  • 消息订阅配置: 配置MqttPahoMessageDrivenChannelAdapter订阅指定主题
  • 消息处理器: 定义ServiceActivator处理接收到的MQTT消息
  • 消息输出通道: 创建用于发送消息到MQTT服务器的通道
关键代码片段
java 复制代码
@Configuration
public class MqttConfig {
    
    @Value("${spring.mqtt.server-uri}")
    private String serverUri;
    
    @Value("${spring.mqtt.client-id}")
    private String clientId;
    
    @Value("${spring.mqtt.username}")
    private String username;
    
    @Value("${spring.mqtt.password}")
    private String password;
    
    // 主题列表数据缓存对象
    public static List<String> topicList = new CopyOnWriteArrayList<>();
    
    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(new String[]{serverUri});
        options.setUserName(username);
        options.setPassword(password.toCharArray());
        factory.setConnectionOptions(options);
        return factory;
    }
    
    @Bean
    public MessageProducer inbound() {
        // 从数据库加载设备主题并订阅
        LambdaQueryWrapper<TbSbjbxx> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.select(TbSbjbxx::getZt);
        List<TbSbjbxx> resultList = tbSbjbxxMapper.selectList(lambdaQueryWrapper);
        resultList.forEach(entity -> topicList.add(entity.getZt()));
        
        String[] topicsArray = topicList.toArray(new String[0]);
        MqttPahoMessageDrivenChannelAdapter adapter = 
            new MqttPahoMessageDrivenChannelAdapter(clientId, mqttClientFactory(), topicsArray);
        adapter.setCompletionTimeout(5000);
        adapter.setQos(2);
        adapter.setOutputChannel(mqttInputChannel());
        return adapter;
    }
    
    @Bean
    @ServiceActivator(inputChannel = "mqttInputChannel")
    public MessageHandler handler() {
        return message -> {
            String payload = message.getPayload().toString();
            String topic = message.getHeaders().get("mqtt_receivedTopic").toString();
            
            // 处理MQTT消息
            boolean insertSuccess = this.mqttMessageProcessMethod(topic, payload);
            
            if (insertSuccess) {
                log.info("主题为:{} 的设备信息接收完成,并成功入库", topic);
            } else {
                log.info("丢弃消息:主题[" + topic + "], 负载:" + payload);
            }
        };
    }
}

2. 数据实体类

TbSbjbxx - 设备基本信息实体

位于: /src/main/java/com/mqtt/domain/entity/TbSbjbxx.java

对应数据库表 tb_sbjbxx,存储设备的基本信息。

java 复制代码
@Data
@TableName("tb_sbjbxx")
@ApiModel(value = "TbSbjbxx", description = "设备基本信息实体类")
public class TbSbjbxx {
    @TableId(type = IdType.AUTO)
    private Integer id;
    
    private String sbxlh;        // 设备序列号
    private String azwz;         // 安装位置
    private Integer sblx;        // 设备类型(1:市电设备;2:微功耗设备)
    private Integer drjId;       // 地热井id
    private String zt;           // 主题(设备唯一固定的主题)
    private Date xgsj;           // 设备信息更新时间
    private String yw;           // 液位(冗余字段)
    private String jwd;          // 井温度(冗余字段)
    private Integer jcjlx;       // 监测井类型
    private String jmc;          // 井名称
    private String xmc;          // 县名称
    private String smc;          // 市名称
    private String lon;          // 经度
    private String lat;          // 纬度
    public Integer sbzt;         // 设备状态(0:正常;1:异常)
    private Double cgqxs;        // 传感器下深(米)
}
TbSbxxjl - 设备信息记录实体

位于: /src/main/java/com/mqtt/domain/entity/TbSbxxjl.java

对应数据库表 tb_sbxxjl,存储设备上报的历史数据记录。

java 复制代码
@Data
@TableName("tb_sbxxjl")
@ApiModel("设备信息记录实体")
public class TbSbxxjl {
    @TableId(type = IdType.AUTO)
    private Integer id;
    
    private String sbxlh;        // 设备序列号
    private Date sjscsj;         // 数据上传时间
    private String dcdy;         // 电池电压
    private String jwd;          // 井温度
    private String yw;           // 液位
    private String lon;          // 经度
    private String lat;          // 纬度
    private String sbzt;         // 主题
    private Integer sbjbxxId;    // 设备基本信息ID
}
TbMicroEmqxDataEntity - 微功耗设备数据实体

用于解析微功耗设备发送的JSON格式数据。

java 复制代码
@Data
public class TbMicroEmqxDataEntity {
    @Data
    public class LocationEntity {
        private Double latitude;
        private Double longitude;
        private Double altitude;
    }

    @Data
    public class dataDict {
        private Double level;    // 液位
        private Double temp;     // 传感器温度
        private Double vdd;      // 电池电压
    }

    private String id;
    private String event;
    private LocationEntity location;
    private String time;
    private dataDict dict;
    private String imei;
    private String subtopic;
    private String devicename;
    private String softversion;
}

3. 数据处理工具类

TbMicroEmqxDataEntityToTbSbxxjlEntityUtil

负责将微功耗设备的原始数据转换为数据库实体对象。

java 复制代码
public class TbMicroEmqxDataEntityToTbSbxxjlEntityUtil {
    public static TbSbxxjl toTbSbxxjlEntity(TbMicroEmqxDataEntity tbMicroEmqxDataEntity) {
        if (tbMicroEmqxDataEntity == null) {
            return null;
        }

        TbSbxxjl target = new TbSbxxjl();
        
        // 设备序列号
        target.setSbxlh(tbMicroEmqxDataEntity.getImei());
        
        // 数据上传时间
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
            target.setSjscsj(sdf.parse(tbMicroEmqxDataEntity.getTime()));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        
        // 电池电压
        target.setDcdy(String.valueOf(tbMicroEmqxDataEntity.getDict().getVdd()));
        
        // 井温度
        target.setJwd(String.valueOf(tbMicroEmqxDataEntity.getDict().getTemp()));
        
        // 液位
        target.setYw(String.valueOf(tbMicroEmqxDataEntity.getDict().getLevel()));
        
        // 经纬度
        if (tbMicroEmqxDataEntity.getLocation() != null) {
            target.setLon(String.valueOf(tbMicroEmqxDataEntity.getLocation().getLongitude()));
            target.setLat(String.valueOf(tbMicroEmqxDataEntity.getLocation().getLatitude()));
        }
        
        return target;
    }
}

工作流程

1. 系统启动阶段

  1. Spring容器启动,加载MqttConfig配置类
  2. 从数据库 tb_sbjbxx 表中查询所有设备的主题(zt字段)
  3. 将主题列表缓存到 topicList 静态变量中
  4. 创建MQTT客户端并订阅所有设备主题
  5. 建立与MQTT服务器的连接

2. 消息接收阶段

  1. 设备通过MQTT协议向指定主题发布消息
  2. MqttPahoMessageDrivenChannelAdapter接收到消息
  3. 消息通过mqttInputChannel传递到handler处理器
  4. handler提取消息的主题(topic)和负载(payload)

3. 消息处理阶段

  1. 验证主题是否在允许的主题列表中
  2. 根据主题查询设备基本信息(tb_sbjbxx)
  3. 根据设备类型(sblx)选择不同的解析方式:
    • 市电设备(sblx=1): 直接解析JSON获取IMEI、level、temp、time等字段
    • 微功耗设备(sblx=2): 解析复杂的JSON结构,提取imei、dict、location、time等字段
  4. 将解析后的数据转换为TbSbxxjl实体对象
  5. 更新设备基本信息表中的冗余字段(yw、jwd)
  6. 插入设备信息记录到 tb_sbxxjl
  7. 保存原始消息到 tb_sbxxjl_ys

4. 数据流转图

复制代码
设备 → MQTT Broker → Spring Integration → 消息处理器 → 数据解析 → 数据库存储
                                            ↓
                                    主题验证 → 设备类型判断 → 不同解析策略
                                            ↓
                                    tb_sbjbxx (设备基本信息)
                                    tb_sbxxjl (设备历史记录)
                                    tb_sbxxjl_ys (原始消息)

使用示例

设备端发送数据

微功耗设备数据格式
json 复制代码
{
  "id": "msg_001",
  "event": "upload",
  "imei": "867297065936981",
  "time": "20240809143025",
  "devicename": "微功耗设备01",
  "softversion": "v1.0",
  "subtopic": "DZSDCQETZ/s867297065936981",
  "location": {
    "latitude": 38.0428,
    "longitude": 114.5149,
    "altitude": 50.5
  },
  "dict": {
    "level": 12.5,
    "temp": 25.8,
    "vdd": 3.6
  }
}
市电设备数据格式
json 复制代码
{
  "IMEI": "867297065936977",
  "time": "2024-08-09 14:30:25",
  "level": 15.3,
  "temp": 26.5
}

后端接收处理

系统会自动处理接收到的消息,无需额外的代码调用。消息处理逻辑在 MqttConfig.handler() 方法中自动执行。

查询设备数据

可以通过以下Controller接口查询设备数据:

TbSbjbxxController - 设备基本信息管理
java 复制代码
@RestController
@RequestMapping("/deviceInfoOperation")
@Api(tags = {"====== 获取设备基本信息接口 ======"})
public class TbSbjbxxController {
    
    /**
     * 分页查询设备基本信息
     */
    @PostMapping("/pageList")
    public R pageList(@RequestBody @Validated SbjbxxPageListParam param) {
        Page<TbSbjbxx> page = tbSbjbxxService.pageList(param);
        return R.ok(page);
    }
    
    /**
     * 新增设备基本信息
     */
    @PostMapping("/insert")
    public R insert(@RequestBody @Validated SbjbxxInsertParam param) {
        tbSbjbxxService.insert(param);
        return R.ok();
    }
    
    /**
     * 更新设备基本信息
     */
    @PostMapping("/update")
    public R update(@RequestBody @Validated SbjbxxUpdateParam param) {
        tbSbjbxxService.update(param);
        return R.ok();
    }
    
    /**
     * 删除设备基本信息
     */
    @PostMapping("/delete")
    public R delete(@RequestBody @Validated SbjbxxDeleteParam param) {
        tbSbjbxxService.delete(param);
        return R.ok();
    }
}
TbSbxxjlController - 设备消息记录查询
java 复制代码
@RestController
@RequestMapping("/getDeviceMessage")
public class TbSbxxjlController {
    @PostMapping("/getDrjTemperatureAndWaterLevel")
    public R getDrjTemperatureAndWaterLevel(@RequestBody DrjParam param) {
        List<DrjTemperatureAndWaterLevelVO> list = 
            tbSbxxjlService.getDrjTemperatureAndWaterLevel(param);
        return R.ok(list);
    }
}

tb_sbxxjl - 设备信息记录表

字段名 类型 说明
id INTEGER 主键ID(自增)
sbxlh VARCHAR 设备序列号
sjscsj TIMESTAMP 数据上传时间
dcdy VARCHAR 电池电压
jwd VARCHAR 井温度
yw VARCHAR 液位
lon VARCHAR 经度
lat VARCHAR 纬度
sbzt VARCHAR 设备主题
sbjbxx_id INTEGER 设备基本信息ID

tb_sbxxjl_ys - 设备原始消息表

字段名 类型 说明
id INTEGER 主键ID(自增)
sbzt VARCHAR 设备主题
ysxx TEXT 原始消息内容
jssj TIMESTAMP 接收时间

注意事项

1. 主题管理

  • 系统启动时会自动从 tb_sbjbxx 表加载所有设备主题
  • 只有主题列表中的设备消息才会被处理和存储
  • 新增设备后需要重启应用或手动刷新主题列表

2. 设备类型区分

  • 市电设备(sblx=1): 数据结构简单,直接包含IMEI、level、temp、time字段
  • 微功耗设备(sblx=2): 数据结构复杂,包含嵌套的dict、location对象

3. QoS设置

  • 系统使用QoS 2级别,确保消息恰好传递一次
  • 适用于对数据完整性要求较高的场景

4. 异步发送

  • 消息发送采用异步模式(setAsync(true)
  • 提高系统吞吐量,避免阻塞主线程

5. 错误处理

  • 消息处理过程中捕获所有异常,防止单个消息失败影响整体运行
  • 无效主题的消息会被丢弃并记录日志

6. 性能优化建议

  • 对于高并发场景,考虑使用消息队列缓冲
  • 定期清理历史数据,避免数据库过大
  • 监控MQTT连接状态,实现断线重连机制

常见问题

Q1: 如何添加新的设备主题?

A:tb_sbjbxx 表中插入新的设备记录,包含正确的主题(zt字段),然后重启应用。

Q2: 如何修改MQTT服务器地址?

A: 修改 application-dev.yml 中的 spring.mqtt.server-uri 配置项。

Q3: 设备消息未入库怎么办?

A: 检查以下几点:

  1. 设备主题是否在 tb_sbjbxx 表中存在
  2. MQTT服务器连接是否正常
  3. 查看应用日志,确认是否有异常信息
  4. 验证设备发送的数据格式是否正确

Q4: 如何查看接收到的原始消息?

A: 查询 tb_sbxxjl_ys 表,该表存储了所有设备发送的原始消息内容。

Q5: 如何实现消息的重试机制?

A: 当前版本未实现自动重试,可以在 handler() 方法中添加重试逻辑,或使用Spring Retry框架。

相关推荐
唐青枫1 小时前
Java MyBatis 实战指南:XML 映射、动态 SQL 与数据访问层设计
java·mybatis
_日拱一卒1 小时前
LeetCode:39组合总和
java·算法·leetcode·职场和发展
郝学胜-神的一滴1 小时前
力扣 662 :二叉树最大宽度
java·数据结构·c++·python·算法·leetcode·职场和发展
仙俊红1 小时前
反射到底解决什么问题?
java·开发语言
小森林之主1 小时前
凌晨3点的闹钟:分布式定时任务设计实战
java·redis·任务调度·cron·分布式定时任务
yaoxin5211231 小时前
430. Java 日期时间 API - 时间计算 Temporal 包
java·前端·python
北京耐用通信1 小时前
耐达讯自动化NY-N801网关实现Modbus转Profinet协议转换应用案例
人工智能·物联网·网络协议·自动化·信息与通信
星马梦缘1 小时前
数据库 第十三章 未完结版本
java·网络·数据库
程序猿乐锅1 小时前
【JAVASE | 第十六篇】多线程
java·开发语言