Commit 21472e8dfad464b41dea9bd7b1479c7877e488c9

Authored by haohao
1 parent c3499af5

feat:【IoT 物联网】新增 MQTT HTTP 认证协议,优化设备认证逻辑,更新配置文件以支持 HTTP 认证服务

Showing 13 changed files with 487 additions and 200 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
... ... @@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.iot.gateway.config;
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 4 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber;
5 5 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttHttpAuthProtocol;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
6 9 import lombok.extern.slf4j.Slf4j;
7 10 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8 11 import org.springframework.boot.context.properties.EnableConfigurationProperties;
... ... @@ -42,17 +45,24 @@ public class IotGatewayConfiguration {
42 45 @Slf4j
43 46 public static class MqttProtocolConfiguration {
44 47  
45   - // TODO @haohao:临时注释,避免报错
46   -// @Bean
47   -// public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties) {
48   -// return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getEmqx());
49   -// }
50   -//
51   -// @Bean
52   -// public IotMqttDownstreamSubscriber iotMqttDownstreamSubscriber(IotMqttUpstreamProtocol mqttUpstreamProtocol,
53   -// IotMessageBus messageBus) {
54   -// return new IotMqttDownstreamSubscriber(mqttUpstreamProtocol, messageBus);
55   -// }
  48 + @Bean
  49 + public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties) {
  50 + return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getEmqx());
  51 + }
  52 +
  53 + @Bean
  54 + public IotMqttDownstreamSubscriber iotMqttDownstreamSubscriber(IotMqttUpstreamProtocol mqttUpstreamProtocol,
  55 + IotMessageBus messageBus) {
  56 + return new IotMqttDownstreamSubscriber(mqttUpstreamProtocol, messageBus);
  57 + }
  58 +
  59 + /**
  60 + * MQTT HTTP 认证协议:提供 HTTP 认证接口供 EMQX 调用
  61 + */
  62 + @Bean
  63 + public IotMqttHttpAuthProtocol mqttHttpAuthProtocol(IotGatewayProperties gatewayProperties) {
  64 + return new IotMqttHttpAuthProtocol(gatewayProperties.getProtocol().getEmqx());
  65 + }
