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 103 <optional>true</optional>
104 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 106 <!-- IoT 网络组件:接收来自设备的上行数据 -->
140 107 <!-- <dependency>-->
141 108 <!-- <groupId>cn.iocoder.boot</groupId>-->
... ... @@ -147,13 +114,6 @@
147 114 <!-- <artifactId>yudao-module-iot-net-component-emqx</artifactId>-->
148 115 <!-- <version>${revision}</version>-->
149 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 117 </dependencies>
158 118  
159 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 0 \ No newline at end of file
yudao-server/src/main/resources/application-local.yaml
... ... @@ -69,13 +69,13 @@ spring:
69 69 url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
70 70 username: root
71 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 80 # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
81 81 data:
... ...