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