Commit 3c9985978b081cbdff7541b13f98d347b26d1fd9

Authored by 安浩浩
1 parent c6b58b0e

【功能完善】IoT: 更新 MQTT 主题配置为数组,重构 EMQX 认证逻辑,优化异常处理和响应格式

yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/TaosAspect.java deleted
1 -package cn.iocoder.yudao.module.iot.framework.tdengine.core;  
2 -  
3 -import lombok.extern.slf4j.Slf4j;  
4 -import org.aspectj.lang.ProceedingJoinPoint;  
5 -import org.aspectj.lang.annotation.Around;  
6 -import org.aspectj.lang.annotation.Aspect;  
7 -import org.springframework.stereotype.Component;  
8 -  
9 -import java.sql.Timestamp;  
10 -import java.util.Map;  
11 -  
12 -// TODO @haohao:这个还需要的么?  
13 -/**  
14 - * TaosAspect 是一个处理 Taos 数据库返回值的切面。  
15 - */  
16 -@Aspect  
17 -@Component  
18 -@Slf4j  
19 -public class TaosAspect {  
20 -  
21 - @Around("execution(java.util.Map<String,Object> cn.iocoder.yudao.module.iot.dal.tdengine.*.*(..))")  
22 - public Object handleType(ProceedingJoinPoint joinPoint) {  
23 - Map<String, Object> result = null;  
24 - try {  
25 - result = (Map<String, Object>) joinPoint.proceed();  
26 - result.replaceAll((key, value) -> {  
27 - if (value instanceof byte[]) {  
28 - return new String((byte[]) value);  
29 - } else if (value instanceof Timestamp) {  
30 - return ((Timestamp) value).getTime();  
31 - }  
32 - return value;  
33 - });  
34 - } catch (Throwable e) {  
35 - log.error("TaosAspect handleType error", e);  
36 - }  
37 - return result;  
38 - }  
39 -}  
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/util/IotPluginCommonUtils.java
@@ -49,4 +49,18 @@ public class IotPluginCommonUtils { @@ -49,4 +49,18 @@ public class IotPluginCommonUtils {
49 .end(result); 49 .end(result);
50 } 50 }
51 51
  52 + /**
  53 + * 将对象转换为JSON字符串后写入响应
  54 + *
  55 + * @param routingContext 路由上下文
  56 + * @param data 要转换为JSON的数据对象
  57 + */
  58 + @SuppressWarnings("deprecation")
  59 + public static void writeJson(RoutingContext routingContext, Object data) {
  60 + routingContext.response()
  61 + .setStatusCode(200)
  62 + .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
  63 + .end(JsonUtils.toJsonString(data));
  64 + }
  65 +
