Commit 3c9985978b081cbdff7541b13f98d347b26d1fd9
1 parent
c6b58b0e
【功能完善】IoT: 更新 MQTT 主题配置为数组,重构 EMQX 认证逻辑,优化异常处理和响应格式
Showing
8 changed files
with
49 additions
and
94 deletions
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
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<RoutingContext> { |
| 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
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); | ... | ... |