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报文日志”菜单,需在后台菜单管理中一并移除。
This commit is contained in:
buliping committed 2026-11-10 16:06:23 +08:00
1 parent f5e8d7f564
commit 1def568f24
54 files changed
+690 -4734

No files matched your search

@@ -1,113 +0,0 @@
package com.ruoyi.web.controller.tool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.access.prepost.PreAuthorize;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.iot.domain.MqttMessageDTO;
import com.ruoyi.iot.lock.protocol.YdlinkFrameDTO;
import com.ruoyi.iot.service.IMqttService;
/**
* 独立 QA 测试环境专属:Mock MQTT 消息检查与设备回执驱动 Controller
* 仅在配置 train.iot.mock-mode=true 时加载,且必须经过管理员权限校验,禁止生产泄露与匿名调用
*/
@RestController
@RequestMapping("/qa/mock")
@ConditionalOnProperty(name = {"iot.mqtt.mock", "iot.qa-api-enabled"}, havingValue = "true")
@PreAuthorize("@ss.hasRole('admin') or @ss.hasPermi('iot:command:edit')")
public class QaMockMqttApiController
{
@Autowired
private IMqttService mqttService;
@Autowired
private com.ruoyi.iot.lock.service.ILockCommandService commandService;
/**
* 获取所有拦截到的下行指令
*/
@GetMapping("/published")
public AjaxResult getPublishedMessages()
{
return AjaxResult.success(mqttService.getMockPublishedMessages());
}
/**
* 清空已拦截的下行指令
*/
@PostMapping("/clear")
public AjaxResult clearPublishedMessages()
{
mqttService.getMockPublishedMessages().clear();
return AjaxResult.success("已清空");
}
/**
* 模拟注入网关与门锁回执报文
*
* @param body 参数字典包含:
* groupCode: 分组编号 (如 GRP_FUSION_TEST)
* commandNo: 命令流水号 seq
* deviceSn: 门锁SN (如 LOCK-ZY-101)
* op: 操作类型 (add / delete)
* code: 结果码 (0=门锁成功, 10=网关已接收, 20=门锁执行失败)
* msg: 说明消息
*/
@PostMapping("/send-ack")
public AjaxResult sendAck(@RequestBody Map<String, Object> body)
{
String groupCode = (String) body.getOrDefault("groupCode", "GRP_FUSION_TEST");
String commandNo = (String) body.get("commandNo");
String deviceSn = (String) body.getOrDefault("deviceSn", "LOCK-ZY-101");
String op = (String) body.getOrDefault("op", "add");
String code = String.valueOf(body.getOrDefault("code", "0"));
String msg = (String) body.getOrDefault("msg", "mock execution ok");
if (commandNo == null || commandNo.isEmpty())
{
return AjaxResult.error("commandNo (seq) 不能为空");
}
String topic = "ydlink/" + groupCode + "/thing/data/batch_report";
YdlinkFrameDTO frame = new YdlinkFrameDTO();
frame.setDevId(groupCode);
frame.setSeq(commandNo);
com.ruoyi.iot.lock.domain.LockCommand command = commandService.selectCommandList(new com.ruoyi.iot.lock.domain.LockCommand())
.stream().filter(c -> commandNo.equals(c.getCommandNo())).findFirst().orElseThrow();
com.ruoyi.iot.lock.protocol.YdlinkFrameDTO original = com.ruoyi.iot.lock.protocol.YdlinkCodec.fromJson(command.getPayload());
if (original == null) return AjaxResult.error("Command payload is invalid");
groupCode = original.getDevId(); deviceSn = command.getDeviceSn();
op = String.valueOf(original.getParams().get(0));
topic = command.getTopic() + "_report";
frame.setDevId(groupCode); frame.setOp(original.getOp());
frame.setAt(String.valueOf(System.currentTimeMillis() / 1000L));
List<Object> records = new ArrayList<>();
records.add(Arrays.asList(op, deviceSn, code, "person_mock", msg));
frame.setParams(records);
String payload = JSON.toJSONString(frame);
mqttService.mockReceive(topic, payload);
return AjaxResult.success("Mock 回执注入成功", Map.of(
"topic", topic,
"seq", commandNo,
"code", code,
"payload", payload
));
}
}
@@ -176,7 +176,7 @@ iot:
#base-url: http://localhost:8096 # 相对路径拼接后端 base URL 用;本地联调用默认值即可,生产环境改成公网地址(如 http://<公网IP>:<端口>)
# 人脸特征提取服务(数据库 lock_service_config 中 config_type=02 的配置优先级更高)
face:
# 必须与 iot.mqtt.environment-id 一致,否则 requireFaceIdentity 判定环境不一致而拒绝人脸提取授权
# 必须与 iot.lock-security.environment-id 一致,否则 requireFaceIdentity 判定环境不一致而拒绝人脸提取授权
environment-id: railway-iot-01
endpoint: http://192.168.1.202:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
#endpoint: http://192.168.1.230:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
@@ -192,14 +192,14 @@ iot:
avatar_profile: http://192.168.1.106:8096
face_api_key: To_eIlokUB6vf6HPSFugljLaAZh38AOI3al5xwb8rFU
mqtt:
# 开关,false 时不创建任何连接
# 门禁写操作安全策略(原 MQTT 配置中与传输无关的策略项,MQTT 已整体移除)
lock-security:
# 策略总开关,false 时禁止任何真实设备写操作
enabled: true
mock: false
protocol-verified: true # 真实协议证据已审批,允许真实下发(默认 false,必须显式开)
retry-task-enabled: false # 开启命令补偿调度(每 60s 捞 READY 指令兜底下发,默认 false)
protocol-verified: true # 真实协议证据已审批
environment-id: railway-iot-01 # 真实环境标识,必须不含 "mock"(避免回退到默认 local-mock)
real-device-write-enabled: true # 真实下行总开关(默认 false,必须显式开)
real-device-write-enabled: true # 真实写操作总开关(同时约束人脸 HTTP 设备授权)
device-whitelist: [
'807104B5', '80745D79','80745D95','80745D6F',
'80745DA3','80745D78','80745D89','80745D93',
@@ -218,43 +218,3 @@ iot:
'80745DA9','80745D91','80745D6B','80745DA6',
'80745D81','80745D9E','80745D83','80745DA0',
'E8F7AFF69483']
# MQTT服务器地址,ssl 场景使用 ssl://host:8883
broker-url: tcp://192.168.1.202:1883
#broker-url: tcp://192.168.1.230:1883
# 客户端ID,同一服务器下必须唯一;为空时自动生成
client-id:
username: admin3
password: admin3
# false 时服务端保留离线消息,需配合固定 client-id
clean-session: true
connection-timeout: 30
keep-alive-interval: 60
# 断线自动重连
automatic-reconnect: true
max-reconnect-delay: 30000
# 连接健康检查周期(毫秒),0 表示不做定时重连
health-check-interval: 30000
# 发布消息等待服务端确认时间(毫秒),0 表示不等待
publish-timeout: 3000
# 消息持久化目录,为空使用内存
persistence-directory:
default-qos: 1
default-retained: false
# 只订阅上行主题(下行由平台发布,不消费自己发出的指令)
topics:
- topic: ydlink/+/thing/onoff_line # 网关上下线
qos: 1
- topic: ydlink/+/thing/property/set_response # 属性设置响应
qos: 1
- topic: ydlink/+/thing/action/execute_response # 远程开关锁响应
qos: 1
- topic: ydlink/+/thing/data/batch_report # 授权下发结果
qos: 1
- topic: ydlink/+/thing/event/trigger # 开门记录上报
qos: 1
# 遗嘱消息:无法区分主动断开与异常掉线,门禁场景建议关闭(离线由 GatewayOfflineCheckTask 判定)
# will:
# topic: ydlink/status
# payload: offline
# qos: 1
# retained: true
@@ -176,7 +176,7 @@ iot:
#base-url: http://localhost:8096 # 相对路径拼接后端 base URL 用;本地联调用默认值即可,生产环境改成公网地址(如 http://<公网IP>:<端口>)
# 人脸特征提取服务(数据库 lock_service_config 中 config_type=02 的配置优先级更高)
face:
# 必须与 iot.mqtt.environment-id 一致,否则 requireFaceIdentity 判定环境不一致而拒绝人脸提取授权
# 必须与 iot.lock-security.environment-id 一致,否则 requireFaceIdentity 判定环境不一致而拒绝人脸提取授权
environment-id: railway-iot-01
#endpoint: http://192.168.1.202:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
endpoint: http://192.168.1.230:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
@@ -191,14 +191,13 @@ iot:
enabled: true
api-key: please-change-me
path-prefix: /openapi/iot
mqtt:
# 开关,false 时不创建任何连接
# 门禁写操作安全策略(原 MQTT 配置中与传输无关的策略项,MQTT 已整体移除)
lock-security:
enabled: true
mock: false
protocol-verified: true # 真实协议证据已审批,允许真实下发(默认 false,必须显式开)
retry-task-enabled: false # 开启命令补偿调度(每 60s 捞 READY 指令兜底下发,默认 false)
environment-id: railway-iot-01 # 真实环境标识,必须不含 "mock"(避免回退到默认 local-mock)
real-device-write-enabled: true # 真实下行总开关(默认 false,必须显式开)
protocol-verified: true
environment-id: railway-iot-01
real-device-write-enabled: true
device-whitelist: [
'807104B5', '80745D79','80745D95','80745D6F',
'80745DA3','80745D78','80745D89','80745D93',
@@ -217,43 +216,3 @@ iot:
'80745DA9','80745D91','80745D6B','80745DA6',
'80745D81','80745D9E','80745D83','80745DA0',
'E8F7AFF69483']
# MQTT服务器地址,ssl 场景使用 ssl://host:8883
#broker-url: tcp://192.168.1.202:1883
broker-url: tcp://192.168.1.230:1883
# 客户端ID,同一服务器下必须唯一;为空时自动生成
client-id:
username: admin3
password: admin3
# false 时服务端保留离线消息,需配合固定 client-id
clean-session: true
connection-timeout: 30
keep-alive-interval: 60
# 断线自动重连
automatic-reconnect: true
max-reconnect-delay: 30000
# 连接健康检查周期(毫秒),0 表示不做定时重连
health-check-interval: 30000
# 发布消息等待服务端确认时间(毫秒),0 表示不等待
publish-timeout: 3000
# 消息持久化目录,为空使用内存
persistence-directory:
default-qos: 1
default-retained: false
# 只订阅上行主题(下行由平台发布,不消费自己发出的指令)
topics:
- topic: ydlink/+/thing/onoff_line # 网关上下线
qos: 1
- topic: ydlink/+/thing/property/set_response # 属性设置响应
qos: 1
- topic: ydlink/+/thing/action/execute_response # 远程开关锁响应
qos: 1
- topic: ydlink/+/thing/data/batch_report # 授权下发结果
qos: 1
- topic: ydlink/+/thing/event/trigger # 开门记录上报
qos: 1
# 遗嘱消息:无法区分主动断开与异常掉线,门禁场景建议关闭(离线由 GatewayOfflineCheckTask 判定)
# will:
# topic: ydlink/status
# payload: offline
# qos: 1
# retained: true
@@ -176,7 +176,7 @@ iot:
#base-url: http://localhost:8096 # 相对路径拼接后端 base URL 用;本地联调用默认值即可,生产环境改成公网地址(如 http://<公网IP>:<端口>)
# 人脸特征提取服务(数据库 lock_service_config 中 config_type=02 的配置优先级更高)
face:
# 必须与 iot.mqtt.environment-id 一致,否则 requireFaceIdentity 判定环境不一致而拒绝人脸提取授权
# 必须与 iot.lock-security.environment-id 一致,否则 requireFaceIdentity 判定环境不一致而拒绝人脸提取授权
environment-id: railway-iot-01
#endpoint: http://192.168.1.202:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
endpoint: http://192.168.106.24:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
@@ -191,14 +191,13 @@ iot:
avatar_profile: http://192.168.106.24:8096
face_api_key: To_eIlokUB6vf6HPSFugljLaAZh38AOI3al5xwb8rFU
mqtt:
# 开关,false 时不创建任何连接
# 门禁写操作安全策略(原 MQTT 配置中与传输无关的策略项,MQTT 已整体移除)
lock-security:
enabled: true
mock: false
protocol-verified: true # 真实协议证据已审批,允许真实下发(默认 false,必须显式开)
retry-task-enabled: false # 开启命令补偿调度(每 60s 捞 READY 指令兜底下发,默认 false)
environment-id: railway-iot-01 # 真实环境标识,必须不含 "mock"(避免回退到默认 local-mock)
real-device-write-enabled: true # 真实下行总开关(默认 false,必须显式开)
protocol-verified: true
environment-id: railway-iot-01
real-device-write-enabled: true
device-whitelist: [
'807104B5', '80745D79','80745D95','80745D6F',
'80745DA3','80745D78','80745D89','80745D93',
@@ -217,43 +216,3 @@ iot:
'80745DA9','80745D91','80745D6B','80745DA6',
'80745D81','80745D9E','80745D83','80745DA0',
'E8F7AFF69483']
# MQTT服务器地址,ssl 场景使用 ssl://host:8883
#broker-url: tcp://192.168.1.202:1883
broker-url: tcp://192.168.106.24:1883
# 客户端ID,同一服务器下必须唯一;为空时自动生成
client-id:
username: admin3
password: admin3
# false 时服务端保留离线消息,需配合固定 client-id
clean-session: true
connection-timeout: 30
keep-alive-interval: 60
# 断线自动重连
automatic-reconnect: true
max-reconnect-delay: 30000
# 连接健康检查周期(毫秒),0 表示不做定时重连
health-check-interval: 30000
# 发布消息等待服务端确认时间(毫秒),0 表示不等待
publish-timeout: 3000
# 消息持久化目录,为空使用内存
persistence-directory:
default-qos: 1
default-retained: false
# 只订阅上行主题(下行由平台发布,不消费自己发出的指令)
topics:
- topic: ydlink/+/thing/onoff_line # 网关上下线
qos: 1
- topic: ydlink/+/thing/property/set_response # 属性设置响应
qos: 1
- topic: ydlink/+/thing/action/execute_response # 远程开关锁响应
qos: 1
- topic: ydlink/+/thing/data/batch_report # 授权下发结果
qos: 1
- topic: ydlink/+/thing/event/trigger # 开门记录上报
qos: 1
# 遗嘱消息:无法区分主动断开与异常掉线,门禁场景建议关闭(离线由 GatewayOfflineCheckTask 判定)
# will:
# topic: ydlink/status
# payload: offline
# qos: 1
# retained: true
@@ -176,7 +176,7 @@ iot:
#base-url: http://localhost:8096 # 相对路径拼接后端 base URL 用;本地联调用默认值即可,生产环境改成公网地址(如 http://<公网IP>:<端口>)
# 人脸特征提取服务(数据库 lock_service_config 中 config_type=02 的配置优先级更高)
face:
# 必须与 iot.mqtt.environment-id 一致,否则 requireFaceIdentity 判定环境不一致而拒绝人脸提取授权
# 必须与 iot.lock-security.environment-id 一致,否则 requireFaceIdentity 判定环境不一致而拒绝人脸提取授权
environment-id: railway-iot-01
endpoint: http://192.168.1.202:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
#endpoint: http://192.168.1.230:8802/bdws_api/Third/Api/Easen/getFaceFeatureData
@@ -192,14 +192,13 @@ iot:
avatar_profile: http://192.168.1.106:8096
face_api_key: To_eIlokUB6vf6HPSFugljLaAZh38AOI3al5xwb8rFU
mqtt:
# 开关,false 时不创建任何连接
# 门禁写操作安全策略(原 MQTT 配置中与传输无关的策略项,MQTT 已整体移除)
lock-security:
enabled: true
mock: false
protocol-verified: true # 真实协议证据已审批,允许真实下发(默认 false,必须显式开)
retry-task-enabled: false # 开启命令补偿调度(每 60s 捞 READY 指令兜底下发,默认 false)
environment-id: railway-iot-01 # 真实环境标识,必须不含 "mock"(避免回退到默认 local-mock)
real-device-write-enabled: true # 真实下行总开关(默认 false,必须显式开)
protocol-verified: true
environment-id: railway-iot-01
real-device-write-enabled: true
device-whitelist: [
'807104B5', '80745D79','80745D95','80745D6F',
'80745DA3','80745D78','80745D89','80745D93',
@@ -218,43 +217,3 @@ iot:
'80745DA9','80745D91','80745D6B','80745DA6',
'80745D81','80745D9E','80745D83','80745DA0',
'E8F7AFF69483']
# MQTT服务器地址,ssl 场景使用 ssl://host:8883
broker-url: tcp://192.168.1.202:1883
#broker-url: tcp://192.168.1.230:1883
# 客户端ID,同一服务器下必须唯一;为空时自动生成
client-id:
username: admin3
password: admin3
# false 时服务端保留离线消息,需配合固定 client-id
clean-session: true
connection-timeout: 30
keep-alive-interval: 60
# 断线自动重连
automatic-reconnect: true
max-reconnect-delay: 30000
# 连接健康检查周期(毫秒),0 表示不做定时重连
health-check-interval: 30000
# 发布消息等待服务端确认时间(毫秒),0 表示不等待
publish-timeout: 3000
# 消息持久化目录,为空使用内存
persistence-directory:
default-qos: 1
default-retained: false
# 只订阅上行主题(下行由平台发布,不消费自己发出的指令)
topics:
- topic: ydlink/+/thing/onoff_line # 网关上下线
qos: 1
- topic: ydlink/+/thing/property/set_response # 属性设置响应
qos: 1
- topic: ydlink/+/thing/action/execute_response # 远程开关锁响应
qos: 1
- topic: ydlink/+/thing/data/batch_report # 授权下发结果
qos: 1
- topic: ydlink/+/thing/event/trigger # 开门记录上报
qos: 1
# 遗嘱消息:无法区分主动断开与异常掉线,门禁场景建议关闭(离线由 GatewayOfflineCheckTask 判定)
# will:
# topic: ydlink/status
# payload: offline
# qos: 1
# retained: true