56 66 }
57 67  
58 68 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
... ... @@ -104,6 +104,10 @@ public class IotGatewayProperties {
104 104 @NotNull(message = "是否开启不能为空")
105 105 private Boolean enabled;
106 106 /**
  107 + * HTTP 认证端口
  108 + */
  109 + private Integer httpAuthPort = 8090;
  110 + /**
107 111 * MQTT 服务器地址
108 112 */
109 113 private String mqttHost;
... ... @@ -127,10 +131,6 @@ public class IotGatewayProperties {
127 131 * MQTT 主题
128 132 */
129 133 private List<String> mqttTopics;
130   - /**
131   - * 认证端口
132   - */
133   - private Integer authPort;
134 134  
135 135 }
136 136  
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttDownstreamSubscriber.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2 2  
3 3 import cn.hutool.json.JSONObject;
  4 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  5 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;
  6 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
4 7 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
5 8 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
6 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7 10 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8 11 import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceCacheService;
10 12 import jakarta.annotation.PostConstruct;
11 13 import jakarta.annotation.Resource;
12 14 import lombok.RequiredArgsConstructor;
... ... @@ -25,7 +27,7 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
25 27 private final IotMessageBus messageBus;
26 28  
27 29 @Resource
28   - private IotDeviceCacheService deviceCacheService;
  30 + private IotDeviceCommonApi deviceApi;
29 31  
30 32 @PostConstruct
31 33 public void init() {
... ... @@ -81,7 +83,9 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
81 83 String method = message.getMethod();
82 84  
83 85 // 通过 deviceId 获取设备信息
84   - IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfoById(message.getDeviceId());
  86 + IotDeviceGetReqDTO reqDTO = new IotDeviceGetReqDTO();
  87 + reqDTO.setId(message.getDeviceId());
  88 + IotDeviceRespDTO deviceInfo = deviceApi.getDevice(reqDTO).getData();
85 89 if (deviceInfo == null) {
86 90 log.warn("[handlePropertyMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
87 91 return;
... ... @@ -116,7 +120,9 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
116 120 String method = message.getMethod();
117 121  
118 122 // 通过 deviceId 获取设备信息
119   - IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfoById(message.getDeviceId());
  123 + IotDeviceGetReqDTO reqDTO = new IotDeviceGetReqDTO();
  124 + reqDTO.setId(message.getDeviceId());
  125 + IotDeviceRespDTO deviceInfo = deviceApi.getDevice(reqDTO).getData();
120 126 if (deviceInfo == null) {
121 127 log.warn("[handleServiceMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
122 128 return;
... ... @@ -141,7 +147,9 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
141 147 */
142 148 private void handleConfigMessage(IotDeviceMessage message) {
143 149 // 通过 deviceId 获取设备信息
144   - IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfoById(message.getDeviceId());
  150 + IotDeviceGetReqDTO reqDTO = new IotDeviceGetReqDTO();
  151 + reqDTO.setId(message.getDeviceId());
  152 + IotDeviceRespDTO deviceInfo = deviceApi.getDevice(reqDTO).getData();
145 153 if (deviceInfo == null) {
146 154 log.warn("[handleConfigMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
147 155 return;
... ... @@ -163,7 +171,9 @@ public class IotMqttDownstreamSubscriber implements IotMessageSubscriber&lt;IotDevi
163 171 */
164 172 private void handleOtaMessage(IotDeviceMessage message) {
165 173 // 通过 deviceId 获取设备信息
166   - IotDeviceCacheService.DeviceInfo deviceInfo = deviceCacheService.getDeviceInfoById(message.getDeviceId());
  174 + IotDeviceGetReqDTO reqDTO = new IotDeviceGetReqDTO();
  175 + reqDTO.setId(message.getDeviceId());
  176 + IotDeviceRespDTO deviceInfo = deviceApi.getDevice(reqDTO).getData();
167 177 if (deviceInfo == null) {
168 178 log.warn("[handleOtaMessage][设备信息不存在][deviceId: {}]", message.getDeviceId());
169 179 return;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttHttpAuthProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
  2 +
  3 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttHttpAuthHandler;
  5 +import io.vertx.core.AbstractVerticle;
  6 +import io.vertx.core.Vertx;
  7 +import io.vertx.core.http.HttpServer;
  8 +import io.vertx.ext.web.Router;
  9 +import io.vertx.ext.web.handler.BodyHandler;
  10 +import jakarta.annotation.PostConstruct;
  11 +import jakarta.annotation.PreDestroy;
  12 +import lombok.RequiredArgsConstructor;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +
  15 +/**
  16 + * IoT 网关 MQTT HTTP 认证协议:提供 HTTP 认证接口供 EMQX 调用
  17 + *
  18 + * @author 芋道源码
  19 + */
  20 +@RequiredArgsConstructor
  21 +@Slf4j
  22 +public class IotMqttHttpAuthProtocol extends AbstractVerticle {
  23 +
  24 + private final IotGatewayProperties.EmqxProperties emqxProperties;
  25 +
  26 + private HttpServer httpServer;
  27 + private Vertx vertx;
  28 +
  29 + @Override
  30 + @PostConstruct
  31 + public void start() {
  32 + log.info("[start][开始启动 MQTT HTTP 认证协议服务]");
  33 +
  34 + // 创建 Vertx 实例
  35 + this.vertx = Vertx.vertx();
  36 +
  37 + // 创建路由
  38 + Router router = Router.router(vertx);
  39 + router.route().handler(BodyHandler.create());
  40 +
  41 + // 创建 MQTT 认证处理器
  42 + IotMqttHttpAuthHandler authHandler = new IotMqttHttpAuthHandler();
  43 +
  44 + // 添加 MQTT 认证路由
  45 + router.post("/mqtt/auth/authenticate").handler(authHandler::authenticate);
  46 + router.post("/mqtt/auth/connected").handler(authHandler::connected);
  47 + router.post("/mqtt/auth/disconnected").handler(authHandler::disconnected);
  48 +
  49 + // 启动 HTTP 服务器,使用独立端口
  50 + int authPort = getAuthPort();
  51 + try {
  52 + httpServer = vertx.createHttpServer()
  53 + .requestHandler(router)
  54 + .listen(authPort)
  55 + .result();
  56 + log.info("[start][MQTT HTTP 认证协议启动成功,端口:{}]", authPort);
  57 + } catch (Exception e) {
  58 + log.error("[start][MQTT HTTP 认证协议启动失败]", e);
  59 + throw e;
  60 + }
  61 + }
  62 +
  63 + @Override
  64 + @PreDestroy
  65 + public void stop() {
  66 + if (httpServer != null) {
  67 + try {
  68 + httpServer.close().result();
  69 + log.info("[stop][MQTT HTTP 认证协议已停止]");
  70 + } catch (Exception e) {
  71 + log.error("[stop][MQTT HTTP 认证协议停止失败]", e);
  72 + }
  73 + }
  74 +
  75 + if (vertx != null) {
  76 + try {
  77 + vertx.close();
  78 + log.info("[stop][Vertx 已关闭]");
  79 + } catch (Exception e) {
  80 + log.warn("[stop][关闭 Vertx 异常]", e);
  81 + }
  82 + }
  83 + }
  84 +
  85 + /**
  86 + * 获取认证服务端口
  87 + * 从配置中获取,默认使用 8090 端口
  88 + */
  89 + private int getAuthPort() {
  90 + return emqxProperties.getHttpAuthPort() != null ? emqxProperties.getHttpAuthPort() : 8090;
  91 + }
  92 +
  93 + public String getServerId() {
  94 + return "mqtt_auth_gateway_" + getAuthPort();
  95 + }
  96 +}
0 97 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.http 0 → 100644
  1 +### IoT MQTT HTTP 认证服务测试文件
  2 +### 用于测试独立的 MQTT HTTP 认证服务接口
  3 +### 服务由 IotMqttHttpAuthProtocol 启动,运行在配置的端口上(默认 8090)
  4 +### 注意:这些接口通常由 EMQX 调用,也可以用于手动测试
  5 +
  6 +### 配置说明:
  7 +### 在 application-local.yaml 中可以配置端口:
  8 +### yudao:
  9 +### iot:
  10 +### gateway:
  11 +### protocol:
  12 +### emqx:
  13 +### http-auth-port: 8090 # 可以修改为其他端口
  14 +
  15 +### 1. MQTT 设备认证接口 => 成功认证
  16 +POST http://localhost:8090/mqtt/auth/authenticate
  17 +Content-Type: application/json
  18 +
  19 +{
  20 + "clientid": "test_product.test_device",
  21 + "username": "test_device&test_product",
  22 + "password": "test_device_secret_hmac_password"
  23 +}
  24 +
  25 +### 2. MQTT 设备认证接口 => 参数不完整(clientid 为空)
  26 +POST http://localhost:8090/mqtt/auth/authenticate
  27 +Content-Type: application/json
  28 +
  29 +{
  30 + "clientid": "",
  31 + "username": "test_device&test_product",
  32 + "password": "test_device_secret"
  33 +}
  34 +
  35 +### 3. MQTT 设备认证接口 => 参数不完整(username 为空)
  36 +POST http://localhost:8090/mqtt/auth/authenticate
  37 +Content-Type: application/json
  38 +
  39 +{
  40 + "clientid": "test_product.test_device",
  41 + "username": "",
  42 + "password": "test_device_secret"
  43 +}
  44 +
  45 +### 4. MQTT 设备认证接口 => 参数不完整(password 为空)
  46 +POST http://localhost:8090/mqtt/auth/authenticate
  47 +Content-Type: application/json
  48 +
  49 +{
  50 + "clientid": "test_product.test_device",
  51 + "username": "test_device&test_product",
  52 + "password": ""
  53 +}
  54 +
  55 +### 5. MQTT 设备认证接口 => 认证失败(错误的设备密钥)
  56 +POST http://localhost:8090/mqtt/auth/authenticate
  57 +Content-Type: application/json
  58 +
  59 +{
  60 + "clientid": "test_product.test_device",
  61 + "username": "test_device&test_product",
  62 + "password": "wrong_password"
  63 +}
  64 +
  65 +### 6. EMQX 客户端连接事件钩子 => 设备上线
  66 +POST http://localhost:8090/mqtt/auth/connected
  67 +Content-Type: application/json
  68 +
  69 +{
  70 + "clientid": "test_product.test_device",
  71 + "username": "test_device&test_product",
  72 + "timestamp": 1703808000000,
  73 + "peername": "192.168.1.100:52036",
  74 + "sockname": "127.0.0.1:1883"
  75 +}
  76 +
  77 +### 7. EMQX 客户端连接事件钩子 => 用户名为空
  78 +POST http://localhost:8090/mqtt/auth/connected
  79 +Content-Type: application/json
  80 +
  81 +{
  82 + "clientid": "test_product.test_device",
  83 + "username": "",
  84 + "timestamp": 1703808000000,
  85 + "peername": "192.168.1.100:52036",
  86 + "sockname": "127.0.0.1:1883"
  87 +}
  88 +
  89 +### 8. EMQX 客户端断开连接事件钩子 => 设备下线(正常断开)
  90 +POST http://localhost:8090/mqtt/auth/disconnected
  91 +Content-Type: application/json
  92 +
  93 +{
  94 + "clientid": "test_product.test_device",
  95 + "username": "test_device&test_product",
  96 + "reason": "normal",
  97 + "timestamp": 1703808060000
  98 +}
  99 +
  100 +### 9. 使用自定义端口测试(如果配置了不同端口)
  101 +### 假设配置了端口 8091:
  102 +### POST http://localhost:8091/mqtt/auth/authenticate
  103 +### Content-Type: application/json
  104 +###
  105 +### {
  106 +### "clientid": "test_product.test_device",
  107 +### "username": "test_device&test_product",
  108 +### "password": "test_device_secret_hmac_password"
  109 +### }
  110 +
  111 +### EMQX 配置参考:
  112 +### authentication = [
  113 +### {
  114 +### mechanism = http
  115 +### method = post
  116 +### url = "http://localhost:8090/mqtt/auth/authenticate" # 使用配置的端口
  117 +### body = {
  118 +### clientid = "${clientid}"
  119 +### username = "${username}"
  120 +### password = "${password}"
  121 +### }
  122 +### headers = {
  123 +### "content-type" = "application/json"
  124 +### }
  125 +### }
  126 +### ]
  127 +###
  128 +### webhooks = [
  129 +### {
  130 +### name = "client_connected"
  131 +### url = "http://localhost:8090/mqtt/auth/connected" # 使用配置的端口
  132 +### events = ["client.connected"]
  133 +### },
  134 +### {
  135 +### name = "client_disconnected"
  136 +### url = "http://localhost:8090/mqtt/auth/disconnected" # 使用配置的端口
  137 +### events = ["client.disconnected"]
  138 +### }
  139 +### ]
0 140 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/IotMqttUpstreamProtocol.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.ArrayUtil;
5 4 import cn.hutool.core.util.IdUtil;
6 5 import cn.hutool.core.util.ObjUtil;
7 6 import cn.hutool.core.util.StrUtil;
8   -import cn.hutool.extra.spring.SpringUtil;
9   -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
10 7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
11 8 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
12   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttAuthRouter;
13 9 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttUpstreamRouter;
14 10 import io.netty.handler.codec.mqtt.MqttQoS;
15 11 import io.vertx.core.Vertx;
... ... @@ -43,8 +39,6 @@ public class IotMqttUpstreamProtocol {
43 39 private Vertx vertx;
44 40 private MqttClient mqttClient;
45 41 private IotMqttUpstreamRouter messageRouter;
46   - private IotMqttAuthRouter authRouter;
47   - private IotDeviceMessageProducer deviceMessageProducer;
48 42  
49 43 /**
50 44 * 服务运行状态标志
... ... @@ -61,9 +55,7 @@ public class IotMqttUpstreamProtocol {
61 55  
62 56 // 初始化组件
63 57 this.vertx = Vertx.vertx();
64   - this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
65 58 this.messageRouter = new IotMqttUpstreamRouter(this);
66   - this.authRouter = new IotMqttAuthRouter(this);
67 59  
68 60 // 创建 MQTT 客户端
69 61 MqttClientOptions options = new MqttClientOptions()
... ... @@ -255,13 +247,4 @@ public class IotMqttUpstreamProtocol {
255 247 return mqttClient;
256 248 }
257 249  
258   - /**
259   - * 获取认证路由器
260   - *
261   - * @return 认证路由器
262   - */
263   - public IotMqttAuthRouter getAuthRouter() {
264   - return authRouter;
265   - }
266   -
267 250 }
268 251 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttAuthRouter.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.hutool.extra.spring.SpringUtil;
5   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6   -import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
8   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9   -import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
10   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
11   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
12   -import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
13   -import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
14   -import lombok.RequiredArgsConstructor;
15   -import lombok.extern.slf4j.Slf4j;
16   -
17   -/**
18   - * IoT 网关 MQTT 认证路由器
19   - * <p>
20   - * 处理设备的 MQTT 连接认证和连接状态管理
21   - *
22   - * @author 芋道源码
23   - */
24   -@RequiredArgsConstructor
25   -@Slf4j
26   -public class IotMqttAuthRouter {
27   -
28   - private final IotMqttUpstreamProtocol protocol;
29   - private final IotDeviceMessageProducer deviceMessageProducer;
30   - private final IotDeviceTokenService deviceTokenService;
31   - private final IotDeviceCommonApi deviceCommonApi;
32   - private final IotDeviceMessageService deviceMessageService;
33   -
34   - public IotMqttAuthRouter(IotMqttUpstreamProtocol protocol) {
35   - this.protocol = protocol;
36   - this.deviceMessageProducer = SpringUtil.getBean(IotDeviceMessageProducer.class);
37   - this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
38   - this.deviceCommonApi = SpringUtil.getBean(IotDeviceCommonApi.class);
39   - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
40   - }
41   -
42   - /**
43   - * 处理设备认证
44   - *
45   - * @param clientId 客户端 ID
46   - * @param username 用户名
47   - * @param password 密码
48   - * @return 认证结果
49   - */
50   - public boolean authenticate(String clientId, String username, String password) {
51   - try {
52   - log.info("[authenticate][开始认证设备][clientId: {}][username: {}]", clientId, username);
53   -
54   - // 1. 参数校验
55   - if (StrUtil.isEmpty(clientId) || StrUtil.isEmpty(username) || StrUtil.isEmpty(password)) {
56   - log.warn("[authenticate][认证参数不完整][clientId: {}][username: {}]", clientId, username);
57   - return false;
58   - }
59   -
60   - // 2. 执行认证
61   - CommonResult<Boolean> result = deviceCommonApi.authDevice(new IotDeviceAuthReqDTO()
62   - .setClientId(clientId).setUsername(username).setPassword(password));
63   - result.checkError();
64   - if (!Boolean.TRUE.equals(result.getData())) {
65   - log.warn("[authenticate][设备认证失败][clientId: {}][username: {}]", clientId, username);
66   - return false;
67   - }
68   -
69   - log.info("[authenticate][设备认证成功][clientId: {}][username: {}]", clientId, username);
70   - return true;
71   - } catch (Exception e) {
72   - log.error("[authenticate][设备认证异常][clientId: {}][username: {}]", clientId, username, e);
73   - return false;
74   - }
75   - }
76   -
77   - /**
78   - * 处理设备连接事件
79   - *
80   - * @param clientId 客户端 ID
81   - * @param username 用户名
82   - */
83   - public void handleClientConnected(String clientId, String username) {
84   - try {
85   - log.info("[handleClientConnected][设备连接][clientId: {}][username: {}]", clientId, username);
86   -
87   - // 解析设备信息并发送上线消息
88   - handleDeviceStateChange(username, true);
89   - } catch (Exception e) {
90   - log.error("[handleClientConnected][处理设备连接事件异常][clientId: {}][username: {}]", clientId, username, e);
91   - }
92   - }
93   -
94   - /**
95   - * 处理设备断开连接事件
96   - *
97   - * @param clientId 客户端 ID
98   - * @param username 用户名
99   - */
100   - public void handleClientDisconnected(String clientId, String username) {
101   - try {
102   - log.info("[handleClientDisconnected][设备断开连接][clientId: {}][username: {}]", clientId, username);
103   -
104   - // 解析设备信息并发送下线消息
105   - handleDeviceStateChange(username, false);
106   - } catch (Exception e) {
107   - log.error("[handleClientDisconnected][处理设备断开连接事件异常][clientId: {}][username: {}]", clientId, username, e);
108   - }
109   - }
110   -
111   - /**
112   - * 处理设备状态变化
113   - *
114   - * @param username 用户名
115   - * @param online 是否在线
116   - */
117   - private void handleDeviceStateChange(String username, boolean online) {
118   - // 解析设备信息
119   - if (StrUtil.isEmpty(username) || "undefined".equals(username)) {
120   - log.warn("[handleDeviceStateChange][用户名为空或未定义][username: {}]", username);
121   - return;
122   - }
123   -
124   - IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.parseUsername(username);
125   - if (deviceInfo == null) {
126   - log.warn("[handleDeviceStateChange][无法解析设备信息][username: {}]", username);
127   - return;
128   - }
129   -
130   - try {
131   - // 使用 IotDeviceMessageService 构建设备状态消息
132   - IotDeviceMessage message;
133   - if (online) {
134   - message = deviceMessageService.buildDeviceMessageOfStateOnline(
135   - deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId());
136   - log.info("[handleDeviceStateChange][发送设备上线消息成功][username: {}]", username);
137   - } else {
138   - message = deviceMessageService.buildDeviceMessageOfStateOffline(
139   - deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId());
140   - log.info("[handleDeviceStateChange][发送设备下线消息成功][username: {}]", username);
141   - }
142   -
143   - deviceMessageProducer.sendDeviceMessage(message);
144   - } catch (Exception e) {
145   - log.error("[handleDeviceStateChange][发送设备状态消息失败][username: {}][online: {}]", username, online, e);
146   - }
147   - }
148   -
149   -}
150 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttEventHandler.java
... ... @@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 6 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
7 7 import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
8 8 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
9   -import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
  9 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10 10 import lombok.RequiredArgsConstructor;
11 11 import lombok.extern.slf4j.Slf4j;
12 12  
... ... @@ -49,7 +49,7 @@ public class IotMqttEventHandler extends IotMqttAbstractHandler {
49 49 // 使用 IotDeviceMessageService 解码消息
50 50 byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
51 51 IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
52   - messageBytes, productKey, deviceName, protocol.getServerId());
  52 + messageBytes, productKey, deviceName);
53 53  
54 54 // 发送消息
55 55 deviceMessageProducer.sendDeviceMessage(message);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttHttpAuthHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router;
  2 +
  3 +import cn.hutool.core.util.BooleanUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.hutool.extra.spring.SpringUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  8 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
  9 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  10 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  11 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  12 +import io.vertx.core.json.JsonObject;
  13 +import io.vertx.ext.web.RoutingContext;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +
  16 +/**
  17 + * IoT 网关 MQTT HTTP 认证处理器
  18 + * <p>
  19 + * 处理 EMQX 的认证请求和事件钩子
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +@Slf4j
  24 +public class IotMqttHttpAuthHandler {
  25 +
  26 + /**
  27 + * EMQX 认证接口
  28 + */
  29 + public void authenticate(RoutingContext context) {
  30 + try {
  31 + // 解析请求体
  32 + JsonObject body = context.body().asJsonObject();
  33 + if (body == null) {
  34 + sendErrorResponse(context, 400, "请求体不能为空");
  35 + return;
  36 + }
  37 +
  38 + String clientid = body.getString("clientid");
  39 + String username = body.getString("username");
  40 + String password = body.getString("password");
  41 +
  42 + log.info("[authenticate][EMQX 设备认证请求][clientId: {}][username: {}]", clientid, username);
  43 +
  44 + // 参数校验
  45 + if (StrUtil.isEmpty(clientid) || StrUtil.isEmpty(username) || StrUtil.isEmpty(password)) {
  46 + log.warn("[authenticate][认证参数不完整][clientId: {}][username: {}]", clientid, username);
  47 + sendErrorResponse(context, 400, "认证参数不完整");
  48 + return;
  49 + }
  50 +
  51 + // 执行设备认证
  52 + IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
  53 + CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()
  54 + .setClientId(clientid)
  55 + .setUsername(username)
  56 + .setPassword(password));
  57 +
  58 + result.checkError();
  59 + if (!BooleanUtil.isTrue(result.getData())) {
  60 + log.warn("[authenticate][设备认证失败][clientId: {}][username: {}]", clientid, username);
  61 + sendErrorResponse(context, 401, "设备认证失败");
  62 + return;
  63 + }
  64 +
  65 + log.info("[authenticate][设备认证成功][clientId: {}][username: {}]", clientid, username);
  66 + sendSuccessResponse(context, "认证成功");
  67 +
  68 + } catch (Exception e) {
  69 + log.error("[authenticate][设备认证异常]", e);
  70 + sendErrorResponse(context, 500, "认证服务异常");
  71 + }
  72 + }
  73 +
  74 + /**
  75 + * EMQX 客户端连接事件钩子
  76 + */
  77 + public void connected(RoutingContext context) {
  78 + try {
  79 + // 解析请求体
  80 + JsonObject body = context.body().asJsonObject();
  81 + if (body == null) {
  82 + sendErrorResponse(context, 400, "请求体不能为空");
  83 + return;
  84 + }
  85 +
  86 + String clientid = body.getString("clientid");
  87 + String username = body.getString("username");
  88 + Long timestamp = body.getLong("timestamp");
  89 +
  90 + log.info("[connected][设备连接事件][clientId: {}][username: {}]", clientid, username);
  91 +
  92 + handleDeviceStateChange(username, true);
  93 + sendSuccessResponse(context, "处理成功");
  94 +
  95 + } catch (Exception e) {
  96 + log.error("[connected][处理设备连接事件失败]", e);
  97 + sendErrorResponse(context, 500, "处理失败");
  98 + }
  99 + }
  100 +
  101 + /**
  102 + * EMQX 客户端断开连接事件钩子
  103 + */
  104 + public void disconnected(RoutingContext context) {
  105 + try {
  106 + // 解析请求体
  107 + JsonObject body = context.body().asJsonObject();
  108 + if (body == null) {
  109 + sendErrorResponse(context, 400, "请求体不能为空");
  110 + return;
  111 + }
  112 +
  113 + String clientid = body.getString("clientid");
  114 + String username = body.getString("username");
  115 + String reason = body.getString("reason");
  116 + Long timestamp = body.getLong("timestamp");
  117 +
  118 + log.info("[disconnected][设备断开连接事件][clientId: {}][username: {}][reason: {}]",
  119 + clientid, username, reason);
  120 +
  121 + handleDeviceStateChange(username, false);
  122 + sendSuccessResponse(context, "处理成功");
  123 +
  124 + } catch (Exception e) {
  125 + log.error("[disconnected][处理设备断开连接事件失败]", e);
  126 + sendErrorResponse(context, 500, "处理失败");
  127 + }
  128 + }
  129 +
  130 + /**
  131 + * 处理设备状态变化
  132 + *
  133 + * @param username 用户名
  134 + * @param online 是否在线
  135 + */
  136 + private void handleDeviceStateChange(String username, boolean online) {
  137 + if (StrUtil.isEmpty(username) || "undefined".equals(username)) {
  138 + log.warn("[handleDeviceStateChange][用户名为空或未定义][username: {}]", username);
  139 + return;
  140 + }
  141 +
  142 + // 解析设备信息
  143 + IotDeviceAuthUtils.DeviceInfo deviceInfo = IotDeviceAuthUtils.parseUsername(username);
  144 + if (deviceInfo == null) {
  145 + log.warn("[handleDeviceStateChange][无法解析设备信息][username: {}]", username);
  146 + return;
  147 + }
  148 +
  149 + try {
  150 + // 获取服务器 ID
  151 + String serverId = "mqtt_auth_gateway";
  152 +
  153 + // 构建设备状态消息
  154 + IotDeviceMessageService deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  155 + IotDeviceMessage message;
  156 + if (online) {
  157 + message = IotDeviceMessage.buildStateOnline();
  158 + log.info("[handleDeviceStateChange][发送设备上线消息成功][username: {}]", username);
  159 + } else {
  160 + message = IotDeviceMessage.buildStateOffline();
  161 + log.info("[handleDeviceStateChange][发送设备下线消息成功][username: {}]", username);
  162 + }
  163 +
  164 + // 发送消息到消息总线
  165 + deviceMessageService.sendDeviceMessage(message,
  166 + deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId);
  167 + } catch (Exception e) {
  168 + log.error("[handleDeviceStateChange][发送设备状态消息失败][username: {}][online: {}]",
  169 + username, online, e);
  170 + }
  171 + }
  172 +
  173 + /**
  174 + * 发送成功响应
  175 + */
  176 + private void sendSuccessResponse(RoutingContext context, String message) {
  177 + context.response()
  178 + .setStatusCode(200)
  179 + .putHeader("Content-Type", "text/plain; charset=utf-8")
  180 + .end(message);
  181 + }
  182 +
  183 + /**
  184 + * 发送错误响应
  185 + */
  186 + private void sendErrorResponse(RoutingContext context, int statusCode, String message) {
  187 + context.response()
  188 + .setStatusCode(statusCode)
  189 + .putHeader("Content-Type", "text/plain; charset=utf-8")
  190 + .end(message);
  191 + }
  192 +}
0 193 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttPropertyHandler.java
... ... @@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 6 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
7 7 import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
8 8 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
9   -import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
  9 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10 10 import lombok.RequiredArgsConstructor;
11 11 import lombok.extern.slf4j.Slf4j;
12 12  
... ... @@ -65,7 +65,7 @@ public class IotMqttPropertyHandler extends IotMqttAbstractHandler {
65 65 // 使用 IotDeviceMessageService 解码消息
66 66 byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
67 67 IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
68   - messageBytes, productKey, deviceName, protocol.getServerId());
  68 + messageBytes, productKey, deviceName);
69 69  
70 70 // 发送消息
71 71 deviceMessageProducer.sendDeviceMessage(message);
... ... @@ -100,7 +100,7 @@ public class IotMqttPropertyHandler extends IotMqttAbstractHandler {
100 100 // 使用 IotDeviceMessageService 解码消息
101 101 byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
102 102 IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
103   - messageBytes, productKey, deviceName, protocol.getServerId());
  103 + messageBytes, productKey, deviceName);
104 104  
105 105 // 发送消息
106 106 deviceMessageProducer.sendDeviceMessage(message);
... ... @@ -132,7 +132,7 @@ public class IotMqttPropertyHandler extends IotMqttAbstractHandler {
132 132 // 使用 IotDeviceMessageService 解码消息
133 133 byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
134 134 IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
135   - messageBytes, productKey, deviceName, protocol.getServerId());
  135 + messageBytes, productKey, deviceName);
136 136  
137 137 // 发送消息
138 138 deviceMessageProducer.sendDeviceMessage(message);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttServiceHandler.java
... ... @@ -6,7 +6,7 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 6 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
7 7 import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
8 8 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
9   -import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
  9 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10 10 import lombok.RequiredArgsConstructor;
11 11 import lombok.extern.slf4j.Slf4j;
12 12  
... ... @@ -49,7 +49,7 @@ public class IotMqttServiceHandler extends IotMqttAbstractHandler {
49 49 // 使用 IotDeviceMessageService 解码消息
50 50 byte[] messageBytes = payload.getBytes(StandardCharsets.UTF_8);
51 51 IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(
52   - messageBytes, productKey, deviceName, protocol.getServerId());
  52 + messageBytes, productKey, deviceName);
53 53  
54 54 // 发送消息
55 55 deviceMessageProducer.sendDeviceMessage(message);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/router/IotMqttUpstreamRouter.java
... ... @@ -5,7 +5,7 @@ import cn.hutool.extra.spring.SpringUtil;
5 5 import cn.iocoder.yudao.module.iot.core.mq.producer.IotDeviceMessageProducer;
6 6 import cn.iocoder.yudao.module.iot.gateway.enums.IotDeviceTopicEnum;
7 7 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
8   -import cn.iocoder.yudao.module.iot.gateway.service.message.IotDeviceMessageService;
  8 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
9 9 import io.vertx.mqtt.messages.MqttPublishMessage;
10 10 import lombok.RequiredArgsConstructor;
11 11 import lombok.extern.slf4j.Slf4j;
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application-local.yaml
1   -server:
2   - port: 8090 # IoT 网关服务端口
  1 +# ==================== IoT 网关本地开发环境配置 ====================
3 2  
4 3 --- #################### 消息队列相关 ####################
5 4  
... ... @@ -26,16 +25,23 @@ yudao:
26 25 # 针对引入的 HTTP 组件的配置
27 26 # ====================================
28 27 http:
  28 + enabled: true
29 29 server-port: 8092
30 30 # ====================================
31 31 # 针对引入的 EMQX 组件的配置
32 32 # ====================================
33 33 emqx:
  34 + enabled: true
  35 + http-auth-port: 8090 # MQTT HTTP 认证服务端口
34 36 mqtt-host: 127.0.0.1 # MQTT Broker 地址
35 37 mqtt-port: 1883 # MQTT Broker 端口
36 38 mqtt-username: admin # MQTT 用户名
37 39 mqtt-password: public # MQTT 密码
38   - auth-port: 8101 # 认证端口
  40 + mqtt-ssl: false # 是否开启 SSL
  41 + mqtt-topics:
  42 + - "/sys/#" # 系统主题(设备上报)
  43 + - "/ota/#" # OTA 升级主题
  44 + - "/config/#" # 配置主题
39 45  
40 46 # 消息总线配置
41 47 message-bus:
... ... @@ -50,4 +56,4 @@ logging:
50 56 cn.iocoder.yudao.module.iot.gateway.protocol.mqtt: DEBUG
51 57 cn.iocoder.yudao.module.iot.gateway.protocol.http: DEBUG
52 58 # MQTT 客户端日志
53   - io.vertx.mqtt: DEBUG
54 59 \ No newline at end of file
  60 + io.vertx.mqtt: DEBUG
55 61 \ No newline at end of file
... ...