Commit cf52a16f6cb941c2cdb65fec0fb532da34904a34

Authored by YunaiV
1 parent 02c3aa74

reactor:【IoT 物联网】移除 script 脚本,简化逻辑复杂度

Showing 31 changed files with 7 additions and 2815 deletions
yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -103,39 +103,6 @@ @@ -103,39 +103,6 @@
103 <optional>true</optional> 103 <optional>true</optional>
104 </dependency> 104 </dependency>
105 105
106 - <!-- JavaScript 引擎 - 使用 GraalJS 替代 Nashorn -->  
107 - <!-- TODO @haohao:得考虑下,jdk8 可能不支持 graalvm,后续哈;【优先级:低】 -->  
108 - <dependency>  
109 - <groupId>org.graalvm.sdk</groupId>  
110 - <artifactId>graal-sdk</artifactId>  
111 - <version>22.3.0</version>  
112 - </dependency>  
113 - <dependency>  
114 - <groupId>org.graalvm.js</groupId>  
115 - <artifactId>js</artifactId>  
116 - <version>22.3.0</version>  
117 - </dependency>  
118 - <dependency>  
119 - <groupId>org.graalvm.js</groupId>  
120 - <artifactId>js-scriptengine</artifactId>  
121 - <version>22.3.0</version>  
122 - </dependency>  
123 -  
124 - <!-- TODO @芋艿:合理注释 -->  
125 - <!-- IoT 数据桥梁的执行器所需消息队列。如果您只需要使用 rocketmq 那么则注释掉其它消息队列即可 -->  
126 - <!-- <dependency>-->  
127 - <!-- <groupId>org.apache.rocketmq</groupId>-->  
128 - <!-- <artifactId>rocketmq-spring-boot-starter</artifactId>-->  
129 - <!-- </dependency>-->  
130 - <!-- <dependency>-->  
131 - <!-- <groupId>org.springframework.kafka</groupId>-->  
132 - <!-- <artifactId>spring-kafka</artifactId>-->  
133 - <!-- </dependency>-->  
134 - <!-- <dependency>-->  
135 - <!-- <groupId>org.springframework.boot</groupId>-->  
136 - <!-- <artifactId>spring-boot-starter-amqp</artifactId>-->  
137 - <!-- </dependency>-->  
138 -  
139 <!-- IoT 网络组件:接收来自设备的上行数据 --> 106 <!-- IoT 网络组件:接收来自设备的上行数据 -->
140 <!-- <dependency>--> 107 <!-- <dependency>-->
141 <!-- <groupId>cn.iocoder.boot</groupId>--> 108 <!-- <groupId>cn.iocoder.boot</groupId>-->
@@ -147,13 +114,6 @@ @@ -147,13 +114,6 @@
147 <!-- <artifactId>yudao-module-iot-net-component-emqx</artifactId>--> 114 <!-- <artifactId>yudao-module-iot-net-component-emqx</artifactId>-->
148 <!-- <version>${revision}</version>--> 115 <!-- <version>${revision}</version>-->
149 <!-- </dependency>--> 116 <!-- </dependency>-->
150 -  
151 - <!-- 脚本相关 -->  
152 -<!-- <dependency>-->  
153 -<!-- <groupId>cn.iocoder.boot</groupId>-->  
154 -<!-- <artifactId>yudao-module-iot-script</artifactId>-->  
155 -<!-- <version>${revision}</version>-->  
156 -<!-- </dependency>-->  
157 </dependencies> 117 </dependencies>
158 118
159 </project> 119 </project>
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java deleted
1 -package cn.iocoder.yudao.module.iot;  
2 -  
3 -import cn.hutool.script.ScriptUtil;  
4 -import javax.script.Bindings;  
5 -import javax.script.ScriptEngine;  
6 -import javax.script.ScriptException;  
7 -  
8 -/**  
9 - * TODO 芋艿:测试脚本的接入  
10 - */  
11 -public class ScriptTest {  
12 -  
13 - public static void main2(String[] args) {  
14 - // 创建一个 Groovy 脚本引擎  
15 - ScriptEngine engine = ScriptUtil.createGroovyEngine();  
16 -  
17 - // 创建绑定参数  
18 - Bindings bindings = engine.createBindings();  
19 - bindings.put("name", "Alice");  
20 - bindings.put("age", 30);  
21 -  
22 - // 定义一个稍微复杂的 Groovy 脚本  
23 - String script = "def greeting = 'Hello, ' + name + '!';\n" +  
24 - "def ageInFiveYears = age + 5;\n" +  
25 - "def message = greeting + ' In five years, you will be ' + ageInFiveYears + ' years old.';\n" +  
26 - "return message.toUpperCase();\n";  
27 -  
28 - try {  
29 - // 执行脚本并获取结果  
30 - Object result = engine.eval(script, bindings);  
31 - System.out.println(result); // 输出: HELLO, ALICE! IN FIVE YEARS, YOU WILL BE 35 YEARS OLD.  
32 - } catch (ScriptException e) {  
33 - e.printStackTrace();  
34 - }  
35 - }  
36 -  
37 - public static void main(String[] args) {  
38 - // 创建一个 JavaScript 脚本引擎  
39 - ScriptEngine jsEngine = ScriptUtil.createJsEngine();  
40 -  
41 - // 创建绑定参数  
42 - Bindings jsBindings = jsEngine.createBindings();  
43 - jsBindings.put("name", "Bob");  
44 - jsBindings.put("age", 25);  
45 -  
46 - // 定义一个简单的 JavaScript 脚本  
47 - String jsScript = "var greeting = 'Hello, ' + name + '!';\n" +  
48 - "var ageInTenYears = age + 10;\n" +  
49 - "var message = greeting + ' In ten years, you will be ' + ageInTenYears + ' years old.';\n" +  
50 - "message.toUpperCase();\n";  
51 -  
52 - try {  
53 - // 执行脚本并获取结果  
54 - Object jsResult = jsEngine.eval(jsScript, jsBindings);  
55 - System.out.println(jsResult); // 输出: HELLO, BOB! IN TEN YEARS, YOU WILL BE 35 YEARS OLD.  
56 - } catch (ScriptException e) {  
57 - e.printStackTrace();  
58 - }  
59 - }  
60 -  
61 -}  
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductScriptController.java deleted
1 -package cn.iocoder.yudao.module.iot.controller.admin.product;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
4 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
5 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
6 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.*;  
7 -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductScriptDO;  
8 -import cn.iocoder.yudao.module.iot.script.example.ProductScriptSamples;  
9 -import cn.iocoder.yudao.module.iot.service.product.IotProductScriptService;  
10 -import io.swagger.v3.oas.annotations.Operation;  
11 -import io.swagger.v3.oas.annotations.Parameter;  
12 -import io.swagger.v3.oas.annotations.tags.Tag;  
13 -import jakarta.annotation.Resource;  
14 -import jakarta.validation.Valid;  
15 -import org.springframework.security.access.prepost.PreAuthorize;  
16 -import org.springframework.validation.annotation.Validated;  
17 -import org.springframework.web.bind.annotation.*;  
18 -  
19 -import java.util.List;  
20 -  
21 -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;  
22 -  
23 -@Tag(name = "管理后台 - IoT 产品脚本信息")  
24 -@RestController  
25 -@RequestMapping("/iot/product-script")  
26 -@Validated  
27 -public class IotProductScriptController {  
28 -  
29 - @Resource  
30 - private IotProductScriptService productScriptService;  
31 -  
32 - @Resource  
33 - private ProductScriptSamples scriptSamples;  
34 -  
35 - @PostMapping("/create")  
36 - @Operation(summary = "创建产品脚本")  
37 - @PreAuthorize("@ss.hasPermission('iot:product-script:create')")  
38 - public CommonResult<Long> createProductScript(@Valid @RequestBody IotProductScriptSaveReqVO createReqVO) {  
39 - return success(productScriptService.createProductScript(createReqVO));  
40 - }  
41 -  
42 - @PutMapping("/update")  
43 - @Operation(summary = "更新产品脚本")  
44 - @PreAuthorize("@ss.hasPermission('iot:product-script:update')")  
45 - public CommonResult<Boolean> updateProductScript(@Valid @RequestBody IotProductScriptSaveReqVO updateReqVO) {  
46 - productScriptService.updateProductScript(updateReqVO);  
47 - return success(true);  
48 - }  
49 -  
50 - @DeleteMapping("/delete")  
51 - @Operation(summary = "删除产品脚本")  
52 - @Parameter(name = "id", description = "编号", required = true)  
53 - @PreAuthorize("@ss.hasPermission('iot:product-script:delete')")  
54 - public CommonResult<Boolean> deleteProductScript(@RequestParam("id") Long id) {  
55 - productScriptService.deleteProductScript(id);  
56 - return success(true);  
57 - }  
58 -  
59 - @GetMapping("/get")  
60 - @Operation(summary = "获得产品脚本详情")  
61 - @Parameter(name = "id", description = "编号", required = true, example = "1024")  
62 - @PreAuthorize("@ss.hasPermission('iot:product-script:query')")  
63 - public CommonResult<IotProductScriptRespVO> getProductScript(@RequestParam("id") Long id) {  
64 - IotProductScriptDO productScript = productScriptService.getProductScript(id);  
65 - return success(BeanUtils.toBean(productScript, IotProductScriptRespVO.class));  
66 - }  
67 -  
68 - @GetMapping("/list-by-product")  
69 - @Operation(summary = "获得产品的脚本列表")  
70 - @Parameter(name = "productId", description = "产品编号", required = true, example = "1024")  
71 - @PreAuthorize("@ss.hasPermission('iot:product-script:query')")  
72 - public CommonResult<List<IotProductScriptRespVO>> getProductScriptListByProductId(  
73 - @RequestParam("productId") Long productId) {  
74 - List<IotProductScriptDO> list = productScriptService.getProductScriptListByProductId(productId);  
75 - return success(BeanUtils.toBean(list, IotProductScriptRespVO.class));  
76 - }  
77 -  
78 - @GetMapping("/page")  
79 - @Operation(summary = "获得产品脚本分页")  
80 - @PreAuthorize("@ss.hasPermission('iot:product-script:query')")  
81 - public CommonResult<PageResult<IotProductScriptRespVO>> getProductScriptPage(  
82 - @Valid IotProductScriptPageReqVO pageReqVO) {  
83 - PageResult<IotProductScriptDO> pageResult = productScriptService.getProductScriptPage(pageReqVO);  
84 - return success(BeanUtils.toBean(pageResult, IotProductScriptRespVO.class));  
85 - }  
86 -  
87 - @PostMapping("/test")  
88 - @Operation(summary = "测试产品脚本")  
89 - @PreAuthorize("@ss.hasPermission('iot:product-script:test')")  
90 - public CommonResult<IotProductScriptTestRespVO> testProductScript(  
91 - @Valid @RequestBody IotProductScriptTestReqVO testReqVO) {  
92 - return success(productScriptService.testProductScript(testReqVO));  
93 - }  
94 -  
95 - @PutMapping("/update-status")  
96 - @Operation(summary = "更新产品脚本状态")  
97 - @PreAuthorize("@ss.hasPermission('iot:product-script:update')")  
98 - public CommonResult<Boolean> updateProductScriptStatus(  
99 - @Valid @RequestBody IotProductScriptUpdateStatusReqVO updateStatusReqVO) {  
100 - productScriptService.updateProductScriptStatus(updateStatusReqVO.getId(), updateStatusReqVO.getStatus());  
101 - return success(true);  
102 - }  
103 -  
104 - @GetMapping("/sample")  
105 - @Operation(summary = "获取示例脚本")  
106 - @Parameter(name = "type", description = "脚本类型(1=属性解析, 2=事件解析, 3=命令编码)", required = true, example = "1")  
107 - @PreAuthorize("@ss.hasPermission('iot:product-script:query')")  
108 - public CommonResult<String> getSampleScript(@RequestParam("type") Integer type) {  
109 - String sample;  
110 - // TODO @haohao:要不枚举下?  
111 - switch (type) {  
112 - case 1:  
113 - sample = scriptSamples.getPropertyParserSample();  
114 - break;  
115 - case 2:  
116 - sample = scriptSamples.getEventParserSample();  
117 - break;  
118 - case 3:  
119 - sample = scriptSamples.getCommandEncoderSample();  
120 - break;  
121 - default:  
122 - // TODO @haohao:不支持,返回 error 会不会好点哈?例如说,参数不正确;  
123 - sample = "// 不支持的脚本类型";  
124 - }  
125 - return success(sample);  
126 - }  
127 -}  
128 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptPageReqVO.java deleted
1 -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
4 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
5 -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptLanguageEnum;  
6 -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptStatusEnum;  
7 -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptTypeEnum;  
8 -import io.swagger.v3.oas.annotations.media.Schema;  
9 -import lombok.Data;  
10 -import lombok.EqualsAndHashCode;  
11 -import lombok.ToString;  
12 -import org.springframework.format.annotation.DateTimeFormat;  
13 -  
14 -import java.time.LocalDateTime;  
15 -  
16 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
17 -  
18 -@Schema(description = "管理后台 - IoT 产品脚本信息分页 Request VO")  
19 -@Data  
20 -@EqualsAndHashCode(callSuper = true)  
21 -@ToString(callSuper = true)  
22 -public class IotProductScriptPageReqVO extends PageParam {  
23 -  
24 - @Schema(description = "产品ID", example = "28277")  
25 - private Long productId;  
26 -  
27 - @Schema(description = "产品唯一标识符")  
28 - private String productKey;  
29 -  
30 - @Schema(description = "脚本类型", example = "1")  
31 - @InEnum(IotProductScriptTypeEnum.class)  
32 - private Integer scriptType;  
33 -  
34 - @Schema(description = "脚本语言")  
35 - @InEnum(IotProductScriptLanguageEnum.class)  
36 - private String scriptLanguage;  
37 -  
38 - @Schema(description = "状态", example = "0")  
39 - @InEnum(IotProductScriptStatusEnum.class)  
40 - private Integer status;  
41 -  
42 - @Schema(description = "备注说明", example = "你说的对")  
43 - private String remark;  
44 -  
45 - @Schema(description = "最后测试时间")  
46 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
47 - private LocalDateTime[] lastTestTime;  
48 -  
49 - @Schema(description = "创建时间")  
50 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
51 - private LocalDateTime[] createTime;  
52 -  
53 -}  
54 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptRespVO.java deleted
1 -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;  
2 -  
3 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
4 -import com.alibaba.excel.annotation.ExcelProperty;  
5 -import io.swagger.v3.oas.annotations.media.Schema;  
6 -import lombok.Data;  
7 -  
8 -import java.time.LocalDateTime;  
9 -  
10 -@Schema(description = "管理后台 - IoT 产品脚本信息 Response VO")  
11 -@Data  
12 -@ExcelIgnoreUnannotated  
13 -public class IotProductScriptRespVO {  
14 -  
15 - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "26565")  
16 - @ExcelProperty("主键")  
17 - private Long id;  
18 -  
19 - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28277")  
20 - @ExcelProperty("产品ID")  
21 - private Long productId;  
22 -  
23 - @Schema(description = "产品唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED)  
24 - @ExcelProperty("产品唯一标识符")  
25 - private String productKey;  
26 -  
27 - @Schema(description = "脚本类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
28 - @ExcelProperty("脚本类型")  
29 - private Integer scriptType;  
30 -  
31 - @Schema(description = "脚本内容", requiredMode = Schema.RequiredMode.REQUIRED)  
32 - @ExcelProperty("脚本内容")  
33 - private String scriptContent;  
34 -  
35 - @Schema(description = "脚本语言", requiredMode = Schema.RequiredMode.REQUIRED)  
36 - @ExcelProperty("脚本语言")  
37 - private String scriptLanguage;  
38 -  
39 - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")  
40 - @ExcelProperty("状态")  
41 - private Integer status;  
42 -  
43 - @Schema(description = "备注说明", example = "你说的对")  
44 - @ExcelProperty("备注说明")  
45 - private String remark;  
46 -  
47 - @Schema(description = "最后测试时间")  
48 - @ExcelProperty("最后测试时间")  
49 - private LocalDateTime lastTestTime;  
50 -  
51 - @Schema(description = "最后测试结果(0=失败 1=成功)")  
52 - @ExcelProperty("最后测试结果(0=失败 1=成功)")  
53 - private Integer lastTestResult;  
54 -  
55 - @Schema(description = "脚本版本号", requiredMode = Schema.RequiredMode.REQUIRED)  
56 - @ExcelProperty("脚本版本号")  
57 - private Integer version;  
58 -  
59 - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)  
60 - @ExcelProperty("创建时间")  
61 - private LocalDateTime createTime;  
62 -  
63 -}  
64 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptSaveReqVO.java deleted
1 -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;  
2 -  
3 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
4 -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptLanguageEnum;  
5 -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptStatusEnum;  
6 -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptTypeEnum;  
7 -import io.swagger.v3.oas.annotations.media.Schema;  
8 -import jakarta.validation.constraints.NotEmpty;  
9 -import jakarta.validation.constraints.NotNull;  
10 -import lombok.Data;  
11 -  
12 -@Schema(description = "管理后台 - IoT 产品脚本信息新增/修改 Request VO")  
13 -@Data  
14 -public class IotProductScriptSaveReqVO {  
15 -  
16 - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "26565")  
17 - private Long id;  
18 -  
19 - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28277")  
20 - @NotNull(message = "产品ID不能为空")  
21 - private Long productId;  
22 -  
23 - @Schema(description = "产品唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED)  
24 - @NotEmpty(message = "产品唯一标识符不能为空")  
25 - private String productKey;  
26 -  
27 - @Schema(description = "脚本类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
28 - @NotNull(message = "脚本类型不能为空")  
29 - @InEnum(IotProductScriptTypeEnum.class)  
30 - private Integer scriptType;  
31 -  
32 - @Schema(description = "脚本内容", requiredMode = Schema.RequiredMode.REQUIRED)  
33 - @NotEmpty(message = "脚本内容不能为空")  
34 - private String scriptContent;  
35 -  
36 - @Schema(description = "脚本语言", requiredMode = Schema.RequiredMode.REQUIRED)  
37 - @NotEmpty(message = "脚本语言不能为空")  
38 - @InEnum(IotProductScriptLanguageEnum.class)  
39 - private String scriptLanguage;  
40 -  
41 - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")  
42 - @NotNull(message = "状态不能为空")  
43 - @InEnum(IotProductScriptStatusEnum.class)  
44 - private Integer status;  
45 -  
46 - @Schema(description = "备注说明", example = "你说的对")  
47 - private String remark;  
48 -  
49 -}  
50 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestReqVO.java deleted
1 -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;  
2 -  
3 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
4 -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptTypeEnum;  
5 -import io.swagger.v3.oas.annotations.media.Schema;  
6 -import jakarta.validation.constraints.NotEmpty;  
7 -import jakarta.validation.constraints.NotNull;  
8 -import lombok.Data;  
9 -  
10 -@Schema(description = "管理后台 - IoT 产品脚本测试 Request VO")  
11 -@Data  
12 -public class IotProductScriptTestReqVO {  
13 -  
14 - @Schema(description = "脚本ID,如果已保存脚本则传入", example = "1024")  
15 - private Long id;  
16 -  
17 - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")  
18 - @NotNull(message = "产品ID不能为空")  
19 - private Long productId;  
20 -  
21 - @Schema(description = "脚本类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
22 - @NotNull(message = "脚本类型不能为空")  
23 - @InEnum(value = IotProductScriptTypeEnum.class)  
24 - private Integer scriptType;  
25 -  
26 - @Schema(description = "脚本内容", requiredMode = Schema.RequiredMode.REQUIRED)  
27 - @NotEmpty(message = "脚本内容不能为空")  
28 - private String scriptContent;  
29 -  
30 - @Schema(description = "脚本语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "javascript")  
31 - @NotEmpty(message = "脚本语言不能为空")  
32 - private String scriptLanguage;  
33 -  
34 - @Schema(description = "测试输入数据", requiredMode = Schema.RequiredMode.REQUIRED)  
35 - @NotEmpty(message = "测试输入数据不能为空")  
36 - private String testInput;  
37 -  
38 -}  
39 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestRespVO.java deleted
1 -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -  
6 -@Schema(description = "管理后台 - IoT 产品脚本测试 Response VO")  
7 -@Data  
8 -public class IotProductScriptTestRespVO {  
9 -  
10 - @Schema(description = "测试是否成功", requiredMode = Schema.RequiredMode.REQUIRED)  
11 - private Boolean success;  
12 -  
13 - @Schema(description = "测试结果输出")  
14 - private Object output;  
15 -  
16 - @Schema(description = "错误消息,失败时返回")  
17 - private String errorMessage;  
18 -  
19 - @Schema(description = "执行耗时(毫秒)")  
20 - private Long executionTimeMs;  
21 -  
22 - // 静态工厂方法 - 成功  
23 - public static IotProductScriptTestRespVO success(Object output, Long executionTimeMs) {  
24 - IotProductScriptTestRespVO respVO = new IotProductScriptTestRespVO();  
25 - respVO.setSuccess(true);  
26 - respVO.setOutput(output);  
27 - respVO.setExecutionTimeMs(executionTimeMs);  
28 - return respVO;  
29 - }  
30 -  
31 - // 静态工厂方法 - 失败  
32 - public static IotProductScriptTestRespVO error(String errorMessage, Long executionTimeMs) {  
33 - IotProductScriptTestRespVO respVO = new IotProductScriptTestRespVO();  
34 - respVO.setSuccess(false);  
35 - respVO.setErrorMessage(errorMessage);  
36 - respVO.setExecutionTimeMs(executionTimeMs);  
37 - return respVO;  
38 - }  
39 -}  
40 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptUpdateStatusReqVO.java deleted
1 -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;  
2 -  
3 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
4 -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptStatusEnum;  
5 -import io.swagger.v3.oas.annotations.media.Schema;  
6 -import jakarta.validation.constraints.NotNull;  
7 -import lombok.Data;  
8 -  
9 -@Schema(description = "管理后台 - IoT 产品脚本状态更新 Request VO")  
10 -@Data  
11 -public class IotProductScriptUpdateStatusReqVO {  
12 -  
13 - @Schema(description = "脚本ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")  
14 - @NotNull(message = "脚本ID不能为空")  
15 - private Long id;  
16 -  
17 - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")  
18 - @NotNull(message = "状态不能为空")  
19 - @InEnum(IotProductScriptStatusEnum.class)  
20 - private Integer status;  
21 -  
22 -}  
23 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductScriptDO.java deleted
1 -package cn.iocoder.yudao.module.iot.dal.dataobject.product;  
2 -  
3 -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;  
4 -import com.baomidou.mybatisplus.annotation.KeySequence;  
5 -import com.baomidou.mybatisplus.annotation.TableId;  
6 -import com.baomidou.mybatisplus.annotation.TableName;  
7 -import lombok.*;  
8 -  
9 -import java.time.LocalDateTime;  
10 -  
11 -// TODO @haohao:类似阿里云的脚本,貌似是一个?这个可以简化么?【微信讨论哈】类似阿里云,貌似是加了个 topic?  
12 -/**  
13 - * IoT 产品脚本信息 DO  
14 - *  
15 - * @author 芋道源码  
16 - */  
17 -@TableName("iot_product_script")  
18 -@KeySequence("iot_product_script_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。  
19 -@Data  
20 -@EqualsAndHashCode(callSuper = true)  
21 -@ToString(callSuper = true)  
22 -@Builder  
23 -@NoArgsConstructor  
24 -@AllArgsConstructor  
25 -public class IotProductScriptDO extends BaseDO {  
26 -  
27 - /**  
28 - * 主键  
29 - */  
30 - @TableId  
31 - private Long id;  
32 - /**  
33 - * 产品ID  
34 - */  
35 - private Long productId;  
36 - /**  
37 - * 产品唯一标识符  
38 - */  
39 - private String productKey;  
40 - /**  
41 - * 脚本类型(property_parser=属性解析,event_parser=事件解析,command_encoder=命令编码)  
42 - */  
43 - private String scriptType;  
44 - /**  
45 - * 脚本内容  
46 - */  
47 - private String scriptContent;  
48 - /**  
49 - * 脚本语言  
50 - */  
51 - private String scriptLanguage;  
52 - /**  
53 - * 状态(0=禁用 1=启用)  
54 - */  
55 - private Integer status;  
56 - /**  
57 - * 备注说明  
58 - */  
59 - private String remark;  
60 - /**  
61 - * 最后测试时间  
62 - */  
63 - private LocalDateTime lastTestTime;  
64 - /**  
65 - * 最后测试结果(0=失败 1=成功)  
66 - */  
67 - private Integer lastTestResult;  
68 - /**  
69 - * 脚本版本号  
70 - */  
71 - private Integer version;  
72 -  
73 -}  
74 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductScriptMapper.java deleted
1 -package cn.iocoder.yudao.module.iot.dal.mysql.product;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
4 -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;  
5 -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;  
6 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptPageReqVO;  
7 -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductScriptDO;  
8 -import org.apache.ibatis.annotations.Mapper;  
9 -  
10 -/**  
11 - * IoT 产品脚本信息 Mapper  
12 - *  
13 - * @author 芋道源码  
14 - */  
15 -@Mapper  
16 -public interface IotProductScriptMapper extends BaseMapperX<IotProductScriptDO> {  
17 -  
18 - default PageResult<IotProductScriptDO> selectPage(IotProductScriptPageReqVO reqVO) {  
19 - return selectPage(reqVO, new LambdaQueryWrapperX<IotProductScriptDO>()  
20 - .eqIfPresent(IotProductScriptDO::getProductId, reqVO.getProductId())  
21 - .eqIfPresent(IotProductScriptDO::getProductKey, reqVO.getProductKey())  
22 - .eqIfPresent(IotProductScriptDO::getScriptType, reqVO.getScriptType())  
23 - .eqIfPresent(IotProductScriptDO::getScriptLanguage, reqVO.getScriptLanguage())  
24 - .eqIfPresent(IotProductScriptDO::getStatus, reqVO.getStatus())  
25 - .eqIfPresent(IotProductScriptDO::getRemark, reqVO.getRemark())  
26 - .betweenIfPresent(IotProductScriptDO::getLastTestTime, reqVO.getLastTestTime())  
27 - .betweenIfPresent(IotProductScriptDO::getCreateTime, reqVO.getCreateTime())  
28 - .orderByDesc(IotProductScriptDO::getId));  
29 - }  
30 -  
31 -}  
32 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/ScriptExample.java deleted
1 -package cn.iocoder.yudao.module.iot.script;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.iocoder.yudao.module.iot.script.context.DefaultScriptContext;  
5 -import cn.iocoder.yudao.module.iot.script.context.ScriptContext;  
6 -import cn.iocoder.yudao.module.iot.script.service.ScriptService;  
7 -import lombok.extern.slf4j.Slf4j;  
8 -import org.springframework.beans.factory.annotation.Autowired;  
9 -import org.springframework.stereotype.Component;  
10 -  
11 -import java.util.Map;  
12 -  
13 -// TODO @haohao:挪到 test 目录下  
14 -/**  
15 - * 脚本使用示例类  
16 - */  
17 -@Slf4j  
18 -@Component  
19 -public class ScriptExample {  
20 -  
21 - @Autowired  
22 - private ScriptService scriptService;  
23 -  
24 - /**  
25 - * 执行简单的 JavaScript 脚本  
26 - *  
27 - * @return 执行结果  
28 - */  
29 - public Object executeSimpleScript() {  
30 - // 简单的脚本内容  
31 - String script = "var result = a + b; result;";  
32 -  
33 - // 创建参数  
34 - Map<String, Object> params = MapUtil.newHashMap();  
35 - params.put("a", 10);  
36 - params.put("b", 20);  
37 -  
38 - // 执行脚本  
39 - return scriptService.executeJavaScript(script, params);  
40 - }  
41 -  
42 - /**  
43 - * 执行包含函数的 JavaScript 脚本  
44 - *  
45 - * @return 执行结果  
46 - */  
47 - public Object executeScriptWithFunction() {  
48 - // 包含函数的脚本内容  
49 - String script = "function calc(x, y) { return x * y; } calc(a, b);";  
50 -  
51 - // 创建上下文  
52 - ScriptContext context = new DefaultScriptContext();  
53 - context.setParameter("a", 5);  
54 - context.setParameter("b", 6);  
55 -  
56 - // 执行脚本  
57 - return scriptService.executeJavaScript(script, context);  
58 - }  
59 -  
60 - /**  
61 - * 执行包含工具类使用的脚本  
62 - *  
63 - * @return 执行结果  
64 - */  
65 - public Object executeScriptWithUtils() {  
66 - // 使用工具类的脚本内容  
67 - String script = "var data = {name: 'test', value: 123}; utils.toJson(data);";  
68 -  
69 - // 执行脚本  
70 - return scriptService.executeJavaScript(script, MapUtil.newHashMap());  
71 - }  
72 -  
73 - /**  
74 - * 执行包含日志输出的脚本  
75 - *  
76 - * @return 执行结果  
77 - */  
78 - public Object executeScriptWithLogging() {  
79 - // 包含日志输出的脚本内容  
80 - String script = "log.info('脚本开始执行...'); " +  
81 - "var result = a + b; " +  
82 - "log.info('计算结果: ' + result); " +  
83 - "result;";  
84 -  
85 - // 创建参数  
86 - Map<String, Object> params = MapUtil.newHashMap();  
87 - params.put("a", 100);  
88 - params.put("b", 200);  
89 -  
90 - // 执行脚本  
91 - return scriptService.executeJavaScript(script, params);  
92 - }  
93 -  
94 - /**  
95 - * 演示脚本安全性验证  
96 - *  
97 - * @return 是否安全  
98 - */  
99 - public boolean validateScriptSecurity() {  
100 - // 安全的脚本  
101 - String safeScript = "var x = 10; var y = 20; x + y;";  
102 - boolean safeResult = scriptService.validateScript("js", safeScript);  
103 -  
104 - // 不安全的脚本  
105 - String unsafeScript = "java.lang.System.exit(0);";  
106 - boolean unsafeResult = scriptService.validateScript("js", unsafeScript);  
107 -  
108 - log.info("安全脚本验证结果: {}", safeResult);  
109 - log.info("不安全脚本验证结果: {}", unsafeResult);  
110 -  
111 - return safeResult && !unsafeResult;  
112 - }  
113 -}  
114 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/config/ScriptConfiguration.java deleted
1 -package cn.iocoder.yudao.module.iot.script.config;  
2 -  
3 -import cn.iocoder.yudao.module.iot.script.engine.ScriptEngineFactory;  
4 -import org.springframework.context.annotation.Bean;  
5 -import org.springframework.context.annotation.Configuration;  
6 -import org.springframework.context.annotation.Primary;  
7 -  
8 -/**  
9 - * 脚本模块配置类  
10 - */  
11 -@Configuration  
12 -public class ScriptConfiguration {  
13 -  
14 - /**  
15 - * 创建脚本引擎工厂  
16 - *  
17 - * @return 脚本引擎工厂  
18 - */  
19 - @Bean  
20 - @Primary  
21 - public ScriptEngineFactory scriptEngineFactory() {  
22 - return new ScriptEngineFactory();  
23 - }  
24 -}  
25 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DefaultScriptContext.java deleted
1 -package cn.iocoder.yudao.module.iot.script.context;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -  
5 -import java.util.Map;  
6 -  
7 -/**  
8 - * 默认脚本上下文实现  
9 - */  
10 -public class DefaultScriptContext implements ScriptContext {  
11 -  
12 - /**  
13 - * 上下文参数  
14 - */  
15 - private final Map<String, Object> parameters = MapUtil.newHashMap();  
16 -  
17 - /**  
18 - * 上下文函数  
19 - */  
20 - private final Map<String, Object> functions = MapUtil.newHashMap();  
21 -  
22 - @Override  
23 - public Map<String, Object> getParameters() {  
24 - return parameters;  
25 - }  
26 -  
27 - @Override  
28 - public Map<String, Object> getFunctions() {  
29 - return functions;  
30 - }  
31 -  
32 - @Override  
33 - public void setParameter(String key, Object value) {  
34 - parameters.put(key, value);  
35 - }  
36 -  
37 - @Override  
38 - public Object getParameter(String key) {  
39 - return parameters.get(key);  
40 - }  
41 -  
42 - @Override  
43 - public void registerFunction(String name, Object function) {  
44 - functions.put(name, function);  
45 - }  
46 -}  
47 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DeviceScriptContext.java deleted
1 -package cn.iocoder.yudao.module.iot.script.context;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.hutool.core.util.StrUtil;  
5 -import lombok.Getter;  
6 -import lombok.extern.slf4j.Slf4j;  
7 -  
8 -import java.util.Map;  
9 -  
10 -/**  
11 - * 设备脚本上下文,提供设备相关的上下文信息  
12 - */  
13 -@Slf4j  
14 -public class DeviceScriptContext extends DefaultScriptContext {  
15 -  
16 - /**  
17 - * 产品 Key  
18 - */  
19 - @Getter  
20 - private String productKey;  
21 -  
22 - /**  
23 - * 设备名称  
24 - */  
25 - @Getter  
26 - private String deviceName;  
27 -  
28 - /**  
29 - * 设备属性数据缓存  
30 - */  
31 - private Map<String, Object> properties;  
32 -  
33 - /**  
34 - * 使用产品 Key 和设备名称初始化上下文  
35 - *  
36 - * @param productKey 产品 Key  
37 - * @param deviceName 设备名称,可以为 null  
38 - * @return 当前上下文实例,用于链式调用  
39 - */  
40 - public DeviceScriptContext withDeviceInfo(String productKey, String deviceName) {  
41 - this.productKey = productKey;  
42 - this.deviceName = deviceName;  
43 -  
44 - // 添加到参数中,便于脚本访问  
45 - setParameter("productKey", productKey);  
46 - if (StrUtil.isNotEmpty(deviceName)) {  
47 - setParameter("deviceName", deviceName);  
48 - }  
49 - return this;  
50 - }  
51 -  
52 - /**  
53 - * 设置设备属性数据  
54 - *  
55 - * @param properties 属性数据  
56 - * @return 当前上下文实例,用于链式调用  
57 - */  
58 - public DeviceScriptContext withProperties(Map<String, Object> properties) {  
59 - this.properties = properties;  
60 - if (MapUtil.isNotEmpty(properties)) {  
61 - setParameter("properties", properties);  
62 - }  
63 - return this;  
64 - }  
65 -  
66 - /**  
67 - * 获取设备属性值  
68 - *  
69 - * @param key 属性标识符  
70 - * @return 属性值  
71 - */  
72 - public Object getProperty(String key) {  
73 - if (MapUtil.isEmpty(properties)) {  
74 - return null;  
75 - }  
76 - return properties.get(key);  
77 - }  
78 -  
79 - /**  
80 - * 设置设备属性值  
81 - *  
82 - * @param key 属性标识符  
83 - * @param value 属性值  
84 - */  
85 - public void setProperty(String key, Object value) {  
86 - if (this.properties == null) {  
87 - this.properties = MapUtil.newHashMap();  
88 - setParameter("properties", this.properties);  
89 - }  
90 - this.properties.put(key, value);  
91 - }  
92 -}  
93 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/ScriptContext.java deleted
1 -package cn.iocoder.yudao.module.iot.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 - /**  
41 - * 注册函数  
42 - *  
43 - * @param name 函数名称  
44 - * @param function 函数对象  
45 - */  
46 - void registerFunction(String name, Object function);  
47 -}  
48 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/AbstractScriptEngine.java deleted
1 -package cn.iocoder.yudao.module.iot.script.engine;  
2 -  
3 -import cn.iocoder.yudao.module.iot.script.context.ScriptContext;  
4 -import cn.iocoder.yudao.module.iot.script.sandbox.ScriptSandbox;  
5 -import lombok.extern.slf4j.Slf4j;  
6 -  
7 -/**  
8 - * 抽象脚本引擎,提供脚本引擎的基本框架  
9 - */  
10 -@Slf4j  
11 -public abstract class AbstractScriptEngine implements ScriptEngine {  
12 -  
13 - /**  
14 - * 脚本沙箱,用于提供安全执行环境  
15 - */  
16 - protected final ScriptSandbox sandbox;  
17 -  
18 - /**  
19 - * 构造函数  
20 - *  
21 - * @param sandbox 脚本沙箱  
22 - */  
23 - protected AbstractScriptEngine(ScriptSandbox sandbox) {  
24 - this.sandbox = sandbox;  
25 - }  
26 -  
27 - @Override  
28 - public Object execute(String script, ScriptContext context) {  
29 - try {  
30 - // 执行前验证脚本安全性  
31 - sandbox.validate(script);  
32 - // 执行脚本  
33 - return doExecute(script, context);  
34 - } catch (Exception e) {  
35 - log.error("执行脚本出错:{}", e.getMessage(), e);  
36 - throw new RuntimeException("脚本执行失败:" + e.getMessage(), e);  
37 - }  
38 - }  
39 -  
40 - /**  
41 - * 执行脚本的具体实现  
42 - *  
43 - * @param script 脚本内容  
44 - * @param context 脚本上下文  
45 - * @return 脚本执行结果  
46 - * @throws Exception 执行异常  
47 - */  
48 - protected abstract Object doExecute(String script, ScriptContext context) throws Exception;  
49 -}  
50 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/JsScriptEngine.java deleted
1 -package cn.iocoder.yudao.module.iot.script.engine;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.hutool.core.util.IdUtil;  
5 -import cn.hutool.core.util.StrUtil;  
6 -import cn.iocoder.yudao.module.iot.script.context.ScriptContext;  
7 -import cn.iocoder.yudao.module.iot.script.sandbox.ScriptSandbox;  
8 -import cn.iocoder.yudao.module.iot.script.util.ScriptUtils;  
9 -import lombok.extern.slf4j.Slf4j;  
10 -import org.graalvm.polyglot.*;  
11 -  
12 -import java.nio.file.Path;  
13 -import java.util.Map;  
14 -import java.util.concurrent.ConcurrentHashMap;  
15 -import java.util.concurrent.TimeUnit;  
16 -import java.util.concurrent.atomic.AtomicReference;  
17 -  
18 -/**  
19 - * JavaScript 脚本引擎实现,基于 GraalJS Context API  
20 - */  
21 -@Slf4j  
22 -public class JsScriptEngine extends AbstractScriptEngine implements AutoCloseable {  
23 -  
24 - /**  
25 - * JavaScript 引擎类型  
26 - */  
27 - public static final String TYPE = "js";  
28 -  
29 - /**  
30 - * 脚本语言类型  
31 - */  
32 - private static final String LANGUAGE_ID = "js";  
33 -  
34 - /**  
35 - * GraalJS 上下文  
36 - */  
37 - private final Context context;  
38 -  
39 - /**  
40 - * 脚本源代码缓存  
41 - */  
42 - private final Map<String, Source> sourceCache = new ConcurrentHashMap<>();  
43 -  
44 - /**  
45 - * 脚本缓存的最大数量  
46 - */  
47 - private static final int MAX_CACHE_SIZE = 1000;  
48 -  
49 - /**  
50 - * 构造函数  
51 - *  
52 - * @param sandbox JavaScript 沙箱  
53 - */  
54 - public JsScriptEngine(ScriptSandbox sandbox) {  
55 - super(sandbox);  
56 -  
57 - // 创建安全的主机访问配置  
58 - HostAccess hostAccess = HostAccess.newBuilder()  
59 - .allowPublicAccess(true) // 允许访问公共方法和字段  
60 - .allowArrayAccess(true) // 允许数组访问  
61 - .allowListAccess(true) // 允许 List 访问  
62 - .allowMapAccess(true) // 允许 Map 访问  
63 - .build();  
64 -  
65 - // 创建隔离的临时目录路径  
66 - // TODO @haohao:貌似没用到?  
67 - Path tempDirectory = Path.of(System.getProperty("java.io.tmpdir"), "graaljs-" + IdUtil.fastSimpleUUID());  
68 -  
69 - // 初始化 GraalJS 上下文  
70 - this.context = Context.newBuilder(LANGUAGE_ID)  
71 - .allowHostAccess(hostAccess) // 使用安全的主机访问配置  
72 - .allowHostClassLookup(className -> false) // 禁止查找 Java 类  
73 - .allowIO(false) // 禁止文件 IO  
74 - .allowNativeAccess(false) // 禁止本地访问  
75 - .allowCreateThread(false) // 禁止创建线程  
76 - .allowEnvironmentAccess(EnvironmentAccess.NONE) // 禁止环境变量访问  
77 - .allowExperimentalOptions(false) // 禁止实验性选项  
78 - .option("js.ecmascript-version", "2021") // 使用最新的 ECMAScript 标准  
79 - .option("js.foreign-object-prototype", "false") // 禁用外部对象原型  
80 - .option("js.nashorn-compat", "false") // 关闭 Nashorn 兼容模式以获得更好性能  
81 - .build();  
82 - }  
83 -  
84 - @Override  
85 - protected Object doExecute(String script, ScriptContext context) throws Exception {  
86 - if (StrUtil.isBlank(script)) {  
87 - return null;  
88 - }  
89 -  
90 - try {  
91 - // 绑定上下文变量  
92 - bindContextVariables(context);  
93 -  
94 - // 从缓存获取或创建脚本源  
95 - Source source = getOrCreateSource(script);  
96 -  
97 - // 执行脚本并捕获结果,添加超时控制  
98 - // TODO @haohao:通过线程池 + future 会好点?  
99 - Value result;  
100 - Thread executionThread = Thread.currentThread();  
101 - Thread watchdogThread = new Thread(() -> {  
102 - try {  
103 - // 等待 5 秒  
104 - TimeUnit.SECONDS.sleep(5);  
105 - // 如果执行线程还在运行,中断它  
106 - if (executionThread.isAlive()) {  
107 - log.warn("脚本执行超时,强制中断");  
108 - executionThread.interrupt();  
109 - }  
110 - } catch (InterruptedException ignored) {  
111 - // 忽略中断  
112 - }  
113 - });  
114 -  
115 - watchdogThread.setDaemon(true);  
116 - watchdogThread.start();  
117 -  
118 - try {  
119 - result = this.context.eval(source);  
120 - } finally {  
121 - watchdogThread.interrupt(); // 确保看门狗线程停止  
122 - }  
123 -  
124 - // 转换结果为 Java 对象  
125 - return convertResultToJava(result);  
126 - } catch (PolyglotException e) {  
127 - handleScriptException(e, script);  
128 - throw e;  
129 - }  
130 - }  
131 -  
132 - /**  
133 - * 绑定上下文变量  
134 - *  
135 - * @param context 脚本上下文  
136 - */  
137 - private void bindContextVariables(ScriptContext context) {  
138 - Value bindings = this.context.getBindings(LANGUAGE_ID);  
139 -  
140 - // 添加上下文参数  
141 - if (MapUtil.isNotEmpty(context.getParameters())) {  
142 - context.getParameters().forEach(bindings::putMember);  
143 - }  
144 -  
145 - // 添加上下文函数  
146 - if (MapUtil.isNotEmpty(context.getFunctions())) {  
147 - context.getFunctions().forEach(bindings::putMember);  
148 - }  
149 -  
150 - // 添加工具类  
151 - bindings.putMember("utils", ScriptUtils.getInstance());  
152 -  
153 - // 添加日志对象  
154 - bindings.putMember("log", log);  
155 -  
156 - // 添加控制台输出(限制并重定向到日志)  
157 - AtomicReference<StringBuilder> consoleBuffer = new AtomicReference<>(new StringBuilder());  
158 -  
159 - Value console = this.context.eval(LANGUAGE_ID, "({\n" +  
160 - " log: function(msg) { _consoleLog(msg, 'INFO'); },\n" +  
161 - " info: function(msg) { _consoleLog(msg, 'INFO'); },\n" +  
162 - " warn: function(msg) { _consoleLog(msg, 'WARN'); },\n" +  
163 - " error: function(msg) { _consoleLog(msg, 'ERROR'); }\n" +  
164 - "})");  
165 -  
166 - bindings.putMember("console", console);  
167 -  
168 - bindings.putMember("_consoleLog", (java.util.function.BiConsumer<String, String>) (message, level) -> {  
169 - String formattedMsg = String.valueOf(message);  
170 - switch (level) {  
171 - case "INFO":  
172 - log.info("Script console: {}", formattedMsg);  
173 - break;  
174 - case "WARN":  
175 - log.warn("Script console: {}", formattedMsg);  
176 - break;  
177 - case "ERROR":  
178 - log.error("Script console: {}", formattedMsg);  
179 - break;  
180 - default:  
181 - log.info("Script console: {}", formattedMsg);  
182 - }  
183 -  
184 - // 将输出添加到缓冲区  
185 - StringBuilder buffer = consoleBuffer.get();  
186 - if (buffer.length() > 10000) {  
187 - buffer = new StringBuilder();  
188 - consoleBuffer.set(buffer);  
189 - }  
190 - buffer.append(formattedMsg).append("\n");  
191 - });  
192 - }  
193 -  
194 - /**  
195 - * 从缓存中获取或创建脚本源  
196 - *  
197 - * @param script 脚本内容  
198 - * @return 脚本源  
199 - */  
200 - private Source getOrCreateSource(String script) {  
201 - // 如果缓存太大,清理部分缓存  
202 - if (sourceCache.size() > MAX_CACHE_SIZE) {  
203 - int itemsToRemove = (int) (MAX_CACHE_SIZE * 0.2); // 清理 20% 的缓存  
204 - sourceCache.keySet().stream()  
205 - .limit(itemsToRemove)  
206 - .toList()  
207 - .forEach(sourceCache::remove);  
208 - }  
209 -  
210 - // 使用脚本的哈希码作为缓存键  
211 - String cacheKey = String.valueOf(script.hashCode());  
212 -  
213 - return sourceCache.computeIfAbsent(cacheKey, key -> {  
214 - try {  
215 - return Source.newBuilder(LANGUAGE_ID, script, "script-" + key + ".js").cached(true).build();  
216 - } catch (Exception e) {  
217 - log.error("创建脚本源失败: {}", e.getMessage(), e);  
218 - throw new RuntimeException("创建脚本源失败: " + e.getMessage(), e);  
219 - }  
220 - });  
221 - }  
222 -  
223 - /**  
224 - * 将 GraalJS 结果转换为 Java 对象  
225 - *  
226 - * @param result GraalJS 执行结果  
227 - * @return Java 对象  
228 - */  
229 - private Object convertResultToJava(Value result) {  
230 - if (result == null || result.isNull()) {  
231 - return null;  
232 - }  
233 -  
234 - if (result.isString()) {  
235 - return result.asString();  
236 - }  
237 -  
238 - if (result.isNumber()) {  
239 - if (result.fitsInInt()) {  
240 - return result.asInt();  
241 - }  
242 - if (result.fitsInLong()) {  
243 - return result.asLong();  
244 - }  
245 - if (result.fitsInFloat()) {  
246 - return result.asFloat();  
247 - }  
248 - if (result.fitsInDouble()) {  
249 - return result.asDouble();  
250 - }  
251 - }  
252 -  
253 - if (result.isBoolean()) {  
254 - return result.asBoolean();  
255 - }  
256 -  
257 - if (result.hasArrayElements()) {  
258 - int size = (int) result.getArraySize();  
259 - Object[] array = new Object[size];  
260 - for (int i = 0; i < size; i++) {  
261 - array[i] = convertResultToJava(result.getArrayElement(i));  
262 - }  
263 - return array;  
264 - }  
265 -  
266 - if (result.hasMembers()) {  
267 - Map<String, Object> map = MapUtil.newHashMap();  
268 - for (String key : result.getMemberKeys()) {  
269 - map.put(key, convertResultToJava(result.getMember(key)));  
270 - }  
271 - return map;  
272 - }  
273 -  
274 - if (result.isHostObject()) {  
275 - return result.asHostObject();  
276 - }  
277 -  
278 - // 默认情况下尝试转换为字符串  
279 - return result.toString();  
280 - }  
281 -  
282 - /**  
283 - * 处理脚本执行异常  
284 - *  
285 - * @param e 多语言异常  
286 - * @param script 原始脚本  
287 - */  
288 - private void handleScriptException(PolyglotException e, String script) {  
289 - if (e.isCancelled()) {  
290 - log.error("脚本执行被取消,可能超出资源限制");  
291 - } else if (e.isHostException()) {  
292 - Throwable hostException = e.asHostException();  
293 - log.error("脚本执行时发生 Java 异常: {}", hostException.getMessage(), hostException);  
294 - } else if (e.isGuestException()) {  
295 - if (e.getSourceLocation() != null) {  
296 - log.error("脚本执行错误: {} 位于行 {},列 {}",  
297 - e.getMessage(),  
298 - e.getSourceLocation().getStartLine(),  
299 - e.getSourceLocation().getStartColumn());  
300 -  
301 - // 尝试显示错误位置上下文  
302 - try {  
303 - String[] lines = script.split("\n");  
304 - int lineNumber = e.getSourceLocation().getStartLine();  
305 - if (lineNumber > 0 && lineNumber <= lines.length) {  
306 - int contextStart = Math.max(1, lineNumber - 2);  
307 - int contextEnd = Math.min(lines.length, lineNumber + 2);  
308 -  
309 - StringBuilder context = new StringBuilder();  
310 - for (int i = contextStart; i <= contextEnd; i++) {  
311 - if (i == lineNumber) {  
312 - context.append("> "); // 标记错误行  
313 - } else {  
314 - context.append(" ");  
315 - }  
316 - context.append(i).append(": ").append(lines[i - 1]).append("\n");  
317 - }  
318 - log.error("脚本上下文:\n{}", context);  
319 - }  
320 - } catch (Exception ignored) {  
321 - // 忽略上下文显示失败  
322 - }  
323 - } else {  
324 - log.error("脚本执行错误: {}", e.getMessage());  
325 - }  
326 - } else {  
327 - log.error("脚本执行时发生未知错误: {}", e.getMessage(), e);  
328 - }  
329 - }  
330 -  
331 - @Override  
332 - public String getType() {  
333 - return TYPE;  
334 - }  
335 -  
336 - @Override  
337 - public void close() {  
338 - try {  
339 - // 清除脚本缓存  
340 - sourceCache.clear();  
341 -  
342 - // 关闭 GraalJS 上下文,释放资源  
343 - context.close(true);  
344 - } catch (Exception e) {  
345 - log.warn("关闭 GraalJS 引擎时发生错误: {}", e.getMessage());  
346 - }  
347 - }  
348 -}  
349 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngine.java deleted
1 -package cn.iocoder.yudao.module.iot.script.engine;  
2 -  
3 -import cn.iocoder.yudao.module.iot.script.context.ScriptContext;  
4 -  
5 -/**  
6 - * 脚本引擎接口,定义脚本执行的核心功能  
7 - */  
8 -public interface ScriptEngine {  
9 -  
10 - /**  
11 - * 执行脚本  
12 - *  
13 - * @param script 脚本内容  
14 - * @param context 脚本上下文  
15 - * @return 脚本执行结果  
16 - */  
17 - Object execute(String script, ScriptContext context);  
18 -  
19 - /**  
20 - * 获取脚本引擎类型  
21 - *  
22 - * @return 脚本引擎类型  
23 - */  
24 - String getType();  
25 -}  
26 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngineFactory.java deleted
1 -package cn.iocoder.yudao.module.iot.script.engine;  
2 -  
3 -import cn.iocoder.yudao.module.iot.script.sandbox.JsSandbox;  
4 -import lombok.extern.slf4j.Slf4j;  
5 -import org.springframework.beans.factory.DisposableBean;  
6 -import org.springframework.stereotype.Component;  
7 -  
8 -import java.util.Map;  
9 -import java.util.concurrent.ConcurrentHashMap;  
10 -  
11 -/**  
12 - * 脚本引擎工厂,用于创建和缓存不同类型的脚本引擎,支持资源生命周期管理  
13 - */  
14 -@Slf4j  
15 -@Component  
16 -public class ScriptEngineFactory implements DisposableBean {  
17 -  
18 - /**  
19 - * 脚本引擎缓存  
20 - */  
21 - private final Map<String, ScriptEngine> engines = new ConcurrentHashMap<>();  
22 -  
23 - /**  
24 - * 获取脚本引擎  
25 - *  
26 - * @param type 脚本类型  
27 - * @return 脚本引擎  
28 - */  
29 - public ScriptEngine getEngine(String type) {  
30 - // 从缓存中获取引擎  
31 - return engines.computeIfAbsent(type, this::createEngine);  
32 - }  
33 -  
34 - /**  
35 - * 创建脚本引擎  
36 - *  
37 - * @param type 脚本类型  
38 - * @return 脚本引擎  
39 - */  
40 - private ScriptEngine createEngine(String type) {  
41 - try {  
42 - if (JsScriptEngine.TYPE.equals(type)) {  
43 - log.info("创建 GraalJS 脚本引擎");  
44 - return new JsScriptEngine(new JsSandbox());  
45 - }  
46 -  
47 - log.warn("不支持的脚本类型: {}", type);  
48 - return null;  
49 - } catch (Exception e) {  
50 - log.error("创建脚本引擎 [{}] 失败: {}", type, e.getMessage(), e);  
51 - throw new RuntimeException("创建脚本引擎失败: " + e.getMessage(), e);  
52 - }  
53 - }  
54 -  
55 - /**  
56 - * 释放指定类型的引擎资源  
57 - *  
58 - * @param type 脚本类型  
59 - */  
60 - public void releaseEngine(String type) {  
61 - ScriptEngine engine = engines.remove(type);  
62 - if (engine instanceof AutoCloseable) {  
63 - try {  
64 - ((AutoCloseable) engine).close();  
65 - log.info("已释放脚本引擎资源: {}", type);  
66 - } catch (Exception e) {  
67 - log.warn("释放脚本引擎 [{}] 资源时发生错误: {}", type, e.getMessage());  
68 - }  
69 - }  
70 - }  
71 -  
72 - /**  
73 - * 清理所有引擎资源  
74 - */  
75 - public void releaseAllEngines() {  
76 - engines.keySet().forEach(this::releaseEngine);  
77 - log.info("已清理所有脚本引擎资源");  
78 - }  
79 -  
80 - @Override  
81 - public void destroy() {  
82 - log.info("应用关闭,释放所有脚本引擎资源...");  
83 - releaseAllEngines();  
84 - }  
85 -}  
86 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/GraalJsExample.java deleted
1 -package cn.iocoder.yudao.module.iot.script.example;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.iocoder.yudao.module.iot.script.context.DefaultScriptContext;  
5 -import cn.iocoder.yudao.module.iot.script.context.ScriptContext;  
6 -import cn.iocoder.yudao.module.iot.script.service.ScriptService;  
7 -import lombok.extern.slf4j.Slf4j;  
8 -import org.springframework.beans.factory.annotation.Autowired;  
9 -import org.springframework.stereotype.Component;  
10 -  
11 -import java.util.Map;  
12 -import java.util.concurrent.CompletableFuture;  
13 -import java.util.concurrent.ExecutionException;  
14 -import java.util.concurrent.TimeUnit;  
15 -import java.util.concurrent.TimeoutException;  
16 -  
17 -// TODO @haohao:搞到 test 里面哈;  
18 -/**  
19 - * GraalJS 脚本引擎示例  
20 - * <p>  
21 - * 展示了如何使用 GraalJS 脚本引擎的各种功能  
22 - */  
23 -@Slf4j  
24 -@Component  
25 -public class GraalJsExample {  
26 -  
27 - @Autowired  
28 - private ScriptService scriptService;  
29 -  
30 - /**  
31 - * 执行简单的 JavaScript 脚本  
32 - *  
33 - * @return 执行结果  
34 - */  
35 - public Object executeSimpleScript() {  
36 - // 简单的脚本内容  
37 - String script = "var result = a + b; result;";  
38 -  
39 - // 创建参数  
40 - Map<String, Object> params = MapUtil.newHashMap();  
41 - params.put("a", 10);  
42 - params.put("b", 20);  
43 -  
44 - // 执行脚本  
45 - return scriptService.executeJavaScript(script, params);  
46 - }  
47 -  
48 - /**  
49 - * 执行现代 JavaScript 语法(ES6+)  
50 - *  
51 - * @return 执行结果  
52 - */  
53 - public Object executeModernJavaScript() {  
54 - // 使用现代 JavaScript 语法  
55 - String script = "// 使用箭头函数\n" +  
56 - "const add = (a, b) => a + b;\n" +  
57 - "\n" +  
58 - "// 使用解构赋值\n" +  
59 - "const {c, d} = params;\n" +  
60 - "\n" +  
61 - "// 使用模板字符串\n" +  
62 - "const result = `计算结果: ${add(c, d)}`;\n" +  
63 - "\n" +  
64 - "// 使用可选链操作符\n" +  
65 - "const value = params?.e?.value ?? 'default';\n" +  
66 - "\n" +  
67 - "// 返回一个对象\n" +  
68 - "({\n" +  
69 - " sum: add(c, d),\n" +  
70 - " message: result,\n" +  
71 - " defaultValue: value\n" +  
72 - "})";  
73 -  
74 - // 创建参数  
75 - Map<String, Object> params = MapUtil.newHashMap();  
76 - params.put("params", MapUtil.builder()  
77 - .put("c", 30)  
78 - .put("d", 40)  
79 - .build());  
80 -  
81 - // 执行脚本  
82 - return scriptService.executeJavaScript(script, params);  
83 - }  
84 -  
85 - /**  
86 - * 执行带错误处理的脚本  
87 - *  
88 - * @return 执行结果  
89 - */  
90 - public Object executeWithErrorHandling() {  
91 - // 包含错误处理的脚本  
92 - String script = "try {\n" +  
93 - " // 故意制造错误\n" +  
94 - " if (!nonExistentVar) {\n" +  
95 - " throw new Error('手动抛出的错误');\n" +  
96 - " }\n" +  
97 - "} catch (error) {\n" +  
98 - " console.error('捕获到错误: ' + error.message);\n" +  
99 - " return { success: false, error: error.message };\n" +  
100 - "}\n" +  
101 - "\n" +  
102 - "return { success: true, data: 'No error' };";  
103 -  
104 - // 执行脚本  
105 - return scriptService.executeJavaScript(script, MapUtil.newHashMap());  
106 - }  
107 -  
108 - /**  
109 - * 演示超时控制  
110 - *  
111 - * @return 执行结果  
112 - */  
113 - public Object executeWithTimeout() {  
114 - // 这个脚本会导致无限循环  
115 - String script = "// 无限循环\n" +  
116 - "var counter = 0;\n" +  
117 - "while(true) {\n" +  
118 - " counter++;\n" +  
119 - " if (counter % 1000000 === 0) {\n" +  
120 - " console.log('Still running: ' + counter);\n" +  
121 - " }\n" +  
122 - "}\n" +  
123 - "return counter;";  
124 -  
125 - // 使用 CompletableFuture 和超时控制  
126 - CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {  
127 - try {  
128 - return scriptService.executeJavaScript(script, MapUtil.newHashMap());  
129 - } catch (Exception e) {  
130 - log.error("脚本执行失败: {}", e.getMessage());  
131 - return "执行失败: " + e.getMessage();  
132 - }  
133 - });  
134 -  
135 - try {  
136 - // 等待结果,最多 10 秒  
137 - return future.get(10, TimeUnit.SECONDS);  
138 - } catch (InterruptedException | ExecutionException e) {  
139 - return "执行异常: " + e.getMessage();  
140 - } catch (TimeoutException e) {  
141 - future.cancel(true);  
142 - return "执行超时,已强制终止";  
143 - }  
144 - }  
145 -  
146 - /**  
147 - * 演示 JSON 处理  
148 - *  
149 - * @return 执行结果  
150 - */  
151 - public Object executeJsonProcessing() {  
152 - // JSON 处理示例  
153 - String script = "// 解析传入的 JSON\n" +  
154 - "var data = JSON.parse(jsonString);\n" +  
155 - "\n" +  
156 - "// 处理 JSON 数据\n" +  
157 - "var result = {\n" +  
158 - " name: data.name.toUpperCase(),\n" +  
159 - " age: data.age + 1,\n" +  
160 - " address: data.address || 'Unknown',\n" +  
161 - " tags: [...data.tags, 'processed'],\n" +  
162 - " timestamp: Date.now()\n" +  
163 - "};\n" +  
164 - "\n" +  
165 - "// 转换回 JSON 字符串\n" +  
166 - "JSON.stringify(result);";  
167 -  
168 - // 创建上下文  
169 - ScriptContext context = new DefaultScriptContext();  
170 - context.setParameter("jsonString",  
171 - "{\"name\":\"test user\",\"age\":25,\"tags\":[\"tag1\",\"tag2\"]}");  
172 -  
173 - // 执行脚本  
174 - return scriptService.executeJavaScript(script, context);  
175 - }  
176 -  
177 - /**  
178 - * 演示数据转换  
179 - *  
180 - * @return 执行结果  
181 - */  
182 - public Object executeDataConversion() {  
183 - // 数据转换和处理示例  
184 - String script = "// 使用 utils 工具类进行数据转换\n" +  
185 - "var stringValue = utils.isEmpty(input) ? \"默认值\" : input;\n" +  
186 - "var numberValue = utils.convert(stringValue, \"number\");\n" +  
187 - "\n" +  
188 - "// 创建一个复杂数据结构\n" +  
189 - "var result = {\n" +  
190 - " original: input,\n" +  
191 - " stringValue: stringValue,\n" +  
192 - " numberValue: numberValue,\n" +  
193 - " booleanValue: Boolean(numberValue),\n" +  
194 - " isValid: utils.isNotEmpty(input)\n" +  
195 - "};\n" +  
196 - "\n" +  
197 - "// 记录处理结果\n" +  
198 - "log.info(\"处理结果: \" + utils.toJson(result));\n" +  
199 - "\n" +  
200 - "return result;";  
201 -  
202 - // 创建参数  
203 - Map<String, Object> params = MapUtil.newHashMap();  
204 - params.put("input", "42");  
205 -  
206 - // 执行脚本  
207 - return scriptService.executeJavaScript(script, params);  
208 - }  
209 -}  
210 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/ProductScriptSamples.java deleted
1 -package cn.iocoder.yudao.module.iot.script.example;  
2 -  
3 -import lombok.extern.slf4j.Slf4j;  
4 -import org.springframework.stereotype.Component;  
5 -  
6 -/**  
7 - * 产品脚本示例类,提供各种类型的产品脚本示例代码  
8 - */  
9 -@Slf4j  
10 -@Component  
11 -public class ProductScriptSamples {  
12 -  
13 - /**  
14 - * 获取属性解析脚本示例  
15 - *  
16 - * @return 属性解析脚本示例代码  
17 - */  
18 - public String getPropertyParserSample() {  
19 - return "/**\n" +  
20 - " * 属性上报数据解析脚本示例\n" +  
21 - " * @param input 设备上报的原始数据\n" +  
22 - " * @param productKey 产品标识\n" +  
23 - " * @param method 方法类型,固定为 property\n" +  
24 - " * @param properties 当前设备的属性数据\n" +  
25 - " * @return 解析后的属性数据\n" +  
26 - " */\n" +  
27 - "function parseProperty(input, productKey) {\n" +  
28 - " // 记录日志\n" +  
29 - " console.log('开始解析属性数据: ' + input);\n" +  
30 - " \n" +  
31 - " try {\n" +  
32 - " // 假设上报的是 JSON 字符串\n" +  
33 - " var data = JSON.parse(input);\n" +  
34 - " \n" +  
35 - " // 构建属性数据结构\n" +  
36 - " var result = {\n" +  
37 - " // 属性上报的时间戳,毫秒级\n" +  
38 - " timestamp: data.timestamp || Date.now(),\n" +  
39 - " // 属性数据\n" +  
40 - " params: {}\n" +  
41 - " };\n" +  
42 - " \n" +  
43 - " // 处理属性值\n" +  
44 - " if (data.temperature) {\n" +  
45 - " result.params.temperature = parseFloat(data.temperature);\n" +  
46 - " }\n" +  
47 - " \n" +  
48 - " if (data.humidity) {\n" +  
49 - " result.params.humidity = parseFloat(data.humidity);\n" +  
50 - " }\n" +  
51 - " \n" +  
52 - " console.log('属性解析结果: ' + JSON.stringify(result));\n" +  
53 - " return result;\n" +  
54 - " } catch (error) {\n" +  
55 - " console.error('解析属性数据失败: ' + error.message);\n" +  
56 - " throw new Error('解析失败: ' + error.message);\n" +  
57 - " }\n" +  
58 - "};\n" +  
59 - "\n" +  
60 - "// 执行解析\n" +  
61 - "parseProperty(input, productKey);";  
62 - }  
63 -  
64 - /**  
65 - * 获取事件解析脚本示例  
66 - *  
67 - * @return 事件解析脚本示例代码  
68 - */  
69 - public String getEventParserSample() {  
70 - return "/**\n" +  
71 - " * 事件数据解析脚本示例\n" +  
72 - " * @param input 设备上报的原始数据\n" +  
73 - " * @param productKey 产品标识\n" +  
74 - " * @param method 方法类型,固定为 event\n" +  
75 - " * @param identifier 事件标识符\n" +  
76 - " * @return 解析后的事件数据\n" +  
77 - " */\n" +  
78 - "function parseEvent(input, productKey, identifier) {\n" +  
79 - " // 记录日志\n" +  
80 - " console.log('开始解析事件数据: ' + input);\n" +  
81 - " console.log('事件标识符: ' + identifier);\n" +  
82 - " \n" +  
83 - " try {\n" +  
84 - " // 假设上报的是 JSON 字符串\n" +  
85 - " var data = JSON.parse(input);\n" +  
86 - " \n" +  
87 - " // 构建事件数据结构\n" +  
88 - " var result = {\n" +  
89 - " // 事件标识符\n" +  
90 - " identifier: identifier || 'alert',\n" +  
91 - " // 事件上报的时间戳,毫秒级\n" +  
92 - " timestamp: data.timestamp || Date.now(),\n" +  
93 - " // 事件参数\n" +  
94 - " params: {}\n" +  
95 - " };\n" +  
96 - " \n" +  
97 - " // 根据不同事件类型处理参数\n" +  
98 - " if (result.identifier === 'alert') {\n" +  
99 - " result.params.level = data.level || 'info';\n" +  
100 - " result.params.message = data.message || '';\n" +  
101 - " } else if (result.identifier === 'error') {\n" +  
102 - " result.params.code = data.code || 0;\n" +  
103 - " result.params.message = data.message || '';\n" +  
104 - " }\n" +  
105 - " \n" +  
106 - " console.log('事件解析结果: ' + JSON.stringify(result));\n" +  
107 - " return result;\n" +  
108 - " } catch (error) {\n" +  
109 - " console.error('解析事件数据失败: ' + error.message);\n" +  
110 - " throw new Error('解析失败: ' + error.message);\n" +  
111 - " }\n" +  
112 - "};\n" +  
113 - "\n" +  
114 - "// 执行解析\n" +  
115 - "parseEvent(input, productKey, identifier);";  
116 - }  
117 -  
118 - /**  
119 - * 获取命令编码脚本示例  
120 - *  
121 - * @return 命令编码脚本示例代码  
122 - */  
123 - public String getCommandEncoderSample() {  
124 - return "/**\n" +  
125 - " * 命令数据编码脚本示例\n" +  
126 - " * @param input 平台下发的命令数据\n" +  
127 - " * @param productKey 产品标识\n" +  
128 - " * @param method 方法类型,固定为 command\n" +  
129 - " * @param cmdParams 命令参数\n" +  
130 - " * @return 编码后的命令数据\n" +  
131 - " */\n" +  
132 - "function encodeCommand(input, productKey, cmdParams) {\n" +  
133 - " // 记录日志\n" +  
134 - " console.log('开始编码命令数据: ' + input);\n" +  
135 - " console.log('命令参数: ' + JSON.stringify(cmdParams));\n" +  
136 - " \n" +  
137 - " try {\n" +  
138 - " // 输入可能是 JSON 字符串或对象\n" +  
139 - " var data = typeof input === 'string' ? JSON.parse(input) : input;\n" +  
140 - " \n" +  
141 - " // 获取命令名称和值\n" +  
142 - " var cmdName = cmdParams.cmdName || '';\n" +  
143 - " var cmdValue = cmdParams.cmdValue;\n" +  
144 - " \n" +  
145 - " // 构建设备可识别的命令格式\n" +  
146 - " var result = {\n" +  
147 - " cmd: cmdName,\n" +  
148 - " value: cmdValue,\n" +  
149 - " timestamp: Date.now()\n" +  
150 - " };\n" +  
151 - " \n" +  
152 - " // 根据不同命令类型构建参数\n" +  
153 - " if (cmdName === 'setValue') {\n" +  
154 - " // 无需额外处理\n" +  
155 - " } else if (cmdName === 'control') {\n" +  
156 - " result.mode = data.mode || 'auto';\n" +  
157 - " result.action = data.action || 'start';\n" +  
158 - " }\n" +  
159 - " \n" +  
160 - " // 转换为设备能识别的格式(此处以 JSON 字符串为例)\n" +  
161 - " var encodedResult = JSON.stringify(result);\n" +  
162 - " \n" +  
163 - " console.log('命令编码结果: ' + encodedResult);\n" +  
164 - " return encodedResult;\n" +  
165 - " } catch (error) {\n" +  
166 - " console.error('编码命令数据失败: ' + error.message);\n" +  
167 - " throw new Error('编码失败: ' + error.message);\n" +  
168 - " }\n" +  
169 - "};\n" +  
170 - "\n" +  
171 - "// 执行编码\n" +  
172 - "encodeCommand(input, productKey, cmdParams);";  
173 - }  
174 -}  
175 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/package-info.java deleted
1 -/**  
2 - * IoT 脚本模块,提供脚本引擎、执行环境和沙箱功能,支持 JavaScript 脚本的执行  
3 - */  
4 -package cn.iocoder.yudao.module.iot.script;  
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/JsSandbox.java deleted
1 -package cn.iocoder.yudao.module.iot.script.sandbox;  
2 -  
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.hutool.core.util.StrUtil;  
5 -import lombok.extern.slf4j.Slf4j;  
6 -  
7 -import java.util.ArrayList;  
8 -import java.util.Arrays;  
9 -import java.util.List;  
10 -import java.util.regex.Matcher;  
11 -import java.util.regex.Pattern;  
12 -  
13 -/**  
14 - * JavaScript 沙箱实现,提供脚本安全性验证  
15 - */  
16 -@Slf4j  
17 -public class JsSandbox implements ScriptSandbox {  
18 -  
19 - /**  
20 - * JavaScript 沙箱类型  
21 - */  
22 - public static final String TYPE = "js";  
23 -  
24 - /**  
25 - * 不安全的关键字列表  
26 - */  
27 - private final List<String> unsafeKeywords = new ArrayList<>();  
28 -  
29 - /**  
30 - * 可能导致高资源消耗的关键字  
31 - */  
32 - private final List<String> highResourceKeywords = new ArrayList<>();  
33 -  
34 - /**  
35 - * 不安全的包/类访问模式  
36 - */  
37 - private final List<Pattern> unsafePatterns = new ArrayList<>();  
38 -  
39 - /**  
40 - * 递归或循环嵌套深度检测模式  
41 - */  
42 - private final List<Pattern> recursionPatterns = new ArrayList<>();  
43 -  
44 - /**  
45 - * 允许的脚本最大长度(字节)  
46 - */  
47 - private static final int MAX_SCRIPT_LENGTH = 100 * 1024; // 100KB  
48 -  
49 - /**  
50 - * 脚本安全验证超时时间(毫秒)  
51 - */  
52 - private static final long VALIDATION_TIMEOUT = 1000; // 1秒  
53 -  
54 - /**  
55 - * 构造函数,初始化不安全的关键字和模式  
56 - */  
57 - public JsSandbox() {  
58 - // 初始化 Java 相关的不安全关键字  
59 - // TODO @haohao:可以使用 addAll 哈。  
60 - Arrays.asList(  
61 - "java.lang.System",  
62 - "java.io",  
63 - "java.net",  
64 - "java.nio",  
65 - "java.security",  
66 - "java.rmi",  
67 - "java.lang.reflect",  
68 - "java.sql",  
69 - "javax.sql",  
70 - "javax.naming",  
71 - "javax.script",  
72 - "javax.tools",  
73 - "org.omg",  
74 - "org.graalvm.polyglot",  
75 - "sun.",  
76 - "javafx.",  
77 - "Packages.",  
78 - "com.sun.",  
79 - "com.oracle.").forEach(unsafeKeywords::add);  
80 -  
81 - // GraalJS 特有的不安全关键字  
82 - Arrays.asList(  
83 - "Polyglot.import",  
84 - "Polyglot.eval",  
85 - "Java.type",  
86 - "allowHostAccess",  
87 - "allowNativeAccess",  
88 - "allowIO",  
89 - "allowHostClassLoading",  
90 - "allowAllAccess",  
91 - "allowExperimentalOptions",  
92 - "Context.Builder",  
93 - "Context.create",  
94 - "Context.getCurrent",  
95 - "Context.newBuilder",  
96 - "__proto__",  
97 - "__defineGetter__",  
98 - "__defineSetter__",  
99 - "__lookupGetter__",  
100 - "__lookupSetter__",  
101 - "__noSuchMethod__",  
102 - "constructor.constructor",  
103 - "Object.constructor").forEach(unsafeKeywords::add);  
104 -  
105 - // 可能导致高资源消耗的关键字  
106 - Arrays.asList(  
107 - "while(true)",  
108 - "for(;;)",  
109 - "do{",  
110 - "BigInt",  
111 - "Promise.all",  
112 - "setTimeout",  
113 - "setInterval",  
114 - "new Array(",  
115 - "Array(",  
116 - "new ArrayBuffer(",  
117 - ".repeat(",  
118 - ".forEach(",  
119 - ".map(",  
120 - ".reduce(").forEach(highResourceKeywords::add);  
121 -  
122 - // 初始化不安全的模式  
123 - // 系统访问和进程执行  
124 - unsafePatterns.add(Pattern.compile("java\\.lang\\.Runtime"));  
125 - unsafePatterns.add(Pattern.compile("java\\.lang\\.ProcessBuilder"));  
126 - unsafePatterns.add(Pattern.compile("java\\.lang\\.reflect"));  
127 -  
128 - // 特殊对象和操作  
129 - unsafePatterns.add(Pattern.compile("Packages"));  
130 - unsafePatterns.add(Pattern.compile("JavaImporter"));  
131 - unsafePatterns.add(Pattern.compile("load\\s*\\("));  
132 - unsafePatterns.add(Pattern.compile("loadWithNewGlobal\\s*\\("));  
133 - unsafePatterns.add(Pattern.compile("exit\\s*\\("));  
134 - unsafePatterns.add(Pattern.compile("quit\\s*\\("));  
135 - unsafePatterns.add(Pattern.compile("eval\\s*\\("));  
136 -  
137 - // GraalJS 特有的不安全模式  
138 - unsafePatterns.add(Pattern.compile("Polyglot\\."));  
139 - unsafePatterns.add(Pattern.compile("Java\\.type\\s*\\("));  
140 - unsafePatterns.add(Pattern.compile("Context\\."));  
141 - unsafePatterns.add(Pattern.compile("Engine\\."));  
142 -  
143 - // 原型污染检测  
144 - unsafePatterns.add(Pattern.compile("(?:Object|Array|String|Number|Boolean|Function|RegExp|Date)\\.prototype"));  
145 - unsafePatterns.add(Pattern.compile("\\['constructor'\\]"));  
146 - unsafePatterns.add(Pattern.compile("\\[\"constructor\"\\]"));  
147 - unsafePatterns.add(Pattern.compile("\\['__proto__'\\]"));  
148 - unsafePatterns.add(Pattern.compile("\\[\"__proto__\"\\]"));  
149 -  
150 - // 检测可能导致无限递归或循环的模式  
151 - recursionPatterns.add(Pattern.compile("for\\s*\\([^\\)]*\\)\\s*\\{[^\\}]*for\\s*\\(")); // 嵌套循环  
152 - recursionPatterns.add(Pattern.compile("while\\s*\\([^\\)]*\\)\\s*\\{[^\\}]*while\\s*\\(")); // 嵌套 while  
153 - recursionPatterns.add(Pattern.compile("function\\s+[a-zA-Z0-9_$]+\\s*\\([^\\)]*\\)\\s*\\{[^\\}]*\\1\\s*\\(")); // 递归函数调用  
154 - }  
155 -  
156 - @Override  
157 - public boolean validate(String script) {  
158 - if (StrUtil.isBlank(script)) {  
159 - return true;  
160 - }  
161 -  
162 - // 检查脚本长度  
163 - if (script.length() > MAX_SCRIPT_LENGTH) {  
164 - log.warn("脚本长度超过限制: {} > {}", script.length(), MAX_SCRIPT_LENGTH);  
165 - return false;  
166 - }  
167 -  
168 - // 使用超时机制进行验证  
169 - final boolean[] result = {true};  
170 - Thread validationThread = new Thread(() -> {  
171 - // 检查不安全的关键字  
172 - if (containsUnsafeKeywords(script)) {  
173 - result[0] = false;  
174 - return;  
175 - }  
176 -  
177 - // 检查不安全的模式  
178 - if (matchesUnsafePatterns(script)) {  
179 - result[0] = false;  
180 - return;  
181 - }  
182 -  
183 - // 检查可能导致高资源消耗的构造  
184 - if (containsHighResourcePatterns(script)) {  
185 - log.warn("脚本包含可能导致高资源消耗的构造,需要注意");  
186 - // 不直接拒绝,而是记录警告  
187 - }  
188 -  
189 - // 分析脚本复杂度  
190 - analyzeScriptComplexity(script);  
191 - });  
192 -  
193 - validationThread.start();  
194 - try {  
195 - validationThread.join(VALIDATION_TIMEOUT);  
196 - if (validationThread.isAlive()) {  
197 - validationThread.interrupt();  
198 - log.warn("脚本安全验证超时");  
199 - return false;  
200 - }  
201 - } catch (InterruptedException e) {  
202 - log.warn("脚本安全验证被中断");  
203 - return false;  
204 - }  
205 -  
206 - return result[0];  
207 - }  
208 -  
209 - @Override  
210 - public String getType() {  
211 - return TYPE;  
212 - }  
213 -  
214 - /**  
215 - * 检查脚本是否包含不安全的关键字  
216 - *  
217 - * @param script 脚本内容  
218 - * @return 是否包含不安全的关键字  
219 - */  
220 - private boolean containsUnsafeKeywords(String script) {  
221 - if (CollUtil.isEmpty(unsafeKeywords)) {  
222 - return false;  
223 - }  
224 -  
225 - for (String keyword : unsafeKeywords) {  
226 - if (script.contains(keyword)) {  
227 - log.warn("脚本包含不安全的关键字: {}", keyword);  
228 - return true;  
229 - }  
230 - }  
231 - return false;  
232 - }  
233 -  
234 - /**  
235 - * 检查脚本是否匹配不安全的模式  
236 - *  
237 - * @param script 脚本内容  
238 - * @return 是否匹配不安全的模式  
239 - */  
240 - private boolean matchesUnsafePatterns(String script) {  
241 - if (CollUtil.isEmpty(unsafePatterns)) {  
242 - return false;  
243 - }  
244 -  
245 - for (Pattern pattern : unsafePatterns) {  
246 - Matcher matcher = pattern.matcher(script);  
247 - if (matcher.find()) {  
248 - log.warn("脚本匹配到不安全的模式: {}", pattern.pattern());  
249 - return true;  
250 - }  
251 - }  
252 - return false;  
253 - }  
254 -  
255 - /**  
256 - * 检查脚本是否包含可能导致高资源消耗的模式  
257 - *  
258 - * @param script 脚本内容  
259 - * @return 是否包含高资源消耗模式  
260 - */  
261 - private boolean containsHighResourcePatterns(String script) {  
262 - if (CollUtil.isEmpty(highResourceKeywords)) {  
263 - return false;  
264 - }  
265 -  
266 - boolean result = false;  
267 - for (String pattern : highResourceKeywords) {  
268 - if (script.contains(pattern)) {  
269 - log.warn("脚本包含高资源消耗模式: {}", pattern);  
270 - result = true;  
271 - }  
272 - }  
273 -  
274 - // 还要检查递归或嵌套循环模式  
275 - for (Pattern pattern : recursionPatterns) {  
276 - Matcher matcher = pattern.matcher(script);  
277 - if (matcher.find()) {  
278 - log.warn("脚本包含嵌套循环或递归调用: {}", pattern.pattern());  
279 - result = true;  
280 - }  
281 - }  
282 -  
283 - return result;  
284 - }  
285 -  
286 - /**  
287 - * 分析脚本复杂度  
288 - *  
289 - * @param script 脚本内容  
290 - */  
291 - private void analyzeScriptComplexity(String script) {  
292 - // 计算循环和条件语句的数量  
293 - int forCount = countOccurrences(script, "for(");  
294 - forCount += countOccurrences(script, "for (");  
295 -  
296 - int whileCount = countOccurrences(script, "while(");  
297 - whileCount += countOccurrences(script, "while (");  
298 -  
299 - int doWhileCount = countOccurrences(script, "do{");  
300 - doWhileCount += countOccurrences(script, "do {");  
301 -  
302 - int funcCount = countOccurrences(script, "function");  
303 -  
304 - // 记录复杂度评估  
305 - if (forCount + whileCount + doWhileCount > 10) {  
306 - log.warn("脚本循环结构过多: for={}, while={}, do-while={}", forCount, whileCount, doWhileCount);  
307 - }  
308 -  
309 - if (funcCount > 20) {  
310 - log.warn("脚本函数定义过多: {}", funcCount);  
311 - }  
312 - }  
313 -  
314 - /**  
315 - * 计算字符串出现次数  
316 - *  
317 - * @param source 源字符串  
318 - * @param substring 子字符串  
319 - * @return 出现次数  
320 - */  
321 - private int countOccurrences(String source, String substring) {  
322 - int count = 0;  
323 - int index = 0;  
324 - while ((index = source.indexOf(substring, index)) != -1) {  
325 - count++;  
326 - index += substring.length();  
327 - }  
328 - return count;  
329 - }  
330 -}  
331 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/ScriptSandbox.java deleted
1 -package cn.iocoder.yudao.module.iot.script.sandbox;  
2 -  
3 -/**  
4 - * 脚本沙箱接口,提供脚本安全性验证  
5 - */  
6 -public interface ScriptSandbox {  
7 -  
8 - /**  
9 - * 验证脚本内容是否安全  
10 - *  
11 - * @param script 脚本内容  
12 - * @return 脚本是否安全  
13 - */  
14 - boolean validate(String script);  
15 -  
16 - /**  
17 - * 获取沙箱类型  
18 - *  
19 - * @return 沙箱类型  
20 - */  
21 - String getType();  
22 -}  
23 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptService.java deleted
1 -package cn.iocoder.yudao.module.iot.script.service;  
2 -  
3 -import cn.iocoder.yudao.module.iot.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 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptServiceImpl.java deleted
1 -package cn.iocoder.yudao.module.iot.script.service;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.hutool.core.util.StrUtil;  
5 -import cn.iocoder.yudao.module.iot.script.context.DefaultScriptContext;  
6 -import cn.iocoder.yudao.module.iot.script.context.ScriptContext;  
7 -import cn.iocoder.yudao.module.iot.script.engine.JsScriptEngine;  
8 -import cn.iocoder.yudao.module.iot.script.engine.ScriptEngine;  
9 -import cn.iocoder.yudao.module.iot.script.engine.ScriptEngineFactory;  
10 -import cn.iocoder.yudao.module.iot.script.sandbox.JsSandbox;  
11 -import cn.iocoder.yudao.module.iot.script.sandbox.ScriptSandbox;  
12 -import lombok.extern.slf4j.Slf4j;  
13 -import org.springframework.beans.factory.annotation.Autowired;  
14 -import org.springframework.stereotype.Service;  
15 -  
16 -import java.util.Map;  
17 -  
18 -/**  
19 - * 脚本服务实现类  
20 - */  
21 -@Slf4j  
22 -@Service  
23 -public class ScriptServiceImpl implements ScriptService {  
24 -  
25 - @Autowired  
26 - private ScriptEngineFactory engineFactory;  
27 -  
28 - @Override  
29 - public Object executeScript(String scriptType, String script, ScriptContext context) {  
30 - if (StrUtil.isBlank(scriptType) || StrUtil.isBlank(script)) {  
31 - return null;  
32 - }  
33 -  
34 - ScriptEngine engine = engineFactory.getEngine(scriptType);  
35 - if (engine == null) {  
36 - log.error("找不到脚本引擎: {}", scriptType);  
37 - throw new RuntimeException("不支持的脚本类型: " + scriptType);  
38 - }  
39 -  
40 - try {  
41 - return engine.execute(script, context);  
42 - } catch (Exception e) {  
43 - // TODO @haohao:最好打印一些参数;下面类似的也是  
44 - log.error("执行脚本失败: {}", e.getMessage(), e);  
45 - throw new RuntimeException("执行脚本失败: " + e.getMessage(), e);  
46 - }  
47 - }  
48 -  
49 - @Override  
50 - public Object executeScript(String scriptType, String script, Map<String, Object> params) {  
51 - ScriptContext context = createContext(params);  
52 - return executeScript(scriptType, script, context);  
53 - }  
54 -  
55 - @Override  
56 - public Object executeJavaScript(String script, ScriptContext context) {  
57 - return executeScript(JsScriptEngine.TYPE, script, context);  
58 - }  
59 -  
60 - @Override  
61 - public Object executeJavaScript(String script, Map<String, Object> params) {  
62 - return executeScript(JsScriptEngine.TYPE, script, params);  
63 - }  
64 -  
65 - @Override  
66 - public boolean validateScript(String scriptType, String script) {  
67 - if (StrUtil.isBlank(scriptType) || StrUtil.isBlank(script)) {  
68 - return true;  
69 - }  
70 -  
71 - ScriptSandbox sandbox = getSandbox(scriptType);  
72 - if (sandbox == null) {  
73 - log.warn("找不到对应的脚本沙箱: {}", scriptType);  
74 - return false;  
75 - }  
76 -  
77 - try {  
78 - return sandbox.validate(script);  
79 - } catch (Exception e) {  
80 - log.error("验证脚本安全性失败: {}", e.getMessage(), e);  
81 - return false;  
82 - }  
83 - }  
84 -  
85 - /**  
86 - * 根据脚本类型获取对应的沙箱实现  
87 - *  
88 - * @param scriptType 脚本类型  
89 - * @return 沙箱实现  
90 - */  
91 - private ScriptSandbox getSandbox(String scriptType) {  
92 - if (JsScriptEngine.TYPE.equals(scriptType)) {  
93 - return new JsSandbox();  
94 - }  
95 - return null;  
96 - }  
97 -  
98 - /**  
99 - * 根据参数创建脚本上下文  
100 - *  
101 - * @param params 参数  
102 - * @return 脚本上下文  
103 - */  
104 - private ScriptContext createContext(Map<String, Object> params) {  
105 - ScriptContext context = new DefaultScriptContext();  
106 - if (MapUtil.isNotEmpty(params)) {  
107 - params.forEach(context::setParameter);  
108 - }  
109 - return context;  
110 - }  
111 -}  
112 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/util/ScriptUtils.java deleted
1 -package cn.iocoder.yudao.module.iot.script.util;  
2 -  
3 -import cn.hutool.core.convert.Convert;  
4 -import cn.hutool.core.map.MapUtil;  
5 -import cn.hutool.core.util.StrUtil;  
6 -import cn.hutool.json.JSONUtil;  
7 -import lombok.extern.slf4j.Slf4j;  
8 -  
9 -import java.util.Map;  
10 -  
11 -/**  
12 - * 脚本工具类,提供给脚本执行环境使用的工具方法  
13 - */  
14 -@Slf4j  
15 -public class ScriptUtils {  
16 -  
17 - /**  
18 - * 单例实例  
19 - */  
20 - private static final ScriptUtils INSTANCE = new ScriptUtils();  
21 -  
22 - /**  
23 - * 获取单例实例  
24 - *  
25 - * @return 工具类实例  
26 - */  
27 - public static ScriptUtils getInstance() {  
28 - return INSTANCE;  
29 - }  
30 -  
31 - // TODO @haohao:使用 lombok 简化掉  
32 - private ScriptUtils() {  
33 - // 私有构造函数  
34 - }  
35 -  
36 - /**  
37 - * 字符串是否为空  
38 - *  
39 - * @param str 字符串  
40 - * @return 是否为空  
41 - */  
42 - public boolean isEmpty(String str) {  
43 - return StrUtil.isEmpty(str);  
44 - }  
45 -  
46 - /**  
47 - * 字符串是否不为空  
48 - *  
49 - * @param str 字符串  
50 - * @return 是否不为空  
51 - */  
52 - public boolean isNotEmpty(String str) {  
53 - return StrUtil.isNotEmpty(str);  
54 - }  
55 -  
56 - /**  
57 - * 将对象转为 JSON 字符串  
58 - *  
59 - * @param obj 对象  
60 - * @return JSON 字符串  
61 - */  
62 - public String toJson(Object obj) {  
63 - return JSONUtil.toJsonStr(obj);  
64 - }  
65 -  
66 - /**  
67 - * 将 JSON 字符串转为 Map  
68 - *  
69 - * @param json JSON 字符串  
70 - * @return Map 对象  
71 - */  
72 - public Map<String, Object> parseJson(String json) {  
73 - if (StrUtil.isEmpty(json)) {  
74 - return MapUtil.newHashMap();  
75 - }  
76 - try {  
77 - return JSONUtil.toBean(json, Map.class);  
78 - } catch (Exception e) {  
79 - log.error("JSON 解析失败: {}", json, e);  
80 - return MapUtil.newHashMap();  
81 - }  
82 - }  
83 -  
84 - /**  
85 - * 类型转换  
86 - *  
87 - * @param value 值  
88 - * @param type 目标类型  
89 - * @param <T> 泛型  
90 - * @return 转换后的值  
91 - */  
92 - public <T> T convert(Object value, Class<T> type) {  
93 - return Convert.convert(type, value);  
94 - }  
95 -  
96 - /**  
97 - * 从 Map 中获取值  
98 - *  
99 - * @param map Map 对象  
100 - * @param key 键  
101 - * @return 值  
102 - */  
103 - public Object get(Map<String, Object> map, String key) {  
104 - return MapUtil.get(map, key, Object.class);  
105 - }  
106 -  
107 - /**  
108 - * 从 Map 中获取字符串  
109 - *  
110 - * @param map Map 对象  
111 - * @param key 键  
112 - * @return 字符串值  
113 - */  
114 - public String getString(Map<String, Object> map, String key) {  
115 - return MapUtil.getStr(map, key);  
116 - }  
117 -  
118 - /**  
119 - * 从 Map 中获取整数  
120 - *  
121 - * @param map Map 对象  
122 - * @param key 键  
123 - * @return 整数值  
124 - */  
125 - public Integer getInt(Map<String, Object> map, String key) {  
126 - return MapUtil.getInt(map, key);  
127 - }  
128 -  
129 - /**  
130 - * 从 Map 中获取布尔值  
131 - *  
132 - * @param map Map 对象  
133 - * @param key 键  
134 - * @return 布尔值  
135 - */  
136 - public Boolean getBool(Map<String, Object> map, String key) {  
137 - return MapUtil.getBool(map, key);  
138 - }  
139 -  
140 - /**  
141 - * 从 Map 中获取双精度浮点数  
142 - *  
143 - * @param map Map 对象  
144 - * @param key 键  
145 - * @return 双精度浮点数值  
146 - */  
147 - public Double getDouble(Map<String, Object> map, String key) {  
148 - return MapUtil.getDouble(map, key);  
149 - }  
150 -  
151 - /**  
152 - * 获取当前时间戳(毫秒)  
153 - *  
154 - * @return 时间戳  
155 - */  
156 - public long currentTimeMillis() {  
157 - return System.currentTimeMillis();  
158 - }  
159 -}  
160 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptService.java deleted
1 -package cn.iocoder.yudao.module.iot.service.product;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
4 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptPageReqVO;  
5 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptSaveReqVO;  
6 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptTestReqVO;  
7 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptTestRespVO;  
8 -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductScriptDO;  
9 -import jakarta.validation.Valid;  
10 -  
11 -import java.util.List;  
12 -  
13 -/**  
14 - * IoT 产品脚本信息 Service 接口  
15 - *  
16 - * @author 芋道源码  
17 - */  
18 -public interface IotProductScriptService {  
19 -  
20 - /**  
21 - * 创建IoT 产品脚本信息  
22 - *  
23 - * @param createReqVO 创建信息  
24 - * @return 编号  
25 - */  
26 - Long createProductScript(@Valid IotProductScriptSaveReqVO createReqVO);  
27 -  
28 - /**  
29 - * 更新IoT 产品脚本信息  
30 - *  
31 - * @param updateReqVO 更新信息  
32 - */  
33 - void updateProductScript(@Valid IotProductScriptSaveReqVO updateReqVO);  
34 -  
35 - /**  
36 - * 删除IoT 产品脚本信息  
37 - *  
38 - * @param id 编号  
39 - */  
40 - void deleteProductScript(Long id);  
41 -  
42 - /**  
43 - * 获得IoT 产品脚本信息  
44 - *  
45 - * @param id 编号  
46 - * @return IoT 产品脚本信息  
47 - */  
48 - IotProductScriptDO getProductScript(Long id);  
49 -  
50 - /**  
51 - * 获得IoT 产品脚本信息分页  
52 - *  
53 - * @param pageReqVO 分页查询  
54 - * @return IoT 产品脚本信息分页  
55 - */  
56 - PageResult<IotProductScriptDO> getProductScriptPage(IotProductScriptPageReqVO pageReqVO);  
57 -  
58 - /**  
59 - * 获取产品的脚本列表  
60 - *  
61 - * @param productId 产品ID  
62 - * @return 脚本列表  
63 - */  
64 - List<IotProductScriptDO> getProductScriptListByProductId(Long productId);  
65 -  
66 - /**  
67 - * 测试产品脚本  
68 - *  
69 - * @param testReqVO 测试请求  
70 - * @return 测试结果  
71 - */  
72 - IotProductScriptTestRespVO testProductScript(@Valid IotProductScriptTestReqVO testReqVO);  
73 -  
74 - /**  
75 - * 更新产品脚本状态  
76 - *  
77 - * @param id 脚本ID  
78 - * @param status 状态  
79 - */  
80 - void updateProductScriptStatus(Long id, Integer status);  
81 -  
82 -}  
83 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptServiceImpl.java deleted
1 -package cn.iocoder.yudao.module.iot.service.product;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
4 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
5 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptPageReqVO;  
6 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptSaveReqVO;  
7 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptTestReqVO;  
8 -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptTestRespVO;  
9 -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;  
10 -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductScriptDO;  
11 -import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductScriptMapper;  
12 -import cn.iocoder.yudao.module.iot.script.context.DeviceScriptContext;  
13 -import cn.iocoder.yudao.module.iot.script.service.ScriptService;  
14 -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;  
15 -import jakarta.annotation.Resource;  
16 -import lombok.extern.slf4j.Slf4j;  
17 -import org.springframework.stereotype.Service;  
18 -import org.springframework.validation.annotation.Validated;  
19 -  
20 -import java.time.LocalDateTime;  
21 -import java.util.HashMap;  
22 -import java.util.List;  
23 -import java.util.Map;  
24 -  
25 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
26 -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS;  
27 -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_SCRIPT_NOT_EXISTS;  
28 -  
29 -// TODO @芋艿:后续再 review 哈!  
30 -/**  
31 - * IoT 产品脚本信息 Service 实现类  
32 - *  
33 - * @author 芋道源码  
34 - */  
35 -@Service  
36 -@Validated  
37 -@Slf4j  
38 -public class IotProductScriptServiceImpl implements IotProductScriptService {  
39 -  
40 - @Resource  
41 - private IotProductScriptMapper productScriptMapper;  
42 -  
43 - @Resource  
44 - private IotProductService productService;  
45 -  
46 - @Resource  
47 - private ScriptService scriptService;  
48 -  
49 - @Override  
50 - public Long createProductScript(IotProductScriptSaveReqVO createReqVO) {  
51 - // 验证产品是否存在  
52 - validateProductExists(createReqVO.getProductId());  
53 -  
54 - // 插入  
55 - IotProductScriptDO productScript = BeanUtils.toBean(createReqVO, IotProductScriptDO.class);  
56 - // 初始化版本为1  
57 - productScript.setVersion(1);  
58 - // 初始化测试相关字段  
59 - productScript.setLastTestResult(null);  
60 - productScript.setLastTestTime(null);  
61 - productScriptMapper.insert(productScript);  
62 - // 返回  
63 - return productScript.getId();  
64 - }  
65 -  
66 - @Override  
67 - public void updateProductScript(IotProductScriptSaveReqVO updateReqVO) {  
68 - // 校验存在  
69 - validateProductScriptExists(updateReqVO.getId());  
70 -  
71 - // 获取旧的记录,保留版本号和测试信息  
72 - IotProductScriptDO oldScript = getProductScript(updateReqVO.getId());  
73 -  
74 - // 更新  
75 - IotProductScriptDO updateObj = BeanUtils.toBean(updateReqVO, IotProductScriptDO.class);  
76 - // 更新版本号  
77 - updateObj.setVersion(oldScript.getVersion() + 1);  
78 - // 保留测试相关信息  
79 - updateObj.setLastTestTime(oldScript.getLastTestTime());  
80 - updateObj.setLastTestResult(oldScript.getLastTestResult());  
81 - productScriptMapper.updateById(updateObj);  
82 - }  
83 -  
84 - @Override  
85 - public void deleteProductScript(Long id) {  
86 - // 校验存在  
87 - validateProductScriptExists(id);  
88 - // 删除  
89 - productScriptMapper.deleteById(id);  
90 - }  
91 -  
92 - private void validateProductScriptExists(Long id) {  
93 - if (productScriptMapper.selectById(id) == null) {  
94 - throw exception(PRODUCT_SCRIPT_NOT_EXISTS);  
95 - }  
96 - }  
97 -  
98 - private void validateProductExists(Long productId) {  
99 - IotProductDO product = productService.getProduct(productId);  
100 - if (product == null) {  
101 - throw exception(PRODUCT_NOT_EXISTS);  
102 - }  
103 - }  
104 -  
105 - @Override  
106 - public IotProductScriptDO getProductScript(Long id) {  
107 - return productScriptMapper.selectById(id);  
108 - }  
109 -  
110 - @Override  
111 - public PageResult<IotProductScriptDO> getProductScriptPage(IotProductScriptPageReqVO pageReqVO) {  
112 - return productScriptMapper.selectPage(pageReqVO);  
113 - }  
114 -  
115 - @Override  
116 - public List<IotProductScriptDO> getProductScriptListByProductId(Long productId) {  
117 - return productScriptMapper.selectList(new LambdaQueryWrapper<IotProductScriptDO>()  
118 - .eq(IotProductScriptDO::getProductId, productId)  
119 - .orderByDesc(IotProductScriptDO::getId));  
120 - }  
121 -  
122 - @Override  
123 - public IotProductScriptTestRespVO testProductScript(IotProductScriptTestReqVO testReqVO) {  
124 - long startTime = System.currentTimeMillis();  
125 -  
126 - try {  
127 - // 验证产品是否存在  
128 - validateProductExists(testReqVO.getProductId());  
129 -  
130 - // 根据ID获取已保存的脚本(如果有)  
131 - IotProductScriptDO existingScript = null;  
132 - if (testReqVO.getId() != null) {  
133 - existingScript = getProductScript(testReqVO.getId());  
134 - }  
135 -  
136 - // 创建测试上下文  
137 - IotProductDO product = productService.getProduct(testReqVO.getProductId());  
138 - DeviceScriptContext context = new DeviceScriptContext();  
139 -  
140 - // 设置设备上下文(使用产品信息,测试时无具体设备)  
141 - context.withDeviceInfo(product.getProductKey(), null);  
142 -  
143 - // 设置输入参数  
144 - Map<String, Object> params = new HashMap<>();  
145 - params.put("input", testReqVO.getTestInput());  
146 - params.put("productKey", product.getProductKey());  
147 - params.put("scriptType", testReqVO.getScriptType());  
148 -  
149 - // 根据脚本类型设置特定参数  
150 - switch (testReqVO.getScriptType()) {  
151 - case 1: // PROPERTY_PARSER  
152 - params.put("method", "property");  
153 - // 添加一些模拟的属性数据  
154 - Map<String, Object> properties = new HashMap<>();  
155 - properties.put("temp", 25.5);  
156 - properties.put("humidity", 60);  
157 - context.withProperties(properties);  
158 - break;  
159 - case 2: // EVENT_PARSER  
160 - params.put("method", "event");  
161 - params.put("identifier", "default");  
162 - // 添加事件数据  
163 - Map<String, Object> eventParams = new HashMap<>();  
164 - eventParams.put("timestamp", System.currentTimeMillis());  
165 - params.put("eventParams", eventParams);  
166 - break;  
167 - case 3: // COMMAND_ENCODER  
168 - params.put("method", "command");  
169 - // 添加命令参数  
170 - Map<String, Object> cmdParams = new HashMap<>();  
171 - cmdParams.put("cmdName", "setValue");  
172 - cmdParams.put("cmdValue", 100);  
173 - params.put("cmdParams", cmdParams);  
174 - break;  
175 - default:  
176 - // 默认不添加额外参数  
177 - }  
178 -  
179 - // 添加所有参数到上下文  
180 - for (Map.Entry<String, Object> entry : params.entrySet()) {  
181 - context.setParameter(entry.getKey(), entry.getValue());  
182 - }  
183 -  
184 - // 执行脚本  
185 - Object result = scriptService.executeScript(  
186 - testReqVO.getScriptLanguage(),  
187 - testReqVO.getScriptContent(),  
188 - context);  
189 -  
190 - // 更新测试结果(如果是已保存的脚本)  
191 - if (existingScript != null) {  
192 - IotProductScriptDO updateObj = new IotProductScriptDO();  
193 - updateObj.setId(existingScript.getId());  
194 - updateObj.setLastTestTime(LocalDateTime.now());  
195 - updateObj.setLastTestResult(1); // 1表示成功  
196 - productScriptMapper.updateById(updateObj);  
197 - }  
198 -  
199 - long executionTime = System.currentTimeMillis() - startTime;  
200 - return IotProductScriptTestRespVO.success(result, executionTime);  
201 -  
202 - } catch (Exception e) {  
203 - log.error("[testProductScript][测试脚本异常]", e);  
204 -  
205 - // 如果是已保存的脚本,更新测试失败状态  
206 - if (testReqVO.getId() != null) {  
207 - try {  
208 - IotProductScriptDO updateObj = new IotProductScriptDO();  
209 - updateObj.setId(testReqVO.getId());  
210 - updateObj.setLastTestTime(LocalDateTime.now());  
211 - updateObj.setLastTestResult(0); // 0表示失败  
212 - productScriptMapper.updateById(updateObj);  
213 - } catch (Exception ex) {  
214 - log.error("[testProductScript][更新脚本测试结果异常]", ex);  
215 - }  
216 - }  
217 -  
218 - long executionTime = System.currentTimeMillis() - startTime;  
219 - return IotProductScriptTestRespVO.error(e.getMessage(), executionTime);  
220 - }  
221 - }  
222 -  
223 - @Override  
224 - public void updateProductScriptStatus(Long id, Integer status) {  
225 - // 校验存在  
226 - validateProductScriptExists(id);  
227 -  
228 - // 更新状态  
229 - IotProductScriptDO updateObj = new IotProductScriptDO();  
230 - updateObj.setId(id);  
231 - updateObj.setStatus(status);  
232 - productScriptMapper.updateById(updateObj);  
233 - }  
234 -}  
235 \ No newline at end of file 0 \ No newline at end of file
yudao-server/src/main/resources/application-local.yaml
@@ -69,13 +69,13 @@ spring: @@ -69,13 +69,13 @@ spring:
69 url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true 69 url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
70 username: root 70 username: root
71 password: 123456 71 password: 123456
72 -# tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!)  
73 -# url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro  
74 -# driver-class-name: com.taosdata.jdbc.rs.RestfulDriver  
75 -# username: root  
76 -# password: taosdata  
77 -# druid:  
78 -# validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL 72 + tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!)
  73 + url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro
  74 + driver-class-name: com.taosdata.jdbc.rs.RestfulDriver
  75 + username: root
  76 + password: taosdata
  77 + druid:
  78 + validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL
79 79
80 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 80 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
81 data: 81 data: