Files
railwaysystem/RuoYi-Vue/docs/mqtt_usage_and_status.md
T
buliping 1def568f24 refactor(iot): 移除 MQTT 传输层,仅保留设备开放接口与门禁写操作策略
- 依赖:删除根 pom 的 paho.version 与 org.eclipse.paho.client.mqttv3 依赖声明,
  同步移除 ruoyi-iot 模块中的 Paho 依赖,模块描述改为“iot设备数据与开放接口”
- MQTT 协议栈整体删除:IotMqttConfig、IotMqttProperties、MqttClientManager、
  MqttMessageDispatcher、MqttCallbackHandler、MqttConnectionCallback、
  IMqttMessageListener、MqttMessageDTO、MqttPublishRequest、MqttMessageEvent、
  IMqttService/MqttServiceImpl、IotMqttController,以及 lock 侧 topic 订阅与
  LockMessageListener、AccessRecordHandler、CommandRespHandler、
  GatewayOnlineHandler、LockSignalHandler
- MQTT 报文留痕链路删除:LockMqttLog 实体、LockMqttLogMapper(+XML)、
  ILockMqttLogService/LockMqttLogServiceImpl、LockMqttLogController,
  以及对应的前端页面 build_ui/src/views/iot/mqttlog 与 API build_ui/src/api/iot/mqttlog.js
- 命令下发改为仅落库:LockCommandServiceImpl 派发时直接置 TRANSPORT_DISABLED 失败,
  retryPending 返回 0;删除 GatewayOfflineCheckTask、LockCommandRetryTask、
  LockDeviceOfflineCheckTask 等依赖 MQTT 的补偿与离线检查定时任务
- 保留与传输无关的门禁写操作策略:新增 IotLockSecurityProperties(iot.lock-security),
  RealIotSecurityGate 及其测试改用该配置类
- 配置与调用处清理:application*.yml 中 iot.mqtt 段迁移为 iot.lock-security,
  删除 broker-url/client-id/username/password/topics/will/qos 等连接参数,
  修正 iot.face 环境标识注释引用;QA 工具启动参数改用 --iot.lock-security.*,
  并移除 browser_regression.js 的 mqttlog 路由
- 测试同步:删除依赖 MQTT 与传输层的 CommandRespHandlerMockTest、
  Round5sEgressIsolationTest、Round5sExecutionIntegrationTest、
  Round5v4BrowserFixture、Round5v4KingbaseIntegrationTest;
  更新 RealIotSecurityGateTest、FaceExtractClientMockTest、
  TrainIotLockAuthBridgeMockTest

说明:数据库侧 lock_mqtt_log 表结构与历史验收文档保留,未做数据迁移;
前端若仍配置了“MQTT报文日志”菜单,需在后台菜单管理中一并移除。
2026-11-10 16:06:23 +08:00

