Commit ab4b148df3c38f5e227415422aa45aaef8d64ccf

Authored by 安浩浩
1 parent f124584d

feat:【IOT】添加 IoT 脚本模块及相关配置,删除不再使用的插件模块

Showing 91 changed files with 624 additions and 5040 deletions
... ... @@ -24,7 +24,7 @@
24 24 <!-- <module>yudao-module-crm</module>-->
25 25 <!-- <module>yudao-module-erp</module>-->
26 26 <!-- <module>yudao-module-ai</module>-->
27   -<!-- <module>yudao-module-iot</module>-->
  27 + <module>yudao-module-iot</module>
28 28 </modules>
29 29  
30 30 <name>${project.artifactId}</name>
... ...
yudao-module-iot/pom.xml
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3   - xmlns="http://maven.apache.org/POM/4.0.0"
4   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3 + xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 4 <parent>
6 5 <artifactId>yudao</artifactId>
7 6 <groupId>cn.iocoder.boot</groupId>
... ... @@ -11,8 +10,7 @@
11 10 <module>yudao-module-iot-api</module>
12 11 <module>yudao-module-iot-biz</module>
13 12 <module>yudao-module-iot-net-components</module>
14   - <module>yudao-module-iot-script</module>
15   - <!-- <module>yudao-module-iot-plugins</module>-->
  13 + <module>yudao-module-iot-protocol</module>
16 14 </modules>
17 15 <modelVersion>4.0.0</modelVersion>
18 16  
... ... @@ -22,7 +20,7 @@
22 20 <name>${project.artifactId}</name>
23 21 <description>
24 22 物联网模块
25   - <!-- TODO 芋艿:需要补充下说明! -->
  23 + <!-- TODO 芋艿:需要补充下说明! -->
26 24 </description>
27 25  
28 26 </project>
29 27 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/pom.xml
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <project xmlns="http://maven.apache.org/POM/4.0.0"
3   - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 4 <parent>
6 5 <artifactId>yudao-module-iot</artifactId>
7 6 <groupId>cn.iocoder.boot</groupId>
... ... @@ -15,7 +14,7 @@
15 14 <name>${project.artifactId}</name>
16 15 <description>
17 16 物联网 模块,主要实现 产品管理、设备管理、协议管理等功能。
18   - <!-- TODO 芋艿:后续补充下 -->
  17 + <!-- TODO 芋艿:后续补充下 -->
19 18 </description>
20 19  
21 20 <dependencies>
... ... @@ -29,6 +28,12 @@
29 28 <artifactId>yudao-module-iot-api</artifactId>
30 29 <version>${revision}</version>
31 30 </dependency>
  31 + <!-- IoT 协议模块 -->
  32 + <dependency>
  33 + <groupId>cn.iocoder.boot</groupId>
  34 + <artifactId>yudao-module-iot-protocol</artifactId>
  35 + <version>${revision}</version>
  36 + </dependency>
32 37  
33 38 <dependency>
34 39 <groupId>cn.iocoder.boot</groupId>
... ... @@ -91,25 +96,22 @@
91 96 <optional>true</optional>
92 97 </dependency>
93 98  
94   - <!-- TODO @芋艿:bom 管理 -->
  99 + <!-- JavaScript 引擎 - 使用 GraalJS 替代 Nashorn -->
  100 + <!-- TODO @haohao:得考虑下,jdk8 可能不支持 graalvm,后续哈;【优先级:低】 -->
95 101 <dependency>
96   - <groupId>org.apache.groovy</groupId>
97   - <artifactId>groovy-all</artifactId>
98   - <version>4.0.25</version>
99   - <type>pom</type>
  102 + <groupId>org.graalvm.sdk</groupId>
  103 + <artifactId>graal-sdk</artifactId>
  104 + <version>22.3.0</version>
100 105 </dependency>
101   -
102   - <!-- TODO @芋艿:bom 管理 -->
103 106 <dependency>
104 107 <groupId>org.graalvm.js</groupId>
105 108 <artifactId>js</artifactId>
106   - <version>24.1.2</version>
107   - <type>pom</type>
  109 + <version>22.3.0</version>
108 110 </dependency>
109 111 <dependency>
110 112 <groupId>org.graalvm.js</groupId>
111 113 <artifactId>js-scriptengine</artifactId>
112   - <version>24.1.2</version>
  114 + <version>22.3.0</version>
113 115 </dependency>
114 116  
115 117 <!-- TODO @芋艿:合理注释 -->
... ... @@ -140,11 +142,11 @@
140 142 <!-- </dependency>-->
141 143  
142 144 <!-- 脚本相关 -->
143   - <dependency>
144   - <groupId>cn.iocoder.boot</groupId>
145   - <artifactId>yudao-module-iot-script</artifactId>
146   - <version>${revision}</version>
147   - </dependency>
  145 +<!-- <dependency>-->
  146 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  147 +<!-- <artifactId>yudao-module-iot-script</artifactId>-->
  148 +<!-- <version>${revision}</version>-->
  149 +<!-- </dependency>-->
148 150  
149 151  
150 152 </dependencies>
... ...
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/ScriptExample.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/ScriptExample.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/config/ScriptConfiguration.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/config/ScriptConfiguration.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/context/DefaultScriptContext.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DefaultScriptContext.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/context/DeviceScriptContext.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DeviceScriptContext.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/context/ScriptContext.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/ScriptContext.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/engine/AbstractScriptEngine.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/AbstractScriptEngine.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/engine/JsScriptEngine.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/JsScriptEngine.java
... ... @@ -73,7 +73,7 @@ public class JsScriptEngine extends AbstractScriptEngine implements AutoCloseabl
73 73 .allowIO(false) // 禁止文件 IO
74 74 .allowNativeAccess(false) // 禁止本地访问
75 75 .allowCreateThread(false) // 禁止创建线程
76   - .allowEnvironmentAccess(org.graalvm.polyglot.EnvironmentAccess.NONE) // 禁止环境变量访问
  76 + .allowEnvironmentAccess(EnvironmentAccess.NONE) // 禁止环境变量访问
77 77 .allowExperimentalOptions(false) // 禁止实验性选项
78 78 .option("js.ecmascript-version", "2021") // 使用最新的 ECMAScript 标准
79 79 .option("js.foreign-object-prototype", "false") // 禁用外部对象原型
... ...
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngine.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngine.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngineFactory.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngineFactory.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/example/GraalJsExample.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/GraalJsExample.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/example/ProductScriptSamples.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/ProductScriptSamples.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/package-info.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/package-info.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/JsSandbox.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/JsSandbox.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/ScriptSandbox.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/ScriptSandbox.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptService.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptService.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptServiceImpl.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptServiceImpl.java
yudao-module-iot/yudao-module-iot-script/src/main/java/cn/iocoder/yudao/module/iot/script/util/ScriptUtils.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/util/ScriptUtils.java
yudao-module-iot/yudao-module-iot-net-components/yudao-module-iot-net-component-core/pom.xml
... ... @@ -24,6 +24,13 @@
24 24 <version>${revision}</version>
25 25 </dependency>
26 26  
  27 + <!-- IoT 协议模块 -->
  28 + <dependency>
  29 + <groupId>cn.iocoder.boot</groupId>
  30 + <artifactId>yudao-module-iot-protocol</artifactId>
  31 + <version>${revision}</version>
  32 + </dependency>
  33 +