52 } 66 }
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotPluginEmqxProperties.java
@@ -36,11 +36,10 @@ public class IotPluginEmqxProperties { @@ -36,11 +36,10 @@ public class IotPluginEmqxProperties {
36 */ 36 */
37 private boolean mqttSsl; 37 private boolean mqttSsl;
38 38
39 - // TODO @haohao:这个是不是改成数组?  
40 /** 39 /**
41 - * 订阅的主题 40 + * 订阅的主题列表
42 */ 41 */
43 - private String mqttTopics; 42 + private String[] mqttTopics;
44 43
45 /** 44 /**
46 * 认证端口 45 * 认证端口
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/IotDeviceUpstreamServer.java
@@ -67,6 +67,7 @@ public class IotDeviceUpstreamServer { @@ -67,6 +67,7 @@ public class IotDeviceUpstreamServer {
67 router.route().handler(BodyHandler.create()); // 处理 Body 67 router.route().handler(BodyHandler.create()); // 处理 Body
68 router.post(IotDeviceAuthVertxHandler.PATH) 68 router.post(IotDeviceAuthVertxHandler.PATH)
69 // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀? 69 // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀?
  70 + // 回复:MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式
70 .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi)); 71 .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
71 // 创建 HttpServer 实例 72 // 创建 HttpServer 实例
72 this.server = vertx.createHttpServer().requestHandler(router); 73 this.server = vertx.createHttpServer().requestHandler(router);
@@ -165,15 +166,14 @@ public class IotDeviceUpstreamServer { @@ -165,15 +166,14 @@ public class IotDeviceUpstreamServer {
165 * @return 订阅结果的Future 166 * @return 订阅结果的Future
166 */ 167 */
167 private Future<Void> subscribeToTopics() { 168 private Future<Void> subscribeToTopics() {
168 - String topicsStr = emqxProperties.getMqttTopics();  
169 - if (topicsStr == null || topicsStr.trim().isEmpty()) { 169 + String[] topics = emqxProperties.getMqttTopics();
  170 + if (topics == null || topics.length == 0) {
170 log.warn("[subscribeToTopics] 未配置MQTT主题,跳过订阅"); 171 log.warn("[subscribeToTopics] 未配置MQTT主题,跳过订阅");
171 return Future.succeededFuture(); 172 return Future.succeededFuture();
172 } 173 }
173 174
174 log.info("[subscribeToTopics] 开始订阅设备上行消息主题"); 175 log.info("[subscribeToTopics] 开始订阅设备上行消息主题");
175 176
176 - String[] topics = topicsStr.split(TOPIC_SEPARATOR);  
177 Future<Void> compositeFuture = Future.succeededFuture(); 177 Future<Void> compositeFuture = Future.succeededFuture();
178 178
179 for (String topic : topics) { 179 for (String topic : topics) {
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/router/IotDeviceAuthVertxHandler.java
@@ -10,9 +10,12 @@ import io.vertx.ext.web.RoutingContext; @@ -10,9 +10,12 @@ import io.vertx.ext.web.RoutingContext;
10 import lombok.RequiredArgsConstructor; 10 import lombok.RequiredArgsConstructor;
11 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
12 12
  13 +import java.util.Collections;
  14 +
13 /** 15 /**
14 * IoT Emqx 连接认证的 Vert.x Handler 16 * IoT Emqx 连接认证的 Vert.x Handler
15 - * <a href="https://docs.emqx.com/zh/emqx/latest/access-control/authn/http.html">...</a> 17 + * <a href=
  18 + * "https://docs.emqx.com/zh/emqx/latest/access-control/authn/http.html">...</a>
16 * 19 *
17 * @author haohao 20 * @author haohao
18 */ 21 */
@@ -27,30 +30,29 @@ public class IotDeviceAuthVertxHandler implements Handler&lt;RoutingContext&gt; { @@ -27,30 +30,29 @@ public class IotDeviceAuthVertxHandler implements Handler&lt;RoutingContext&gt; {
27 @Override 30 @Override
28 @SuppressWarnings("unchecked") 31 @SuppressWarnings("unchecked")
29 public void handle(RoutingContext routingContext) { 32 public void handle(RoutingContext routingContext) {
30 - // TODO @haohao:try catch 兜底异常  
31 - JsonObject json = routingContext.body().asJsonObject();  
32 - String clientId = json.getString("clientid");  
33 - String username = json.getString("username");  
34 - String password = json.getString("password");  
35 -  
36 - IotDeviceEmqxAuthReqDTO authReqDTO = buildDeviceEmqxAuthReqDTO(clientId, username, password);  
37 -  
38 - CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);  
39 - if (authResult.getCode() != 0 || !authResult.getData()) {  
40 - denyAccess(routingContext);  
41 - return; 33 + try {
  34 + JsonObject json = routingContext.body().asJsonObject();
  35 + String clientId = json.getString("clientid");
  36 + String username = json.getString("username");
  37 + String password = json.getString("password");
  38 +
  39 + // 构建认证请求DTO
  40 + IotDeviceEmqxAuthReqDTO authReqDTO = new IotDeviceEmqxAuthReqDTO()
  41 + .setClientId(clientId)
  42 + .setUsername(username)
  43 + .setPassword(password);
  44 +
  45 + // 调用认证API
  46 + CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);
  47 + if (authResult.getCode() != 0 || !authResult.getData()) {
  48 + IotPluginCommonUtils.writeJson(routingContext, Collections.singletonMap("result", "deny"));
  49 + return;
  50 + }
  51 +
  52 + IotPluginCommonUtils.writeJson(routingContext, Collections.singletonMap("result", "allow"));
  53 + } catch (Exception e) {
  54 + log.error("[handle][EMQX认证异常]", e);
  55 + IotPluginCommonUtils.writeJson(routingContext, Collections.singletonMap("result", "deny"));
42 } 56 }
43 - // TODO @haohao:貌似可以考虑封装一个 writeJson ,里面有个参数是 data,然后里面去 JsonUtils.toJsonString(data)  
44 - IotPluginCommonUtils.writeJson(routingContext, "{\"result\": \"allow\"}");  
45 - }  
46 -  
47 - // TODO @haohao:下面两个简单方法,貌似可以考虑不抽小方法哈。  
48 - private void denyAccess(RoutingContext routingContext) {  
49 - IotPluginCommonUtils.writeJson(routingContext, "{\"result\": \"deny\"}");  
50 } 57 }
51 -  
52 - private IotDeviceEmqxAuthReqDTO buildDeviceEmqxAuthReqDTO(String clientId, String username, String password) {  
53 - return new IotDeviceEmqxAuthReqDTO().setClientId(clientId).setUsername(username).setPassword(password);  
54 - }  
55 -  
56 } 58 }
57 \ No newline at end of file 59 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/resources/application.yml
@@ -15,5 +15,6 @@ yudao: @@ -15,5 +15,6 @@ yudao:
15 mqtt-ssl: false 15 mqtt-ssl: false
16 mqtt-username: yudao 16 mqtt-username: yudao
17 mqtt-password: 123456 17 mqtt-password: 123456
18 - mqtt-topics: "/sys/#" 18 + mqtt-topics:
  19 + - "/sys/#"
19 auth-port: 8101 20 auth-port: 8101
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/IotHttpPluginApplication.java
@@ -4,7 +4,6 @@ import lombok.extern.slf4j.Slf4j; @@ -4,7 +4,6 @@ import lombok.extern.slf4j.Slf4j;
4 import org.springframework.boot.SpringApplication; 4 import org.springframework.boot.SpringApplication;
5 import org.springframework.boot.WebApplicationType; 5 import org.springframework.boot.WebApplicationType;
6 import org.springframework.boot.autoconfigure.SpringBootApplication; 6 import org.springframework.boot.autoconfigure.SpringBootApplication;
7 -import org.springframework.context.ConfigurableApplicationContext;  
8 7
9 /** 8 /**
10 * 独立运行入口 9 * 独立运行入口
@@ -16,14 +15,7 @@ public class IotHttpPluginApplication { @@ -16,14 +15,7 @@ public class IotHttpPluginApplication {
16 public static void main(String[] args) { 15 public static void main(String[] args) {
17 SpringApplication application = new SpringApplication(IotHttpPluginApplication.class); 16 SpringApplication application = new SpringApplication(IotHttpPluginApplication.class);
18 application.setWebApplicationType(WebApplicationType.NONE); 17 application.setWebApplicationType(WebApplicationType.NONE);
19 - ConfigurableApplicationContext context = application.run(args);  
20 -  
21 - // 手动获取 VertxService 并启动  
22 - // TODO @haohao:可以放在 bean 的 init 里么?回复:会和插件模式冲突 @芋艿,测试下  
23 - // TODO @haohao:貌似去掉,没有问题额。。。  
24 -// IotDeviceUpstreamServer vertxService = context.getBean(IotDeviceUpstreamServer.class);  
25 -// vertxService.start();  
26 - 18 + application.run(args);
27 log.info("[main][独立模式启动完成]"); 19 log.info("[main][独立模式启动完成]");
28 } 20 }
29 21
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/IotHttpVertxPlugin.java
@@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.iot.plugin.http.config; @@ -2,11 +2,9 @@ package cn.iocoder.yudao.module.iot.plugin.http.config;
2 2
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.hutool.extra.spring.SpringUtil; 4 import cn.hutool.extra.spring.SpringUtil;
5 -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;  
6 import lombok.extern.slf4j.Slf4j; 5 import lombok.extern.slf4j.Slf4j;
7 import org.pf4j.PluginWrapper; 6 import org.pf4j.PluginWrapper;
8 import org.pf4j.spring.SpringPlugin; 7 import org.pf4j.spring.SpringPlugin;
9 -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;  
10 import org.springframework.context.ApplicationContext; 8 import org.springframework.context.ApplicationContext;
11 import org.springframework.context.annotation.AnnotationConfigApplicationContext; 9 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
12 10
@@ -29,11 +27,6 @@ public class IotHttpVertxPlugin extends SpringPlugin { @@ -29,11 +27,6 @@ public class IotHttpVertxPlugin extends SpringPlugin {
29 ApplicationContext pluginContext = getApplicationContext(); 27 ApplicationContext pluginContext = getApplicationContext();
30 Assert.notNull(pluginContext, "pluginContext 不能为空"); 28 Assert.notNull(pluginContext, "pluginContext 不能为空");
31 29
32 - // 2. 启动 Vert.x  
33 - // TODO @haohao:貌似去掉,没有问题额。。。  
34 -// IotDeviceUpstreamServer vertxService = pluginContext.getBean(IotDeviceUpstreamServer.class);  
35 -// vertxService.start();  
36 -  
37 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动成功...]"); 30 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动成功...]");
38 } catch (Exception e) { 31 } catch (Exception e) {
39 log.error("[HttpVertxPlugin][HttpVertxPlugin 插件开启动异常...]", e); 32 log.error("[HttpVertxPlugin][HttpVertxPlugin 插件开启动异常...]", e);
@@ -44,13 +37,6 @@ public class IotHttpVertxPlugin extends SpringPlugin { @@ -44,13 +37,6 @@ public class IotHttpVertxPlugin extends SpringPlugin {
44 public void stop() { 37 public void stop() {
45 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件停止开始...]"); 38 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件停止开始...]");
46 try { 39 try {
47 - // 停止服务器  
48 -// ApplicationContext pluginContext = getApplicationContext();  
49 -// if (pluginContext != null) {  
50 -// IotDeviceUpstreamServer vertxService = pluginContext.getBean(IotDeviceUpstreamServer.class);  
51 -// vertxService.stop();  
52 -// }  
53 -  
54 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件停止成功...]"); 40 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件停止成功...]");
55 } catch (Exception e) { 41 } catch (Exception e) {
56 log.error("[HttpVertxPlugin][HttpVertxPlugin 插件停止异常...]", e); 42 log.error("[HttpVertxPlugin][HttpVertxPlugin 插件停止异常...]", e);