260 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MQTT 使用情况与生效状态分析
> **分析对象**:RuoYi-Vue(`railwaysystem`)全部模块
> **分析时间**:2026-11-10
> **分析方式**:源码静态梳理 + 运行时探测(进程、端口、Broker 连通性、日志),**未修改任何代码**
---
## 目录
- [1. 结论摘要](#1-结论摘要)
- [2. MQTT 功能清单](#2-mqtt-功能清单)
- [2.1 连接与消息通道](#21-连接与消息通道)
- [2.2 上行:设备 → 平台(5 个订阅主题)](#22-上行设备--平台5-个订阅主题)
- [2.3 下行:平台 → 设备](#23-下行平台--设备)
- [2.4 运维与辅助](#24-运维与辅助)
- [3. 相关代码文件清单](#3-相关代码文件清单)
- [4. 生效状态分析](#4-生效状态分析)
- [4.1 配置层](#41-配置层)
- [4.2 运行时证据](#42-运行时证据)
- [4.3 日志证据](#43-日志证据)
- [4.4 断连时的代码行为](#44-断连时的代码行为)
- [4.5 功能可用性对照](#45-功能可用性对照)
- [5. 判断依据与恢复方向](#5-判断依据与恢复方向)
---
## 1. 结论摘要
| 维度 | 结论 |
|---|---|
| 配置状态 | **已启用**:`enabled=true`、`mock=false`、`protocol-verified=true`、`real-device-write-enabled=true` |
| 运行状态 | **当前未生效(连接断开)**:应用进程对 Broker 处于 `SYN_SENT`,健康检查每约 30s 重连并持续失败 |
| 消息收发 | **上行不可用**(未订阅,收不到任何上行报文);**下行不可用**(命令派发前即判失败) |
| 代码可用性 | **链路完好**:2026-11-09 日志中 27 次连接成功,并有真实上行报文处理记录 |
| 根因定位 | **环境问题,非代码缺陷**:配置的 Broker `192.168.1.202:1883` 网络不可达;本机 `127.0.0.1:1883` 实际运行着 mosquitto,但配置未指向 |
> 一句话结论:**MQTT 代码与配置都已就绪,但当前连不上配置的 Broker,所有门禁上下行功能处于停摆状态。**
---
## 2. MQTT 功能清单
### 2.1 连接与消息通道
| # | 功能点 | 说明 | 关键代码 |
|---|---|---|---|
| 1 | 客户端建立与重连 | 启动异步连接;失败后由 `iot-mqtt-health` 线程每 30s 兜底重连 | `MqttClientManager#connect / startHealthExecutor` |
| 2 | 启动自动订阅 | 5 个上行主题,QoS 1;重连后 `resubscribe()` 恢复订阅 | `MqttClientManager#resubscribe` |
| 3 | 消息分发 | 按主题分发至业务监听器,并发布 `MqttMessageEvent`;单监听器异常隔离 | `MqttMessageDispatcher#dispatch` |
| 4 | 报文留痕 | 收/发报文写入 `lock_mqtt_log`,payload 脱敏为 `[redacted]` | `LockMqttLogServiceImpl` |
### 2.2 上行:设备 → 平台(5 个订阅主题)
| # | 功能点 | 主题 | 处理结果 |
|---|---|---|---|
| 5 | 网关上下线 | `ydlink/+/thing/onoff_line` | 更新网关在线状态 |
| 6 | 属性设置响应 | `ydlink/+/thing/property/set_response` | 命令状态机推进 |
| 7 | 远程开关锁响应 | `ydlink/+/thing/action/execute_response` | 命令状态机推进 |
| 8 | 授权批量下发回执 | `ydlink/+/thing/data/batch_report` | 授权命令确认 / 拒绝 |
| 9 | 开门记录上报 | `ydlink/+/thing/event/trigger` | 写入开门记录 |
| 10 | 信号/电量上报 | `ydlink/+/thing/event/trigger`(op=RSSI) | 更新设备信号值 |
### 2.3 下行:平台 → 设备
| # | 功能点 | 触发入口 | 说明 |
|---|---|---|---|
| 11 | 门锁授权下发 / 撤销 | 学员入住、退宿、换宿 | 事务提交后派发(outbox 模式) |
| 12 | 远程开锁 / 关锁 | 管理端门锁操作 | 同上 |
| 13 | 网关属性设置(校时、网络参数) | 管理端网关管理 | 同上 |
| 14 | 命令补偿重发 | `LockCommandRetryTask`(每 60s) | **`retry-task-enabled=false`,当前不执行** |
| 15 | 派发安全门禁 | `RealIotSecurityGate` | 环境隔离、SN 白名单、协议审批、真实写入总开关;不通过则 `POLICY_BLOCKED_NOT_ATTEMPTED` |
### 2.4 运维与辅助
| # | 功能点 | 说明 |
|---|---|---|
| 16 | `/iot/mqtt/status|publish|subscribe` | 仅 `mock=true` 或 `qa-api-enabled=true` 时加载,**当前不加载** |
| 17 | `/qa/mock/**` 模拟接口 | 同上,**当前不可用** |
| 18 | 网关离线判定 | `GatewayOfflineCheckTask`、`LockDeviceOfflineCheckTask`(定时任务,遗嘱消息已关闭) |
> **不属于 MQTT 的部分**:报到机人脸上传后的"门锁入住"走 HTTP 开放平台(`TrainLockStayApiClient#callStayCheckin`),不受本次断连影响。
---
## 3. 相关代码文件清单
### 3.1 连接核心与配置(`ruoyi-iot`)
```
com/ruoyi/iot/config/IotMqttProperties.java
com/ruoyi/iot/config/IotMqttConfig.java
com/ruoyi/iot/core/MqttClientManager.java
com/ruoyi/iot/core/MqttCallbackHandler.java
com/ruoyi/iot/core/MqttConnectionCallback.java
com/ruoyi/iot/core/MqttMessageDispatcher.java
com/ruoyi/iot/core/IMqttMessageListener.java
com/ruoyi/iot/domain/MqttMessageDTO.java
com/ruoyi/iot/domain/MqttPublishRequest.java
com/ruoyi/iot/event/MqttMessageEvent.java
com/ruoyi/iot/service/IMqttService.java
com/ruoyi/iot/service/impl/MqttServiceImpl.java
com/ruoyi/iot/controller/IotMqttController.java
```
### 3.2 门禁协议与消息处理
```
com/ruoyi/iot/lock/mqtt/LockTopicSubscriber.java
com/ruoyi/iot/lock/mqtt/LockMessageListener.java
com/ruoyi/iot/lock/mqtt/handler/GatewayOnlineHandler.java
com/ruoyi/iot/lock/mqtt/handler/CommandRespHandler.java
com/ruoyi/iot/lock/mqtt/handler/AccessRecordHandler.java
com/ruoyi/iot/lock/mqtt/handler/LockSignalHandler.java
com/ruoyi/iot/lock/protocol/YdlinkCodec.java
com/ruoyi/iot/lock/protocol/YdlinkFrameDTO.java
com/ruoyi/iot/lock/protocol/YdlinkOp.java
com/ruoyi/iot/lock/protocol/YdlinkTopicBuilder.java
com/ruoyi/iot/lock/protocol/YdlinkTopicDTO.java
com/ruoyi/iot/lock/protocol/YdlinkTopicType.java
```
### 3.3 门禁业务
```
com/ruoyi/iot/lock/security/RealIotSecurityGate.java
com/ruoyi/iot/lock/service/impl/LockCommandServiceImpl.java
com/ruoyi/iot/lock/service/impl/LockAuthServiceImpl.java
com/ruoyi/iot/lock/service/impl/LockDeviceServiceImpl.java
com/ruoyi/iot/lock/service/impl/LockGatewayServiceImpl.java
com/ruoyi/iot/lock/service/impl/LockMqttLogServiceImpl.java
com/ruoyi/iot/lock/service/ILockMqttLogService.java
com/ruoyi/iot/lock/domain/LockMqttLog.java
com/ruoyi/iot/lock/mapper/LockMqttLogMapper.java
com/ruoyi/iot/lock/controller/LockMqttLogController.java
com/ruoyi/iot/lock/task/LockCommandRetryTask.java
com/ruoyi/iot/lock/task/GatewayOfflineCheckTask.java
com/ruoyi/iot/lock/task/LockDeviceOfflineCheckTask.java
```
### 3.4 业务桥接(`ruoyi-train`)
```
com/ruoyi/train/integration/lock/service/impl/TrainIotLockAuthBridgeServiceImpl.java
com/ruoyi/train/integration/lock/service/ITrainLockAuthBridgeService.java
com/ruoyi/train/integration/lock/dto/LockAuthBridgeResult.java
```
> 同目录 `client/TrainLockStayApiClient.java`、`dto/LockStayCheckinResult.java` 为 **HTTP** 门锁开放平台调用,不走 MQTT。
### 3.5 QA 与测试
```
ruoyi-admin/src/main/java/com/ruoyi/web/controller/tool/QaMockMqttApiController.java
ruoyi-iot/src/test/java/...(MqttClientManagerTest、MqttMessageDispatcherTest、LockMessageListenerTest 等)
```
### 3.6 配置文件
```
ruoyi-admin/src/main/resources/application.yml # iot.mqtt.*(195-259 行,当前生效)
ruoyi-admin/src/main/resources/application_localhost.yml # broker 指向 202/24
ruoyi-admin/src/main/resources/application_localhost_24.yml
ruoyi-admin/src/main/resources/application_localhost_230.yml
```
> 激活 profile 为 `druid`,其 `application-druid.yml` 无 `iot.mqtt` 覆盖项,最终以 `application.yml` 为准。
---
## 4. 生效状态分析
### 4.1 配置层
```yaml
mqtt:
enabled: true # 创建连接
mock: false # 真实收发
protocol-verified: true # 允许真实下发
real-device-write-enabled: true
environment-id: railway-iot-01
retry-task-enabled: false # 补偿任务关闭
broker-url: tcp://192.168.1.202:1883
username: admin3
password: admin3
automatic-reconnect: true
health-check-interval: 30000
topics: 5 个上行主题(QoS 1)
```
### 4.2 运行时证据
| 检查项 | 结果 | 含义 |
|---|---|---|
| 应用进程 | `java.exe` PID 26688 监听 `0.0.0.0:8096` | 应用正在运行 |
| MQTT socket | 对 `192.168.1.202:1883` 处于 **`SYN_SENT`** | TCP 连接未建立,正在重试 |
| Broker 连通性 | `Test-NetConnection 192.168.1.202:1883` → TCP 失败、Ping 超时 | Broker 不可达 |
| 本机 1883 | `127.0.0.1:1883` 由 `mosquitto.exe`(PID 6032)监听 | 本地有可用 Broker,配置未指向 |
### 4.3 日志证据
| 日志 | 证据 |
|---|---|
| `logs/sys-error.log` | `[iot-mqtt-health] ERROR MqttClientManager - MQTT 连接失败, broker=tcp://192.168.1.202:1883`,累计 **448 次**,间隔约 30s;今日 13:52:50 仍有新记录 |
| `logs/sys-info.2026-11-09.log` | `MQTT 连接成功` **27 次**;含真实上行处理(网关状态更新、开门记录落库) |
| `logs/sys-info.log`(今日) | 无任何 MQTT 行 —— 今日连接从未成功,失败仅写 error 日志 |
### 4.4 断连时的代码行为
```java
// LockCommandServiceImpl#dispatch
if (!mqttService.isConnected()) {
log.warn("[TRACE-DISPATCH] cid={} isConnected=false -> TRANSPORT_DISCONNECTED", commandId);
command.setCommandStatus(LockConstants.STATUS_FAIL);
command.setErrorMsg("TRANSPORT_DISCONNECTED_NOT_ATTEMPTED");
commandMapper.updateCommand(command);
return null;
}
```
断连时命令**在发送前即被标记失败**,不会进入 `AWAITING_ACK`,因此不存在"平台显示已下发、设备未收到"的假成功。
### 4.5 功能可用性对照
| 功能 | 状态 | 依据 |
|---|---|---|
| MQTT 连接 | ❌ 未连接 | `SYN_SENT` + 448 次失败日志 + Broker 不可达 |
| 上行 5 个主题 | ❌ 无订阅 | 订阅在 `connect()` 成功后执行,当前未连接 |
| 网关状态 / 开门记录 / 回执处理 | ❌ 停止 | 依赖上行报文 |
| 授权下发 / 远程开关锁 / 网关设置 | ❌ 直接失败 | `isConnected=false` → `STATUS_FAIL` |
| 命令补偿重发 | ❌ 不执行 | `retry-task-enabled=false` |
| `/iot/mqtt/status`、`/qa/mock/**` | ❌ 未加载 | `@ConditionalOnProperty` 条件不满足,**无法用接口自证状态** |
| 报到机人脸采集与门锁入住(HTTP) | ✅ 正常 | 走 HTTP 开放平台,与 MQTT 无关 |
---
## 5. 判断依据与恢复方向
### 5.1 判断依据
1. **配置齐备**:开关、Mock、协议审批、真实写入、白名单均已就位 → 具备生效条件。
2. **运行证据否证**:应用存活但 TCP 未连通,健康检查持续失败 → 连接未建立。
3. **产物证据缺失**:无订阅即无上行;命令侧有明确的失败状态码 → 收发均不可用。
4. **历史反证代码无缺陷**:曾出现 27 次连接成功与真实报文处理 → 问题在环境与配置指向,而非实现。
### 5.2 恢复方向(无需改代码)
1. 排查 `192.168.1.202:1883`:主机连通性、防火墙、Broker 进程、网段隔离;
2. 若现场已有本机 Broker,将 `iot.mqtt.broker-url` 改为 `tcp://127.0.0.1:1883`,并核对 `username/password`;
3. 恢复后按序验证:
- 日志出现 `MQTT 连接成功`
- 5 个主题订阅成功
- 收到网关 `onoff_line` 上行
- 对白名单内 SN 下发一次授权,确认回执进入 `CONFIRMED`
4. 如需离线补偿能力,再评估开启 `retry-task-enabled=true`(会周期性重发至真实设备,属生产变更,需审批)。
---
*文档结束。如需补充时序图或按模块拆分,可在此基础上扩展。*