27 34 <dependency>
28 35 <groupId>org.springframework.boot</groupId>
29 36 <artifactId>spring-boot-starter</artifactId>
... ...
yudao-module-iot/yudao-module-iot-plugins/pom.xml deleted
1   -<?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3   - xmlns="http://maven.apache.org/POM/4.0.0"
4   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5   - <parent>
6   - <artifactId>yudao-module-iot</artifactId>
7   - <groupId>cn.iocoder.boot</groupId>
8   - <version>${revision}</version>
9   - </parent>
10   - <modules>
11   - <module>yudao-module-iot-plugin-common</module>
12   - <module>yudao-module-iot-plugin-script</module>
13   - <module>yudao-module-iot-plugin-http</module>
14   - <module>yudao-module-iot-plugin-mqtt</module>
15   - <module>yudao-module-iot-plugin-emqx</module>
16   - </modules>
17   -
18   - <modelVersion>4.0.0</modelVersion>
19   -
20   - <artifactId>yudao-module-iot-plugins</artifactId>
21   - <packaging>pom</packaging>
22   -
23   - <name>${project.artifactId}</name>
24   - <description>
25   - 物联网 插件 模块
26   - </description>
27   -
28   -</project>
29 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/IotPluginCommonAutoConfiguration.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.config;
2   -
3   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
4   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
5   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
6   -import cn.iocoder.yudao.module.iot.plugin.common.heartbeat.IotPluginInstanceHeartbeatJob;
7   -import cn.iocoder.yudao.module.iot.plugin.common.upstream.IotDeviceUpstreamClient;
8   -import org.springframework.boot.autoconfigure.AutoConfiguration;
9   -import org.springframework.boot.context.properties.EnableConfigurationProperties;
10   -import org.springframework.boot.web.client.RestTemplateBuilder;
11   -import org.springframework.context.annotation.Bean;
12   -import org.springframework.scheduling.annotation.EnableScheduling;
13   -import org.springframework.web.client.RestTemplate;
14   -
15   -/**
16   - * IoT 插件的通用自动配置类
17   - *
18   - * @author haohao
19   - */
20   -@AutoConfiguration
21   -@EnableConfigurationProperties(IotPluginCommonProperties.class)
22   -@EnableScheduling // 开启定时任务,因为 IotPluginInstanceHeartbeatJob 是一个定时任务
23   -public class IotPluginCommonAutoConfiguration {
24   -
25   - @Bean
26   - public RestTemplate restTemplate(IotPluginCommonProperties properties) {
27   - return new RestTemplateBuilder()
28   - .connectTimeout(properties.getUpstreamConnectTimeout())
29   - .readTimeout(properties.getUpstreamReadTimeout())
30   - .build();
31   - }
32   -
33   - @Bean
34   - public IotDeviceUpstreamApi deviceUpstreamApi(IotPluginCommonProperties properties,
35   - RestTemplate restTemplate) {
36   - return new IotDeviceUpstreamClient(properties, restTemplate);
37   - }
38   -
39   - @Bean(initMethod = "start", destroyMethod = "stop")
40   - public IotDeviceDownstreamServer deviceDownstreamServer(IotPluginCommonProperties properties,
41   - IotDeviceDownstreamHandler deviceDownstreamHandler) {
42   - return new IotDeviceDownstreamServer(properties, deviceDownstreamHandler);
43   - }
44   -
45   - @Bean(initMethod = "init", destroyMethod = "stop")
46   - public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob(IotDeviceUpstreamApi deviceDataApi,
47   - IotDeviceDownstreamServer deviceDownstreamServer,
48   - IotPluginCommonProperties commonProperties) {
49   - return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer, commonProperties);
50   - }
51   -
52   -}
53 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/IotPluginCommonProperties.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.config;
2   -
3   -import jakarta.validation.constraints.NotEmpty;
4   -import lombok.Data;
5   -import org.springframework.boot.context.properties.ConfigurationProperties;
6   -import org.springframework.validation.annotation.Validated;
7   -
8   -import java.time.Duration;
9   -
10   -/**
11   - * IoT 插件的通用配置类
12   - *
13   - * @author haohao
14   - */
15   -@ConfigurationProperties(prefix = "yudao.iot.plugin.common")
16   -@Validated
17   -@Data
18   -public class IotPluginCommonProperties {
19   -
20   - /**
21   - * 上行连接超时的默认值
22   - */
23   - public static final Duration UPSTREAM_CONNECT_TIMEOUT_DEFAULT = Duration.ofSeconds(30);
24   - /**
25   - * 上行读取超时的默认值
26   - */
27   - public static final Duration UPSTREAM_READ_TIMEOUT_DEFAULT = Duration.ofSeconds(30);
28   -
29   - /**
30   - * 下行端口 - 随机
31   - */
32   - public static final Integer DOWNSTREAM_PORT_RANDOM = 0;
33   -
34   - /**
35   - * 上行 URL
36   - */
37   - @NotEmpty(message = "上行 URL 不能为空")
38   - private String upstreamUrl;
39   - /**
40   - * 上行连接超时
41   - */
42   - private Duration upstreamConnectTimeout = UPSTREAM_CONNECT_TIMEOUT_DEFAULT;
43   - /**
44   - * 上行读取超时
45   - */
46   - private Duration upstreamReadTimeout = UPSTREAM_READ_TIMEOUT_DEFAULT;
47   -
48   - /**
49   - * 下行端口
50   - */
51   - private Integer downstreamPort = DOWNSTREAM_PORT_RANDOM;
52   -
53   - /**
54   - * 插件包标识符
55   - */
56   - @NotEmpty(message = "插件包标识符不能为空")
57   - private String pluginKey;
58   -
59   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/IotDeviceDownstreamHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.downstream;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
5   -
6   -/**
7   - * IoT 设备下行处理器
8   - *
9   - * 目的:每个 plugin 需要实现,用于处理 server 下行的指令(请求),从而实现从 server => plugin => device 的下行流程
10   - *
11   - * @author 芋道源码
12   - */
13   -public interface IotDeviceDownstreamHandler {
14   -
15   - /**
16   - * 调用设备服务
17   - *
18   - * @param invokeReqDTO 调用设备服务的请求
19   - * @return 是否成功
20   - */
21   - CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO);
22   -
23   - /**
24   - * 获取设备属性
25   - *
26   - * @param getReqDTO 获取设备属性的请求
27   - * @return 是否成功
28   - */
29   - CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO);
30   -
31   - /**
32   - * 设置设备属性
33   - *
34   - * @param setReqDTO 设置设备属性的请求
35   - * @return 是否成功
36   - */
37   - CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO);
38   -
39   - /**
40   - * 设置设备配置
41   - *
42   - * @param setReqDTO 设置设备配置的请求
43   - * @return 是否成功
44   - */
45   - CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO);
46   -
47   - /**
48   - * 升级设备 OTA
49   - *
50   - * @param upgradeReqDTO 升级设备 OTA 的请求
51   - * @return 是否成功
52   - */
53   - CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO);
54   -
55   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/IotDeviceDownstreamServer.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.downstream;
2   -
3   -import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
4   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.router.*;
5   -import io.vertx.core.Vertx;
6   -import io.vertx.core.http.HttpServer;
7   -import io.vertx.ext.web.Router;
8   -import io.vertx.ext.web.handler.BodyHandler;
9   -import lombok.extern.slf4j.Slf4j;
10   -
11   -/**
12   - * IoT 设备下行服务端,接收来自 server 服务器的请求,转发给 device 设备
13   - *
14   - * @author 芋道源码
15   - */
16   -@Slf4j
17   -public class IotDeviceDownstreamServer {
18   -
19   - private final Vertx vertx;
20   - private final HttpServer server;
21   - private final IotPluginCommonProperties properties;
22   -
23   - public IotDeviceDownstreamServer(IotPluginCommonProperties properties,
24   - IotDeviceDownstreamHandler deviceDownstreamHandler) {
25   - this.properties = properties;
26   - // 创建 Vertx 实例
27   - this.vertx = Vertx.vertx();
28   - // 创建 Router 实例
29   - Router router = Router.router(vertx);
30   - router.route().handler(BodyHandler.create()); // 处理 Body
31   - router.post(IotDeviceServiceInvokeVertxHandler.PATH)
32   - .handler(new IotDeviceServiceInvokeVertxHandler(deviceDownstreamHandler));
33   - router.post(IotDevicePropertySetVertxHandler.PATH)
34   - .handler(new IotDevicePropertySetVertxHandler(deviceDownstreamHandler));
35   - router.post(IotDevicePropertyGetVertxHandler.PATH)
36   - .handler(new IotDevicePropertyGetVertxHandler(deviceDownstreamHandler));
37   - router.post(IotDeviceConfigSetVertxHandler.PATH)
38   - .handler(new IotDeviceConfigSetVertxHandler(deviceDownstreamHandler));
39   - router.post(IotDeviceOtaUpgradeVertxHandler.PATH)
40   - .handler(new IotDeviceOtaUpgradeVertxHandler(deviceDownstreamHandler));
41   - // 创建 HttpServer 实例
42   - this.server = vertx.createHttpServer().requestHandler(router);
43   - }
44   -
45   - /**
46   - * 启动 HTTP 服务器
47   - */
48   - public void start() {
49   - log.info("[start][开始启动]");
50   - server.listen(properties.getDownstreamPort())
51   - .toCompletionStage()
52   - .toCompletableFuture()
53   - .join();
54   - log.info("[start][启动完成,端口({})]", this.server.actualPort());
55   - }
56   -
57   - /**
58   - * 停止所有
59   - */
60   - public void stop() {
61   - log.info("[stop][开始关闭]");
62   - try {
63   - // 关闭 HTTP 服务器
64   - if (server != null) {
65   - server.close()
66   - .toCompletionStage()
67   - .toCompletableFuture()
68   - .join();
69   - }
70   -
71   - // 关闭 Vertx 实例
72   - if (vertx != null) {
73   - vertx.close()
74   - .toCompletionStage()
75   - .toCompletableFuture()
76   - .join();
77   - }
78   - log.info("[stop][关闭完成]");
79   - } catch (Exception e) {
80   - log.error("[stop][关闭异常]", e);
81   - throw new RuntimeException(e);
82   - }
83   - }
84   -
85   - /**
86   - * 获得端口
87   - *
88   - * @return 端口
89   - */
90   - public int getPort() {
91   - return this.server.actualPort();
92   - }
93   -
94   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceConfigSetVertxHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.downstream.router;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceConfigSetReqDTO;
5   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6   -import cn.iocoder.yudao.module.iot.plugin.common.pojo.IotStandardResponse;
7   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
8   -import io.vertx.core.Handler;
9   -import io.vertx.core.json.JsonObject;
10   -import io.vertx.ext.web.RoutingContext;
11   -import lombok.RequiredArgsConstructor;
12   -import lombok.extern.slf4j.Slf4j;
13   -
14   -import java.util.Map;
15   -
16   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
17   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
18   -
19   -/**
20   - * IoT 设备配置设置 Vertx Handler
21   - *
22   - * 芋道源码
23   - */
24   -@Slf4j
25   -@RequiredArgsConstructor
26   -public class IotDeviceConfigSetVertxHandler implements Handler<RoutingContext> {
27   -
28   - // TODO @haohao:是不是可以把 PATH、Method 所有的,抽到一个枚举类里?因为 topic、path、method 相当于不同的几个表达?
29   - public static final String PATH = "/sys/:productKey/:deviceName/thing/service/config/set";
30   - public static final String METHOD = "thing.service.config.set";
31   -
32   - private final IotDeviceDownstreamHandler deviceDownstreamHandler;
33   -
34   - @Override
35   - @SuppressWarnings("unchecked")
36   - public void handle(RoutingContext routingContext) {
37   - // 1. 解析参数
38   - IotDeviceConfigSetReqDTO reqDTO;
39   - try {
40   - String productKey = routingContext.pathParam("productKey");
41   - String deviceName = routingContext.pathParam("deviceName");
42   - JsonObject body = routingContext.body().asJsonObject();
43   - String requestId = body.getString("requestId");
44   - Map<String, Object> config = (Map<String, Object>) body.getMap().get("config");
45   - reqDTO = ((IotDeviceConfigSetReqDTO) new IotDeviceConfigSetReqDTO()
46   - .setRequestId(requestId).setProductKey(productKey).setDeviceName(deviceName))
47   - .setConfig(config);
48   - } catch (Exception e) {
49   - log.error("[handle][路径参数({}) 解析参数失败]", routingContext.pathParams(), e);
50   - IotStandardResponse errorResponse = IotStandardResponse.error(
51   - null, METHOD, BAD_REQUEST.getCode(), BAD_REQUEST.getMsg());
52   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
53   - return;
54   - }
55   -
56   - // 2. 调用处理器
57   - try {
58   - CommonResult<Boolean> result = deviceDownstreamHandler.setDeviceConfig(reqDTO);
59   -
60   - // 3. 响应结果
61   - IotStandardResponse response = result.isSuccess() ?
62   - IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData())
63   - : IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
64   - IotPluginCommonUtils.writeJsonResponse(routingContext, response);
65   - } catch (Exception e) {
66   - log.error("[handle][请求参数({}) 配置设置异常]", reqDTO, e);
67   - IotStandardResponse errorResponse = IotStandardResponse.error(
68   - reqDTO.getRequestId(), METHOD, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
69   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
70   - }
71   - }
72   -
73   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceOtaUpgradeVertxHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.downstream.router;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceOtaUpgradeReqDTO;
5   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6   -import cn.iocoder.yudao.module.iot.plugin.common.pojo.IotStandardResponse;
7   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
8   -import io.vertx.core.Handler;
9   -import io.vertx.core.json.JsonObject;
10   -import io.vertx.ext.web.RoutingContext;
11   -import lombok.RequiredArgsConstructor;
12   -import lombok.extern.slf4j.Slf4j;
13   -
14   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
15   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
16   -
17   -/**
18   - * IoT 设备 OTA 升级 Vertx Handler
19   - * <p>
20   - * 芋道源码
21   - */
22   -@Slf4j
23   -@RequiredArgsConstructor
24   -public class IotDeviceOtaUpgradeVertxHandler implements Handler<RoutingContext> {
25   -
26   - public static final String PATH = "/ota/:productKey/:deviceName/upgrade";
27   - public static final String METHOD = "ota.device.upgrade";
28   -
29   - private final IotDeviceDownstreamHandler deviceDownstreamHandler;
30   -
31   - @Override
32   - public void handle(RoutingContext routingContext) {
33   - // 1. 解析参数
34   - IotDeviceOtaUpgradeReqDTO reqDTO;
35   - try {
36   - String productKey = routingContext.pathParam("productKey");
37   - String deviceName = routingContext.pathParam("deviceName");
38   - JsonObject body = routingContext.body().asJsonObject();
39   - String requestId = body.getString("requestId");
40   - Long firmwareId = body.getLong("firmwareId");
41   - String version = body.getString("version");
42   - String signMethod = body.getString("signMethod");
43   - String fileSign = body.getString("fileSign");
44   - Long fileSize = body.getLong("fileSize");
45   - String fileUrl = body.getString("fileUrl");
46   - String information = body.getString("information");
47   - reqDTO = ((IotDeviceOtaUpgradeReqDTO) new IotDeviceOtaUpgradeReqDTO()
48   - .setRequestId(requestId).setProductKey(productKey).setDeviceName(deviceName))
49   - .setFirmwareId(firmwareId).setVersion(version)
50   - .setSignMethod(signMethod).setFileSign(fileSign).setFileSize(fileSize).setFileUrl(fileUrl)
51   - .setInformation(information);
52   - } catch (Exception e) {
53   - log.error("[handle][路径参数({}) 解析参数失败]", routingContext.pathParams(), e);
54   - IotStandardResponse errorResponse = IotStandardResponse.error(
55   - null, METHOD, BAD_REQUEST.getCode(), BAD_REQUEST.getMsg());
56   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
57   - return;
58   - }
59   -
60   - // 2. 调用处理器
61   - try {
62   - CommonResult<Boolean> result = deviceDownstreamHandler.upgradeDeviceOta(reqDTO);
63   -
64   - // 3. 响应结果
65   - // TODO @haohao:可以考虑 IotStandardResponse.of(requestId, method, CommonResult)
66   - IotStandardResponse response = result.isSuccess() ?
67   - IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData())
68   - :IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
69   - IotPluginCommonUtils.writeJsonResponse(routingContext, response);
70   - } catch (Exception e) {
71   - log.error("[handle][请求参数({}) OTA 升级异常]", reqDTO, e);
72   - // TODO @haohao:可以考虑 IotStandardResponse.of(requestId, method, ErrorCode)
73   - IotStandardResponse errorResponse = IotStandardResponse.error(
74   - reqDTO.getRequestId(), METHOD, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
75   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
76   - }
77   - }
78   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDevicePropertyGetVertxHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.downstream.router;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDevicePropertyGetReqDTO;
5   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6   -import cn.iocoder.yudao.module.iot.plugin.common.pojo.IotStandardResponse;
7   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
8   -import io.vertx.core.Handler;
9   -import io.vertx.core.json.JsonObject;
10   -import io.vertx.ext.web.RoutingContext;
11   -import lombok.RequiredArgsConstructor;
12   -import lombok.extern.slf4j.Slf4j;
13   -
14   -import java.util.List;
15   -
16   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
17   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
18   -
19   -/**
20   - * IoT 设备服务获取 Vertx Handler
21   - *
22   - * 芋道源码
23   - */
24   -@Slf4j
25   -@RequiredArgsConstructor
26   -public class IotDevicePropertyGetVertxHandler implements Handler<RoutingContext> {
27   -
28   - public static final String PATH = "/sys/:productKey/:deviceName/thing/service/property/get";
29   - public static final String METHOD = "thing.service.property.get";
30   -
31   - private final IotDeviceDownstreamHandler deviceDownstreamHandler;
32   -
33   - @Override
34   - @SuppressWarnings("unchecked")
35   - public void handle(RoutingContext routingContext) {
36   - // 1. 解析参数
37   - IotDevicePropertyGetReqDTO reqDTO;
38   - try {
39   - String productKey = routingContext.pathParam("productKey");
40   - String deviceName = routingContext.pathParam("deviceName");
41   - JsonObject body = routingContext.body().asJsonObject();
42   - String requestId = body.getString("requestId");
43   - List<String> identifiers = (List<String>) body.getMap().get("identifiers");
44   - reqDTO = ((IotDevicePropertyGetReqDTO) new IotDevicePropertyGetReqDTO()
45   - .setRequestId(requestId).setProductKey(productKey).setDeviceName(deviceName))
46   - .setIdentifiers(identifiers);
47   - } catch (Exception e) {
48   - log.error("[handle][路径参数({}) 解析参数失败]", routingContext.pathParams(), e);
49   - IotStandardResponse errorResponse = IotStandardResponse.error(
50   - null, METHOD, BAD_REQUEST.getCode(), BAD_REQUEST.getMsg());
51   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
52   - return;
53   - }
54   -
55   - // 2. 调用处理器
56   - try {
57   - CommonResult<Boolean> result = deviceDownstreamHandler.getDeviceProperty(reqDTO);
58   -
59   - // 3. 响应结果
60   - IotStandardResponse response;
61   - if (result.isSuccess()) {
62   - response = IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData());
63   - } else {
64   - response = IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
65   - }
66   - IotPluginCommonUtils.writeJsonResponse(routingContext, response);
67   - } catch (Exception e) {
68   - log.error("[handle][请求参数({}) 属性获取异常]", reqDTO, e);
69   - IotStandardResponse errorResponse = IotStandardResponse.error(
70   - reqDTO.getRequestId(), METHOD, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
71   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
72   - }
73   - }
74   -
75   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDevicePropertySetVertxHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.downstream.router;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDevicePropertySetReqDTO;
5   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6   -import cn.iocoder.yudao.module.iot.plugin.common.pojo.IotStandardResponse;
7   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
8   -import io.vertx.core.Handler;
9   -import io.vertx.core.json.JsonObject;
10   -import io.vertx.ext.web.RoutingContext;
11   -import lombok.RequiredArgsConstructor;
12   -import lombok.extern.slf4j.Slf4j;
13   -
14   -import java.util.Map;
15   -
16   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
17   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
18   -
19   -/**
20   - * IoT 设置设备属性 Vertx Handler
21   - *
22   - * 芋道源码
23   - */
24   -@Slf4j
25   -@RequiredArgsConstructor
26   -public class IotDevicePropertySetVertxHandler implements Handler<RoutingContext> {
27   -
28   - public static final String PATH = "/sys/:productKey/:deviceName/thing/service/property/set";
29   - public static final String METHOD = "thing.service.property.set";
30   -
31   - private final IotDeviceDownstreamHandler deviceDownstreamHandler;
32   -
33   - @Override
34   - @SuppressWarnings("unchecked")
35   - public void handle(RoutingContext routingContext) {
36   - // 1. 解析参数
37   - IotDevicePropertySetReqDTO reqDTO;
38   - try {
39   - String productKey = routingContext.pathParam("productKey");
40   - String deviceName = routingContext.pathParam("deviceName");
41   - JsonObject body = routingContext.body().asJsonObject();
42   - String requestId = body.getString("requestId");
43   - Map<String, Object> properties = (Map<String, Object>) body.getMap().get("properties");
44   - reqDTO = ((IotDevicePropertySetReqDTO) new IotDevicePropertySetReqDTO()
45   - .setRequestId(requestId).setProductKey(productKey).setDeviceName(deviceName))
46   - .setProperties(properties);
47   - } catch (Exception e) {
48   - log.error("[handle][路径参数({}) 解析参数失败]", routingContext.pathParams(), e);
49   - IotStandardResponse errorResponse = IotStandardResponse.error(
50   - null, METHOD, BAD_REQUEST.getCode(), BAD_REQUEST.getMsg());
51   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
52   - return;
53   - }
54   -
55   - // 2. 调用处理器
56   - try {
57   - CommonResult<Boolean> result = deviceDownstreamHandler.setDeviceProperty(reqDTO);
58   -
59   - // 3. 响应结果
60   - IotStandardResponse response;
61   - if (result.isSuccess()) {
62   - response = IotStandardResponse.success(reqDTO.getRequestId(), METHOD, result.getData());
63   - } else {
64   - response = IotStandardResponse.error(reqDTO.getRequestId(), METHOD, result.getCode(), result.getMsg());
65   - }
66   - IotPluginCommonUtils.writeJsonResponse(routingContext, response);
67   - } catch (Exception e) {
68   - log.error("[handle][请求参数({}) 属性设置异常]", reqDTO, e);
69   - IotStandardResponse errorResponse = IotStandardResponse.error(
70   - reqDTO.getRequestId(), METHOD, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
71   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
72   - }
73   - }
74   -
75   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceServiceInvokeVertxHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.downstream.router;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceServiceInvokeReqDTO;
5   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6   -import cn.iocoder.yudao.module.iot.plugin.common.pojo.IotStandardResponse;
7   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
8   -import io.vertx.core.Handler;
9   -import io.vertx.core.json.JsonObject;
10   -import io.vertx.ext.web.RoutingContext;
11   -import lombok.RequiredArgsConstructor;
12   -import lombok.extern.slf4j.Slf4j;
13   -
14   -import java.util.Map;
15   -
16   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
17   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
18   -
19   -/**
20   - * IoT 设备服务调用 Vertx Handler
21   - *
22   - * 芋道源码
23   - */
24   -@Slf4j
25   -@RequiredArgsConstructor
26   -public class IotDeviceServiceInvokeVertxHandler implements Handler<RoutingContext> {
27   -
28   - public static final String PATH = "/sys/:productKey/:deviceName/thing/service/:identifier";
29   - public static final String METHOD_PREFIX = "thing.service.";
30   - public static final String METHOD_SUFFIX = "";
31   -
32   - private final IotDeviceDownstreamHandler deviceDownstreamHandler;
33   -
34   - @Override
35   - @SuppressWarnings("unchecked")
36   - public void handle(RoutingContext routingContext) {
37   - // 1. 解析参数
38   - IotDeviceServiceInvokeReqDTO reqDTO;
39   - try {
40   - String productKey = routingContext.pathParam("productKey");
41   - String deviceName = routingContext.pathParam("deviceName");
42   - String identifier = routingContext.pathParam("identifier");
43   - JsonObject body = routingContext.body().asJsonObject();
44   - String requestId = body.getString("requestId");
45   - Map<String, Object> params = (Map<String, Object>) body.getMap().get("params");
46   - reqDTO = ((IotDeviceServiceInvokeReqDTO) new IotDeviceServiceInvokeReqDTO()
47   - .setRequestId(requestId).setProductKey(productKey).setDeviceName(deviceName))
48   - .setIdentifier(identifier).setParams(params);
49   - } catch (Exception e) {
50   - log.error("[handle][路径参数({}) 解析参数失败]", routingContext.pathParams(), e);
51   - String method = METHOD_PREFIX + routingContext.pathParam("identifier") + METHOD_SUFFIX;
52   - IotStandardResponse errorResponse = IotStandardResponse.error(
53   - null, method, BAD_REQUEST.getCode(), BAD_REQUEST.getMsg());
54   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
55   - return;
56   - }
57   -
58   - // 2. 调用处理器
59   - try {
60   - CommonResult<Boolean> result = deviceDownstreamHandler.invokeDeviceService(reqDTO);
61   -
62   - // 3. 响应结果
63   - String method = METHOD_PREFIX + reqDTO.getIdentifier() + METHOD_SUFFIX;
64   - IotStandardResponse response;
65   - if (result.isSuccess()) {
66   - response = IotStandardResponse.success(reqDTO.getRequestId(), method, result.getData());
67   - } else {
68   - response = IotStandardResponse.error(reqDTO.getRequestId(), method, result.getCode(), result.getMsg());
69   - }
70   - IotPluginCommonUtils.writeJsonResponse(routingContext, response);
71   - } catch (Exception e) {
72   - log.error("[handle][请求参数({}) 服务调用异常]", reqDTO, e);
73   - String method = METHOD_PREFIX + reqDTO.getIdentifier() + METHOD_SUFFIX;
74   - IotStandardResponse errorResponse = IotStandardResponse.error(
75   - reqDTO.getRequestId(), method, INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
76   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
77   - }
78   - }
79   -
80   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/heartbeat/IotPluginInstanceHeartbeatJob.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.heartbeat;
2   -
3   -import cn.hutool.system.SystemUtil;
4   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
7   -import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
8   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
9   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
10   -import lombok.RequiredArgsConstructor;
11   -import lombok.extern.slf4j.Slf4j;
12   -import org.springframework.scheduling.annotation.Scheduled;
13   -
14   -import java.util.concurrent.TimeUnit;
15   -
16   -/**
17   - * IoT 插件实例心跳 Job
18   - *
19   - * 用于定时发送心跳给服务端
20   - */
21   -@RequiredArgsConstructor
22   -@Slf4j
23   -public class IotPluginInstanceHeartbeatJob {
24   -
25   - private final IotDeviceUpstreamApi deviceUpstreamApi;
26   - private final IotDeviceDownstreamServer deviceDownstreamServer;
27   - private final IotPluginCommonProperties commonProperties;
28   -
29   - public void init() {
30   - CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(true));
31   - log.info("[init][上线结果:{})]", result);
32   - }
33   -
34   - public void stop() {
35   - CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(false));
36   - log.info("[stop][下线结果:{})]", result);
37   - }
38   -
39   - @Scheduled(initialDelay = 3, fixedRate = 3, timeUnit = TimeUnit.MINUTES) // 3 分钟执行一次
40   - public void execute() {
41   - CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(true));
42   - log.info("[execute][心跳结果:{})]", result);
43   - }
44   -
45   - private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(Boolean online) {
46   - return new IotPluginInstanceHeartbeatReqDTO()
47   - .setPluginKey(commonProperties.getPluginKey()).setProcessId(IotPluginCommonUtils.getProcessId())
48   - .setHostIp(SystemUtil.getHostInfo().getAddress()).setDownstreamPort(deviceDownstreamServer.getPort())
49   - .setOnline(online);
50   - }
51   -
52   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/package-info.java deleted
1   -// TODO @芋艿:注释
2   -package cn.iocoder.yudao.module.iot.plugin.common;
3 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/upstream/IotDeviceUpstreamClient.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.upstream;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
5   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
6   -import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
7   -import lombok.RequiredArgsConstructor;
8   -import lombok.extern.slf4j.Slf4j;
9   -import org.springframework.web.client.RestTemplate;
10   -
11   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
12   -
13   -/**
14   - * 设备数据 Upstream 上行客户端
15   - *
16   - * 通过 HTTP 调用远程的 IotDeviceUpstreamApi 接口
17   - *
18   - * @author haohao
19   - */
20   -@RequiredArgsConstructor
21   -@Slf4j
22   -public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi {
23   -
24   - public static final String URL_PREFIX = "/rpc-api/iot/device/upstream";
25   -
26   - private final IotPluginCommonProperties properties;
27   -
28   - private final RestTemplate restTemplate;
29   -
30   - @Override
31   - public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
32   - String url = properties.getUpstreamUrl() + URL_PREFIX + "/update-state";
33   - return doPost(url, updateReqDTO);
34   - }
35   -
36   - @Override
37   - public CommonResult<Boolean> reportDeviceEvent(IotDeviceEventReportReqDTO reportReqDTO) {
38   - String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-event";
39   - return doPost(url, reportReqDTO);
40   - }
41   -
42   - // TODO @芋艿:待实现
43   - @Override
44   - public CommonResult<Boolean> registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
45   - return null;
46   - }
47   -
48   - // TODO @芋艿:待实现
49   - @Override
50   - public CommonResult<Boolean> registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
51   - return null;
52   - }
53   -
54   - // TODO @芋艿:待实现
55   - @Override
56   - public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
57   - return null;
58   - }
59   -
60   - @Override
61   - public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
62   - String url = properties.getUpstreamUrl() + URL_PREFIX + "/authenticate-emqx-connection";
63   - return doPost(url, authReqDTO);
64   - }
65   -
66   - @Override
67   - public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
68   - String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property";
69   - return doPost(url, reportReqDTO);
70   - }
71   -
72   - @Override
73   - public CommonResult<Boolean> heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO) {
74   - String url = properties.getUpstreamUrl() + URL_PREFIX + "/heartbeat-plugin-instance";
75   - return doPost(url, heartbeatReqDTO);
76   - }
77   -
78   - @SuppressWarnings("unchecked")
79   - private <T> CommonResult<Boolean> doPost(String url, T requestBody) {
80   - try {
81   - CommonResult<Boolean> result = restTemplate.postForObject(url, requestBody,
82   - (Class<CommonResult<Boolean>>) (Class<?>) CommonResult.class);
83   - log.info("[doPost][url({}) requestBody({}) result({})]", url, requestBody, result);
84   - return result;
85   - } catch (Exception e) {
86   - log.error("[doPost][url({}) requestBody({}) 发生异常]", url, requestBody, e);
87   - return CommonResult.error(INTERNAL_SERVER_ERROR);
88   - }
89   - }
90   -
91   -}
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 deleted
1   -package cn.iocoder.yudao.module.iot.plugin.common.util;
2   -
3   -import cn.hutool.core.util.IdUtil;
4   -import cn.hutool.core.util.StrUtil;
5   -import cn.hutool.system.SystemUtil;
6   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7   -import cn.iocoder.yudao.module.iot.plugin.common.pojo.IotStandardResponse;
8   -import io.vertx.core.http.HttpHeaders;
9   -import io.vertx.ext.web.RoutingContext;
10   -import org.springframework.http.MediaType;
11   -
12   -/**
13   - * IoT 插件的通用工具类
14   - *
15   - * @author 芋道源码
16   - */
17   -public class IotPluginCommonUtils {
18   -
19   - /**
20   - * 流程实例的进程编号
21   - */
22   - private static String processId;
23   -
24   - public static String getProcessId() {
25   - if (StrUtil.isEmpty(processId)) {
26   - initProcessId();
27   - }
28   - return processId;
29   - }
30   -
31   - private synchronized static void initProcessId() {
32   - processId = String.format("%s@%d@%s", // IP@PID@${uuid}
33   - SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID(), IdUtil.fastSimpleUUID());
34   - }
35   -
36   - /**
37   - * 将对象转换为JSON字符串后写入HTTP响应
38   - *
39   - * @param routingContext 路由上下文
40   - * @param data 数据对象
41   - */
42   - @SuppressWarnings("deprecation")
43   - public static void writeJsonResponse(RoutingContext routingContext, Object data) {
44   - routingContext.response()
45   - .setStatusCode(200)
46   - .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
47   - .end(JsonUtils.toJsonString(data));
48   - }
49   -
50   - /**
51   - * 生成标准JSON格式的响应并写入HTTP响应(基于IotStandardResponse)
52   - * <p>
53   - * 推荐使用此方法,统一MQTT和HTTP的响应格式。使用方式:
54   - *
55   - * <pre>
56   - * // 成功响应
57   - * IotStandardResponse response = IotStandardResponse.success(requestId, method, data);
58   - * IotPluginCommonUtils.writeJsonResponse(routingContext, response);
59   - *
60   - * // 错误响应
61   - * IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method, code, message);
62   - * IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
63   - * </pre>
64   - *
65   - * @param routingContext 路由上下文
66   - * @param response IotStandardResponse响应对象
67   - */
68   - @SuppressWarnings("deprecation")
69   - public static void writeJsonResponse(RoutingContext routingContext, IotStandardResponse response) {
70   - routingContext.response()
71   - .setStatusCode(200)
72   - .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
73   - .end(JsonUtils.toJsonString(response));
74   - }
75   -
76   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted
1   -cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonAutoConfiguration
2 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/plugin.properties deleted
1   -plugin.id=yudao-module-iot-plugin-emqx
2   -plugin.class=cn.iocoder.yudao.module.iot.plugin.emqx.config.IotEmqxPlugin
3   -plugin.version=1.0.0
4   -plugin.provider=yudao
5   -plugin.dependencies=
6   -plugin.description=yudao-module-iot-plugin-emqx-1.0.0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/pom.xml deleted
1   -<?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3   - xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
4   - http://maven.apache.org/POM/4.0.0
5   - http://maven.apache.org/xsd/maven-4.0.0.xsd">
6   - <parent>
7   - <artifactId>yudao-module-iot-plugins</artifactId>
8   - <groupId>cn.iocoder.boot</groupId>
9   - <version>${revision}</version>
10   - </parent>
11   - <modelVersion>4.0.0</modelVersion>
12   - <packaging>jar</packaging>
13   -
14   - <artifactId>yudao-module-iot-plugin-emqx</artifactId>
15   - <version>1.0.0</version>
16   -
17   - <name>${project.artifactId}</name>
18   - <!-- TODO @芋艿:待整理 -->
19   - <description>
20   - 物联网 插件模块 - emqx 插件
21   - </description>
22   -
23   - <properties>
24   - <!-- 插件相关 -->
25   - <plugin.id>emqx-plugin</plugin.id>
26   - <plugin.class>cn.iocoder.yudao.module.iot.plugin.emqx.config.IotEmqxPlugin</plugin.class>
27   - <plugin.version>${project.version}</plugin.version>
28   - <plugin.provider>yudao</plugin.provider>
29   - <plugin.description>${project.artifactId}-${project.version}</plugin.description>
30   - <plugin.dependencies/>
31   - </properties>
32   -
33   - <build>
34   - <plugins>
35   - <!-- 插件模式 zip -->
36   - <plugin>
37   - <groupId>org.apache.maven.plugins</groupId>
38   - <artifactId>maven-antrun-plugin</artifactId>
39   - <version>1.6</version>
40   - <executions>
41   - <execution>
42   - <id>unzip jar file</id>
43   - <phase>package</phase>
44   - <configuration>
45   - <target>
46   - <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}"
47   - dest="target/plugin-classes"/>
48   - </target>
49   - </configuration>
50   - <goals>
51   - <goal>run</goal>
52   - </goals>
53   - </execution>
54   - </executions>
55   - </plugin>
56   -
57   - <plugin>
58   - <artifactId>maven-assembly-plugin</artifactId>
59   - <version>2.3</version>
60   - <configuration>
61   - <descriptors>
62   - <descriptor>
63   - src/main/assembly/assembly.xml
64   - </descriptor>
65   - </descriptors>
66   - <appendAssemblyId>false</appendAssemblyId>
67   - </configuration>
68   - <executions>
69   - <execution>
70   - <id>make-assembly</id>
71   - <phase>package</phase>
72   - <goals>
73   - <goal>attached</goal>
74   - </goals>
75   - </execution>
76   - </executions>
77   - </plugin>
78   -
79   - <!-- 插件模式 jar -->
80   - <plugin>
81   - <groupId>org.apache.maven.plugins</groupId>
82   - <artifactId>maven-jar-plugin</artifactId>
83   - <version>2.4</version>
84   - <configuration>
85   - <archive>
86   - <manifestEntries>
87   - <Plugin-Id>${plugin.id}</Plugin-Id>
88   - <Plugin-Class>${plugin.class}</Plugin-Class>
89   - <Plugin-Version>${plugin.version}</Plugin-Version>
90   - <Plugin-Provider>${plugin.provider}</Plugin-Provider>
91   - <Plugin-Description>${plugin.description}</Plugin-Description>
92   - <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
93   - </manifestEntries>
94   - </archive>
95   - </configuration>
96   - </plugin>
97   - <!-- <plugin>-->
98   - <!-- <groupId>org.apache.maven.plugins</groupId>-->
99   - <!-- <artifactId>maven-shade-plugin</artifactId>-->
100   - <!-- <version>3.6.0</version>-->
101   - <!-- <executions>-->
102   - <!-- <execution>-->
103   - <!-- <phase>package</phase>-->
104   - <!-- <goals>-->
105   - <!-- <goal>shade</goal>-->
106   - <!-- </goals>-->
107   - <!-- <configuration>-->
108   - <!-- <minimizeJar>true</minimizeJar>-->
109   - <!-- </configuration>-->
110   - <!-- </execution>-->
111   - <!-- </executions>-->
112   - <!-- <configuration>-->
113   - <!-- <archive>-->
114   - <!-- <manifestEntries>-->
115   - <!-- <Plugin-Id>${plugin.id}</Plugin-Id>-->
116   - <!-- <Plugin-Class>${plugin.class}</Plugin-Class>-->
117   - <!-- <Plugin-Version>${plugin.version}</Plugin-Version>-->
118   - <!-- <Plugin-Provider>${plugin.provider}</Plugin-Provider>-->
119   - <!-- <Plugin-Description>${plugin.description}</Plugin-Description>-->
120   - <!-- <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>-->
121   - <!-- </manifestEntries>-->
122   - <!-- </archive>-->
123   - <!-- </configuration>-->
124   - <!-- </plugin>-->
125   -
126   - <!-- 独立模式 -->
127   - <plugin>
128   - <groupId>org.springframework.boot</groupId>
129   - <artifactId>spring-boot-maven-plugin</artifactId>
130   - <version>${spring.boot.version}</version>
131   - <executions>
132   - <execution>
133   - <goals>
134   - <goal>repackage</goal>
135   - </goals>
136   - <configuration>
137   - <classifier>-standalone</classifier>
138   - </configuration>
139   - </execution>
140   - </executions>
141   - </plugin>
142   -
143   - </plugins>
144   - </build>
145   -
146   - <dependencies>
147   - <dependency>
148   - <groupId>cn.iocoder.boot</groupId>
149   - <artifactId>yudao-module-iot-plugin-common</artifactId>
150   - <version>${revision}</version>
151   - </dependency>
152   -
153   - <!-- Web 相关 -->
154   - <dependency>
155   - <groupId>org.springframework.boot</groupId>
156   - <artifactId>spring-boot-starter-web</artifactId>
157   - </dependency>
158   -
159   - <!-- 工具类相关 -->
160   - <dependency>
161   - <groupId>io.vertx</groupId>
162   - <artifactId>vertx-web</artifactId>
163   - </dependency>
164   - <dependency>
165   - <groupId>io.vertx</groupId>
166   - <artifactId>vertx-mqtt</artifactId>
167   - </dependency>
168   - </dependencies>
169   -</project>
170 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/assembly/assembly.xml deleted
1   -<assembly>
2   - <id>plugin</id>
3   - <formats>
4   - <format>zip</format>
5   - </formats>
6   - <includeBaseDirectory>false</includeBaseDirectory>
7   - <dependencySets>
8   - <dependencySet>
9   - <useProjectArtifact>false</useProjectArtifact>
10   - <scope>runtime</scope>
11   - <outputDirectory>lib</outputDirectory>
12   - <includes>
13   - <include>*:jar:*</include>
14   - </includes>
15   - </dependencySet>
16   - </dependencySets>
17   - <!--
18   - <fileSets>
19   - <fileSet>
20   - <directory>target/classes</directory>
21   - <outputDirectory>classes</outputDirectory>
22   - </fileSet>
23   - </fileSets>
24   - -->
25   - <fileSets>
26   - <fileSet>
27   - <directory>target/plugin-classes</directory>
28   - <outputDirectory>classes</outputDirectory>
29   - </fileSet>
30   - </fileSets>
31   -</assembly>
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/IotEmqxPluginApplication.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx;
2   -
3   -import lombok.extern.slf4j.Slf4j;
4   -import org.springframework.boot.SpringApplication;
5   -import org.springframework.boot.WebApplicationType;
6   -import org.springframework.boot.autoconfigure.SpringBootApplication;
7   -
8   -/**
9   - * IoT Emqx 插件的独立运行入口
10   - */
11   -@Slf4j
12   -@SpringBootApplication
13   -public class IotEmqxPluginApplication {
14   -
15   - public static void main(String[] args) {
16   - SpringApplication application = new SpringApplication(IotEmqxPluginApplication.class);
17   - application.setWebApplicationType(WebApplicationType.NONE);
18   - application.run(args);
19   - log.info("[main][独立模式启动完成]");
20   - }
21   -
22   -}
23 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotEmqxPlugin.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx.config;
2   -
3   -import cn.hutool.extra.spring.SpringUtil;
4   -import lombok.extern.slf4j.Slf4j;
5   -import org.pf4j.PluginWrapper;
6   -import org.pf4j.spring.SpringPlugin;
7   -import org.springframework.context.ApplicationContext;
8   -import org.springframework.context.annotation.AnnotationConfigApplicationContext;
9   -
10   -/**
11   - * EMQX 插件实现类
12   - *
13   - * 基于 PF4J 插件框架,实现 EMQX 消息中间件的集成:负责插件的生命周期管理,包括启动、停止和应用上下文的创建
14   - *
15   - * @author haohao
16   - */
17   -@Slf4j
18   -public class IotEmqxPlugin extends SpringPlugin {
19   -
20   - public IotEmqxPlugin(PluginWrapper wrapper) {
21   - super(wrapper);
22   - }
23   -
24   - @Override
25   - public void start() {
26   - log.info("[EmqxPlugin][EmqxPlugin 插件启动开始...]");
27   - try {
28   - log.info("[EmqxPlugin][EmqxPlugin 插件启动成功...]");
29   - } catch (Exception e) {
30   - log.error("[EmqxPlugin][EmqxPlugin 插件开启动异常...]", e);
31   - }
32   - }
33   -
34   - @Override
35   - public void stop() {
36   - log.info("[EmqxPlugin][EmqxPlugin 插件停止开始...]");
37   - try {
38   - log.info("[EmqxPlugin][EmqxPlugin 插件停止成功...]");
39   - } catch (Exception e) {
40   - log.error("[EmqxPlugin][EmqxPlugin 插件停止异常...]", e);
41   - }
42   - }
43   -
44   - @Override
45   - protected ApplicationContext createApplicationContext() {
46   - // 创建插件自己的 ApplicationContext
47   - AnnotationConfigApplicationContext pluginContext = new AnnotationConfigApplicationContext();
48   - // 设置父容器为主应用的 ApplicationContext (确保主应用中提供的类可用)
49   - pluginContext.setParent(SpringUtil.getApplicationContext());
50   - // 继续使用插件自己的 ClassLoader 以加载插件内部的类
51   - pluginContext.setClassLoader(getWrapper().getPluginClassLoader());
52   - // 扫描当前插件的自动配置包
53   - // TODO @芋艿:是不是要配置下包
54   - pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.emqx.config");
55   - pluginContext.refresh();
56   - return pluginContext;
57   - }
58   -
59   -}
60 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotPluginEmqxAutoConfiguration.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx.config;
2   -
3   -import cn.hutool.core.util.IdUtil;
4   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
5   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6   -import cn.iocoder.yudao.module.iot.plugin.emqx.downstream.IotDeviceDownstreamHandlerImpl;
7   -import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.IotDeviceUpstreamServer;
8   -import io.vertx.core.Vertx;
9   -import io.vertx.mqtt.MqttClient;
10   -import io.vertx.mqtt.MqttClientOptions;
11   -import lombok.extern.slf4j.Slf4j;
12   -import org.springframework.boot.context.properties.EnableConfigurationProperties;
13   -import org.springframework.context.annotation.Bean;
14   -import org.springframework.context.annotation.Configuration;
15   -
16   -/**
17   - * IoT 插件 EMQX 的专用自动配置类
18   - *
19   - * @author haohao
20   - */
21   -@Slf4j
22   -@Configuration
23   -@EnableConfigurationProperties(IotPluginEmqxProperties.class)
24   -public class IotPluginEmqxAutoConfiguration {
25   -
26   - @Bean
27   - public Vertx vertx() {
28   - return Vertx.vertx();
29   - }
30   -
31   - @Bean
32   - public MqttClient mqttClient(Vertx vertx, IotPluginEmqxProperties emqxProperties) {
33   - MqttClientOptions options = new MqttClientOptions()
34   - .setClientId("yudao-iot-downstream-" + IdUtil.fastSimpleUUID())
35   - .setUsername(emqxProperties.getMqttUsername())
36   - .setPassword(emqxProperties.getMqttPassword())
37   - .setSsl(emqxProperties.getMqttSsl());
38   - return MqttClient.create(vertx, options);
39   - }
40   -
41   - @Bean(initMethod = "start", destroyMethod = "stop")
42   - public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,
43   - IotPluginEmqxProperties emqxProperties,
44   - Vertx vertx,
45   - MqttClient mqttClient) {
46   - return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi, vertx, mqttClient);
47   - }
48   -
49   - @Bean
50   - public IotDeviceDownstreamHandler deviceDownstreamHandler(MqttClient mqttClient) {
51   - return new IotDeviceDownstreamHandlerImpl(mqttClient);
52   - }
53   -
54   -}
55 0 \ No newline at end of file
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 deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx.config;
2   -
3   -import lombok.Data;
4   -import org.springframework.boot.context.properties.ConfigurationProperties;
5   -import org.springframework.validation.annotation.Validated;
6   -
7   -/**
8   - * 物联网插件 - EMQX 配置
9   - *
10   - * @author 芋道源码
11   - */
12   -@ConfigurationProperties(prefix = "yudao.iot.plugin.emqx")
13   -@Validated
14   -@Data
15   -public class IotPluginEmqxProperties {
16   -
17   - // TODO @haohao:参数校验,加下,啊哈
18   -
19   - /**
20   - * 服务主机
21   - */
22   - private String mqttHost;
23   - /**
24   - * 服务端口
25   - */
26   - private Integer mqttPort;
27   - /**
28   - * 服务用户名
29   - */
30   - private String mqttUsername;
31   - /**
32   - * 服务密码
33   - */
34   - private String mqttPassword;
35   - /**
36   - * 是否启用 SSL
37   - */
38   - private Boolean mqttSsl;
39   -
40   - /**
41   - * 订阅的主题列表
42   - */
43   - private String[] mqttTopics;
44   -
45   - /**
46   - * 认证端口
47   - */
48   - private Integer authPort;
49   -
50   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/downstream/IotDeviceDownstreamHandlerImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx.downstream;
2   -
3   -import cn.hutool.core.util.IdUtil;
4   -import cn.hutool.json.JSONObject;
5   -import cn.hutool.json.JSONUtil;
6   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7   -import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
8   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
9   -import io.netty.handler.codec.mqtt.MqttQoS;
10   -import io.vertx.core.buffer.Buffer;
11   -import io.vertx.mqtt.MqttClient;
12   -import lombok.extern.slf4j.Slf4j;
13   -
14   -import java.util.Map;
15   -
16   -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.MQTT_TOPIC_ILLEGAL;
17   -
18   -/**
19   - * EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类
20   - *
21   - * @author 芋道源码
22   - */
23   -@Slf4j
24   -public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
25   -
26   - private static final String SYS_TOPIC_PREFIX = "/sys/";
27   -
28   - // TODO @haohao:是不是可以类似 IotDeviceConfigSetVertxHandler 的建议,抽到统一的枚举类
29   - // TODO @haohao:讨论,感觉 mqtt 和 http,可以做个相对统一的格式哈。;回复 都使用 Alink 格式,方便后续扩展。
30   - // 设备服务调用 标准 JSON
31   - // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}
32   - // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}_reply
33   - private static final String SERVICE_TOPIC_PREFIX = "/thing/service/";
34   -
35   - // 设置设备属性 标准 JSON
36   - // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set
37   - // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply
38   - private static final String PROPERTY_SET_TOPIC = "/thing/service/property/set";
39   -
40   - private final MqttClient mqttClient;
41   -
42   - /**
43   - * 构造函数
44   - *
45   - * @param mqttClient MQTT客户端
46   - */
47   - public IotDeviceDownstreamHandlerImpl(MqttClient mqttClient) {
48   - this.mqttClient = mqttClient;
49   - }
50   -
51   - @Override
52   - public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO reqDTO) {
53   - log.info("[invokeService][开始调用设备服务][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
54   -
55   - // 验证参数
56   - if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null || reqDTO.getIdentifier() == null) {
57   - log.error("[invokeService][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
58   - return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg());
59   - }
60   -
61   - try {
62   - // 构建请求主题
63   - String topic = buildServiceTopic(reqDTO.getProductKey(), reqDTO.getDeviceName(), reqDTO.getIdentifier());
64   - // 构建请求消息
65   - String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId();
66   - JSONObject request = buildServiceRequest(requestId, reqDTO.getIdentifier(), reqDTO.getParams());
67   - // 发送消息
68   - publishMessage(topic, request);
69   -
70   - log.info("[invokeService][调用设备服务成功][requestId: {}][topic: {}]", requestId, topic);
71   - return CommonResult.success(true);
72   - } catch (Exception e) {
73   - log.error("[invokeService][调用设备服务异常][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO), e);
74   - return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg());
75   - }
76   - }
77   -
78   - @Override
79   - public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
80   - return CommonResult.success(true);
81   - }
82   -
83   - @Override
84   - public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO reqDTO) {
85   - // 验证参数
86   - log.info("[setProperty][开始设置设备属性][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
87   - if (reqDTO.getProductKey() == null || reqDTO.getDeviceName() == null) {
88   - log.error("[setProperty][参数不完整][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO));
89   - return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg());
90   - }
91   -
92   - try {
93   - // 构建请求主题
94   - String topic = buildPropertySetTopic(reqDTO.getProductKey(), reqDTO.getDeviceName());
95   - // 构建请求消息
96   - String requestId = reqDTO.getRequestId() != null ? reqDTO.getRequestId() : generateRequestId();
97   - JSONObject request = buildPropertySetRequest(requestId, reqDTO.getProperties());
98   - // 发送消息
99   - publishMessage(topic, request);
100   -
101   - log.info("[setProperty][设置设备属性成功][requestId: {}][topic: {}]", requestId, topic);
102   - return CommonResult.success(true);
103   - } catch (Exception e) {
104   - log.error("[setProperty][设置设备属性异常][reqDTO: {}]", JSONUtil.toJsonStr(reqDTO), e);
105   - return CommonResult.error(MQTT_TOPIC_ILLEGAL.getCode(), MQTT_TOPIC_ILLEGAL.getMsg());
106   - }
107   - }
108   -
109   - @Override
110   - public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
111   - return CommonResult.success(true);
112   - }
113   -
114   - @Override
115   - public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
116   - return CommonResult.success(true);
117   - }
118   -
119   - /**
120   - * 构建服务调用主题
121   - */
122   - private String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
123   - return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + SERVICE_TOPIC_PREFIX + serviceIdentifier;
124   - }
125   -
126   - /**
127   - * 构建属性设置主题
128   - */
129   - private String buildPropertySetTopic(String productKey, String deviceName) {
130   - return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + PROPERTY_SET_TOPIC;
131   - }
132   -
133   - // TODO @haohao:这个,后面搞个对象,会不会好点哈?
134   - /**
135   - * 构建服务调用请求
136   - */
137   - private JSONObject buildServiceRequest(String requestId, String serviceIdentifier, Map<String, Object> params) {
138   - return new JSONObject()
139   - .set("id", requestId)
140   - .set("version", "1.0")
141   - .set("method", "thing.service." + serviceIdentifier)
142   - .set("params", params != null ? params : new JSONObject());
143   - }
144   -
145   - /**
146   - * 构建属性设置请求
147   - */
148   - private JSONObject buildPropertySetRequest(String requestId, Map<String, Object> properties) {
149   - return new JSONObject()
150   - .set("id", requestId)
151   - .set("version", "1.0")
152   - .set("method", "thing.service.property.set")
153   - .set("params", properties);
154   - }
155   -
156   - /**
157   - * 发布 MQTT 消息
158   - */
159   - private void publishMessage(String topic, JSONObject payload) {
160   - mqttClient.publish(
161   - topic,
162   - Buffer.buffer(payload.toString()),
163   - MqttQoS.AT_LEAST_ONCE,
164   - false,
165   - false);
166   - log.info("[publishMessage][发送消息成功][topic: {}][payload: {}]", topic, payload);
167   - }
168   -
169   - /**
170   - * 生成请求 ID
171   - */
172   - private String generateRequestId() {
173   - return IdUtil.fastSimpleUUID();
174   - }
175   -
176   -}
177 0 \ No newline at end of file
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 deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx.upstream;
2   -
3   -import cn.hutool.core.util.ArrayUtil;
4   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
5   -import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties;
6   -import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler;
7   -import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceMqttMessageHandler;
8   -import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceWebhookVertxHandler;
9   -import io.netty.handler.codec.mqtt.MqttQoS;
10   -import io.vertx.core.Future;
11   -import io.vertx.core.Vertx;
12   -import io.vertx.core.http.HttpServer;
13   -import io.vertx.ext.web.Router;
14   -import io.vertx.ext.web.handler.BodyHandler;
15   -import io.vertx.mqtt.MqttClient;
16   -import lombok.extern.slf4j.Slf4j;
17   -
18   -import java.util.concurrent.CompletableFuture;
19   -import java.util.concurrent.TimeUnit;
20   -
21   -/**
22   - * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
23   - * <p>
24   - * 协议:HTTP、MQTT
25   - *
26   - * @author haohao
27   - */
28   -@Slf4j
29   -public class IotDeviceUpstreamServer {
30   -
31   - /**
32   - * 重连延迟时间(毫秒)
33   - */
34   - private static final int RECONNECT_DELAY_MS = 5000;
35   - /**
36   - * 连接超时时间(毫秒)
37   - */
38   - private static final int CONNECTION_TIMEOUT_MS = 10000;
39   - /**
40   - * 默认 QoS 级别
41   - */
42   - private static final MqttQoS DEFAULT_QOS = MqttQoS.AT_LEAST_ONCE;
43   -
44   - private final Vertx vertx;
45   - private final HttpServer server;
46   - private final MqttClient client;
47   - private final IotPluginEmqxProperties emqxProperties;
48   - private final IotDeviceMqttMessageHandler mqttMessageHandler;
49   -
50   - /**
51   - * 服务运行状态标志
52   - */
53   - private volatile boolean isRunning = false;
54   -
55   - public IotDeviceUpstreamServer(IotPluginEmqxProperties emqxProperties,
56   - IotDeviceUpstreamApi deviceUpstreamApi,
57   - Vertx vertx,
58   - MqttClient client) {
59   - this.vertx = vertx;
60   - this.emqxProperties = emqxProperties;
61   - this.client = client;
62   -
63   - // 创建 Router 实例
64   - Router router = Router.router(vertx);
65   - router.route().handler(BodyHandler.create()); // 处理 Body
66   - router.post(IotDeviceAuthVertxHandler.PATH)
67   - // TODO @haohao:疑问,mqtt 的认证,需要通过 http 呀?
68   - // 回复:MQTT 认证不必须通过 HTTP 进行,但 HTTP 认证是 EMQX 等 MQTT 服务器支持的一种灵活的认证方式
69   - .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
70   - // 添加 Webhook 处理器,用于处理设备连接和断开连接事件
71   - router.post(IotDeviceWebhookVertxHandler.PATH)
72   - .handler(new IotDeviceWebhookVertxHandler(deviceUpstreamApi));
73   - // 创建 HttpServer 实例
74   - this.server = vertx.createHttpServer().requestHandler(router);
75   - this.mqttMessageHandler = new IotDeviceMqttMessageHandler(deviceUpstreamApi, client);
76   - }
77   -
78   - /**
79   - * 启动 HTTP 服务器、MQTT 客户端
80   - */
81   - public void start() {
82   - if (isRunning) {
83   - log.warn("[start][服务已经在运行中,请勿重复启动]");
84   - return;
85   - }
86   - log.info("[start][开始启动服务]");
87   -
88   - // TODO @haohao:建议先启动 MQTT Broker,再启动 HTTP Server。类似 jdbc 先连接了,在启动 tomcat 的味道
89   - // 1. 启动 HTTP 服务器
90   - CompletableFuture<Void> httpFuture = server.listen(emqxProperties.getAuthPort())
91   - .toCompletionStage()
92   - .toCompletableFuture()
93   - .thenAccept(v -> log.info("[start][HTTP 服务器启动完成,端口: {}]", server.actualPort()));
94   -
95   - // 2. 连接 MQTT Broker
96   - CompletableFuture<Void> mqttFuture = connectMqtt()
97   - .toCompletionStage()
98   - .toCompletableFuture()
99   - .thenAccept(v -> {
100   - // 2.1 添加 MQTT 断开重连监听器
101   - client.closeHandler(closeEvent -> {
102   - log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
103   - reconnectWithDelay();
104   - });
105   - // 2.2 设置 MQTT 消息处理器
106   - setupMessageHandler();
107   - });
108   -
109   - // 3. 等待所有服务启动完成
110   - CompletableFuture.allOf(httpFuture, mqttFuture)
111   - .orTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS) // TODO @芋艿:JDK8 不兼容
112   - .whenComplete((result, error) -> {
113   - if (error != null) {
114   - log.error("[start][服务启动失败]", error);
115   - } else {
116   - isRunning = true;
117   - log.info("[start][所有服务启动完成]");
118   - }
119   - });
120   - }
121   -
122   - /**
123   - * 设置 MQTT 消息处理器
124   - */
125   - private void setupMessageHandler() {
126   - client.publishHandler(mqttMessageHandler::handle);
127   - log.debug("[setupMessageHandler][MQTT 消息处理器设置完成]");
128   - }
129   -
130   - /**
131   - * 重连 MQTT 客户端
132   - */
133   - private void reconnectWithDelay() {
134   - if (!isRunning) {
135   - log.info("[reconnectWithDelay][服务已停止,不再尝试重连]");
136   - return;
137   - }
138   -
139   - vertx.setTimer(RECONNECT_DELAY_MS, id -> {
140   - log.info("[reconnectWithDelay][开始重新连接 MQTT]");
141   - connectMqtt();
142   - });
143   - }
144   -
145   - /**
146   - * 连接 MQTT Broker 并订阅主题
147   - *
148   - * @return 连接结果的Future
149   - */
150   - private Future<Void> connectMqtt() {
151   - return client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost())
152   - .compose(connAck -> {
153   - log.info("[connectMqtt][MQTT客户端连接成功]");
154   - return subscribeToTopics();
155   - })
156   - .recover(error -> {
157   - log.error("[connectMqtt][连接MQTT Broker失败:]", error);
158   - reconnectWithDelay();
159   - return Future.failedFuture(error);
160   - });
161   - }
162   -
163   - /**
164   - * 订阅设备上行消息主题
165   - *
166   - * @return 订阅结果的 Future
167   - */
168   - private Future<Void> subscribeToTopics() {
169   - String[] topics = emqxProperties.getMqttTopics();
170   - if (ArrayUtil.isEmpty(topics)) {
171   - log.warn("[subscribeToTopics][未配置MQTT主题,跳过订阅]");
172   - return Future.succeededFuture();
173   - }
174   - log.info("[subscribeToTopics][开始订阅设备上行消息主题]");
175   -
176   - Future<Void> compositeFuture = Future.succeededFuture();
177   - for (String topic : topics) {
178   - String trimmedTopic = topic.trim();
179   - if (trimmedTopic.isEmpty()) {
180   - continue;
181   - }
182   - compositeFuture = compositeFuture.compose(v -> client.subscribe(trimmedTopic, DEFAULT_QOS.value())
183   - .<Void>map(ack -> {
184   - log.info("[subscribeToTopics][成功订阅主题: {}]", trimmedTopic);
185   - return null;
186   - })
187   - .recover(error -> {
188   - log.error("[subscribeToTopics][订阅主题失败: {}]", trimmedTopic, error);
189   - return Future.<Void>succeededFuture(); // 继续订阅其他主题
190   - }));
191   - }
192   - return compositeFuture;
193   - }
194   -
195   - /**
196   - * 停止所有服务
197   - */
198   - public void stop() {
199   - if (!isRunning) {
200   - log.warn("[stop][服务未运行,无需停止]");
201   - return;
202   - }
203   - log.info("[stop][开始关闭服务]");
204   - isRunning = false;
205   -
206   - try {
207   - // 关闭 HTTP 服务器
208   - if (server != null) {
209   - server.close()
210   - .toCompletionStage()
211   - .toCompletableFuture()
212   - .join();
213   - }
214   -
215   - // 关闭 MQTT 客户端
216   - if (client != null) {
217   - client.disconnect()
218   - .toCompletionStage()
219   - .toCompletableFuture()
220   - .join();
221   - }
222   -
223   - // 关闭 Vertx 实例
224   - if (vertx!= null) {
225   - vertx.close()
226   - .toCompletionStage()
227   - .toCompletableFuture()
228   - .join();
229   - }
230   - log.info("[stop][关闭完成]");
231   - } catch (Exception e) {
232   - log.error("[stop][关闭服务异常]", e);
233   - throw new RuntimeException("关闭 IoT 设备上行服务失败", e);
234   - }
235   - }
236   -}
237 0 \ No newline at end of file
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 deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
5   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEmqxAuthReqDTO;
6   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
7   -import io.vertx.core.Handler;
8   -import io.vertx.core.json.JsonObject;
9   -import io.vertx.ext.web.RoutingContext;
10   -import lombok.RequiredArgsConstructor;
11   -import lombok.extern.slf4j.Slf4j;
12   -
13   -import java.util.Collections;
14   -
15   -/**
16   - * IoT EMQX 连接认证的 Vert.x Handler
17   - *
18   - * 参考:<a href="https://docs.emqx.com/zh/emqx/latest/access-control/authn/http.html">EMQX HTTP</a>
19   - *
20   - * 注意:该处理器需要返回特定格式:{"result": "allow"} 或 {"result": "deny"},
21   - * 以符合 EMQX 认证插件的要求,因此不使用 IotStandardResponse 实体类
22   - *
23   - * @author haohao
24   - */
25   -@RequiredArgsConstructor
26   -@Slf4j
27   -public class IotDeviceAuthVertxHandler implements Handler<RoutingContext> {
28   -
29   - public static final String PATH = "/mqtt/auth";
30   -
31   - private final IotDeviceUpstreamApi deviceUpstreamApi;
32   -
33   - @Override
34   - public void handle(RoutingContext routingContext) {
35   - try {
36   - // 构建认证请求 DTO
37   - JsonObject json = routingContext.body().asJsonObject();
38   - String clientId = json.getString("clientid");
39   - String username = json.getString("username");
40   - String password = json.getString("password");
41   - IotDeviceEmqxAuthReqDTO authReqDTO = new IotDeviceEmqxAuthReqDTO()
42   - .setClientId(clientId)
43   - .setUsername(username)
44   - .setPassword(password);
45   -
46   - // 调用认证 API
47   - CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);
48   - if (authResult.getCode() != 0 || !authResult.getData()) {
49   - // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求
50   - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
51   - return;
52   - }
53   -
54   - // 响应结果
55   - // 注意:这里必须返回 {"result": "allow"} 格式,以符合 EMQX 认证插件的要求
56   - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "allow"));
57   - } catch (Exception e) {
58   - log.error("[handle][EMQX 认证异常]", e);
59   - // 注意:这里必须返回 {"result": "deny"} 格式,以符合 EMQX 认证插件的要求
60   - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "deny"));
61   - }
62   - }
63   -
64   -}
65 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/router/IotDeviceMqttMessageHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.hutool.json.JSONObject;
5   -import cn.hutool.json.JSONUtil;
6   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
8   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
9   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
10   -import cn.iocoder.yudao.module.iot.plugin.common.pojo.IotStandardResponse;
11   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
12   -import io.netty.handler.codec.mqtt.MqttQoS;
13   -import io.vertx.core.buffer.Buffer;
14   -import io.vertx.mqtt.MqttClient;
15   -import io.vertx.mqtt.messages.MqttPublishMessage;
16   -import lombok.extern.slf4j.Slf4j;
17   -
18   -import java.time.LocalDateTime;
19   -import java.util.Arrays;
20   -import java.util.HashMap;
21   -import java.util.Map;
22   -
23   -/**
24   - * IoT 设备 MQTT 消息处理器
25   - *
26   - * 参考:<a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services">设备属性、事件、服务</a>
27   - */
28   -@Slf4j
29   -public class IotDeviceMqttMessageHandler {
30   -
31   - // TODO @haohao:讨论,感觉 mqtt 和 http,可以做个相对统一的格式哈;回复 都使用 Alink 格式,方便后续扩展。
32   - // 设备上报属性 标准 JSON
33   - // 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
34   - // 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
35   -
36   - // 设备上报事件 标准 JSON
37   - // 请求 Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post
38   - // 响应 Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply
39   -
40   - private static final String SYS_TOPIC_PREFIX = "/sys/";
41   - private static final String PROPERTY_POST_TOPIC = "/thing/event/property/post";
42   - private static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/";
43   - private static final String EVENT_POST_TOPIC_SUFFIX = "/post";
44   - private static final String REPLY_SUFFIX = "_reply";
45   - private static final String PROPERTY_METHOD = "thing.event.property.post";
46   - private static final String EVENT_METHOD_PREFIX = "thing.event.";
47   - private static final String EVENT_METHOD_SUFFIX = ".post";
48   -
49   - private final IotDeviceUpstreamApi deviceUpstreamApi;
50   - private final MqttClient mqttClient;
51   -
52   - public IotDeviceMqttMessageHandler(IotDeviceUpstreamApi deviceUpstreamApi, MqttClient mqttClient) {
53   - this.deviceUpstreamApi = deviceUpstreamApi;
54   - this.mqttClient = mqttClient;
55   - }
56   -
57   - /**
58   - * 处理MQTT消息
59   - *
60   - * @param message MQTT发布消息
61   - */
62   - public void handle(MqttPublishMessage message) {
63   - String topic = message.topicName();
64   - String payload = message.payload().toString();
65   - log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload);
66   -
67   - try {
68   - if (StrUtil.isEmpty(payload)) {
69   - log.warn("[messageHandler][消息内容为空][topic: {}]", topic);
70   - return;
71   - }
72   - handleMessage(topic, payload);
73   - } catch (Exception e) {
74   - log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e);
75   - }
76   - }
77   -
78   - /**
79   - * 根据主题类型处理消息
80   - *
81   - * @param topic 主题
82   - * @param payload 消息内容
83   - */
84   - private void handleMessage(String topic, String payload) {
85   - // 校验前缀
86   - if (!topic.startsWith(SYS_TOPIC_PREFIX)) {
87   - log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
88   - return;
89   - }
90   -
91   - // 处理设备属性上报消息
92   - if (topic.endsWith(PROPERTY_POST_TOPIC)) {
93   - log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic);
94   - handlePropertyPost(topic, payload);
95   - return;
96   - }
97   -
98   - // 处理设备事件上报消息
99   - if (topic.contains(EVENT_POST_TOPIC_PREFIX) && topic.endsWith(EVENT_POST_TOPIC_SUFFIX)) {
100   - log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic);
101   - handleEventPost(topic, payload);
102   - return;
103   - }
104   -
105   - // 未知消息类型
106   - log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
107   - }
108   -
109   - /**
110   - * 处理设备属性上报消息
111   - *
112   - * @param topic 主题
113   - * @param payload 消息内容
114   - */
115   - private void handlePropertyPost(String topic, String payload) {
116   - try {
117   - // 解析消息内容
118   - JSONObject jsonObject = JSONUtil.parseObj(payload);
119   - String[] topicParts = parseTopic(topic);
120   - if (topicParts == null) {
121   - return;
122   - }
123   -
124   - // 构建设备属性上报请求对象
125   - IotDevicePropertyReportReqDTO reportReqDTO = buildPropertyReportDTO(jsonObject, topicParts);
126   -
127   - // 调用上游 API 处理设备上报数据
128   - deviceUpstreamApi.reportDeviceProperty(reportReqDTO);
129   - log.info("[handlePropertyPost][处理设备属性上报成功][topic: {}]", topic);
130   -
131   - // 发送响应消息
132   - sendResponse(topic, jsonObject, PROPERTY_METHOD, null);
133   - } catch (Exception e) {
134   - log.error("[handlePropertyPost][处理设备属性上报失败][topic: {}][payload: {}]", topic, payload, e);
135   - }
136   - }
137   -
138   - /**
139   - * 处理设备事件上报消息
140   - *
141   - * @param topic 主题
142   - * @param payload 消息内容
143   - */
144   - private void handleEventPost(String topic, String payload) {
145   - try {
146   - // 解析消息内容
147   - JSONObject jsonObject = JSONUtil.parseObj(payload);
148   - String[] topicParts = parseTopic(topic);
149   - if (topicParts == null) {
150   - return;
151   - }
152   -
153   - // 构建设备事件上报请求对象
154   - IotDeviceEventReportReqDTO reportReqDTO = buildEventReportDTO(jsonObject, topicParts);
155   -
156   - // 调用上游 API 处理设备上报数据
157   - deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
158   - log.info("[handleEventPost][处理设备事件上报成功][topic: {}]", topic);
159   -
160   - // 从 topic 中获取事件标识符
161   - String eventIdentifier = getEventIdentifier(topicParts, topic);
162   - if (eventIdentifier == null) {
163   - return;
164   - }
165   -
166   - // 发送响应消息
167   - String method = EVENT_METHOD_PREFIX + eventIdentifier + EVENT_METHOD_SUFFIX;
168   - sendResponse(topic, jsonObject, method, null);
169   - } catch (Exception e) {
170   - log.error("[handleEventPost][处理设备事件上报失败][topic: {}][payload: {}]", topic, payload, e);
171   - }
172   - }
173   -
174   - /**
175   - * 解析主题,获取主题各部分
176   - *
177   - * @param topic 主题
178   - * @return 主题各部分数组,如果解析失败返回null
179   - */
180   - private String[] parseTopic(String topic) {
181   - String[] topicParts = topic.split("/");
182   - if (topicParts.length < 7) {
183   - log.warn("[parseTopic][主题格式不正确][topic: {}]", topic);
184   - return null;
185   - }
186   - return topicParts;
187   - }
188   -
189   - /**
190   - * 从主题部分中获取事件标识符
191   - *
192   - * @param topicParts 主题各部分
193   - * @param topic 原始主题,用于日志
194   - * @return 事件标识符,如果获取失败返回null
195   - */
196   - private String getEventIdentifier(String[] topicParts, String topic) {
197   - try {
198   - return topicParts[6];
199   - } catch (ArrayIndexOutOfBoundsException e) {
200   - log.warn("[getEventIdentifier][无法从主题中获取事件标识符][topic: {}][topicParts: {}]",
201   - topic, Arrays.toString(topicParts));
202   - return null;
203   - }
204   - }
205   -
206   - /**
207   - * 发送响应消息
208   - *
209   - * @param topic 原始主题
210   - * @param jsonObject 原始消息JSON对象
211   - * @param method 响应方法
212   - * @param customData 自定义数据,可为 null
213   - */
214   - private void sendResponse(String topic, JSONObject jsonObject, String method, Object customData) {
215   - String replyTopic = topic + REPLY_SUFFIX;
216   -
217   - // 响应结果
218   - IotStandardResponse response = IotStandardResponse.success(
219   - jsonObject.getStr("id"), method, customData);
220   - try {
221   - mqttClient.publish(replyTopic, Buffer.buffer(JsonUtils.toJsonString(response)),
222   - MqttQoS.AT_LEAST_ONCE, false, false);
223   - log.info("[sendResponse][发送响应消息成功][topic: {}]", replyTopic);
224   - } catch (Exception e) {
225   - log.error("[sendResponse][发送响应消息失败][topic: {}][response: {}]", replyTopic, response, e);
226   - }
227   - }
228   -
229   - /**
230   - * 构建设备属性上报请求对象
231   - *
232   - * @param jsonObject 消息内容
233   - * @param topicParts 主题部分
234   - * @return 设备属性上报请求对象
235   - */
236   - private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject, String[] topicParts) {
237   - IotDevicePropertyReportReqDTO reportReqDTO = new IotDevicePropertyReportReqDTO();
238   - reportReqDTO.setRequestId(jsonObject.getStr("id"));
239   - reportReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
240   - reportReqDTO.setReportTime(LocalDateTime.now());
241   - reportReqDTO.setProductKey(topicParts[2]);
242   - reportReqDTO.setDeviceName(topicParts[3]);
243   -
244   - // 只使用标准JSON格式处理属性数据
245   - JSONObject params = jsonObject.getJSONObject("params");
246   - if (params == null) {
247   - log.warn("[buildPropertyReportDTO][消息格式不正确,缺少params字段][jsonObject: {}]", jsonObject);
248   - params = new JSONObject();
249   - }
250   -
251   - // 将标准格式的params转换为平台需要的properties格式
252   - Map<String, Object> properties = new HashMap<>();
253   - for (Map.Entry<String, Object> entry : params.entrySet()) {
254   - String key = entry.getKey();
255   - Object valueObj = entry.getValue();
256   -
257   - // 如果是复杂结构(包含value和time)
258   - if (valueObj instanceof JSONObject valueJson) {
259   - properties.put(key, valueJson.getOrDefault("value", valueObj));
260   - } else {
261   - properties.put(key, valueObj);
262   - }
263   - }
264   - reportReqDTO.setProperties(properties);
265   -
266   - return reportReqDTO;
267   - }
268   -
269   - /**
270   - * 构建设备事件上报请求对象
271   - *
272   - * @param jsonObject 消息内容
273   - * @param topicParts 主题部分
274   - * @return 设备事件上报请求对象
275   - */
276   - private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) {
277   - IotDeviceEventReportReqDTO reportReqDTO = new IotDeviceEventReportReqDTO();
278   - reportReqDTO.setRequestId(jsonObject.getStr("id"));
279   - reportReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
280   - reportReqDTO.setReportTime(LocalDateTime.now());
281   - reportReqDTO.setProductKey(topicParts[2]);
282   - reportReqDTO.setDeviceName(topicParts[3]);
283   - reportReqDTO.setIdentifier(topicParts[6]);
284   -
285   - // 只使用标准JSON格式处理事件参数
286   - JSONObject params = jsonObject.getJSONObject("params");
287   - if (params == null) {
288   - log.warn("[buildEventReportDTO][消息格式不正确,缺少params字段][jsonObject: {}]", jsonObject);
289   - params = new JSONObject();
290   - }
291   - reportReqDTO.setParams(params);
292   -
293   - return reportReqDTO;
294   - }
295   -
296   -}
297 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/router/IotDeviceWebhookVertxHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
7   -import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
8   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
9   -import io.vertx.core.Handler;
10   -import io.vertx.core.json.JsonObject;
11   -import io.vertx.ext.web.RoutingContext;
12   -import lombok.RequiredArgsConstructor;
13   -import lombok.extern.slf4j.Slf4j;
14   -
15   -import java.time.LocalDateTime;
16   -import java.util.Collections;
17   -
18   -/**
19   - * IoT EMQX Webhook 事件处理的 Vert.x Handler
20   - *
21   - * 参考:<a href="https://docs.emqx.com/zh/emqx/latest/data-integration/webhook.html">EMQX Webhook</a>
22   - *
23   - * 注意:该处理器需要返回特定格式:{"result": "success"} 或 {"result": "error"},
24   - * 以符合 EMQX Webhook 插件的要求,因此不使用 IotStandardResponse 实体类。
25   - *
26   - * @author haohao
27   - */
28   -@RequiredArgsConstructor
29   -@Slf4j
30   -public class IotDeviceWebhookVertxHandler implements Handler<RoutingContext> {
31   -
32   - public static final String PATH = "/mqtt/webhook";
33   -
34   - private final IotDeviceUpstreamApi deviceUpstreamApi;
35   -
36   - @Override
37   - public void handle(RoutingContext routingContext) {
38   - try {
39   - // 解析请求体
40   - JsonObject json = routingContext.body().asJsonObject();
41   - String event = json.getString("event");
42   - String clientId = json.getString("clientid");
43   - String username = json.getString("username");
44   -
45   - // 处理不同的事件类型
46   - switch (event) {
47   - case "client.connected":
48   - handleClientConnected(clientId, username);
49   - break;
50   - case "client.disconnected":
51   - handleClientDisconnected(clientId, username);
52   - break;
53   - default:
54   - log.info("[handle][未处理的 Webhook 事件] event={}, clientId={}, username={}", event, clientId, username);
55   - break;
56   - }
57   -
58   - // 返回成功响应
59   - // 注意:这里必须返回 {"result": "success"} 格式,以符合 EMQX Webhook 插件的要求
60   - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "success"));
61   - } catch (Exception e) {
62   - log.error("[handle][处理 Webhook 事件异常]", e);
63   - // 注意:这里必须返回 {"result": "error"} 格式,以符合 EMQX Webhook 插件的要求
64   - IotPluginCommonUtils.writeJsonResponse(routingContext, Collections.singletonMap("result", "error"));
65   - }
66   - }
67   -
68   - /**
69   - * 处理客户端连接事件
70   - *
71   - * @param clientId 客户端ID
72   - * @param username 用户名
73   - */
74   - private void handleClientConnected(String clientId, String username) {
75   - // 解析产品标识和设备名称
76   - if (StrUtil.isEmpty(username) || "undefined".equals(username)) {
77   - log.warn("[handleClientConnected][客户端连接事件,但用户名为空] clientId={}", clientId);
78   - return;
79   - }
80   - String[] parts = parseUsername(username);
81   - if (parts == null) {
82   - return;
83   - }
84   -
85   - // 更新设备状态为在线
86   - IotDeviceStateUpdateReqDTO updateReqDTO = new IotDeviceStateUpdateReqDTO();
87   - updateReqDTO.setProductKey(parts[1]);
88   - updateReqDTO.setDeviceName(parts[0]);
89   - updateReqDTO.setState(IotDeviceStateEnum.ONLINE.getState());
90   - updateReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
91   - updateReqDTO.setReportTime(LocalDateTime.now());
92   - CommonResult<Boolean> result = deviceUpstreamApi.updateDeviceState(updateReqDTO);
93   - if (result.getCode() != 0 || !result.getData()) {
94   - log.error("[handleClientConnected][更新设备状态为在线失败] clientId={}, username={}, code={}, msg={}",
95   - clientId, username, result.getCode(), result.getMsg());
96   - } else {
97   - log.info("[handleClientConnected][更新设备状态为在线成功] clientId={}, username={}", clientId, username);
98   - }
99   - }
100   -
101   - /**
102   - * 处理客户端断开连接事件
103   - *
104   - * @param clientId 客户端ID
105   - * @param username 用户名
106   - */
107   - private void handleClientDisconnected(String clientId, String username) {
108   - // 解析产品标识和设备名称
109   - if (StrUtil.isEmpty(username) || "undefined".equals(username)) {
110   - log.warn("[handleClientDisconnected][客户端断开连接事件,但用户名为空] clientId={}", clientId);
111   - return;
112   - }
113   - String[] parts = parseUsername(username);
114   - if (parts == null) {
115   - return;
116   - }
117   -
118   - // 更新设备状态为离线
119   - IotDeviceStateUpdateReqDTO offlineReqDTO = new IotDeviceStateUpdateReqDTO();
120   - offlineReqDTO.setProductKey(parts[1]);
121   - offlineReqDTO.setDeviceName(parts[0]);
122   - offlineReqDTO.setState(IotDeviceStateEnum.OFFLINE.getState());
123   - offlineReqDTO.setProcessId(IotPluginCommonUtils.getProcessId());
124   - offlineReqDTO.setReportTime(LocalDateTime.now());
125   - CommonResult<Boolean> offlineResult = deviceUpstreamApi.updateDeviceState(offlineReqDTO);
126   - if (offlineResult.getCode() != 0 || !offlineResult.getData()) {
127   - log.error("[handleClientDisconnected][更新设备状态为离线失败] clientId={}, username={}, code={}, msg={}",
128   - clientId, username, offlineResult.getCode(), offlineResult.getMsg());
129   - } else {
130   - log.info("[handleClientDisconnected][更新设备状态为离线成功] clientId={}, username={}", clientId, username);
131   - }
132   - }
133   -
134   - /**
135   - * 解析用户名,格式为 deviceName&productKey
136   - *
137   - * @param username 用户名
138   - * @return 解析结果,[0] 为 deviceName,[1] 为 productKey,解析失败返回 null
139   - */
140   - private String[] parseUsername(String username) {
141   - if (StrUtil.isEmpty(username)) {
142   - return null;
143   - }
144   - String[] parts = username.split("&");
145   - if (parts.length != 2) {
146   - log.warn("[parseUsername][用户名格式({})不正确,无法解析产品标识和设备名称]", username);
147   - return null;
148   - }
149   - return parts;
150   - }
151   -
152   -}
153 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/resources/application.yml deleted
1   -spring:
2   - application:
3   - name: yudao-module-iot-plugin-emqx
4   -
5   -yudao:
6   - iot:
7   - plugin:
8   - common:
9   - upstream-url: http://127.0.0.1:48080
10   - downstream-port: 8100
11   - plugin-key: yudao-module-iot-plugin-emqx
12   - emqx:
13   - mqtt-host: 127.0.0.1
14   - mqtt-port: 1883
15   - mqtt-ssl: false
16   - mqtt-username: yudao
17   - mqtt-password: 123456
18   - mqtt-topics:
19   - - "/sys/#"
20   - auth-port: 8101
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/plugin.properties deleted
1   -plugin.id=yudao-module-iot-plugin-http
2   -plugin.class=cn.iocoder.yudao.module.iot.plugin.http.config.IotHttpVertxPlugin
3   -plugin.version=1.0.0
4   -plugin.provider=yudao
5   -plugin.dependencies=
6   -plugin.description=yudao-module-iot-plugin-http-1.0.0
7 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/pom.xml deleted
1   -<?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3   - xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
4   - http://maven.apache.org/POM/4.0.0
5   - http://maven.apache.org/xsd/maven-4.0.0.xsd">
6   - <parent>
7   - <artifactId>yudao-module-iot-plugins</artifactId>
8   - <groupId>cn.iocoder.boot</groupId>
9   - <version>${revision}</version>
10   - </parent>
11   - <modelVersion>4.0.0</modelVersion>
12   - <packaging>jar</packaging>
13   -
14   - <artifactId>yudao-module-iot-plugin-http</artifactId>
15   - <version>1.0.0</version>
16   -
17   - <name>${project.artifactId}</name>
18   - <description>
19   - <!-- TODO @芋艿:注释 -->
20   - 物联网 插件模块 - http 插件
21   - </description>
22   -
23   - <properties>
24   - <!-- 插件相关 -->
25   - <plugin.id>${project.artifactId}</plugin.id>
26   - <plugin.class>cn.iocoder.yudao.module.iot.plugin.http.config.IotHttpVertxPlugin</plugin.class>
27   - <plugin.version>${project.version}</plugin.version>
28   - <plugin.provider>yudao</plugin.provider>
29   - <plugin.description>${project.artifactId}-${project.version}</plugin.description>
30   - <plugin.dependencies/>
31   - </properties>
32   -
33   - <build>
34   - <plugins>
35   - <!-- 插件模式 zip -->
36   - <plugin>
37   - <groupId>org.apache.maven.plugins</groupId>
38   - <artifactId>maven-antrun-plugin</artifactId>
39   - <version>1.6</version>
40   - <executions>
41   - <execution>
42   - <id>unzip jar file</id>
43   - <phase>package</phase>
44   - <configuration>
45   - <target>
46   - <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}"
47   - dest="target/plugin-classes"/>
48   - </target>
49   - </configuration>
50   - <goals>
51   - <goal>run</goal>
52   - </goals>
53   - </execution>
54   - </executions>
55   - </plugin>
56   -
57   - <plugin>
58   - <artifactId>maven-assembly-plugin</artifactId>
59   - <version>2.3</version>
60   - <configuration>
61   - <descriptors>
62   - <descriptor>
63   - src/main/assembly/assembly.xml
64   - </descriptor>
65   - </descriptors>
66   - <appendAssemblyId>false</appendAssemblyId>
67   - </configuration>
68   - <executions>
69   - <execution>
70   - <id>make-assembly</id>
71   - <phase>package</phase>
72   - <goals>
73   - <goal>attached</goal>
74   - </goals>
75   - </execution>
76   - </executions>
77   - </plugin>
78   -
79   - <!-- 插件模式 jar -->
80   - <plugin>
81   - <groupId>org.apache.maven.plugins</groupId>
82   - <artifactId>maven-jar-plugin</artifactId>
83   - <version>2.4</version>
84   - <configuration>
85   - <archive>
86   - <manifestEntries>
87   - <Plugin-Id>${plugin.id}</Plugin-Id>
88   - <Plugin-Class>${plugin.class}</Plugin-Class>
89   - <Plugin-Version>${plugin.version}</Plugin-Version>
90   - <Plugin-Provider>${plugin.provider}</Plugin-Provider>
91   - <Plugin-Description>${plugin.description}</Plugin-Description>
92   - <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
93   - </manifestEntries>
94   - </archive>
95   - </configuration>
96   - </plugin>
97   - <!-- <plugin>-->
98   - <!-- <groupId>org.apache.maven.plugins</groupId>-->
99   - <!-- <artifactId>maven-shade-plugin</artifactId>-->
100   - <!-- <version>3.6.0</version>-->
101   - <!-- <executions>-->
102   - <!-- <execution>-->
103   - <!-- <phase>package</phase>-->
104   - <!-- <goals>-->
105   - <!-- <goal>shade</goal>-->
106   - <!-- </goals>-->
107   - <!-- <configuration>-->
108   - <!-- <minimizeJar>true</minimizeJar>-->
109   - <!-- </configuration>-->
110   - <!-- </execution>-->
111   - <!-- </executions>-->
112   - <!-- <configuration>-->
113   - <!-- <archive>-->
114   - <!-- <manifestEntries>-->
115   - <!-- <Plugin-Id>${plugin.id}</Plugin-Id>-->
116   - <!-- <Plugin-Class>${plugin.class}</Plugin-Class>-->
117   - <!-- <Plugin-Version>${plugin.version}</Plugin-Version>-->
118   - <!-- <Plugin-Provider>${plugin.provider}</Plugin-Provider>-->
119   - <!-- <Plugin-Description>${plugin.description}</Plugin-Description>-->
120   - <!-- <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>-->
121   - <!-- </manifestEntries>-->
122   - <!-- </archive>-->
123   - <!-- </configuration>-->
124   - <!-- </plugin>-->
125   -
126   - <!-- 独立模式 -->
127   - <plugin>
128   - <groupId>org.springframework.boot</groupId>
129   - <artifactId>spring-boot-maven-plugin</artifactId>
130   - <version>${spring.boot.version}</version>
131   - <executions>
132   - <execution>
133   - <goals>
134   - <goal>repackage</goal>
135   - </goals>
136   - <configuration>
137   - <classifier>-standalone</classifier>
138   - </configuration>
139   - </execution>
140   - </executions>
141   - </plugin>
142   -
143   - </plugins>
144   - </build>
145   -
146   - <dependencies>
147   - <dependency>
148   - <groupId>cn.iocoder.boot</groupId>
149   - <artifactId>yudao-module-iot-plugin-common</artifactId>
150   - <version>${revision}</version>
151   - </dependency>
152   -
153   - <!-- Web 相关 -->
154   - <dependency>
155   - <groupId>org.springframework.boot</groupId>
156   - <artifactId>spring-boot-starter-web</artifactId>
157   - </dependency>
158   -
159   - <!-- 工具类相关 -->
160   - <dependency>
161   - <groupId>io.vertx</groupId>
162   - <artifactId>vertx-web</artifactId>
163   - </dependency>
164   -
165   - <!-- 添加脚本引擎模块依赖 -->
166   - <dependency>
167   - <groupId>cn.iocoder.boot</groupId>
168   - <artifactId>yudao-module-iot-plugin-script</artifactId>
169   - <version>${revision}</version>
170   - </dependency>
171   - </dependencies>
172   -</project>
173 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/assembly/assembly.xml deleted
1   -<assembly>
2   - <id>plugin</id>
3   - <formats>
4   - <format>zip</format>
5   - </formats>
6   - <includeBaseDirectory>false</includeBaseDirectory>
7   - <dependencySets>
8   - <dependencySet>
9   - <useProjectArtifact>false</useProjectArtifact>
10   - <scope>runtime</scope>
11   - <outputDirectory>lib</outputDirectory>
12   - <includes>
13   - <include>*:jar:*</include>
14   - </includes>
15   - </dependencySet>
16   - </dependencySets>
17   -
18   - <fileSets>
19   - <fileSet>
20   - <directory>target/plugin-classes</directory>
21   - <outputDirectory>classes</outputDirectory>
22   - </fileSet>
23   - </fileSets>
24   -</assembly>
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/IotHttpPluginApplication.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.http;
2   -
3   -import lombok.extern.slf4j.Slf4j;
4   -import org.springframework.boot.SpringApplication;
5   -import org.springframework.boot.WebApplicationType;
6   -import org.springframework.boot.autoconfigure.SpringBootApplication;
7   -
8   -// TODO @芋艿:是不是搞成 cn.iocoder.yudao.module.iot.plugin?或者 common、script 要自动配置
9   -/**
10   - * 独立运行入口
11   - */
12   -@Slf4j
13   -@SpringBootApplication(scanBasePackages = {
14   - "cn.iocoder.yudao.module.iot.plugin.common", // common 的包
15   - "cn.iocoder.yudao.module.iot.plugin.http", // http 的包
16   - "cn.iocoder.yudao.module.iot.plugin.script" // script 的包
17   -})
18   -public class IotHttpPluginApplication {
19   -
20   - public static void main(String[] args) {
21   - SpringApplication application = new SpringApplication(IotHttpPluginApplication.class);
22   - application.setWebApplicationType(WebApplicationType.NONE);
23   - application.run(args);
24   - log.info("[main][独立模式启动完成]");
25   - }
26   -
27   -}
28 0 \ No newline at end of file
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 deleted
1   -package cn.iocoder.yudao.module.iot.plugin.http.config;
2   -
3   -import cn.hutool.core.lang.Assert;
4   -import cn.hutool.extra.spring.SpringUtil;
5   -import lombok.extern.slf4j.Slf4j;
6   -import org.pf4j.PluginWrapper;
7   -import org.pf4j.spring.SpringPlugin;
8   -import org.springframework.context.ApplicationContext;
9   -import org.springframework.context.annotation.AnnotationConfigApplicationContext;
10   -
11   -// TODO @芋艿:完善注释
12   -/**
13   - * 负责插件的启动和停止,与 Vert.x 的生命周期管理
14   - */
15   -@Slf4j
16   -public class IotHttpVertxPlugin extends SpringPlugin {
17   -
18   - public IotHttpVertxPlugin(PluginWrapper wrapper) {
19   - super(wrapper);
20   - }
21   -
22   - @Override
23   - public void start() {
24   - log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动开始...]");
25   - try {
26   - ApplicationContext pluginContext = getApplicationContext();
27   - Assert.notNull(pluginContext, "pluginContext 不能为空");
28   - log.info("[HttpVertxPlugin][HttpVertxPlugin 插件启动成功...]");
29   - } catch (Exception e) {
30   - log.error("[HttpVertxPlugin][HttpVertxPlugin 插件开启动异常...]", e);
31   - }
32   - }
33   -
34   - @Override
35   - public void stop() {
36   - log.info("[HttpVertxPlugin][HttpVertxPlugin 插件停止开始...]");
37   - try {
38   - log.info("[HttpVertxPlugin][HttpVertxPlugin 插件停止成功...]");
39   - } catch (Exception e) {
40   - log.error("[HttpVertxPlugin][HttpVertxPlugin 插件停止异常...]", e);
41   - }
42   - }
43   -
44   - // TODO @芋艿:思考下,未来要不要。。。
45   - @Override
46   - protected ApplicationContext createApplicationContext() {
47   - // 创建插件自己的 ApplicationContext
48   - AnnotationConfigApplicationContext pluginContext = new AnnotationConfigApplicationContext();
49   - // 设置父容器为主应用的 ApplicationContext (确保主应用中提供的类可用)
50   - pluginContext.setParent(SpringUtil.getApplicationContext());
51   - // 继续使用插件自己的 ClassLoader 以加载插件内部的类
52   - pluginContext.setClassLoader(getWrapper().getPluginClassLoader());
53   - // 扫描当前插件的自动配置包
54   - // TODO @芋艿:后续看看,怎么配置类包
55   - pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.http.config");
56   - pluginContext.refresh();
57   - return pluginContext;
58   - }
59   -
60   -}
61 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/IotPluginHttpAutoConfiguration.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.http.config;
2   -
3   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
4   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
5   -import cn.iocoder.yudao.module.iot.plugin.http.downstream.IotDeviceDownstreamHandlerImpl;
6   -import cn.iocoder.yudao.module.iot.plugin.http.upstream.IotDeviceUpstreamServer;
7   -import org.springframework.boot.context.properties.EnableConfigurationProperties;
8   -import org.springframework.context.ApplicationContext;
9   -import org.springframework.context.annotation.Bean;
10   -import org.springframework.context.annotation.Configuration;
11   -
12   -/**
13   - * IoT 插件 HTTP 的专用自动配置类
14   - *
15   - * @author haohao
16   - */
17   -@Configuration
18   -@EnableConfigurationProperties(IotPluginHttpProperties.class)
19   -public class IotPluginHttpAutoConfiguration {
20   -
21   - @Bean(initMethod = "start", destroyMethod = "stop")
22   - public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,
23   - IotPluginHttpProperties properties,
24   - ApplicationContext applicationContext) {
25   - return new IotDeviceUpstreamServer(properties, deviceUpstreamApi, applicationContext);
26   - }
27   -
28   - @Bean
29   - public IotDeviceDownstreamHandler deviceDownstreamHandler() {
30   - return new IotDeviceDownstreamHandlerImpl();
31   - }
32   -
33   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/config/IotPluginHttpProperties.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.http.config;
2   -
3   -import lombok.Data;
4   -import org.springframework.boot.context.properties.ConfigurationProperties;
5   -import org.springframework.validation.annotation.Validated;
6   -
7   -@ConfigurationProperties(prefix = "yudao.iot.plugin.http")
8   -@Validated
9   -@Data
10   -public class IotPluginHttpProperties {
11   -
12   - /**
13   - * HTTP 服务端口
14   - */
15   - private Integer serverPort;
16   -
17   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/downstream/IotDeviceDownstreamHandlerImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.http.downstream;
2   -
3   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
5   -import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6   -
7   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
8   -
9   -/**
10   - * HTTP 插件的 {@link IotDeviceDownstreamHandler} 实现类
11   - *
12   - * 但是:由于设备通过 HTTP 短链接接入,导致其实无法下行指导给 device 设备,所以基本都是直接返回失败!!!
13   - * 类似 MQTT、WebSocket、TCP 插件,是可以实现下行指令的。
14   - *
15   - * @author 芋道源码
16   - */
17   -public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
18   -
19   - @Override
20   - public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
21   - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持调用设备服务");
22   - }
23   -
24   - @Override
25   - public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
26   - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持获取设备属性");
27   - }
28   -
29   - @Override
30   - public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
31   - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
32   - }
33   -
34   - @Override
35   - public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
36   - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
37   - }
38   -
39   - @Override
40   - public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
41   - return CommonResult.error(NOT_IMPLEMENTED.getCode(), "HTTP 不支持设置设备属性");
42   - }
43   -
44   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/script/HttpScriptService.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.http.script;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.module.iot.plugin.script.context.PluginScriptContext;
5   -import cn.iocoder.yudao.module.iot.plugin.script.service.ScriptService;
6   -import io.vertx.core.json.JsonObject;
7   -import lombok.RequiredArgsConstructor;
8   -import lombok.extern.slf4j.Slf4j;
9   -import org.springframework.stereotype.Service;
10   -
11   -import java.util.HashMap;
12   -import java.util.Map;
13   -import java.util.concurrent.ConcurrentHashMap;
14   -
15   -/**
16   - * HTTP 协议脚本处理服务
17   - * 用于管理和执行设备数据解析脚本
18   - *
19   - * @author haohao
20   - */
21   -@Service
22   -@RequiredArgsConstructor
23   -@Slf4j
24   -public class HttpScriptService {
25   -
26   - private final ScriptService scriptService;
27   -
28   - // TODO @haohao:后续可以考虑放到 guava 缓存
29   - // TODO @haohao:可能要抽一个 script factory 之类的?方便多个 emqx、http 之类复用?
30   - /**
31   - * 脚本缓存,按产品 Key 缓存脚本内容
32   - */
33   - private final Map<String, String> scriptCache = new ConcurrentHashMap<>();
34   -
35   - /**
36   - * 解析设备属性数据
37   - *
38   - * @param productKey 产品Key
39   - * @param deviceName 设备名称
40   - * @param payload 设备上报的原始数据
41   - * @return 解析后的属性数据
42   - */
43   - @SuppressWarnings("unchecked")
44   - public Map<String, Object> parsePropertyData(String productKey, String deviceName, JsonObject payload) {
45   - // 如果没有脚本,直接返回原始数据
46   - String script = getScriptByProductKey(productKey);
47   - if (StrUtil.isBlank(script)) {
48   - if (payload != null && payload.containsKey("params")) {
49   - return payload.getJsonObject("params").getMap();
50   - }
51   - return new HashMap<>();
52   - }
53   -
54   - try {
55   - // 创建脚本上下文
56   - PluginScriptContext context = new PluginScriptContext();
57   - context.withDeviceContext(productKey + ":" + deviceName, null);
58   - context.withParameter("payload", payload.toString());
59   - context.withParameter("method", "property");
60   -
61   - // 执行脚本
62   - Object result = scriptService.executeJavaScript(script, context);
63   - log.debug("[parsePropertyData][产品:{} 设备:{} 原始数据:{} 解析结果:{}]",
64   - productKey, deviceName, payload, result);
65   -
66   - // 处理结果
67   - if (result instanceof Map) {
68   - return (Map<String, Object>) result;
69   - } else if (result instanceof String) {
70   - try {
71   - return new JsonObject((String) result).getMap();
72   - } catch (Exception e) {
73   - log.warn("[parsePropertyData][脚本返回的字符串不是有效的JSON] result:{}", result);
74   - }
75   - }
76   - } catch (Exception e) {
77   - log.error("[parsePropertyData][执行脚本解析属性数据异常] productKey:{} deviceName:{}",
78   - productKey, deviceName, e);
79   - }
80   -
81   - // TODO @芋艿:解析失败,是不是不能返回空?!
82   - // 解析失败,返回空数据
83   - return new HashMap<>();
84   - }
85   -
86   - /**
87   - * 解析设备事件数据
88   - *
89   - * @param productKey 产品Key
90   - * @param deviceName 设备名称
91   - * @param identifier 事件标识符
92   - * @param payload 设备上报的原始数据
93   - * @return 解析后的事件数据
94   - */
95   - @SuppressWarnings("unchecked")
96   - public Map<String, Object> parseEventData(String productKey, String deviceName, String identifier,
97   - JsonObject payload) {
98   - // 如果没有脚本,直接返回原始数据
99   - String script = getScriptByProductKey(productKey);
100   - if (StrUtil.isBlank(script)) {
101   - if (payload != null && payload.containsKey("params")) {
102   - return payload.getJsonObject("params").getMap();
103   - }
104   - return new HashMap<>();
105   - }
106   -
107   - try {
108   - // 创建脚本上下文
109   - PluginScriptContext context = new PluginScriptContext();
110   - context.withDeviceContext(productKey + ":" + deviceName, null);
111   - context.withParameter("payload", payload.toString());
112   - context.withParameter("method", "event");
113   - context.withParameter("identifier", identifier);
114   -
115   - // 执行脚本
116   - Object result = scriptService.executeJavaScript(script, context);
117   - log.debug("[parseEventData][产品:{} 设备:{} 事件:{} 原始数据:{} 解析结果:{}]",
118   - productKey, deviceName, identifier, payload, result);
119   -
120   - // 处理结果
121   - // TODO @haohao:处理结果,可以复用么?
122   - if (result instanceof Map) {
123   - return (Map<String, Object>) result;
124   - } else if (result instanceof String) {
125   - try {
126   - return new JsonObject((String) result).getMap();
127   - } catch (Exception e) {
128   - log.warn("[parseEventData][脚本返回的字符串不是有效的 JSON] result:{}", result);
129   - }
130   - }
131   - } catch (Exception e) {
132   - log.error("[parseEventData][执行脚本解析事件数据异常] productKey:{} deviceName:{} identifier:{}",
133   - productKey, deviceName, identifier, e);
134   - }
135   -
136   - // TODO @芋艿:解析失败,是不是不能返回空?!
137   - // 解析失败,返回空数据
138   - return new HashMap<>();
139   - }
140   -
141   - /**
142   - * 根据产品Key获取脚本
143   - *
144   - * @param productKey 产品Key
145   - * @return 脚本内容
146   - */
147   - private String getScriptByProductKey(String productKey) {
148   - // 从缓存中获取脚本
149   - String script = scriptCache.get(productKey);
150   - if (script != null) {
151   - return script;
152   - }
153   -
154   - // TODO: 实际应用中,这里应从数据库或配置中心获取产品对应的脚本
155   - // 此处仅为示例,提供一个默认脚本
156   - if ("example_product".equals(productKey)) {
157   - script = "/**\n" +
158   - " * 设备数据解析脚本示例\n" +
159   - " * @param payload 设备上报的原始数据\n" +
160   - " * @param method 方法类型:property(属性)或event(事件)\n" +
161   - " * @param identifier 事件标识符(仅当method为event时有值)\n" +
162   - " * @return 解析后的数据\n" +
163   - " */\n" +
164   - "function parse() {\n" +
165   - " // 解析JSON数据\n" +
166   - " var data = JSON.parse(payload);\n" +
167   - " var result = {};\n" +
168   - " \n" +
169   - " // 根据方法类型处理\n" +
170   - " if (method === 'property') {\n" +
171   - " // 属性数据解析\n" +
172   - " if (data.params) {\n" +
173   - " // 直接返回params中的数据\n" +
174   - " return data.params;\n" +
175   - " }\n" +
176   - " } else if (method === 'event') {\n" +
177   - " // 事件数据解析\n" +
178   - " if (data.params) {\n" +
179   - " return data.params;\n" +
180   - " }\n" +
181   - " }\n" +
182   - " \n" +
183   - " return result;\n" +
184   - "}\n" +
185   - "\n" +
186   - "// 执行解析\n" +
187   - "parse();";
188   -
189   - // 缓存脚本
190   - scriptCache.put(productKey, script);
191   - }
192   -
193   - return script;
194   - }
195   -
196   - /**
197   - * 设置产品解析脚本
198   - *
199   - * @param productKey 产品 Key
200   - * @param script 脚本内容
201   - */
202   - public void setScript(String productKey, String script) {
203   - // TODO @haohao:if return 会好点哈
204   - if (StrUtil.isNotBlank(productKey) && StrUtil.isNotBlank(script)) {
205   - // 验证脚本是否有效
206   - if (scriptService.validateScript("js", script)) {
207   - scriptCache.put(productKey, script);
208   - log.info("[setScript][设置产品:{}的解析脚本成功]", productKey);
209   - } else {
210   - log.warn("[setScript][脚本验证失败,不更新缓存] productKey:{}", productKey);
211   - }
212   - }
213   - }
214   -
215   - /**
216   - * 清除产品解析脚本
217   - *
218   - * @param productKey 产品 Key
219   - */
220   - public void clearScript(String productKey) {
221   - if (StrUtil.isBlank(productKey)) {
222   - return;
223   - }
224   - scriptCache.remove(productKey);
225   - log.info("[clearScript][清除产品({})的解析脚本]", productKey);
226   - }
227   -
228   - /**
229   - * 清除所有脚本缓存
230   - */
231   - public void clearAllScripts() {
232   - scriptCache.clear();
233   - log.info("[clearAllScripts][清除所有脚本缓存]");
234   - }
235   -
236   -}
237 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/IotDeviceUpstreamServer.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.http.upstream;
2   -
3   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
4   -import cn.iocoder.yudao.module.iot.plugin.http.config.IotPluginHttpProperties;
5   -import cn.iocoder.yudao.module.iot.plugin.http.upstream.router.IotDeviceUpstreamVertxHandler;
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 lombok.extern.slf4j.Slf4j;
11   -import org.springframework.context.ApplicationContext;
12   -
13   -/**
14   - * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
15   - *
16   - * 协议:HTTP
17   - *
18   - * @author haohao
19   - */
20   -@Slf4j
21   -public class IotDeviceUpstreamServer {
22   -
23   - private final Vertx vertx;
24   - private final HttpServer server;
25   - private final IotPluginHttpProperties properties;
26   -
27   - public IotDeviceUpstreamServer(IotPluginHttpProperties properties,
28   - IotDeviceUpstreamApi deviceUpstreamApi,
29   - ApplicationContext applicationContext) {
30   - this.properties = properties;
31   - // 创建 Vertx 实例
32   - this.vertx = Vertx.vertx();
33   - // 创建 Router 实例
34   - Router router = Router.router(vertx);
35   - router.route().handler(BodyHandler.create()); // 处理 Body
36   -
37   - // 使用统一的 Handler 处理所有上行请求
38   - IotDeviceUpstreamVertxHandler upstreamHandler = new IotDeviceUpstreamVertxHandler(deviceUpstreamApi, applicationContext);
39   - router.post(IotDeviceUpstreamVertxHandler.PROPERTY_PATH).handler(upstreamHandler);
40   - router.post(IotDeviceUpstreamVertxHandler.EVENT_PATH).handler(upstreamHandler);
41   -
42   - // 创建 HttpServer 实例
43   - this.server = vertx.createHttpServer().requestHandler(router);
44   - }
45   -
46   - /**
47   - * 启动 HTTP 服务器
48   - */
49   - public void start() {
50   - log.info("[start][开始启动]");
51   - server.listen(properties.getServerPort())
52   - .toCompletionStage()
53   - .toCompletableFuture()
54   - .join();
55   - log.info("[start][启动完成,端口({})]", this.server.actualPort());
56   - }
57   -
58   - /**
59   - * 停止所有
60   - */
61   - public void stop() {
62   - log.info("[stop][开始关闭]");
63   - try {
64   - // 关闭 HTTP 服务器
65   - if (server != null) {
66   - server.close()
67   - .toCompletionStage()
68   - .toCompletableFuture()
69   - .join();
70   - }
71   -
72   - // 关闭 Vertx 实例
73   - if (vertx != null) {
74   - vertx.close()
75   - .toCompletionStage()
76   - .toCompletableFuture()
77   - .join();
78   - }
79   - log.info("[stop][关闭完成]");
80   - } catch (Exception e) {
81   - log.error("[stop][关闭异常]", e);
82   - throw new RuntimeException(e);
83   - }
84   - }
85   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDeviceUpstreamVertxHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.http.upstream.router;
2   -
3   -import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.IdUtil;
5   -import cn.hutool.core.util.ObjUtil;
6   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7   -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
8   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
9   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
10   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
11   -import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
12   -import cn.iocoder.yudao.module.iot.plugin.common.pojo.IotStandardResponse;
13   -import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
14   -import cn.iocoder.yudao.module.iot.plugin.http.script.HttpScriptService;
15   -import io.vertx.core.Handler;
16   -import io.vertx.core.json.JsonObject;
17   -import io.vertx.ext.web.RoutingContext;
18   -import lombok.extern.slf4j.Slf4j;
19   -import org.springframework.context.ApplicationContext;
20   -
21   -import java.time.LocalDateTime;
22   -import java.util.HashMap;
23   -import java.util.Map;
24   -
25   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
26   -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
27   -
28   -/**
29   - * IoT 设备上行统一处理的 Vert.x Handler
30   - * <p>
31   - * 统一处理设备属性上报和事件上报的请求
32   - *
33   - * @author haohao
34   - */
35   -@Slf4j
36   -public class IotDeviceUpstreamVertxHandler implements Handler<RoutingContext> {
37   -
38   - /**
39   - * 属性上报路径
40   - */
41   - public static final String PROPERTY_PATH = "/sys/:productKey/:deviceName/thing/event/property/post";
42   - /**
43   - * 事件上报路径
44   - */
45   - public static final String EVENT_PATH = "/sys/:productKey/:deviceName/thing/event/:identifier/post";
46   -
47   - private static final String PROPERTY_METHOD = "thing.event.property.post";
48   - private static final String EVENT_METHOD_PREFIX = "thing.event.";
49   - private static final String EVENT_METHOD_SUFFIX = ".post";
50   -
51   - private final IotDeviceUpstreamApi deviceUpstreamApi;
52   - private final HttpScriptService scriptService;
53   -
54   - public IotDeviceUpstreamVertxHandler(IotDeviceUpstreamApi deviceUpstreamApi,
55   - ApplicationContext applicationContext) {
56   - this.deviceUpstreamApi = deviceUpstreamApi;
57   - this.scriptService = applicationContext.getBean(HttpScriptService.class);
58   - }
59   -
60   - @Override
61   - public void handle(RoutingContext routingContext) {
62   - String path = routingContext.request().path();
63   - String requestId = IdUtil.fastSimpleUUID();
64   -
65   - try {
66   - // 1. 解析通用参数
67   - String productKey = routingContext.pathParam("productKey");
68   - String deviceName = routingContext.pathParam("deviceName");
69   - JsonObject body = routingContext.body().asJsonObject();
70   - requestId = ObjUtil.defaultIfBlank(body.getString("id"), requestId);
71   -
72   - // 2. 根据路径模式处理不同类型的请求
73   - CommonResult<Boolean> result;
74   - String method;
75   - if (path.matches(".*/thing/event/property/post")) {
76   - // 处理属性上报
77   - IotDevicePropertyReportReqDTO reportReqDTO = parsePropertyReportRequest(productKey, deviceName,
78   - requestId, body);
79   -
80   - // 设备上线
81   - updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
82   -
83   - // 属性上报
84   - result = deviceUpstreamApi.reportDeviceProperty(reportReqDTO);
85   - method = PROPERTY_METHOD;
86   - } else if (path.matches(".*/thing/event/.+/post")) {
87   - // 处理事件上报
88   - String identifier = routingContext.pathParam("identifier");
89   - IotDeviceEventReportReqDTO reportReqDTO = parseEventReportRequest(productKey, deviceName, identifier,
90   - requestId, body);
91   -
92   - // 设备上线
93   - updateDeviceState(reportReqDTO.getProductKey(), reportReqDTO.getDeviceName());
94   -
95   - // 事件上报
96   - result = deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
97   - method = EVENT_METHOD_PREFIX + identifier + EVENT_METHOD_SUFFIX;
98   - } else {
99   - // 不支持的请求路径
100   - IotStandardResponse errorResponse = IotStandardResponse.error(requestId, "unknown",
101   - BAD_REQUEST.getCode(), "不支持的请求路径");
102   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
103   - return;
104   - }
105   -
106   - // 3. 返回标准响应
107   - IotStandardResponse response;
108   - if (result.isSuccess()) {
109   - response = IotStandardResponse.success(requestId, method, result.getData());
110   - } else {
111   - response = IotStandardResponse.error(requestId, method, result.getCode(), result.getMsg());
112   - }
113   - IotPluginCommonUtils.writeJsonResponse(routingContext, response);
114   - } catch (Exception e) {
115   - log.error("[handle][处理上行请求异常] path={}", path, e);
116   - String method = path.contains("/property/") ? PROPERTY_METHOD
117   - : EVENT_METHOD_PREFIX + (routingContext.pathParams().containsKey("identifier")
118   - ? routingContext.pathParam("identifier")
119   - : "unknown") + EVENT_METHOD_SUFFIX;
120   - IotStandardResponse errorResponse = IotStandardResponse.error(requestId, method,
121   - INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
122   - IotPluginCommonUtils.writeJsonResponse(routingContext, errorResponse);
123   - }
124   - }
125   -
126   - /**
127   - * 更新设备状态
128   - *
129   - * @param productKey 产品 Key
130   - * @param deviceName 设备名称
131   - */
132   - private void updateDeviceState(String productKey, String deviceName) {
133   - deviceUpstreamApi.updateDeviceState(((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO()
134   - .setRequestId(IdUtil.fastSimpleUUID()).setProcessId(IotPluginCommonUtils.getProcessId())
135   - .setReportTime(LocalDateTime.now())
136   - .setProductKey(productKey).setDeviceName(deviceName)).setState(IotDeviceStateEnum.ONLINE.getState()));
137   - }
138   -
139   - /**
140   - * 解析属性上报请求
141   - *
142   - * @param productKey 产品 Key
143   - * @param deviceName 设备名称
144   - * @param requestId 请求 ID
145   - * @param body 请求体
146   - * @return 属性上报请求 DTO
147   - */
148   - private IotDevicePropertyReportReqDTO parsePropertyReportRequest(String productKey, String deviceName,
149   - String requestId, JsonObject body) {
150   - // 使用脚本解析数据
151   - Map<String, Object> properties = scriptService.parsePropertyData(productKey, deviceName, body);
152   -
153   - // 如果脚本解析结果为空,使用默认解析逻辑
154   - // TODO @芋艿:注释说明一下,为什么要这么处理?
155   - if (CollUtil.isNotEmpty(properties)) {
156   - properties = new HashMap<>();
157   - Map<String, Object> params = body.getJsonObject("params") != null ?
158   - body.getJsonObject("params").getMap() : null;
159   - if (params != null) {
160   - // 将标准格式的 params 转换为平台需要的 properties 格式
161   - for (Map.Entry<String, Object> entry : params.entrySet()) {
162   - String key = entry.getKey();
163   - Object valueObj = entry.getValue();
164   - // 如果是复杂结构(包含 value 和 time)
165   - if (valueObj instanceof Map) {
166   - @SuppressWarnings("unchecked")
167   - Map<String, Object> valueMap = (Map<String, Object>) valueObj;
168   - properties.put(key, valueMap.getOrDefault("value", valueObj));
169   - } else {
170   - properties.put(key, valueObj);
171   - }
172   - }
173   - }
174   - }
175   -
176   - // 构建属性上报请求 DTO
177   - return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO().setRequestId(requestId)
178   - .setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
179   - .setProductKey(productKey).setDeviceName(deviceName)).setProperties(properties);
180   - }
181   -
182   - /**
183   - * 解析事件上报请求
184   - *
185   - * @param productKey 产品K ey
186   - * @param deviceName 设备名称
187   - * @param identifier 事件标识符
188   - * @param requestId 请求 ID
189   - * @param body 请求体
190   - * @return 事件上报请求 DTO
191   - */
192   - private IotDeviceEventReportReqDTO parseEventReportRequest(String productKey, String deviceName, String identifier,
193   - String requestId, JsonObject body) {
194   - // 使用脚本解析事件数据
195   - Map<String, Object> params = scriptService.parseEventData(productKey, deviceName, identifier, body);
196   -
197   - // 如果脚本解析结果为空,使用默认解析逻辑
198   - if (CollUtil.isNotEmpty(params)) {
199   - if (body.containsKey("params")) {
200   - params = body.getJsonObject("params").getMap();
201   - } else {
202   - // 兼容旧格式
203   - params = new HashMap<>();
204   - }
205   - }
206   -
207   - // 构建事件上报请求 DTO
208   - return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO().setRequestId(requestId)
209   - .setProcessId(IotPluginCommonUtils.getProcessId()).setReportTime(LocalDateTime.now())
210   - .setProductKey(productKey).setDeviceName(deviceName)).setIdentifier(identifier).setParams(params);
211   - }
212   -}
213 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml deleted
1   -spring:
2   - application:
3   - name: yudao-module-iot-plugin-http
4   -
5   -yudao:
6   - iot:
7   - plugin:
8   - common:
9   - upstream-url: http://127.0.0.1:48080
10   - downstream-port: 8093
11   - plugin-key: yudao-module-iot-plugin-http
12   - http:
13   - server-port: 8092
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-mqtt/plugin.properties deleted
1   -plugin.id=mqtt-plugin
2   -plugin.description=Vert.x MQTT plugin
3   -plugin.class=cn.iocoder.yudao.module.iot.plugin.MqttPlugin
4   -plugin.version=1.0.0
5   -plugin.requires=
6   -plugin.provider=ahh
7   -plugin.license=Apache-2.0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-mqtt/pom.xml deleted
1   -<?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3   - xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
4   - http://maven.apache.org/POM/4.0.0
5   - http://maven.apache.org/xsd/maven-4.0.0.xsd">
6   - <parent>
7   - <artifactId>yudao-module-iot-plugins</artifactId>
8   - <groupId>cn.iocoder.boot</groupId>
9   - <version>${revision}</version>
10   - </parent>
11   - <modelVersion>4.0.0</modelVersion>
12   - <packaging>jar</packaging>
13   -
14   - <artifactId>yudao-module-iot-plugin-mqtt</artifactId>
15   -
16   - <name>${project.artifactId}</name>
17   - <!-- TODO @芋艿:待整理 -->
18   - <description>
19   - 物联网 插件模块 - mqtt 插件
20   - </description>
21   -
22   - <properties>
23   - <!-- 插件相关 -->
24   - <plugin.id>mqtt-plugin</plugin.id>
25   - <plugin.class>cn.iocoder.yudao.module.iot.plugin.MqttPlugin</plugin.class>
26   - <plugin.version>0.0.1</plugin.version>
27   - <plugin.provider>ahh</plugin.provider>
28   - <plugin.description>mqtt-plugin-0.0.1</plugin.description>
29   - <plugin.dependencies/>
30   - </properties>
31   -
32   - <build>
33   - <plugins>
34   - <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
35   - <plugin>
36   - <groupId>org.codehaus.mojo</groupId>
37   - <artifactId>properties-maven-plugin</artifactId>
38   - <version>1.0-alpha-2</version>
39   - <executions>
40   - <execution>
41   - <phase>initialize</phase>
42   - <goals>
43   - <goal>read-project-properties</goal>
44   - </goals>
45   - <configuration>
46   - <files>
47   - <file>plugin.properties</file>
48   - </files>
49   - </configuration>
50   - </execution>
51   - </executions>
52   - </plugin>
53   - -->
54   -
55   - <plugin>
56   - <groupId>org.apache.maven.plugins</groupId>
57   - <artifactId>maven-antrun-plugin</artifactId>
58   - <version>1.6</version>
59   - <executions>
60   - <execution>
61   - <id>unzip jar file</id>
62   - <phase>package</phase>
63   - <configuration>
64   - <target>
65   - <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}"
66   - dest="target/plugin-classes"/>
67   - </target>
68   - </configuration>
69   - <goals>
70   - <goal>run</goal>
71   - </goals>
72   - </execution>
73   - </executions>
74   - </plugin>
75   -
76   - <plugin>
77   - <artifactId>maven-assembly-plugin</artifactId>
78   - <version>2.3</version>
79   - <configuration>
80   - <descriptors>
81   - <descriptor>
82   - src/main/assembly/assembly.xml
83   - </descriptor>
84   - </descriptors>
85   - <appendAssemblyId>false</appendAssemblyId>
86   - </configuration>
87   - <executions>
88   - <execution>
89   - <id>make-assembly</id>
90   - <phase>package</phase>
91   - <goals>
92   - <goal>attached</goal>
93   - </goals>
94   - </execution>
95   - </executions>
96   - </plugin>
97   -
98   - <plugin>
99   - <groupId>org.apache.maven.plugins</groupId>
100   - <artifactId>maven-jar-plugin</artifactId>
101   - <version>2.4</version>
102   - <configuration>
103   - <archive>
104   - <manifestEntries>
105   - <Plugin-Id>${plugin.id}</Plugin-Id>
106   - <Plugin-Class>${plugin.class}</Plugin-Class>
107   - <Plugin-Version>${plugin.version}</Plugin-Version>
108   - <Plugin-Provider>${plugin.provider}</Plugin-Provider>
109   - <Plugin-Description>${plugin.description}</Plugin-Description>
110   - <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
111   - </manifestEntries>
112   - </archive>
113   - </configuration>
114   - </plugin>
115   -
116   - <plugin>
117   - <artifactId>maven-deploy-plugin</artifactId>
118   - <configuration>
119   - <skip>true</skip>
120   - </configuration>
121   - </plugin>
122   - </plugins>
123   - </build>
124   -
125   - <dependencies>
126   - <!-- 其他依赖项 -->
127   - <dependency>
128   - <groupId>org.springframework.boot</groupId>
129   - <artifactId>spring-boot-starter-web</artifactId>
130   - </dependency>
131   - <!-- PF4J Spring 集成 -->
132   - <dependency>
133   - <groupId>org.pf4j</groupId>
134   - <artifactId>pf4j-spring</artifactId>
135   - <scope>provided</scope>
136   - </dependency>
137   - <!-- 项目依赖 -->
138   - <dependency>
139   - <groupId>cn.iocoder.boot</groupId>
140   - <artifactId>yudao-module-iot-api</artifactId>
141   - <version>${revision}</version>
142   - </dependency>
143   - <dependency>
144   - <groupId>org.projectlombok</groupId>
145   - <artifactId>lombok</artifactId>
146   - <version>${lombok.version}</version>
147   - <scope>provided</scope>
148   - </dependency>
149   - <!-- Vert.x MQTT -->
150   - <dependency>
151   - <groupId>io.vertx</groupId>
152   - <artifactId>vertx-mqtt</artifactId>
153   - <version>4.5.11</version>
154   - </dependency>
155   - </dependencies>
156   -</project>
157 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-mqtt/src/main/assembly/assembly.xml deleted
1   -<assembly>
2   - <id>plugin</id>
3   - <formats>
4   - <format>zip</format>
5   - </formats>
6   - <includeBaseDirectory>false</includeBaseDirectory>
7   - <dependencySets>
8   - <dependencySet>
9   - <useProjectArtifact>false</useProjectArtifact>
10   - <scope>runtime</scope>
11   - <outputDirectory>lib</outputDirectory>
12   - <includes>
13   - <include>*:jar:*</include>
14   - </includes>
15   - </dependencySet>
16   - </dependencySets>
17   - <!--
18   - <fileSets>
19   - <fileSet>
20   - <directory>target/classes</directory>
21   - <outputDirectory>classes</outputDirectory>
22   - </fileSet>
23   - </fileSets>
24   - -->
25   - <fileSets>
26   - <fileSet>
27   - <directory>target/plugin-classes</directory>
28   - <outputDirectory>classes</outputDirectory>
29   - </fileSet>
30   - </fileSets>
31   -</assembly>
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-mqtt/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin;
2   -
3   -import lombok.extern.slf4j.Slf4j;
4   -import org.pf4j.Plugin;
5   -import org.pf4j.PluginWrapper;
6   -
7   -// TODO @芋艿:暂未实现
8   -@Slf4j
9   -public class MqttPlugin extends Plugin {
10   -
11   - private MqttServerExtension mqttServerExtension;
12   -
13   - public MqttPlugin(PluginWrapper wrapper) {
14   - super(wrapper);
15   - }
16   -
17   - @Override
18   - public void start() {
19   - log.info("MQTT Plugin started.");
20   - mqttServerExtension = new MqttServerExtension();
21   - mqttServerExtension.startMqttServer();
22   - }
23   -
24   - @Override
25   - public void stop() {
26   - log.info("MQTT Plugin stopped.");
27   - if (mqttServerExtension != null) {
28   - mqttServerExtension.stopMqttServer().onComplete(ar -> {
29   - if (ar.succeeded()) {
30   - log.info("Stopped MQTT Server successfully");
31   - } else {
32   - log.error("Failed to stop MQTT Server: {}", ar.cause().getMessage());
33   - }
34   - });
35   - }
36   - }
37   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-mqtt/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttServerExtension.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin;
2   -
3   -import io.netty.handler.codec.mqtt.MqttProperties;
4   -import io.netty.handler.codec.mqtt.MqttQoS;
5   -import io.vertx.core.Future;
6   -import io.vertx.core.Vertx;
7   -import io.vertx.core.buffer.Buffer;
8   -import io.vertx.mqtt.MqttEndpoint;
9   -import io.vertx.mqtt.MqttServer;
10   -import io.vertx.mqtt.MqttServerOptions;
11   -import io.vertx.mqtt.MqttTopicSubscription;
12   -import io.vertx.mqtt.messages.MqttDisconnectMessage;
13   -import io.vertx.mqtt.messages.MqttPublishMessage;
14   -import io.vertx.mqtt.messages.MqttSubscribeMessage;
15   -import io.vertx.mqtt.messages.MqttUnsubscribeMessage;
16   -import io.vertx.mqtt.messages.codes.MqttSubAckReasonCode;
17   -import lombok.extern.slf4j.Slf4j;
18   -import org.pf4j.Extension;
19   -
20   -import java.nio.charset.Charset;
21   -import java.util.ArrayList;
22   -import java.util.List;
23   -
24   -// TODO @芋艿:暂未实现
25   -/**
26   - * 根据官方示例,整合常见 MQTT 功能到 PF4J 的 Extension 类中
27   - */
28   -@Slf4j
29   -@Extension
30   -public class MqttServerExtension {
31   -
32   - private Vertx vertx;
33   - private MqttServer mqttServer;
34   -
35   - /**
36   - * 启动 MQTT 服务端
37   - * 可根据需要决定是否启用 SSL/TLS、WebSocket、多实例部署等
38   - */
39   - public void startMqttServer() {
40   - // 初始化 Vert.x
41   - vertx = Vertx.vertx();
42   -
43   - // ========== 如果需要 SSL/TLS,请参考下面注释,启用注释并替换端口、证书路径等 ==========
44   - // MqttServerOptions options = new MqttServerOptions()
45   - // .setPort(8883)
46   - // .setKeyCertOptions(new PemKeyCertOptions()
47   - // .setKeyPath("./src/test/resources/tls/server-key.pem")
48   - // .setCertPath("./src/test/resources/tls/server-cert.pem"))
49   - // .setSsl(true);
50   -
51   - // ========== 如果需要 WebSocket,请设置 setUseWebSocket(true) ==========
52   - // options.setUseWebSocket(true);
53   -
54   - // ========== 默认不启用 SSL 的示例 ==========
55   - MqttServerOptions options = new MqttServerOptions()
56   - .setPort(1883)
57   - .setHost("0.0.0.0")
58   - .setUseWebSocket(false); // 如果需要 WebSocket,请改为 true
59   -
60   - mqttServer = MqttServer.create(vertx, options);
61   -
62   - // 指定 endpointHandler,处理客户端连接等
63   - mqttServer.endpointHandler(endpoint -> {
64   - handleClientConnect(endpoint);
65   - handleDisconnect(endpoint);
66   - handleSubscribe(endpoint);
67   - handleUnsubscribe(endpoint);
68   - handlePublish(endpoint);
69   - handlePing(endpoint);
70   - });
71   -
72   - // 启动监听
73   - mqttServer.listen(ar -> {
74   - if (ar.succeeded()) {
75   - log.info("MQTT server is listening on port {}", mqttServer.actualPort());
76   - } else {
77   - log.error("Error on starting the server", ar.cause());
78   - }
79   - });
80   - }
81   -
82   - /**
83   - * 优雅关闭 MQTT 服务端
84   - */
85   - public Future<Void> stopMqttServer() {
86   - if (mqttServer != null) {
87   - return mqttServer.close().onComplete(ar -> {
88   - if (ar.succeeded()) {
89   - log.info("MQTT server closed.");
90   - if (vertx != null) {
91   - vertx.close();
92   - log.info("Vert.x instance closed.");
93   - }
94   - } else {
95   - log.error("Failed to close MQTT server: {}", ar.cause().getMessage());
96   - }
97   - });
98   - }
99   - return Future.succeededFuture();
100   - }
101   -
102   - // ==================== 以下为官方示例中常见事件的处理封装 ====================
103   -
104   - /**
105   - * 处理客户端连接 (CONNECT)
106   - */
107   - private void handleClientConnect(MqttEndpoint endpoint) {
108   - // 打印 CONNECT 的主要信息
109   - log.info("MQTT client [{}] request to connect, clean session = {}",
110   - endpoint.clientIdentifier(), endpoint.isCleanSession());
111   -
112   - if (endpoint.auth() != null) {
113   - log.info("[username = {}, password = {}]", endpoint.auth().getUsername(), endpoint.auth().getPassword());
114   - }
115   - log.info("[properties = {}]", endpoint.connectProperties());
116   -
117   - if (endpoint.will() != null) {
118   - log.info("[will topic = {}, msg = {}, QoS = {}, isRetain = {}]",
119   - endpoint.will().getWillTopic(),
120   - new String(endpoint.will().getWillMessageBytes()),
121   - endpoint.will().getWillQos(),
122   - endpoint.will().isWillRetain());
123   - }
124   -
125   - log.info("[keep alive timeout = {}]", endpoint.keepAliveTimeSeconds());
126   -
127   - // 接受远程客户端的连接
128   - endpoint.accept(false);
129   - }
130   -
131   - /**
132   - * 处理客户端主动断开 (DISCONNECT)
133   - */
134   - private void handleDisconnect(MqttEndpoint endpoint) {
135   - endpoint.disconnectMessageHandler((MqttDisconnectMessage disconnectMessage) -> {
136   - log.info("Received disconnect from client [{}], reason code = {}",
137   - endpoint.clientIdentifier(), disconnectMessage.code());
138   - });
139   - }
140   -
141   - /**
142   - * 处理客户端订阅 (SUBSCRIBE)
143   - */
144   - private void handleSubscribe(MqttEndpoint endpoint) {
145   - endpoint.subscribeHandler((MqttSubscribeMessage subscribe) -> {
146   - List<MqttSubAckReasonCode> reasonCodes = new ArrayList<>();
147   - for (MqttTopicSubscription s : subscribe.topicSubscriptions()) {
148   - log.info("Subscription for {} with QoS {}", s.topicName(), s.qualityOfService());
149   - // 将客户端请求的 QoS 转换为返回给客户端的 reason code(可能是错误码或实际 granted QoS)
150   - reasonCodes.add(MqttSubAckReasonCode.qosGranted(s.qualityOfService()));
151   - }
152   - // 回复 SUBACK,MQTT 5.0 时可指定 reasonCodes、properties
153   - endpoint.subscribeAcknowledge(subscribe.messageId(), reasonCodes, MqttProperties.NO_PROPERTIES);
154   - });
155   - }
156   -
157   - /**
158   - * 处理客户端取消订阅 (UNSUBSCRIBE)
159   - */
160   - private void handleUnsubscribe(MqttEndpoint endpoint) {
161   - endpoint.unsubscribeHandler((MqttUnsubscribeMessage unsubscribe) -> {
162   - for (String topic : unsubscribe.topics()) {
163   - log.info("Unsubscription for {}", topic);
164   - }
165   - // 回复 UNSUBACK,MQTT 5.0 时可指定 reasonCodes、properties
166   - endpoint.unsubscribeAcknowledge(unsubscribe.messageId());
167   - });
168   - }
169   -
170   - /**
171   - * 处理客户端发布的消息 (PUBLISH)
172   - */
173   - private void handlePublish(MqttEndpoint endpoint) {
174   - // 接收 PUBLISH 消息
175   - endpoint.publishHandler((MqttPublishMessage message) -> {
176   - String payload = message.payload().toString(Charset.defaultCharset());
177   - log.info("Received message [{}] on topic [{}] with QoS [{}]",
178   - payload, message.topicName(), message.qosLevel());
179   -
180   - // 根据不同 QoS,回复客户端
181   - if (message.qosLevel() == MqttQoS.AT_LEAST_ONCE) {
182   - endpoint.publishAcknowledge(message.messageId());
183   - } else if (message.qosLevel() == MqttQoS.EXACTLY_ONCE) {
184   - endpoint.publishReceived(message.messageId());
185   - }
186   - });
187   -
188   - // 如果 QoS = 2,需要处理 PUBREL
189   - endpoint.publishReleaseHandler(messageId -> {
190   - endpoint.publishComplete(messageId);
191   - });
192   - }
193   -
194   - /**
195   - * 处理客户端 PINGREQ
196   - */
197   - private void handlePing(MqttEndpoint endpoint) {
198   - endpoint.pingHandler(v -> {
199   - // 这里仅做日志, PINGRESP 已自动发送
200   - log.info("Ping received from client [{}]", endpoint.clientIdentifier());
201   - });
202   - }
203   -
204   - // ==================== 如果需要服务端向客户端发布消息,可用以下示例 ====================
205   -
206   - /**
207   - * 服务端主动向已连接的某个 endpoint 发布消息的示例
208   - * 如果使用 MQTT 5.0,可以传递更多消息属性
209   - */
210   - public void publishToClient(MqttEndpoint endpoint, String topic, String content) {
211   - endpoint.publish(topic,
212   - Buffer.buffer(content),
213   - MqttQoS.AT_LEAST_ONCE, // QoS 自行选择
214   - false,
215   - false);
216   -
217   - // 处理 QoS 1 和 QoS 2 的 ACK
218   - endpoint.publishAcknowledgeHandler(messageId -> {
219   - log.info("Received PUBACK from client [{}] for messageId = {}", endpoint.clientIdentifier(), messageId);
220   - }).publishReceivedHandler(messageId -> {
221   - endpoint.publishRelease(messageId);
222   - }).publishCompletionHandler(messageId -> {
223   - log.info("Received PUBCOMP from client [{}] for messageId = {}", endpoint.clientIdentifier(), messageId);
224   - });
225   - }
226   -
227   - // ==================== 如果需要多实例部署,用于多核扩展,可参考以下思路 ====================
228   - // 例如,在宿主应用或插件中循环启动多个 MqttServerExtension 实例,或使用 Vert.x 的 deployVerticle:
229   - // DeploymentOptions options = new DeploymentOptions().setInstances(10);
230   - // vertx.deployVerticle(() -> new MyMqttVerticle(), options);
231   -
232   -}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/pom.xml deleted
1   -<?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns="http://maven.apache.org/POM/4.0.0"
3   - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5   - <parent>
6   - <artifactId>yudao-module-iot-plugins</artifactId>
7   - <groupId>cn.iocoder.boot</groupId>
8   - <version>${revision}</version>
9   - </parent>
10   - <modelVersion>4.0.0</modelVersion>
11   -
12   - <artifactId>yudao-module-iot-plugin-script</artifactId>
13   - <packaging>jar</packaging>
14   -
15   - <name>${project.artifactId}</name>
16   - <description>IoT 插件脚本模块,提供JS引擎解析等功能</description>
17   -
18   - <dependencies>
19   - <!-- 引入公共模块 -->
20   - <dependency>
21   - <groupId>cn.iocoder.boot</groupId>
22   - <artifactId>yudao-module-iot-api</artifactId>
23   - <version>${revision}</version>
24   - </dependency>
25   -
26   - <!-- Spring相关依赖 -->
27   - <dependency>
28   - <groupId>org.springframework</groupId>
29   - <artifactId>spring-context</artifactId>
30   - </dependency>
31   -
32   - <!-- 工具类相关 -->
33   - <dependency>
34   - <groupId>cn.hutool</groupId>
35   - <artifactId>hutool-all</artifactId>
36   - </dependency>
37   - <dependency>
38   - <groupId>org.projectlombok</groupId>
39   - <artifactId>lombok</artifactId>
40   - <optional>true</optional>
41   - </dependency>
42   - <dependency>
43   - <groupId>org.slf4j</groupId>
44   - <artifactId>slf4j-api</artifactId>
45   - </dependency>
46   -
47   - <!-- JavaScript 引擎 - 使用标准JSR-223实现 -->
48   - <dependency>
49   - <groupId>org.openjdk.nashorn</groupId>
50   - <artifactId>nashorn-core</artifactId>
51   - <version>15.4</version>
52   - </dependency>
53   -
54   - <!-- 测试相关 -->
55   - <dependency>
56   - <groupId>org.springframework.boot</groupId>
57   - <artifactId>spring-boot-starter-test</artifactId>
58   - <scope>test</scope>
59   - </dependency>
60   - </dependencies>
61   -</project>
62 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/ScriptExample.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script;
2   -
3   -import cn.iocoder.yudao.module.iot.plugin.script.context.PluginScriptContext;
4   -import cn.iocoder.yudao.module.iot.plugin.script.service.ScriptService;
5   -import lombok.extern.slf4j.Slf4j;
6   -import org.springframework.beans.factory.annotation.Autowired;
7   -import org.springframework.stereotype.Component;
8   -
9   -import java.util.HashMap;
10   -import java.util.Map;
11   -
12   -// TODO @haohao:写到单测类里;
13   -/**
14   - * 脚本使用示例类
15   - */
16   -@Component
17   -@Slf4j
18   -public class ScriptExample {
19   -
20   - @Autowired
21   - private ScriptService scriptService;
22   -
23   - /**
24   - * 示例:执行简单的JavaScript脚本
25   - */
26   - public void executeSimpleScript() {
27   - String script = "var result = a + b; result;";
28   -
29   - Map<String, Object> params = new HashMap<>();
30   - params.put("a", 10);
31   - params.put("b", 20);
32   -
33   - Object result = scriptService.executeJavaScript(script, params);
34   - log.info("脚本执行结果: {}", result);
35   - }
36   -
37   - /**
38   - * 示例:使用脚本处理设备数据
39   - *
40   - * @param deviceId 设备ID
41   - * @param payload 设备原始数据
42   - * @return 处理后的数据
43   - */
44   - @SuppressWarnings("unchecked")
45   - public Map<String, Object> processDeviceData(String deviceId, String payload) {
46   - // 设备数据处理脚本
47   - String script = "function process() {\n" +
48   - " var data = JSON.parse(payload);\n" +
49   - " var result = {};\n" +
50   - " // 提取温度信息\n" +
51   - " if (data.temp) {\n" +
52   - " result.temperature = data.temp;\n" +
53   - " }\n" +
54   - " // 提取湿度信息\n" +
55   - " if (data.hum) {\n" +
56   - " result.humidity = data.hum;\n" +
57   - " }\n" +
58   - " // 计算额外信息\n" +
59   - " if (data.temp && data.temp > 30) {\n" +
60   - " result.alert = true;\n" +
61   - " result.alertMessage = '温度过高警告';\n" +
62   - " }\n" +
63   - " return result;\n" +
64   - "}\n" +
65   - "process();";
66   -
67   - // 创建脚本上下文
68   - PluginScriptContext context = new PluginScriptContext();
69   - context.withDeviceContext(deviceId, null);
70   - context.withParameter("payload", payload);
71   -
72   - try {
73   - Object result = scriptService.executeJavaScript(script, context);
74   - if (result != null) {
75   - // 处理结果
76   - log.info("设备数据处理结果: {}", result);
77   -
78   - // 安全地将结果转换为Map
79   - if (result instanceof Map) {
80   - return (Map<String, Object>) result;
81   - } else {
82   - log.warn("脚本返回结果类型不是Map: {}", result.getClass().getName());
83   - }
84   - }
85   - } catch (Exception e) {
86   - log.error("处理设备数据失败: {}", e.getMessage());
87   - }
88   -
89   - return new HashMap<>();
90   - }
91   -
92   - /**
93   - * 示例:生成设备命令
94   - *
95   - * @param deviceId 设备ID
96   - * @param command 命令名称
97   - * @param params 命令参数
98   - * @return 格式化的命令字符串
99   - */
100   - public String generateDeviceCommand(String deviceId, String command, Map<String, Object> params) {
101   - // 命令生成脚本
102   - String script = "function generateCommand(cmd, params) {\n" +
103   - " var result = { 'cmd': cmd };\n" +
104   - " if (params) {\n" +
105   - " result.params = params;\n" +
106   - " }\n" +
107   - " result.timestamp = new Date().getTime();\n" +
108   - " result.deviceId = deviceId;\n" +
109   - " return JSON.stringify(result);\n" +
110   - "}\n" +
111   - "generateCommand(command, commandParams);";
112   -
113   - // 创建脚本上下文
114   - PluginScriptContext context = new PluginScriptContext();
115   - context.setParameter("deviceId", deviceId);
116   - context.setParameter("command", command);
117   - context.setParameter("commandParams", params);
118   -
119   - try {
120   - Object result = scriptService.executeJavaScript(script, context);
121   - if (result instanceof String) {
122   - return (String) result;
123   - } else if (result != null) {
124   - log.warn("脚本返回结果类型不是String: {}", result.getClass().getName());
125   - }
126   - } catch (Exception e) {
127   - log.error("生成设备命令失败: {}", e.getMessage());
128   - }
129   -
130   - return null;
131   - }
132   -}
133 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/config/ScriptConfiguration.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.config;
2   -
3   -import cn.iocoder.yudao.module.iot.plugin.script.engine.ScriptEngineFactory;
4   -import cn.iocoder.yudao.module.iot.plugin.script.service.ScriptService;
5   -import cn.iocoder.yudao.module.iot.plugin.script.service.ScriptServiceImpl;
6   -import org.springframework.context.annotation.Bean;
7   -import org.springframework.context.annotation.Configuration;
8   -
9   -// TODO @haohao:这个模块,是不是融合到 plugin-common 里哈?
10   -/**
11   - * 脚本模块配置类
12   - */
13   -@Configuration
14   -public class ScriptConfiguration {
15   -
16   - /**
17   - * 创建脚本引擎工厂
18   - *
19   - * @return 脚本引擎工厂
20   - */
21   - @Bean
22   - public ScriptEngineFactory scriptEngineFactory() {
23   - return new ScriptEngineFactory();
24   - }
25   -
26   - /**
27   - * 创建脚本服务
28   - *
29   - * @param engineFactory 脚本引擎工厂
30   - * @return 脚本服务
31   - */
32   - @Bean
33   - public ScriptService scriptService(ScriptEngineFactory engineFactory) {
34   - ScriptServiceImpl service = new ScriptServiceImpl();
35   - // TODO @haohao:如果有其他配置可以在这里设置
36   - return service;
37   - }
38   -}
39 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/context/PluginScriptContext.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.context;
2   -
3   -import lombok.Getter;
4   -
5   -import java.util.HashMap;
6   -import java.util.Map;
7   -
8   -/**
9   - * 插件脚本上下文,提供插件执行脚本的上下文环境
10   - */
11   -public class PluginScriptContext implements ScriptContext {
12   -
13   - /**
14   - * 上下文参数
15   - */
16   - @Getter
17   - private final Map<String, Object> parameters = new HashMap<>();
18   -
19   - /**
20   - * 上下文函数
21   - */
22   - @Getter
23   - private final Map<String, Object> functions = new HashMap<>();
24   -
25   - /**
26   - * 日志函数接口
27   - */
28   - public interface LogFunction {
29   -
30   - void log(String message);
31   -
32   - }
33   -
34   - /**
35   - * 构建插件脚本上下文
36   - */
37   - public PluginScriptContext() {
38   - // 初始化上下文,注册一些基础函数
39   - LogFunction logFunction = message -> System.out.println("[Plugin Script] " + message);
40   - registerFunction("log", logFunction);
41   - }
42   -
43   - /**
44   - * 构建插件脚本上下文
45   - *
46   - * @param parameters 初始参数
47   - */
48   - public PluginScriptContext(Map<String, Object> parameters) {
49   - this();
50   - if (parameters != null) {
51   - this.parameters.putAll(parameters);
52   - }
53   - }
54   -
55   - @Override
56   - public void setParameter(String key, Object value) {
57   - parameters.put(key, value);
58   - }
59   -
60   - @Override
61   - public Object getParameter(String key) {
62   - return parameters.get(key);
63   - }
64   -
65   - @Override
66   - public void registerFunction(String name, Object function) {
67   - functions.put(name, function);
68   - }
69   -
70   - // TODO @haohao:setParameters?这样的话,with 都是一些比较个性的参数
71   - /**
72   - * 批量设置参数
73   - *
74   - * @param params 参数Map
75   - * @return 当前上下文对象
76   - */
77   - public PluginScriptContext withParameters(Map<String, Object> params) {
78   - if (params != null) {
79   - parameters.putAll(params);
80   - }
81   - return this;
82   - }
83   -
84   - /**
85   - * 添加设备相关的上下文参数
86   - *
87   - * @param deviceId 设备 ID
88   - * @param deviceData 设备数据
89   - * @return 当前上下文对象
90   - */
91   - // TODO @haohao:是不是加个 (String productKey, String deviceName, Map<String, Object> deviceData) {
92   - public PluginScriptContext withDeviceContext(String deviceId, Map<String, Object> deviceData) {
93   - // TODO @haohao:deviceId 一般是分开,还是合并哈?
94   - parameters.put("deviceId", deviceId);
95   - parameters.put("deviceData", deviceData);
96   - return this;
97   - }
98   -
99   - /**
100   - * 添加消息相关的上下文参数
101   - *
102   - * @param topic 消息主题
103   - * @param payload 消息内容
104   - * @return 当前上下文对象
105   - */
106   - public PluginScriptContext withMessageContext(String topic, Object payload) {
107   - parameters.put("topic", topic);
108   - parameters.put("payload", payload);
109   - return this;
110   - }
111   -
112   - // TODO @haohao:setParameter 可以融合哈?
113   - /**
114   - * 设置单个参数
115   - *
116   - * @param key 参数名
117   - * @param value 参数值
118   - * @return 当前上下文对象
119   - */
120   - public PluginScriptContext withParameter(String key, Object value) {
121   - parameters.put(key, value);
122   - return this;
123   - }
124   -
125   -}
126 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/context/ScriptContext.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.context;
2   -
3   -import java.util.Map;
4   -
5   -/**
6   - * 脚本上下文接口,定义脚本执行所需的上下文环境
7   - */
8   -public interface ScriptContext {
9   -
10   - /**
11   - * 获取上下文参数
12   - *
13   - * @return 上下文参数
14   - */
15   - Map<String, Object> getParameters();
16   -
17   - /**
18   - * 获取上下文函数
19   - *
20   - * @return 上下文函数
21   - */
22   - Map<String, Object> getFunctions();
23   -
24   - /**
25   - * 设置上下文参数
26   - *
27   - * @param key 参数名
28   - * @param value 参数值
29   - */
30   - void setParameter(String key, Object value);
31   -
32   - /**
33   - * 获取上下文参数
34   - *
35   - * @param key 参数名
36   - * @return 参数值
37   - */
38   - Object getParameter(String key);
39   -
40   - // TODO @haohao:这个要不也是 setFunction
41   - /**
42   - * 注册函数
43   - *
44   - * @param name 函数名称
45   - * @param function 函数对象
46   - */
47   - void registerFunction(String name, Object function);
48   -
49   -}
50 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/engine/AbstractScriptEngine.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.engine;
2   -
3   -import cn.iocoder.yudao.module.iot.plugin.script.context.ScriptContext;
4   -import cn.iocoder.yudao.module.iot.plugin.script.sandbox.ScriptSandbox;
5   -
6   -import java.util.Map;
7   -
8   -/**
9   - * 抽象脚本引擎基类,定义脚本引擎的基本功能
10   - */
11   -public abstract class AbstractScriptEngine {
12   -
13   - protected ScriptSandbox sandbox;
14   -
15   - /**
16   - * 初始化脚本引擎
17   - */
18   - public abstract void init();
19   -
20   - /**
21   - * 执行脚本
22   - *
23   - * @param script 脚本内容
24   - * @param context 脚本上下文
25   - * @return 脚本执行结果
26   - */
27   - public abstract Object execute(String script, ScriptContext context);
28   -
29   - /**
30   - * 执行脚本
31   - *
32   - * @param script 脚本内容
33   - * @param params 脚本参数
34   - * @return 脚本执行结果
35   - */
36   - public abstract Object execute(String script, Map<String, Object> params);
37   -
38   - /**
39   - * 销毁脚本引擎,释放资源
40   - */
41   - public abstract void destroy();
42   -
43   - /**
44   - * 设置脚本沙箱
45   - *
46   - * @param sandbox 脚本沙箱
47   - */
48   - public void setSandbox(ScriptSandbox sandbox) {
49   - this.sandbox = sandbox;
50   - }
51   -}
52 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/engine/JsScriptEngine.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.engine;
2   -
3   -import cn.hutool.core.map.MapUtil;
4   -import cn.iocoder.yudao.module.iot.plugin.script.context.ScriptContext;
5   -import cn.iocoder.yudao.module.iot.plugin.script.sandbox.JsSandbox;
6   -import cn.iocoder.yudao.module.iot.plugin.script.util.ScriptUtils;
7   -import lombok.extern.slf4j.Slf4j;
8   -
9   -import javax.script.*;
10   -import java.util.Map;
11   -import java.util.concurrent.Callable;
12   -import java.util.concurrent.ConcurrentHashMap;
13   -
14   -/**
15   - * JavaScript 脚本引擎实现
16   - * 使用 JSR-223 Nashorn 脚本引擎
17   - */
18   -@Slf4j
19   -public class JsScriptEngine extends AbstractScriptEngine {
20   -
21   - /**
22   - * 默认脚本执行超时时间(毫秒)
23   - */
24   - private static final long DEFAULT_TIMEOUT_MS = 5000;
25   -
26   - /**
27   - * JavaScript 引擎名称
28   - */
29   - private static final String JS_ENGINE_NAME = "nashorn";
30   -
31   - /**
32   - * 脚本引擎管理器
33   - */
34   - private ScriptEngineManager engineManager;
35   -
36   - /**
37   - * 脚本引擎实例
38   - */
39   - private ScriptEngine engine;
40   -
41   - /**
42   - * 脚本缓存
43   - */
44   - private final Map<String, Object> cachedScripts = new ConcurrentHashMap<>();
45   -
46   - @Override
47   - public void init() {
48   - log.info("初始化 JavaScript 脚本引擎");
49   -
50   - // 创建脚本引擎管理器
51   - engineManager = new ScriptEngineManager();
52   -
53   - // 获取 JavaScript 引擎
54   - engine = engineManager.getEngineByName(JS_ENGINE_NAME);
55   - if (engine == null) {
56   - log.error("无法创建JavaScript引擎,尝试使用 JavaScript 名称获取");
57   - engine = engineManager.getEngineByName("JavaScript");
58   - }
59   - if (engine == null) {
60   - throw new IllegalStateException("无法创建 JavaScript 引擎,请检查环境配置");
61   - }
62   -
63   - log.info("成功创建JavaScript引擎: {}", engine.getClass().getName());
64   -
65   - // 默认使用 JS 沙箱
66   - if (sandbox == null) {
67   - setSandbox(new JsSandbox());
68   - }
69   - }
70   -
71   - @Override
72   - public Object execute(String script, ScriptContext context) {
73   - if (engine == null) {
74   - init();
75   - }
76   -
77   - // 创建可超时执行的任务
78   - Callable<Object> task = () -> {
79   - try {
80   - // 创建脚本绑定
81   - Bindings bindings = new SimpleBindings();
82   - if (context != null) {
83   - // 添加上下文参数
84   - Map<String, Object> contextParams = context.getParameters();
85   - if (MapUtil.isNotEmpty(contextParams)) {
86   - bindings.putAll(contextParams);
87   - }
88   -
89   - // 添加上下文函数
90   - bindings.putAll(context.getFunctions());
91   - }
92   -
93   - // 应用沙箱限制
94   - if (sandbox != null) {
95   - sandbox.applySandbox(engine, script);
96   - }
97   -
98   - // 执行脚本
99   - return engine.eval(script, bindings);
100   - } catch (ScriptException e) {
101   - log.error("执行 JavaScript 脚本异常: {}", e.getMessage());
102   - throw new RuntimeException("脚本执行异常: " + e.getMessage(), e);
103   - }
104   - };
105   -
106   - try {
107   - // 使用超时执行器执行脚本
108   - return ScriptUtils.executeWithTimeout(task, DEFAULT_TIMEOUT_MS);
109   - } catch (Exception e) {
110   - log.error("执行JavaScript脚本错误: {}", e.getMessage());
111   - throw new RuntimeException("脚本执行失败: " + e.getMessage(), e);
112   - }
113   - }
114   -
115   - @Override
116   - public Object execute(String script, Map<String, Object> params) {
117   - if (engine == null) {
118   - init();
119   - }
120   -
121   - // 创建可超时执行的任务
122   - Callable<Object> task = () -> {
123   - try {
124   - // 创建脚本绑定
125   - Bindings bindings = new SimpleBindings();
126   - if (MapUtil.isNotEmpty(params)) {
127   - bindings.putAll(params);
128   - }
129   -
130   - // 应用沙箱限制
131   - if (sandbox != null) {
132   - sandbox.applySandbox(engine, script);
133   - }
134   -
135   - // 执行脚本
136   - return engine.eval(script, bindings);
137   - } catch (ScriptException e) {
138   - log.error("执行 JavaScript 脚本异常: {}", e.getMessage());
139   - throw new RuntimeException("脚本执行异常: " + e.getMessage(), e);
140   - }
141   - };
142   -
143   - try {
144   - // 使用超时执行器执行脚本
145   - return ScriptUtils.executeWithTimeout(task, DEFAULT_TIMEOUT_MS);
146   - } catch (Exception e) {
147   - log.error("执行JavaScript脚本错误: {}", e.getMessage());
148   - throw new RuntimeException("脚本执行失败: " + e.getMessage(), e);
149   - }
150   - }
151   -
152   - @Override
153   - public void destroy() {
154   - log.info("销毁 JavaScript 脚本引擎");
155   - cachedScripts.clear();
156   - engine = null;
157   - engineManager = null;
158   - }
159   -
160   -}
161 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/engine/ScriptEngineFactory.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.engine;
2   -
3   -import cn.hutool.core.lang.Assert;
4   -import lombok.extern.slf4j.Slf4j;
5   -import org.springframework.stereotype.Component;
6   -
7   -/**
8   - * 脚本引擎工厂,用于创建不同类型的脚本引擎
9   - */
10   -@Component
11   -@Slf4j
12   -public class ScriptEngineFactory {
13   -
14   - /**
15   - * 创建 JavaScript 脚本引擎
16   - *
17   - * @return JavaScript脚本引擎
18   - */
19   - public JsScriptEngine createJsEngine() {
20   - log.debug("创建 JavaScript 脚本引擎");
21   - return new JsScriptEngine();
22   - }
23   -
24   - /**
25   - * 根据脚本类型创建对应的脚本引擎
26   - *
27   - * @param scriptType 脚本类型
28   - * @return 脚本引擎
29   - */
30   - public AbstractScriptEngine createEngine(String scriptType) {
31   - Assert.notBlank(scriptType, "脚本类型不能为空");
32   - switch (scriptType.toLowerCase()) {
33   - case "js":
34   - case "javascript":
35   - return createJsEngine();
36   - // 可以在这里添加其他类型的脚本引擎
37   - default:
38   - throw new IllegalArgumentException("不支持的脚本类型: " + scriptType);
39   - }
40   - }
41   -
42   -}
43 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/sandbox/JsSandbox.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.sandbox;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import lombok.extern.slf4j.Slf4j;
5   -
6   -import javax.script.ScriptEngine;
7   -import java.util.Arrays;
8   -import java.util.HashSet;
9   -import java.util.Set;
10   -import java.util.regex.Pattern;
11   -
12   -// TODO @haohao:这个是不是融合到 ScriptEngine 里
13   -/**
14   - * JavaScript 脚本沙箱,限制脚本的执行权限
15   - */
16   -@Slf4j
17   -public class JsSandbox implements ScriptSandbox {
18   -
19   - /**
20   - * 禁止使用的关键字
21   - */
22   - private static final Set<String> FORBIDDEN_KEYWORDS = new HashSet<>(Arrays.asList(
23   - "java.lang.System", "java.io", "java.nio", "java.net", "javax.net",
24   - "java.security", "java.lang.reflect", "eval(", "Function(", "setTimeout",
25   - "setInterval", "exec(", "execSync"));
26   -
27   - /**
28   - * 正则表达式匹配禁止的关键字
29   - */
30   - private static final Pattern FORBIDDEN_PATTERN = Pattern.compile(
31   - "(?:import\\s+\\{\\s*.*\\s*\\}\\s+from)|" +
32   - "(?:require\\s*\\()|" +
33   - "(?:process\\.)|" +
34   - "(?:globalThis\\.)|" +
35   - "(?:\\bfs\\.)|" +
36   - "(?:\\bchild_process\\b)|" +
37   - "(?:\\bwindow\\b)");
38   -
39   - // TODO @haohao:这个没用到哈。
40   - /**
41   - * 脚本执行超时时间(毫秒)
42   - */
43   - private static final long SCRIPT_TIMEOUT_MS = 5000;
44   -
45   - @Override
46   - public void applySandbox(Object engineContext, String script) {
47   - if (!(engineContext instanceof ScriptEngine)) {
48   - throw new IllegalArgumentException("引擎上下文类型不正确,无法应用JavaScript沙箱");
49   - }
50   - ScriptEngine engine = (ScriptEngine) engineContext;
51   -
52   - // 在 Nashorn 引擎中,可以通过以下方式设置安全限制
53   - try {
54   - // 设置严格模式
55   - String securityPrefix = "'use strict';\n";
56   -
57   - // 禁用 Java.type 等访问系统资源的功能
58   - engine.eval("var Java = undefined;");
59   - engine.eval("var JavaImporter = undefined;");
60   - engine.eval("var Packages = undefined;");
61   -
62   - // 增强安全控制可以在这里添加
63   - log.debug("已应用 JavaScript 安全沙箱限制");
64   - } catch (Exception e) {
65   - log.warn("应用 JavaScript 沙箱限制失败: {}", e.getMessage());
66   - }
67   - }
68   -
69   - @Override
70   - public boolean validateScript(String script) {
71   - if (StrUtil.isNotEmpty(script)) {
72   - return false;
73   - }
74   -
75   - // 检查禁止的关键字
76   - for (String keyword : FORBIDDEN_KEYWORDS) {
77   - if (script.contains(keyword)) {
78   - log.warn("脚本包含禁止使用的关键字: {}", keyword);
79   - return false;
80   - }
81   - }
82   -
83   - // 使用正则表达式检查更复杂的模式
84   - if (FORBIDDEN_PATTERN.matcher(script).find()) {
85   - log.warn("脚本包含禁止使用的模式");
86   - return false;
87   - }
88   -
89   - // 脚本长度限制
90   - if (script.length() > 1024 * 100) { // 限制 100 KB
91   - log.warn("脚本太大,超过了限制");
92   - return false;
93   - }
94   -
95   - return true;
96   - }
97   -
98   -}
99 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/sandbox/ScriptSandbox.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.sandbox;
2   -
3   -/**
4   - * 脚本沙箱接口,提供脚本执行的安全限制
5   - */
6   -public interface ScriptSandbox {
7   -
8   - /**
9   - * 应用沙箱限制到脚本执行环境
10   - *
11   - * @param engineContext 引擎上下文
12   - * @param script 要执行的脚本内容
13   - */
14   - void applySandbox(Object engineContext, String script);
15   -
16   - /**
17   - * 检查脚本是否符合安全规则
18   - *
19   - * @param script 要检查的脚本内容
20   - * @return 是否安全
21   - */
22   - boolean validateScript(String script);
23   -
24   -}
25 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/service/ScriptService.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.service;
2   -
3   -import cn.iocoder.yudao.module.iot.plugin.script.context.ScriptContext;
4   -
5   -import java.util.Map;
6   -
7   -/**
8   - * 脚本服务接口,定义脚本执行的核心功能
9   - */
10   -public interface ScriptService {
11   -
12   - /**
13   - * 执行脚本
14   - *
15   - * @param scriptType 脚本类型(如 js、groovy 等)
16   - * @param script 脚本内容
17   - * @param context 脚本上下文
18   - * @return 脚本执行结果
19   - */
20   - Object executeScript(String scriptType, String script, ScriptContext context);
21   -
22   - /**
23   - * 执行脚本
24   - *
25   - * @param scriptType 脚本类型(如 js、groovy 等)
26   - * @param script 脚本内容
27   - * @param params 脚本参数
28   - * @return 脚本执行结果
29   - */
30   - Object executeScript(String scriptType, String script, Map<String, Object> params);
31   -
32   - /**
33   - * 执行 JavaScript 脚本
34   - *
35   - * @param script 脚本内容
36   - * @param context 脚本上下文
37   - * @return 脚本执行结果
38   - */
39   - Object executeJavaScript(String script, ScriptContext context);
40   -
41   - /**
42   - * 执行 JavaScript 脚本
43   - *
44   - * @param script 脚本内容
45   - * @param params 脚本参数
46   - * @return 脚本执行结果
47   - */
48   - Object executeJavaScript(String script, Map<String, Object> params);
49   -
50   - /**
51   - * 验证脚本内容是否安全
52   - *
53   - * @param scriptType 脚本类型
54   - * @param script 脚本内容
55   - * @return 脚本是否安全
56   - */
57   - boolean validateScript(String scriptType, String script);
58   -
59   -}
60 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/service/ScriptServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.service;
2   -
3   -import cn.iocoder.yudao.module.iot.plugin.script.context.PluginScriptContext;
4   -import cn.iocoder.yudao.module.iot.plugin.script.context.ScriptContext;
5   -import cn.iocoder.yudao.module.iot.plugin.script.engine.AbstractScriptEngine;
6   -import cn.iocoder.yudao.module.iot.plugin.script.engine.ScriptEngineFactory;
7   -import cn.iocoder.yudao.module.iot.plugin.script.sandbox.JsSandbox;
8   -import cn.iocoder.yudao.module.iot.plugin.script.sandbox.ScriptSandbox;
9   -import lombok.extern.slf4j.Slf4j;
10   -import org.springframework.stereotype.Service;
11   -
12   -import javax.annotation.PostConstruct;
13   -import javax.annotation.PreDestroy;
14   -import javax.annotation.Resource;
15   -import java.util.Map;
16   -import java.util.concurrent.ConcurrentHashMap;
17   -
18   -/**
19   - * 脚本服务实现类
20   - */
21   -@Service
22   -@Slf4j
23   -public class ScriptServiceImpl implements ScriptService {
24   -
25   - @Resource
26   - private ScriptEngineFactory engineFactory;
27   -
28   - /**
29   - * 脚本引擎缓存,避免重复创建
30   - */
31   - private final Map<String, AbstractScriptEngine> engineCache = new ConcurrentHashMap<>();
32   -
33   - /**
34   - * 脚本沙箱缓存
35   - */
36   - private final Map<String, ScriptSandbox> sandboxCache = new ConcurrentHashMap<>();
37   -
38   - @PostConstruct
39   - public void init() {
40   - // 初始化常用的脚本引擎和沙箱
41   - // TODO @haohao:js 是不是要枚举下哈。
42   - getEngine("js");
43   - sandboxCache.put("js", new JsSandbox());
44   - }
45   -
46   - @PreDestroy
47   - public void destroy() {
48   - // 销毁所有引擎
49   - for (AbstractScriptEngine engine : engineCache.values()) {
50   - try {
51   - engine.destroy();
52   - } catch (Exception e) {
53   - // TODO @haohao:engine 类名
54   - log.error("销毁脚本引擎失败", e);
55   - }
56   - }
57   - engineCache.clear();
58   - sandboxCache.clear();
59   - }
60   -
61   - @Override
62   - public Object executeScript(String scriptType, String script, ScriptContext context) {
63   - // TODO @haohao:可以使用 hutool assert
64   - if (scriptType == null || script == null) {
65   - throw new IllegalArgumentException("脚本类型和内容不能为空");
66   - }
67   -
68   - // 获取脚本引擎
69   - AbstractScriptEngine engine = getEngine(scriptType);
70   -
71   - // 验证脚本是否安全
72   - if (!validateScript(scriptType, script)) {
73   - throw new SecurityException("脚本包含不安全的代码,无法执行");
74   - }
75   -
76   - try {
77   - // 执行脚本
78   - return engine.execute(script, context);
79   - } catch (Exception e) {
80   - // TODO @haohao:最好把 e 堆栈出来哈;然后,engine 类名
81   - log.error("执行脚本失败: {}", e.getMessage());
82   - throw new RuntimeException("执行脚本失败: " + e.getMessage(), e);
83   - }
84   - }
85   -
86   - @Override
87   - public Object executeScript(String scriptType, String script, Map<String, Object> params) {
88   - // 创建默认上下文
89   - ScriptContext context = new PluginScriptContext(params);
90   - // 执行脚本
91   - return executeScript(scriptType, script, context);
92   - }
93   -
94   - @Override
95   - public Object executeJavaScript(String script, ScriptContext context) {
96   - // TODO @haohao:枚举哈
97   - return executeScript("js", script, context);
98   - }
99   -
100   - @Override
101   - public Object executeJavaScript(String script, Map<String, Object> params) {
102   - // TODO @haohao:枚举哈
103   - return executeScript("js", script, params);
104   - }
105   -
106   - @Override
107   - public boolean validateScript(String scriptType, String script) {
108   - ScriptSandbox sandbox = sandboxCache.get(scriptType.toLowerCase());
109   - if (sandbox == null) {
110   - // TODO @haohao:疑问,为啥默认 JsSandbox 哈?
111   - log.warn("[validateScript][找不到脚本类型[{}]对应的沙箱,使用默认 JS 沙箱]", scriptType);
112   - sandbox = new JsSandbox();
113   - sandboxCache.put(scriptType.toLowerCase(), sandbox);
114   - }
115   - return sandbox.validateScript(script);
116   - }
117   -
118   - /**
119   - * 获取脚本引擎,如果不存在则创建
120   - *
121   - * @param scriptType 脚本类型
122   - * @return 脚本引擎
123   - */
124   - private AbstractScriptEngine getEngine(String scriptType) {
125   - return engineCache.computeIfAbsent(scriptType.toLowerCase(), type -> {
126   - AbstractScriptEngine engine = engineFactory.createEngine(type);
127   - engine.init();
128   - return engine;
129   - });
130   - }
131   -}
132 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/java/cn/iocoder/yudao/module/iot/plugin/script/util/ScriptUtils.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script.util;
2   -
3   -import cn.hutool.json.JSONUtil;
4   -import lombok.extern.slf4j.Slf4j;
5   -
6   -import java.util.Map;
7   -import java.util.concurrent.*;
8   -
9   -// TODO @haohao:【重要】 ScriptUtil.createGroovyEngine() 可以服用 hutool 的封装么?
10   -// TODO @haohao:【重要】 js 引擎,可能要看下 jdk8 的兼容性;
11   -// TODO @haohao:【重要】我们要不 script 配置的时候,支持 scriptType?!感觉会更通用一些???groovy、python、js
12   -/**
13   - * 脚本工具类,提供执行脚本的辅助方法
14   - */
15   -@Slf4j
16   -public class ScriptUtils {
17   -
18   - /**
19   - * 默认脚本执行超时时间(毫秒)
20   - */
21   - private static final long DEFAULT_TIMEOUT_MS = 3000;
22   -
23   - /**
24   - * 脚本执行线程池
25   - */
26   - private static final ExecutorService SCRIPT_EXECUTOR = new ThreadPoolExecutor(
27   - 2, 10, 60L, TimeUnit.SECONDS,
28   - new LinkedBlockingQueue<>(100),
29   - r -> new Thread(r, "script-executor-" + r.hashCode()),
30   - new ThreadPoolExecutor.CallerRunsPolicy());
31   -
32   - /**
33   - * 带超时的执行任务
34   - *
35   - * @param task 任务
36   - * @param timeoutMs 超时时间(毫秒)
37   - * @param <T> 返回类型
38   - * @return 任务结果
39   - * @throws RuntimeException 执行异常
40   - */
41   - public static <T> T executeWithTimeout(Callable<T> task, long timeoutMs) {
42   - Future<T> future = SCRIPT_EXECUTOR.submit(task);
43   - try {
44   - return future.get(timeoutMs, TimeUnit.MILLISECONDS);
45   - } catch (TimeoutException e) {
46   - future.cancel(true);
47   - throw new RuntimeException("脚本执行超时,已终止");
48   - } catch (InterruptedException e) {
49   - Thread.currentThread().interrupt();
50   - throw new RuntimeException("脚本执行被中断");
51   - } catch (ExecutionException e) {
52   - throw new RuntimeException("脚本执行失败: " + e.getCause().getMessage(), e.getCause());
53   - }
54   - }
55   -
56   - /**
57   - * 带默认超时的执行任务
58   - *
59   - * @param task 任务
60   - * @param <T> 返回类型
61   - * @return 任务结果
62   - * @throws RuntimeException 执行异常
63   - */
64   - public static <T> T executeWithTimeout(Callable<T> task) {
65   - return executeWithTimeout(task, DEFAULT_TIMEOUT_MS);
66   - }
67   -
68   - /**
69   - * 关闭工具类的线程池
70   - */
71   - public static void shutdown() {
72   - // TODO @芋艿:有没默认工具类,可以 shutdown
73   - SCRIPT_EXECUTOR.shutdown();
74   - try {
75   - if (!SCRIPT_EXECUTOR.awaitTermination(10, TimeUnit.SECONDS)) {
76   - SCRIPT_EXECUTOR.shutdownNow();
77   - }
78   - } catch (InterruptedException e) {
79   - SCRIPT_EXECUTOR.shutdownNow();
80   - Thread.currentThread().interrupt();
81   - }
82   - }
83   -
84   - // TODO @芋艿:要不要使用 JsonUtils
85   - /**
86   - * 将 JSON 字符串转换为 Map
87   - *
88   - * @param json JSON字符串
89   - * @return Map对象,转换失败则返回null
90   - */
91   - @SuppressWarnings("unchecked")
92   - public static Map<String, Object> parseJson(String json) {
93   - try {
94   - return JSONUtil.toBean(json, Map.class);
95   - } catch (Exception e) {
96   - // TODO @haohao:json、e 都打印出来哈
97   - log.error("[parseJson][解析JSON失败: {}]", e.getMessage());
98   - return null;
99   - }
100   - }
101   -
102   - // TODO @芋艿:要不要封装成 utils
103   - /**
104   - * 尝试将对象转换为整数
105   - *
106   - * @param obj 需要转换的对象
107   - * @return 转换后的整数,如果无法转换则返回 null
108   - */
109   - public static Integer toInteger(Object obj) {
110   - if (obj == null) {
111   - return null;
112   - }
113   -
114   - if (obj instanceof Integer) {
115   - return (Integer) obj;
116   - } else if (obj instanceof Number) {
117   - return ((Number) obj).intValue();
118   - } else if (obj instanceof String) {
119   - try {
120   - return Integer.parseInt((String) obj);
121   - } catch (NumberFormatException e) {
122   - log.debug("无法将字符串转换为整数: {}", obj);
123   - return null;
124   - }
125   - }
126   -
127   - log.debug("无法将对象转换为整数: {}", obj.getClass().getName());
128   - return null;
129   - }
130   -
131   - // TODO @芋艿:要不要封装成 utils
132   - /**
133   - * 尝试将对象转换为双精度浮点数
134   - *
135   - * @param obj 需要转换的对象
136   - * @return 转换后的双精度浮点数,如果无法转换则返回null
137   - */
138   - public static Double toDouble(Object obj) {
139   - if (obj == null) {
140   - return null;
141   - }
142   -
143   - if (obj instanceof Double) {
144   - return (Double) obj;
145   - } else if (obj instanceof Number) {
146   - return ((Number) obj).doubleValue();
147   - } else if (obj instanceof String) {
148   - try {
149   - return Double.parseDouble((String) obj);
150   - } catch (NumberFormatException e) {
151   - log.debug("无法将字符串转换为双精度浮点数: {}", obj);
152   - return null;
153   - }
154   - }
155   -
156   - log.debug("无法将对象转换为双精度浮点数: {}", obj.getClass().getName());
157   - return null;
158   - }
159   -
160   - /**
161   - * 比较两个数值是否相等,忽略其具体类型
162   - *
163   - * @param a 第一个数值
164   - * @param b 第二个数值
165   - * @return 如果两个数值相等则返回true,否则返回false
166   - */
167   - public static boolean numbersEqual(Number a, Number b) {
168   - // TODO @haohao:NumberUtil.equals(1, 1D)
169   - if (a == null || b == null) {
170   - return a == b;
171   - }
172   -
173   - return Math.abs(a.doubleValue() - b.doubleValue()) < 0.0000001;
174   - }
175   -
176   -}
177 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted
1   -cn.iocoder.yudao.module.iot.plugin.script.config.ScriptConfiguration
2 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-script/src/test/java/cn/iocoder/yudao/module/iot/plugin/script/ScriptServiceTest.java deleted
1   -package cn.iocoder.yudao.module.iot.plugin.script;
2   -
3   -import cn.iocoder.yudao.module.iot.plugin.script.context.PluginScriptContext;
4   -import cn.iocoder.yudao.module.iot.plugin.script.engine.ScriptEngineFactory;
5   -import cn.iocoder.yudao.module.iot.plugin.script.service.ScriptService;
6   -import cn.iocoder.yudao.module.iot.plugin.script.service.ScriptServiceImpl;
7   -import org.junit.jupiter.api.BeforeEach;
8   -import org.junit.jupiter.api.Test;
9   -
10   -import java.util.HashMap;
11   -import java.util.Map;
12   -
13   -import static org.junit.jupiter.api.Assertions.*;
14   -
15   -/**
16   - * 脚本服务单元测试
17   - */
18   -class ScriptServiceTest {
19   -
20   - private ScriptService scriptService;
21   -
22   - @BeforeEach
23   - void setUp() {
24   - ScriptEngineFactory engineFactory = new ScriptEngineFactory();
25   - ScriptServiceImpl service = new ScriptServiceImpl();
26   -
27   - // 使用反射设置engineFactory
28   - try {
29   - java.lang.reflect.Field field = ScriptServiceImpl.class.getDeclaredField("engineFactory");
30   - field.setAccessible(true);
31   - field.set(service, engineFactory);
32   - } catch (Exception e) {
33   - throw new RuntimeException("设置测试依赖失败", e);
34   - }
35   -
36   - service.init(); // 手动调用初始化方法
37   - this.scriptService = service;
38   - }
39   -
40   - @Test
41   - void testExecuteSimpleScript() {
42   - // 准备
43   - String script = "var result = a + b; result;";
44   - Map<String, Object> params = new HashMap<>();
45   - params.put("a", 10);
46   - params.put("b", 20);
47   -
48   - // 执行
49   - Object result = scriptService.executeJavaScript(script, params);
50   -
51   - // 验证 - 使用delta比较,允许浮点数和整数比较
52   - assertEquals(30.0, ((Number) result).doubleValue(), 0.001);
53   - }
54   -
55   - @Test
56   - void testExecuteObjectResult() {
57   - // 准备
58   - String script = "var obj = { name: 'test', value: 123 }; obj;";
59   -
60   - // 执行
61   - Object result = scriptService.executeJavaScript(script, new HashMap<>());
62   -
63   - // 验证
64   - assertNotNull(result);
65   - assertTrue(result instanceof Map);
66   -
67   - @SuppressWarnings("unchecked")
68   - Map<String, Object> map = (Map<String, Object>) result;
69   - assertEquals("test", map.get("name"));
70   -
71   - // 对于数值,先转换为double再比较
72   - assertEquals(123.0, ((Number) map.get("value")).doubleValue(), 0.001);
73   - }
74   -
75   - @Test
76   - void testExecuteWithContext() {
77   - // 准备
78   - String script = "var message = 'Hello, ' + name + '!'; message;";
79   - PluginScriptContext context = new PluginScriptContext();
80   - context.setParameter("name", "World");
81   -
82   - // 执行
83   - Object result = scriptService.executeJavaScript(script, context);
84   -
85   - // 验证
86   - assertEquals("Hello, World!", result);
87   - }
88   -
89   - @Test
90   - void testScriptWithFunction() {
91   - // 准备
92   - String script = "function add(x, y) { return x + y; } add(a, b);";
93   - Map<String, Object> params = new HashMap<>();
94   - params.put("a", 15);
95   - params.put("b", 25);
96   -
97   - // 执行
98   - Object result = scriptService.executeJavaScript(script, params);
99   -
100   - // 验证 - 使用delta比较,允许浮点数和整数比较
101   - assertEquals(40.0, ((Number) result).doubleValue(), 0.001);
102   - }
103   -
104   - @Test
105   - void testExecuteInvalidScript() {
106   - // 准备
107   - String script = "invalid syntax";
108   -
109   - // 执行和验证
110   - assertThrows(RuntimeException.class, () -> {
111   - scriptService.executeJavaScript(script, new HashMap<>());
112   - });
113   - }
114   -
115   - @Test
116   - void testScriptTimeout() {
117   - // 准备 - 一个无限循环的脚本
118   - String script = "while(true) { }";
119   -
120   - // 执行和验证
121   - assertThrows(RuntimeException.class, () -> {
122   - scriptService.executeJavaScript(script, new HashMap<>());
123   - });
124   - }
125   -}
126 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/pom.xml renamed to yudao-module-iot/yudao-module-iot-protocol/pom.xml
1 1 <?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns="http://maven.apache.org/POM/4.0.0"
3   - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  2 +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3 + xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 4 <parent>
6   - <artifactId>yudao-module-iot-plugins</artifactId>
  5 + <artifactId>yudao-module-iot</artifactId>
7 6 <groupId>cn.iocoder.boot</groupId>
8 7 <version>${revision}</version>
9 8 </parent>
10 9 <modelVersion>4.0.0</modelVersion>
11   - <artifactId>yudao-module-iot-plugin-common</artifactId>
  10 +
  11 + <artifactId>yudao-module-iot-protocol</artifactId>
12 12 <packaging>jar</packaging>
13 13  
14 14 <name>${project.artifactId}</name>
15 15 <description>
16   - <!-- TODO @芋艿:注释 -->
17   - 物联网 插件 模块 - 通用功能
  16 + 物联网协议模块,提供 topic 解析、协议转换等功能
  17 + 作为 yudao-module-iot-biz 和 yudao-module-iot-gateway-server 的共享包
18 18 </description>
19 19  
20 20 <dependencies>
  21 + <!-- 项目内部依赖 -->
21 22 <dependency>
22   - <groupId>org.springframework.boot</groupId>
23   - <artifactId>spring-boot-starter</artifactId>
  23 + <groupId>cn.iocoder.boot</groupId>
  24 + <artifactId>yudao-common</artifactId>
24 25 </dependency>
25 26  
  27 + <!-- Web 相关 -->
26 28 <dependency>
27 29 <groupId>cn.iocoder.boot</groupId>
28   - <artifactId>yudao-module-iot-api</artifactId>
29   - <version>${revision}</version>
  30 + <artifactId>yudao-spring-boot-starter-web</artifactId>
  31 + <scope>provided</scope> <!-- 设为 provided,只有 biz 需要使用 -->
30 32 </dependency>
31 33  
32   - <!-- Web 相关 -->
  34 + <!-- 工具类相关 -->
33 35 <dependency>
34   - <groupId>org.springframework</groupId>
35   - <artifactId>spring-web</artifactId>
  36 + <groupId>org.projectlombok</groupId>
  37 + <artifactId>lombok</artifactId>
36 38 </dependency>
37 39  
38   - <!-- 工具类相关 -->
39 40 <dependency>
40   - <groupId>io.vertx</groupId>
41   - <artifactId>vertx-web</artifactId>
  41 + <groupId>cn.hutool</groupId>
  42 + <artifactId>hutool-all</artifactId>
42 43 </dependency>
43 44  
44   - <!-- 参数校验 -->
  45 + <!-- IoT 相关依赖 -->
  46 + <dependency>
  47 + <groupId>io.vertx</groupId>
  48 + <artifactId>vertx-core</artifactId>
  49 + <scope>provided</scope>
  50 + </dependency>
45 51 <dependency>
46   - <groupId>org.springframework.boot</groupId>
47   - <artifactId>spring-boot-starter-validation</artifactId>
48   - <optional>true</optional>
  52 + <groupId>io.vertx</groupId>
  53 + <artifactId>vertx-web</artifactId>
  54 + <scope>provided</scope>
49 55 </dependency>
50 56 </dependencies>
51 57  
52   -</project>
  58 +</project>
53 59 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/config/IotProtocolAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.protocol.config;
  2 +
  3 +import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser;
  4 +import cn.iocoder.yudao.module.iot.protocol.message.impl.IotAlinkMessageParser;
  5 +import org.springframework.context.annotation.Bean;
  6 +import org.springframework.context.annotation.Configuration;
  7 +
  8 +/**
  9 + * IoT 协议模块自动配置类
  10 + *
  11 + * @author haohao
  12 + */
  13 +@Configuration(proxyBeanMethods = false)
  14 +public class IotProtocolAutoConfiguration {
  15 +
  16 + /**
  17 + * 注册 Alink 协议消息解析器
  18 + *
  19 + * @return Alink 协议消息解析器
  20 + */
  21 + @Bean
  22 + public IotMessageParser iotAlinkMessageParser() {
  23 + return new IotAlinkMessageParser();
  24 + }
  25 +}
0 26 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/constants/IotTopicConstants.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.protocol.constants;
  2 +
  3 +/**
  4 + * IoT 设备主题常量类
  5 + * <p>
  6 + * 用于统一管理 MQTT 协议中的主题常量,基于 Alink 协议规范
  7 + *
  8 + * @author haohao
  9 + */
  10 +public class IotTopicConstants {
  11 +
  12 + /**
  13 + * 系统主题前缀
  14 + */
  15 + public static final String SYS_TOPIC_PREFIX = "/sys/";
  16 +
  17 + /**
  18 + * 服务调用主题前缀
  19 + */
  20 + public static final String SERVICE_TOPIC_PREFIX = "/thing/service/";
  21 +
  22 + /**
  23 + * 设备属性设置主题
  24 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set
  25 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply
  26 + */
  27 + public static final String PROPERTY_SET_TOPIC = "/thing/service/property/set";
  28 +
  29 + /**
  30 + * 设备属性获取主题
  31 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/get
  32 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/get_reply
  33 + */
  34 + public static final String PROPERTY_GET_TOPIC = "/thing/service/property/get";
  35 +
  36 + /**
  37 + * 设备配置设置主题
  38 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/config/set
  39 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/config/set_reply
  40 + */
  41 + public static final String CONFIG_SET_TOPIC = "/thing/service/config/set";
  42 +
  43 + /**
  44 + * 设备OTA升级主题
  45 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade
  46 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/service/ota/upgrade_reply
  47 + */
  48 + public static final String OTA_UPGRADE_TOPIC = "/thing/service/ota/upgrade";
  49 +
  50 + /**
  51 + * 设备属性上报主题
  52 + * 请求Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
  53 + * 响应Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
  54 + */
  55 + public static final String PROPERTY_POST_TOPIC = "/thing/event/property/post";
  56 +
  57 + /**
  58 + * 设备事件上报主题前缀
  59 + */
  60 + public static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/";
  61 +
  62 + /**
  63 + * 设备事件上报主题后缀
  64 + */
  65 + public static final String EVENT_POST_TOPIC_SUFFIX = "/post";
  66 +
  67 + /**
  68 + * 响应主题后缀
  69 + */
  70 + public static final String REPLY_SUFFIX = "_reply";
  71 +
  72 +}
0 73 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotAlinkMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.protocol.message;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.json.JSONObject;
  5 +import lombok.Builder;
  6 +import lombok.Data;
  7 +
  8 +import java.util.Map;
  9 +
  10 +/**
  11 + * IoT Alink 消息模型
  12 + * <p>
  13 + * 基于阿里云 Alink 协议规范实现的标准消息格式
  14 + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/alink-protocol-1">阿里云物联网 —— Alink 协议</a>
  15 + *
  16 + * @author haohao
  17 + */
  18 +@Data
  19 +@Builder
  20 +public class IotAlinkMessage {
  21 +
  22 + /**
  23 + * 消息 ID
  24 + */
  25 + private String id;
  26 +
  27 + /**
  28 + * 协议版本
  29 + */
  30 + @Builder.Default
  31 + private String version = "1.0";
  32 +
  33 + /**
  34 + * 消息方法
  35 + */
  36 + private String method;
  37 +
  38 + /**
  39 + * 消息参数
  40 + */
  41 + private Map<String, Object> params;
  42 +
  43 + /**
  44 + * 转换为 JSONObject
  45 + *
  46 + * @return JSONObject 对象
  47 + */
  48 + public JSONObject toJsonObject() {
  49 + JSONObject json = new JSONObject();
  50 + json.set("id", id);
  51 + json.set("version", version);
  52 + json.set("method", method);
  53 + json.set("params", params != null ? params : new JSONObject());
  54 + return json;
  55 + }
  56 +
  57 + /**
  58 + * 转换为 JSON 字符串
  59 + *
  60 + * @return JSON 字符串
  61 + */
  62 + public String toJsonString() {
  63 + return toJsonObject().toString();
  64 + }
  65 +
  66 + /**
  67 + * 创建设备服务调用消息
  68 + *
  69 + * @param requestId 请求 ID,为空时自动生成
  70 + * @param serviceIdentifier 服务标识符
  71 + * @param params 服务参数
  72 + * @return Alink 消息对象
  73 + */
  74 + public static IotAlinkMessage createServiceInvokeMessage(String requestId, String serviceIdentifier,
  75 + Map<String, Object> params) {
  76 + return IotAlinkMessage.builder()
  77 + .id(requestId != null ? requestId : generateRequestId())
  78 + .method("thing.service." + serviceIdentifier)
  79 + .params(params)
  80 + .build();
  81 + }
  82 +
  83 + /**
  84 + * 创建设备属性设置消息
  85 + *
  86 + * @param requestId 请求 ID,为空时自动生成
  87 + * @param properties 设备属性
  88 + * @return Alink 消息对象
  89 + */
  90 + public static IotAlinkMessage createPropertySetMessage(String requestId, Map<String, Object> properties) {
  91 + return IotAlinkMessage.builder()
  92 + .id(requestId != null ? requestId : generateRequestId())
  93 + .method("thing.service.property.set")
  94 + .params(properties)
  95 + .build();
  96 + }
  97 +
  98 + /**
  99 + * 创建设备属性获取消息
  100 + *
  101 + * @param requestId 请求 ID,为空时自动生成
  102 + * @param identifiers 要获取的属性标识符列表
  103 + * @return Alink 消息对象
  104 + */
  105 + public static IotAlinkMessage createPropertyGetMessage(String requestId, String[] identifiers) {
  106 + JSONObject params = new JSONObject();
  107 + params.set("identifiers", identifiers);
  108 +
  109 + return IotAlinkMessage.builder()
  110 + .id(requestId != null ? requestId : generateRequestId())
  111 + .method("thing.service.property.get")
  112 + .params(params)
  113 + .build();
  114 + }
  115 +
  116 + /**
  117 + * 创建设备配置设置消息
  118 + *
  119 + * @param requestId 请求 ID,为空时自动生成
  120 + * @param configs 设备配置
  121 + * @return Alink 消息对象
  122 + */
  123 + public static IotAlinkMessage createConfigSetMessage(String requestId, Map<String, Object> configs) {
  124 + return IotAlinkMessage.builder()
  125 + .id(requestId != null ? requestId : generateRequestId())
  126 + .method("thing.service.config.set")
  127 + .params(configs)
  128 + .build();
  129 + }
  130 +
  131 + /**
  132 + * 创建设备 OTA 升级消息
  133 + *
  134 + * @param requestId 请求 ID,为空时自动生成
  135 + * @param otaInfo OTA 升级信息
  136 + * @return Alink 消息对象
  137 + */
  138 + public static IotAlinkMessage createOtaUpgradeMessage(String requestId, Map<String, Object> otaInfo) {
  139 + return IotAlinkMessage.builder()
  140 + .id(requestId != null ? requestId : generateRequestId())
  141 + .method("thing.service.ota.upgrade")
  142 + .params(otaInfo)
  143 + .build();
  144 + }
  145 +
  146 + /**
  147 + * 生成请求 ID
  148 + *
  149 + * @return 请求 ID
  150 + */
  151 + public static String generateRequestId() {
  152 + return IdUtil.fastSimpleUUID();
  153 + }
  154 +}
0 155 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotMessageParser.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.protocol.message;
  2 +
  3 +/**
  4 + * IoT 消息解析器接口
  5 + * <p>
  6 + * 用于解析不同协议的消息内容
  7 + *
  8 + * @author haohao
  9 + */
  10 +public interface IotMessageParser {
  11 +
  12 + /**
  13 + * 解析消息
  14 + *
  15 + * @param topic 主题
  16 + * @param payload 消息负载
  17 + * @return 解析后的标准消息,如果解析失败返回 null
  18 + */
  19 + IotAlinkMessage parse(String topic, byte[] payload);
  20 +
  21 + /**
  22 + * 格式化响应消息
  23 + *
  24 + * @param response 标准响应
  25 + * @return 格式化后的响应字节数组
  26 + */
  27 + byte[] formatResponse(IotStandardResponse response);
  28 +
  29 + /**
  30 + * 检查是否能够处理指定主题的消息
  31 + *
  32 + * @param topic 主题
  33 + * @return 如果能处理返回 true,否则返回 false
  34 + */
  35 + boolean canHandle(String topic);
  36 +}
0 37 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/pojo/IotStandardResponse.java renamed to yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/IotStandardResponse.java
1   -package cn.iocoder.yudao.module.iot.plugin.common.pojo;
  1 +package cn.iocoder.yudao.module.iot.protocol.message;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import lombok.Data;
  5 +import lombok.experimental.Accessors;
4 6  
5   -// TODO @芋艿:1)后续考虑,要不要叫 IoT 网关之类的 Response;2)包名 pojo
6 7 /**
7 8 * IoT 标准协议响应实体类
8 9 * <p>
... ... @@ -11,10 +12,11 @@ import lombok.Data;
11 12 * @author haohao
12 13 */
13 14 @Data
  15 +@Accessors(chain = true)
14 16 public class IotStandardResponse {
15 17  
16 18 /**
17   - * 消息ID
  19 + * 消息 ID
18 20 */
19 21 private String id;
20 22  
... ... @@ -46,7 +48,7 @@ public class IotStandardResponse {
46 48 /**
47 49 * 创建成功响应
48 50 *
49   - * @param id 消息ID
  51 + * @param id 消息 ID
50 52 * @param method 方法名
51 53 * @return 成功响应
52 54 */
... ... @@ -57,7 +59,7 @@ public class IotStandardResponse {
57 59 /**
58 60 * 创建成功响应
59 61 *
60   - * @param id 消息ID
  62 + * @param id 消息 ID
61 63 * @param method 方法名
62 64 * @param data 响应数据
63 65 * @return 成功响应
... ... @@ -75,7 +77,7 @@ public class IotStandardResponse {
75 77 /**
76 78 * 创建错误响应
77 79 *
78   - * @param id 消息ID
  80 + * @param id 消息 ID
79 81 * @param method 方法名
80 82 * @param code 错误码
81 83 * @param message 错误消息
... ... @@ -86,9 +88,8 @@ public class IotStandardResponse {
86 88 .setId(id)
87 89 .setCode(code)
88 90 .setData(null)
89   - .setMessage(message)
  91 + .setMessage(StrUtil.blankToDefault(message, "error"))
90 92 .setMethod(method)
91 93 .setVersion("1.0");
92 94 }
93   -
94   -}
95 95 \ No newline at end of file
  96 +}
96 97 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/message/impl/IotAlinkMessageParser.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.protocol.message.impl;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.json.JSONObject;
  5 +import cn.hutool.json.JSONUtil;
  6 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  7 +import cn.iocoder.yudao.module.iot.protocol.message.IotAlinkMessage;
  8 +import cn.iocoder.yudao.module.iot.protocol.message.IotMessageParser;
  9 +import cn.iocoder.yudao.module.iot.protocol.message.IotStandardResponse;
  10 +import cn.iocoder.yudao.module.iot.protocol.util.IotTopicUtils;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +import java.nio.charset.StandardCharsets;
  14 +import java.util.Map;
  15 +
  16 +/**
  17 + * IoT Alink 协议消息解析器实现
  18 + * <p>
  19 + * 基于阿里云 Alink 协议规范实现的消息解析器
  20 + *
  21 + * @author haohao
  22 + */
  23 +@Slf4j
  24 +public class IotAlinkMessageParser implements IotMessageParser {
  25 +
  26 + @Override
  27 + public IotAlinkMessage parse(String topic, byte[] payload) {
  28 + if (payload == null || payload.length == 0) {
  29 + log.warn("[Alink] 收到空消息内容, topic={}", topic);
  30 + return null;
  31 + }
  32 +
  33 + try {
  34 + String message = new String(payload, StandardCharsets.UTF_8);
  35 + if (!JSONUtil.isTypeJSON(message)) {
  36 + log.warn("[Alink] 收到非JSON格式消息, topic={}, message={}", topic, message);
  37 + return null;
  38 + }
  39 +
  40 + JSONObject json = JSONUtil.parseObj(message);
  41 + String id = json.getStr("id");
  42 + String method = json.getStr("method");
  43 +
  44 + if (StrUtil.isBlank(method)) {
  45 + // 尝试从 topic 中解析方法
  46 + method = IotTopicUtils.parseMethodFromTopic(topic);
  47 + if (StrUtil.isBlank(method)) {
  48 + log.warn("[Alink] 无法确定消息方法, topic={}, message={}", topic, message);
  49 + return null;
  50 + }
  51 + }
  52 +
  53 + Map<String, Object> params = (Map<String, Object>) json.getObj("params", Map.class);
  54 + return IotAlinkMessage.builder()
  55 + .id(id)
  56 + .method(method)
  57 + .version(json.getStr("version", "1.0"))
  58 + .params(params)
  59 + .build();
  60 + } catch (Exception e) {
  61 + log.error("[Alink] 解析消息失败, topic={}", topic, e);
  62 + return null;
  63 + }
  64 + }
  65 +
  66 + @Override
  67 + public byte[] formatResponse(IotStandardResponse response) {
  68 + try {
  69 + String json = JsonUtils.toJsonString(response);
  70 + return json.getBytes(StandardCharsets.UTF_8);
  71 + } catch (Exception e) {
  72 + log.error("[Alink] 格式化响应失败", e);
  73 + return new byte[0];
  74 + }
  75 + }
  76 +
  77 + @Override
  78 + public boolean canHandle(String topic) {
  79 + // Alink 协议处理所有系统主题
  80 + return topic != null && topic.startsWith("/sys/");
  81 + }
  82 +}
0 83 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-protocol/src/main/java/cn/iocoder/yudao/module/iot/protocol/util/IotTopicUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.protocol.util;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.module.iot.protocol.constants.IotTopicConstants;
  5 +
  6 +/**
  7 + * IoT 主题工具类
  8 + * <p>
  9 + * 用于构建和解析设备主题
  10 + *
  11 + * @author haohao
  12 + */
  13 +public class IotTopicUtils {
  14 +
  15 + /**
  16 + * 构建设备服务调用主题
  17 + *
  18 + * @param productKey 产品Key
  19 + * @param deviceName 设备名称
  20 + * @param serviceIdentifier 服务标识符
  21 + * @return 完整的主题路径
  22 + */
  23 + public static String buildServiceTopic(String productKey, String deviceName, String serviceIdentifier) {
  24 + return buildDeviceBaseTopic(productKey, deviceName) +
  25 + IotTopicConstants.SERVICE_TOPIC_PREFIX + serviceIdentifier;
  26 + }
  27 +
  28 + /**
  29 + * 构建设备属性设置主题
  30 + *
  31 + * @param productKey 产品Key
  32 + * @param deviceName 设备名称
  33 + * @return 完整的主题路径
  34 + */
  35 + public static String buildPropertySetTopic(String productKey, String deviceName) {
  36 + return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_SET_TOPIC;
  37 + }
  38 +
  39 + /**
  40 + * 构建设备属性获取主题
  41 + *
  42 + * @param productKey 产品Key
  43 + * @param deviceName 设备名称
  44 + * @return 完整的主题路径
  45 + */
  46 + public static String buildPropertyGetTopic(String productKey, String deviceName) {
  47 + return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_GET_TOPIC;
  48 + }
  49 +
  50 + /**
  51 + * 构建设备配置设置主题
  52 + *
  53 + * @param productKey 产品Key
  54 + * @param deviceName 设备名称
  55 + * @return 完整的主题路径
  56 + */
  57 + public static String buildConfigSetTopic(String productKey, String deviceName) {
  58 + return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.CONFIG_SET_TOPIC;
  59 + }
  60 +
  61 + /**
  62 + * 构建设备 OTA 升级主题
  63 + *
  64 + * @param productKey 产品Key
  65 + * @param deviceName 设备名称
  66 + * @return 完整的主题路径
  67 + */
  68 + public static String buildOtaUpgradeTopic(String productKey, String deviceName) {
  69 + return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.OTA_UPGRADE_TOPIC;
  70 + }
  71 +
  72 + /**
  73 + * 构建设备属性上报主题
  74 + *
  75 + * @param productKey 产品Key
  76 + * @param deviceName 设备名称
  77 + * @return 完整的主题路径
  78 + */
  79 + public static String buildPropertyPostTopic(String productKey, String deviceName) {
  80 + return buildDeviceBaseTopic(productKey, deviceName) + IotTopicConstants.PROPERTY_POST_TOPIC;
  81 + }
  82 +
  83 + /**
  84 + * 构建设备事件上报主题
  85 + *
  86 + * @param productKey 产品Key
  87 + * @param deviceName 设备名称
  88 + * @param eventIdentifier 事件标识符
  89 + * @return 完整的主题路径
  90 + */
  91 + public static String buildEventPostTopic(String productKey, String deviceName, String eventIdentifier) {
  92 + return buildDeviceBaseTopic(productKey, deviceName) +
  93 + IotTopicConstants.EVENT_POST_TOPIC_PREFIX + eventIdentifier + IotTopicConstants.EVENT_POST_TOPIC_SUFFIX;
  94 + }
  95 +
  96 + /**
  97 + * 获取响应主题
  98 + *
  99 + * @param requestTopic 请求主题
  100 + * @return 响应主题
  101 + */
  102 + public static String getReplyTopic(String requestTopic) {
  103 + return requestTopic + IotTopicConstants.REPLY_SUFFIX;
  104 + }
  105 +
  106 + /**
  107 + * 构建设备基础主题
  108 + * 格式: /sys/${productKey}/${deviceName}
  109 + *
  110 + * @param productKey 产品Key
  111 + * @param deviceName 设备名称
  112 + * @return 设备基础主题
  113 + */
  114 + public static String buildDeviceBaseTopic(String productKey, String deviceName) {
  115 + return IotTopicConstants.SYS_TOPIC_PREFIX + productKey + "/" + deviceName;
  116 + }
  117 +
  118 + /**
  119 + * 从主题中解析产品Key
  120 + * 格式: /sys/${productKey}/${deviceName}/...
  121 + *
  122 + * @param topic 主题
  123 + * @return 产品Key,如果无法解析则返回null
  124 + */
  125 + public static String parseProductKeyFromTopic(String topic) {
  126 + if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) {
  127 + return null;
  128 + }
  129 +
  130 + String[] parts = topic.split("/");
  131 + if (parts.length < 4) {
  132 + return null;
  133 + }
  134 +
  135 + return parts[2];
  136 + }
  137 +
  138 + /**
  139 + * 从主题中解析设备名称
  140 + * 格式: /sys/${productKey}/${deviceName}/...
  141 + *
  142 + * @param topic 主题
  143 + * @return 设备名称,如果无法解析则返回null
  144 + */
  145 + public static String parseDeviceNameFromTopic(String topic) {
  146 + if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) {
  147 + return null;
  148 + }
  149 +
  150 + String[] parts = topic.split("/");
  151 + if (parts.length < 4) {
  152 + return null;
  153 + }
  154 +
  155 + return parts[3];
  156 + }
  157 +
  158 + /**
  159 + * 从主题中解析方法名
  160 + * 例如:从 /sys/pk/dn/thing/service/property/set 解析出 property.set
  161 + *
  162 + * @param topic 主题
  163 + * @return 方法名,如果无法解析则返回null
  164 + */
  165 + public static String parseMethodFromTopic(String topic) {
  166 + if (StrUtil.isBlank(topic) || !topic.startsWith(IotTopicConstants.SYS_TOPIC_PREFIX)) {
  167 + return null;
  168 + }
  169 +
  170 + // 服务调用主题
  171 + if (topic.contains("/thing/service/")) {
  172 + String servicePart = topic.substring(topic.indexOf("/thing/service/") + "/thing/service/".length());
  173 + return servicePart.replace("/", ".");
  174 + }
  175 +
  176 + // 事件上报主题
  177 + if (topic.contains("/thing/event/")) {
  178 + String eventPart = topic.substring(topic.indexOf("/thing/event/") + "/thing/event/".length());
  179 + return "event." + eventPart.replace("/", ".");
  180 + }
  181 +
  182 + return null;
  183 + }
  184 +}
0 185 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-protocol/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 0 → 100644
  1 +cn.iocoder.yudao.module.iot.protocol.config.IotProtocolAutoConfiguration
0 2 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-script/pom.xml deleted
1   -<?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns="http://maven.apache.org/POM/4.0.0"
3   - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5   - <parent>
6   - <artifactId>yudao-module-iot</artifactId>
7   - <groupId>cn.iocoder.boot</groupId>
8   - <version>${revision}</version>
9   - </parent>
10   - <modelVersion>4.0.0</modelVersion>
11   - <artifactId>yudao-module-iot-script</artifactId>
12   - <packaging>jar</packaging>
13   -
14   - <name>${project.artifactId}</name>
15   - <description>IoT 脚本模块,提供 JavaScript 引擎解析等功能</description>
16   -
17   - <dependencies>
18   - <!-- 引入公共模块 -->
19   - <dependency>
20   - <groupId>cn.iocoder.boot</groupId>
21   - <artifactId>yudao-module-iot-api</artifactId>
22   - <version>${revision}</version>
23   - </dependency>
24   -
25   - <!-- Spring 相关依赖 -->
26   - <dependency>
27   - <groupId>org.springframework</groupId>
28   - <artifactId>spring-context</artifactId>
29   - </dependency>
30   -
31   - <!-- 工具类相关 -->
32   - <dependency>
33   - <groupId>cn.hutool</groupId>
34   - <artifactId>hutool-all</artifactId>
35   - </dependency>
36   - <dependency>
37   - <groupId>org.projectlombok</groupId>
38   - <artifactId>lombok</artifactId>
39   - <optional>true</optional>
40   - </dependency>
41   - <dependency>
42   - <groupId>org.slf4j</groupId>
43   - <artifactId>slf4j-api</artifactId>
44   - </dependency>
45   -
46   - <!-- JavaScript 引擎 - 使用 GraalJS 替代 Nashorn -->
47   - <!-- TODO @haohao:得考虑下,jdk8 可能不支持 graalvm,后续哈;【优先级:低】 -->
48   - <dependency>
49   - <groupId>org.graalvm.sdk</groupId>
50   - <artifactId>graal-sdk</artifactId>
51   - <version>22.3.0</version>
52   - </dependency>
53   - <dependency>
54   - <groupId>org.graalvm.js</groupId>
55   - <artifactId>js</artifactId>
56   - <version>22.3.0</version>
57   - </dependency>
58   - <dependency>
59   - <groupId>org.graalvm.js</groupId>
60   - <artifactId>js-scriptengine</artifactId>
61   - <version>22.3.0</version>
62   - </dependency>
63   -
64   - <!-- 测试相关 -->
65   - <dependency>
66   - <groupId>org.springframework.boot</groupId>
67   - <artifactId>spring-boot-starter-test</artifactId>
68   - <scope>test</scope>
69   - <exclusions>
70   - <exclusion>
71   - <groupId>org.junit.vintage</groupId>
72   - <artifactId>junit-vintage-engine</artifactId>
73   - </exclusion>
74   - </exclusions>
75   - </dependency>
76   - <dependency>
77   - <groupId>cn.iocoder.boot</groupId>
78   - <artifactId>yudao-spring-boot-starter-test</artifactId>
79   - <version>${revision}</version>
80   - <scope>test</scope>
81   - </dependency>
82   - <dependency>
83   - <groupId>org.mockito</groupId>
84   - <artifactId>mockito-core</artifactId>
85   - <scope>test</scope>
86   - </dependency>
87   - <dependency>
88   - <groupId>org.mockito</groupId>
89   - <artifactId>mockito-junit-jupiter</artifactId>
90   - <scope>test</scope>
91   - </dependency>
92   - </dependencies>
93   -
94   -</project>