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 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 36 */
37 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 67 router.route().handler(BodyHandler.create()); // 处理 Body
68 68 router.post(IotDeviceAuthVertxHandler.PATH)
69 69 // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀?
  70 + // 回复:MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式
70 71 .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
71 72 // 创建 HttpServer 实例
72 73 this.server = vertx.createHttpServer().requestHandler(router);
... ... @@ -165,15 +166,14 @@ public class IotDeviceUpstreamServer {
165 166 * @return 订阅结果的Future
166 167 */
167 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 171 log.warn("[subscribeToTopics] 未配置MQTT主题,跳过订阅");
171 172 return Future.succeededFuture();
172 173 }
173 174  
174 175 log.info("[subscribeToTopics] 开始订阅设备上行消息主题");
175 176  
176   - String[] topics = topicsStr.split(TOPIC_SEPARATOR);
177 177 Future<Void> compositeFuture = Future.succeededFuture();
178 178  
179 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 10 import lombok.RequiredArgsConstructor;
11 11 import lombok.extern.slf4j.Slf4j;
12 12  
  13 +import java.util.Collections;
  14 +
13 15 /**
14 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 20 * @author haohao
18 21 */
... ... @@ -27,30 +30,29 @@ public class IotDeviceAuthVertxHandler implements Handler&lt;RoutingContext&gt; {
27 30 @Override
28 31 @SuppressWarnings("unchecked")
29 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 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 15 mqtt-ssl: false
16 16 mqtt-username: yudao
17 17 mqtt-password: 123456
18   - mqtt-topics: "/sys/#"
  18 + mqtt-topics:
  19 + - "/sys/#"
19 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 4 import org.springframework.boot.SpringApplication;
5 5 import org.springframework.boot.WebApplicationType;
6 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 15 public static void main(String[] args) {
17 16 SpringApplication application = new SpringApplication(IotHttpPluginApplication.class);
18 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 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 2  
3 3 import cn.hutool.core.lang.Assert;
4 4 import cn.hutool.extra.spring.SpringUtil;
5   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 5 import lombok.extern.slf4j.Slf4j;
7 6 import org.pf4j.PluginWrapper;
8 7 import org.pf4j.spring.SpringPlugin;
9   -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
10 8 import org.springframework.context.ApplicationContext;
11 9 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
12 10  
... ... @@ -29,11 +27,6 @@ public class IotHttpVertxPlugin extends SpringPlugin {
29 27 ApplicationContext pluginContext = getApplicationContext();
30 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 30 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动成功...]");
38 31 } catch (Exception e) {
39 32 log.error("[HttpVertxPlugin][HttpVertxPlugin 插件开启动异常...]", e);
... ... @@ -44,13 +37,6 @@ public class IotHttpVertxPlugin extends SpringPlugin {
44 37 public void stop() {
45 38 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件停止开始...]");
46 39 try {
47   - // 停止服务器
48   -// ApplicationContext pluginContext = getApplicationContext();
49   -// if (pluginContext != null) {
50   -// IotDeviceUpstreamServer vertxService = pluginContext.getBean(IotDeviceUpstreamServer.class);
51   -// vertxService.stop();
52   -// }
53   -
54 40 log.info("[HttpVertxPlugin][HttpVertxPlugin 插件停止成功...]");
55 41 } catch (Exception e) {
56 42 log.error("[HttpVertxPlugin][HttpVertxPlugin 插件停止异常...]", e);
... ...