From cf52a16f6cb941c2cdb65fec0fb532da34904a34 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 31 May 2025 10:02:01 +0800 Subject: [PATCH] =?UTF-8?q?reactor=EF=BC=9A=E3=80=90IoT=20=E7=89=A9?= =?UTF-8?q?=E8=81=94=E7=BD=91=E3=80=91=E7=A7=BB=E9=99=A4=20script=20?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=EF=BC=8C=E7=AE=80=E5=8C=96=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=A4=8D=E6=9D=82=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-iot/yudao-module-iot-biz/pom.xml | 40 -- .../iocoder/yudao/module/iot/ScriptTest.java | 61 --- .../product/IotProductScriptController.java | 127 ------- .../vo/script/IotProductScriptPageReqVO.java | 53 --- .../vo/script/IotProductScriptRespVO.java | 63 ---- .../vo/script/IotProductScriptSaveReqVO.java | 49 --- .../vo/script/IotProductScriptTestReqVO.java | 38 -- .../vo/script/IotProductScriptTestRespVO.java | 39 -- .../IotProductScriptUpdateStatusReqVO.java | 22 -- .../product/IotProductScriptDO.java | 73 ---- .../mysql/product/IotProductScriptMapper.java | 31 -- .../module/iot/script/ScriptExample.java | 113 ------ .../script/config/ScriptConfiguration.java | 24 -- .../script/context/DefaultScriptContext.java | 46 --- .../script/context/DeviceScriptContext.java | 92 ----- .../iot/script/context/ScriptContext.java | 47 --- .../script/engine/AbstractScriptEngine.java | 49 --- .../iot/script/engine/JsScriptEngine.java | 348 ------------------ .../iot/script/engine/ScriptEngine.java | 25 -- .../script/engine/ScriptEngineFactory.java | 85 ----- .../iot/script/example/GraalJsExample.java | 209 ----------- .../script/example/ProductScriptSamples.java | 174 --------- .../yudao/module/iot/script/package-info.java | 4 - .../module/iot/script/sandbox/JsSandbox.java | 330 ----------------- .../iot/script/sandbox/ScriptSandbox.java | 22 -- .../iot/script/service/ScriptService.java | 58 --- .../iot/script/service/ScriptServiceImpl.java | 111 ------ .../module/iot/script/util/ScriptUtils.java | 159 -------- .../product/IotProductScriptService.java | 82 ----- .../product/IotProductScriptServiceImpl.java | 234 ------------ .../src/main/resources/application-local.yaml | 14 +- 31 files changed, 7 insertions(+), 2815 deletions(-) delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductScriptController.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptPageReqVO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptRespVO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptSaveReqVO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestReqVO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestRespVO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptUpdateStatusReqVO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductScriptDO.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductScriptMapper.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/ScriptExample.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/config/ScriptConfiguration.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DefaultScriptContext.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DeviceScriptContext.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/ScriptContext.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/AbstractScriptEngine.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/JsScriptEngine.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngine.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngineFactory.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/GraalJsExample.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/ProductScriptSamples.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/package-info.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/JsSandbox.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/ScriptSandbox.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptService.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptServiceImpl.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/util/ScriptUtils.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptService.java delete mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptServiceImpl.java diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index acecec22d9..e63cd72987 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -103,39 +103,6 @@ true - - - - org.graalvm.sdk - graal-sdk - 22.3.0 - - - org.graalvm.js - js - 22.3.0 - - - org.graalvm.js - js-scriptengine - 22.3.0 - - - - - - - - - - - - - - - - - @@ -147,13 +114,6 @@ - - - - - - - diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java deleted file mode 100644 index 9f54d60e80..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/ScriptTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.module.iot; - -import cn.hutool.script.ScriptUtil; -import javax.script.Bindings; -import javax.script.ScriptEngine; -import javax.script.ScriptException; - -/** - * TODO 芋艿:测试脚本的接入 - */ -public class ScriptTest { - - public static void main2(String[] args) { - // 创建一个 Groovy 脚本引擎 - ScriptEngine engine = ScriptUtil.createGroovyEngine(); - - // 创建绑定参数 - Bindings bindings = engine.createBindings(); - bindings.put("name", "Alice"); - bindings.put("age", 30); - - // 定义一个稍微复杂的 Groovy 脚本 - String script = "def greeting = 'Hello, ' + name + '!';\n" + - "def ageInFiveYears = age + 5;\n" + - "def message = greeting + ' In five years, you will be ' + ageInFiveYears + ' years old.';\n" + - "return message.toUpperCase();\n"; - - try { - // 执行脚本并获取结果 - Object result = engine.eval(script, bindings); - System.out.println(result); // 输出: HELLO, ALICE! IN FIVE YEARS, YOU WILL BE 35 YEARS OLD. - } catch (ScriptException e) { - e.printStackTrace(); - } - } - - public static void main(String[] args) { - // 创建一个 JavaScript 脚本引擎 - ScriptEngine jsEngine = ScriptUtil.createJsEngine(); - - // 创建绑定参数 - Bindings jsBindings = jsEngine.createBindings(); - jsBindings.put("name", "Bob"); - jsBindings.put("age", 25); - - // 定义一个简单的 JavaScript 脚本 - String jsScript = "var greeting = 'Hello, ' + name + '!';\n" + - "var ageInTenYears = age + 10;\n" + - "var message = greeting + ' In ten years, you will be ' + ageInTenYears + ' years old.';\n" + - "message.toUpperCase();\n"; - - try { - // 执行脚本并获取结果 - Object jsResult = jsEngine.eval(jsScript, jsBindings); - System.out.println(jsResult); // 输出: HELLO, BOB! IN TEN YEARS, YOU WILL BE 35 YEARS OLD. - } catch (ScriptException e) { - e.printStackTrace(); - } - } - -} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductScriptController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductScriptController.java deleted file mode 100644 index 92e52a39f0..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductScriptController.java +++ /dev/null @@ -1,127 +0,0 @@ -package cn.iocoder.yudao.module.iot.controller.admin.product; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.*; -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductScriptDO; -import cn.iocoder.yudao.module.iot.script.example.ProductScriptSamples; -import cn.iocoder.yudao.module.iot.service.product.IotProductScriptService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; - -@Tag(name = "管理后台 - IoT 产品脚本信息") -@RestController -@RequestMapping("/iot/product-script") -@Validated -public class IotProductScriptController { - - @Resource - private IotProductScriptService productScriptService; - - @Resource - private ProductScriptSamples scriptSamples; - - @PostMapping("/create") - @Operation(summary = "创建产品脚本") - @PreAuthorize("@ss.hasPermission('iot:product-script:create')") - public CommonResult createProductScript(@Valid @RequestBody IotProductScriptSaveReqVO createReqVO) { - return success(productScriptService.createProductScript(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新产品脚本") - @PreAuthorize("@ss.hasPermission('iot:product-script:update')") - public CommonResult updateProductScript(@Valid @RequestBody IotProductScriptSaveReqVO updateReqVO) { - productScriptService.updateProductScript(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除产品脚本") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('iot:product-script:delete')") - public CommonResult deleteProductScript(@RequestParam("id") Long id) { - productScriptService.deleteProductScript(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得产品脚本详情") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('iot:product-script:query')") - public CommonResult getProductScript(@RequestParam("id") Long id) { - IotProductScriptDO productScript = productScriptService.getProductScript(id); - return success(BeanUtils.toBean(productScript, IotProductScriptRespVO.class)); - } - - @GetMapping("/list-by-product") - @Operation(summary = "获得产品的脚本列表") - @Parameter(name = "productId", description = "产品编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('iot:product-script:query')") - public CommonResult> getProductScriptListByProductId( - @RequestParam("productId") Long productId) { - List list = productScriptService.getProductScriptListByProductId(productId); - return success(BeanUtils.toBean(list, IotProductScriptRespVO.class)); - } - - @GetMapping("/page") - @Operation(summary = "获得产品脚本分页") - @PreAuthorize("@ss.hasPermission('iot:product-script:query')") - public CommonResult> getProductScriptPage( - @Valid IotProductScriptPageReqVO pageReqVO) { - PageResult pageResult = productScriptService.getProductScriptPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, IotProductScriptRespVO.class)); - } - - @PostMapping("/test") - @Operation(summary = "测试产品脚本") - @PreAuthorize("@ss.hasPermission('iot:product-script:test')") - public CommonResult testProductScript( - @Valid @RequestBody IotProductScriptTestReqVO testReqVO) { - return success(productScriptService.testProductScript(testReqVO)); - } - - @PutMapping("/update-status") - @Operation(summary = "更新产品脚本状态") - @PreAuthorize("@ss.hasPermission('iot:product-script:update')") - public CommonResult updateProductScriptStatus( - @Valid @RequestBody IotProductScriptUpdateStatusReqVO updateStatusReqVO) { - productScriptService.updateProductScriptStatus(updateStatusReqVO.getId(), updateStatusReqVO.getStatus()); - return success(true); - } - - @GetMapping("/sample") - @Operation(summary = "获取示例脚本") - @Parameter(name = "type", description = "脚本类型(1=属性解析, 2=事件解析, 3=命令编码)", required = true, example = "1") - @PreAuthorize("@ss.hasPermission('iot:product-script:query')") - public CommonResult getSampleScript(@RequestParam("type") Integer type) { - String sample; - // TODO @haohao:要不枚举下? - switch (type) { - case 1: - sample = scriptSamples.getPropertyParserSample(); - break; - case 2: - sample = scriptSamples.getEventParserSample(); - break; - case 3: - sample = scriptSamples.getCommandEncoderSample(); - break; - default: - // TODO @haohao:不支持,返回 error 会不会好点哈?例如说,参数不正确; - sample = "// 不支持的脚本类型"; - } - return success(sample); - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptPageReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptPageReqVO.java deleted file mode 100644 index d0dbe23cc2..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptPageReqVO.java +++ /dev/null @@ -1,53 +0,0 @@ -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptLanguageEnum; -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptStatusEnum; -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - IoT 产品脚本信息分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class IotProductScriptPageReqVO extends PageParam { - - @Schema(description = "产品ID", example = "28277") - private Long productId; - - @Schema(description = "产品唯一标识符") - private String productKey; - - @Schema(description = "脚本类型", example = "1") - @InEnum(IotProductScriptTypeEnum.class) - private Integer scriptType; - - @Schema(description = "脚本语言") - @InEnum(IotProductScriptLanguageEnum.class) - private String scriptLanguage; - - @Schema(description = "状态", example = "0") - @InEnum(IotProductScriptStatusEnum.class) - private Integer status; - - @Schema(description = "备注说明", example = "你说的对") - private String remark; - - @Schema(description = "最后测试时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] lastTestTime; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptRespVO.java deleted file mode 100644 index be0a5c92f6..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptRespVO.java +++ /dev/null @@ -1,63 +0,0 @@ -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script; - -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; -import com.alibaba.excel.annotation.ExcelProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; - -@Schema(description = "管理后台 - IoT 产品脚本信息 Response VO") -@Data -@ExcelIgnoreUnannotated -public class IotProductScriptRespVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "26565") - @ExcelProperty("主键") - private Long id; - - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28277") - @ExcelProperty("产品ID") - private Long productId; - - @Schema(description = "产品唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("产品唯一标识符") - private String productKey; - - @Schema(description = "脚本类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @ExcelProperty("脚本类型") - private Integer scriptType; - - @Schema(description = "脚本内容", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("脚本内容") - private String scriptContent; - - @Schema(description = "脚本语言", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("脚本语言") - private String scriptLanguage; - - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - @ExcelProperty("状态") - private Integer status; - - @Schema(description = "备注说明", example = "你说的对") - @ExcelProperty("备注说明") - private String remark; - - @Schema(description = "最后测试时间") - @ExcelProperty("最后测试时间") - private LocalDateTime lastTestTime; - - @Schema(description = "最后测试结果(0=失败 1=成功)") - @ExcelProperty("最后测试结果(0=失败 1=成功)") - private Integer lastTestResult; - - @Schema(description = "脚本版本号", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("脚本版本号") - private Integer version; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptSaveReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptSaveReqVO.java deleted file mode 100644 index 5638795bbf..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptSaveReqVO.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptLanguageEnum; -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptStatusEnum; -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - IoT 产品脚本信息新增/修改 Request VO") -@Data -public class IotProductScriptSaveReqVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "26565") - private Long id; - - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "28277") - @NotNull(message = "产品ID不能为空") - private Long productId; - - @Schema(description = "产品唯一标识符", requiredMode = Schema.RequiredMode.REQUIRED) - @NotEmpty(message = "产品唯一标识符不能为空") - private String productKey; - - @Schema(description = "脚本类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "脚本类型不能为空") - @InEnum(IotProductScriptTypeEnum.class) - private Integer scriptType; - - @Schema(description = "脚本内容", requiredMode = Schema.RequiredMode.REQUIRED) - @NotEmpty(message = "脚本内容不能为空") - private String scriptContent; - - @Schema(description = "脚本语言", requiredMode = Schema.RequiredMode.REQUIRED) - @NotEmpty(message = "脚本语言不能为空") - @InEnum(IotProductScriptLanguageEnum.class) - private String scriptLanguage; - - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - @NotNull(message = "状态不能为空") - @InEnum(IotProductScriptStatusEnum.class) - private Integer status; - - @Schema(description = "备注说明", example = "你说的对") - private String remark; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestReqVO.java deleted file mode 100644 index 605d4af674..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestReqVO.java +++ /dev/null @@ -1,38 +0,0 @@ -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - IoT 产品脚本测试 Request VO") -@Data -public class IotProductScriptTestReqVO { - - @Schema(description = "脚本ID,如果已保存脚本则传入", example = "1024") - private Long id; - - @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") - @NotNull(message = "产品ID不能为空") - private Long productId; - - @Schema(description = "脚本类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "脚本类型不能为空") - @InEnum(value = IotProductScriptTypeEnum.class) - private Integer scriptType; - - @Schema(description = "脚本内容", requiredMode = Schema.RequiredMode.REQUIRED) - @NotEmpty(message = "脚本内容不能为空") - private String scriptContent; - - @Schema(description = "脚本语言", requiredMode = Schema.RequiredMode.REQUIRED, example = "javascript") - @NotEmpty(message = "脚本语言不能为空") - private String scriptLanguage; - - @Schema(description = "测试输入数据", requiredMode = Schema.RequiredMode.REQUIRED) - @NotEmpty(message = "测试输入数据不能为空") - private String testInput; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestRespVO.java deleted file mode 100644 index 3dec9f6988..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestRespVO.java +++ /dev/null @@ -1,39 +0,0 @@ -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Schema(description = "管理后台 - IoT 产品脚本测试 Response VO") -@Data -public class IotProductScriptTestRespVO { - - @Schema(description = "测试是否成功", requiredMode = Schema.RequiredMode.REQUIRED) - private Boolean success; - - @Schema(description = "测试结果输出") - private Object output; - - @Schema(description = "错误消息,失败时返回") - private String errorMessage; - - @Schema(description = "执行耗时(毫秒)") - private Long executionTimeMs; - - // 静态工厂方法 - 成功 - public static IotProductScriptTestRespVO success(Object output, Long executionTimeMs) { - IotProductScriptTestRespVO respVO = new IotProductScriptTestRespVO(); - respVO.setSuccess(true); - respVO.setOutput(output); - respVO.setExecutionTimeMs(executionTimeMs); - return respVO; - } - - // 静态工厂方法 - 失败 - public static IotProductScriptTestRespVO error(String errorMessage, Long executionTimeMs) { - IotProductScriptTestRespVO respVO = new IotProductScriptTestRespVO(); - respVO.setSuccess(false); - respVO.setErrorMessage(errorMessage); - respVO.setExecutionTimeMs(executionTimeMs); - return respVO; - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptUpdateStatusReqVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptUpdateStatusReqVO.java deleted file mode 100644 index 12f02a5ca5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptUpdateStatusReqVO.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.iot.enums.product.IotProductScriptStatusEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -@Schema(description = "管理后台 - IoT 产品脚本状态更新 Request VO") -@Data -public class IotProductScriptUpdateStatusReqVO { - - @Schema(description = "脚本ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") - @NotNull(message = "脚本ID不能为空") - private Long id; - - @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - @NotNull(message = "状态不能为空") - @InEnum(IotProductScriptStatusEnum.class) - private Integer status; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductScriptDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductScriptDO.java deleted file mode 100644 index 64cab3ce95..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductScriptDO.java +++ /dev/null @@ -1,73 +0,0 @@ -package cn.iocoder.yudao.module.iot.dal.dataobject.product; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -import java.time.LocalDateTime; - -// TODO @haohao:类似阿里云的脚本,貌似是一个?这个可以简化么?【微信讨论哈】类似阿里云,貌似是加了个 topic? -/** - * IoT 产品脚本信息 DO - * - * @author 芋道源码 - */ -@TableName("iot_product_script") -@KeySequence("iot_product_script_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class IotProductScriptDO extends BaseDO { - - /** - * 主键 - */ - @TableId - private Long id; - /** - * 产品ID - */ - private Long productId; - /** - * 产品唯一标识符 - */ - private String productKey; - /** - * 脚本类型(property_parser=属性解析,event_parser=事件解析,command_encoder=命令编码) - */ - private String scriptType; - /** - * 脚本内容 - */ - private String scriptContent; - /** - * 脚本语言 - */ - private String scriptLanguage; - /** - * 状态(0=禁用 1=启用) - */ - private Integer status; - /** - * 备注说明 - */ - private String remark; - /** - * 最后测试时间 - */ - private LocalDateTime lastTestTime; - /** - * 最后测试结果(0=失败 1=成功) - */ - private Integer lastTestResult; - /** - * 脚本版本号 - */ - private Integer version; - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductScriptMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductScriptMapper.java deleted file mode 100644 index 96c5ababdf..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductScriptMapper.java +++ /dev/null @@ -1,31 +0,0 @@ -package cn.iocoder.yudao.module.iot.dal.mysql.product; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptPageReqVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductScriptDO; -import org.apache.ibatis.annotations.Mapper; - -/** - * IoT 产品脚本信息 Mapper - * - * @author 芋道源码 - */ -@Mapper -public interface IotProductScriptMapper extends BaseMapperX { - - default PageResult selectPage(IotProductScriptPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(IotProductScriptDO::getProductId, reqVO.getProductId()) - .eqIfPresent(IotProductScriptDO::getProductKey, reqVO.getProductKey()) - .eqIfPresent(IotProductScriptDO::getScriptType, reqVO.getScriptType()) - .eqIfPresent(IotProductScriptDO::getScriptLanguage, reqVO.getScriptLanguage()) - .eqIfPresent(IotProductScriptDO::getStatus, reqVO.getStatus()) - .eqIfPresent(IotProductScriptDO::getRemark, reqVO.getRemark()) - .betweenIfPresent(IotProductScriptDO::getLastTestTime, reqVO.getLastTestTime()) - .betweenIfPresent(IotProductScriptDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(IotProductScriptDO::getId)); - } - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/ScriptExample.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/ScriptExample.java deleted file mode 100644 index 85e04cf527..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/ScriptExample.java +++ /dev/null @@ -1,113 +0,0 @@ -package cn.iocoder.yudao.module.iot.script; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.module.iot.script.context.DefaultScriptContext; -import cn.iocoder.yudao.module.iot.script.context.ScriptContext; -import cn.iocoder.yudao.module.iot.script.service.ScriptService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.Map; - -// TODO @haohao:挪到 test 目录下 -/** - * 脚本使用示例类 - */ -@Slf4j -@Component -public class ScriptExample { - - @Autowired - private ScriptService scriptService; - - /** - * 执行简单的 JavaScript 脚本 - * - * @return 执行结果 - */ - public Object executeSimpleScript() { - // 简单的脚本内容 - String script = "var result = a + b; result;"; - - // 创建参数 - Map params = MapUtil.newHashMap(); - params.put("a", 10); - params.put("b", 20); - - // 执行脚本 - return scriptService.executeJavaScript(script, params); - } - - /** - * 执行包含函数的 JavaScript 脚本 - * - * @return 执行结果 - */ - public Object executeScriptWithFunction() { - // 包含函数的脚本内容 - String script = "function calc(x, y) { return x * y; } calc(a, b);"; - - // 创建上下文 - ScriptContext context = new DefaultScriptContext(); - context.setParameter("a", 5); - context.setParameter("b", 6); - - // 执行脚本 - return scriptService.executeJavaScript(script, context); - } - - /** - * 执行包含工具类使用的脚本 - * - * @return 执行结果 - */ - public Object executeScriptWithUtils() { - // 使用工具类的脚本内容 - String script = "var data = {name: 'test', value: 123}; utils.toJson(data);"; - - // 执行脚本 - return scriptService.executeJavaScript(script, MapUtil.newHashMap()); - } - - /** - * 执行包含日志输出的脚本 - * - * @return 执行结果 - */ - public Object executeScriptWithLogging() { - // 包含日志输出的脚本内容 - String script = "log.info('脚本开始执行...'); " + - "var result = a + b; " + - "log.info('计算结果: ' + result); " + - "result;"; - - // 创建参数 - Map params = MapUtil.newHashMap(); - params.put("a", 100); - params.put("b", 200); - - // 执行脚本 - return scriptService.executeJavaScript(script, params); - } - - /** - * 演示脚本安全性验证 - * - * @return 是否安全 - */ - public boolean validateScriptSecurity() { - // 安全的脚本 - String safeScript = "var x = 10; var y = 20; x + y;"; - boolean safeResult = scriptService.validateScript("js", safeScript); - - // 不安全的脚本 - String unsafeScript = "java.lang.System.exit(0);"; - boolean unsafeResult = scriptService.validateScript("js", unsafeScript); - - log.info("安全脚本验证结果: {}", safeResult); - log.info("不安全脚本验证结果: {}", unsafeResult); - - return safeResult && !unsafeResult; - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/config/ScriptConfiguration.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/config/ScriptConfiguration.java deleted file mode 100644 index 8339b217f2..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/config/ScriptConfiguration.java +++ /dev/null @@ -1,24 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.config; - -import cn.iocoder.yudao.module.iot.script.engine.ScriptEngineFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -/** - * 脚本模块配置类 - */ -@Configuration -public class ScriptConfiguration { - - /** - * 创建脚本引擎工厂 - * - * @return 脚本引擎工厂 - */ - @Bean - @Primary - public ScriptEngineFactory scriptEngineFactory() { - return new ScriptEngineFactory(); - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DefaultScriptContext.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DefaultScriptContext.java deleted file mode 100644 index a75a354307..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DefaultScriptContext.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.context; - -import cn.hutool.core.map.MapUtil; - -import java.util.Map; - -/** - * 默认脚本上下文实现 - */ -public class DefaultScriptContext implements ScriptContext { - - /** - * 上下文参数 - */ - private final Map parameters = MapUtil.newHashMap(); - - /** - * 上下文函数 - */ - private final Map functions = MapUtil.newHashMap(); - - @Override - public Map getParameters() { - return parameters; - } - - @Override - public Map getFunctions() { - return functions; - } - - @Override - public void setParameter(String key, Object value) { - parameters.put(key, value); - } - - @Override - public Object getParameter(String key) { - return parameters.get(key); - } - - @Override - public void registerFunction(String name, Object function) { - functions.put(name, function); - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DeviceScriptContext.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DeviceScriptContext.java deleted file mode 100644 index 1518736b55..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/DeviceScriptContext.java +++ /dev/null @@ -1,92 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.context; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; - -/** - * 设备脚本上下文,提供设备相关的上下文信息 - */ -@Slf4j -public class DeviceScriptContext extends DefaultScriptContext { - - /** - * 产品 Key - */ - @Getter - private String productKey; - - /** - * 设备名称 - */ - @Getter - private String deviceName; - - /** - * 设备属性数据缓存 - */ - private Map properties; - - /** - * 使用产品 Key 和设备名称初始化上下文 - * - * @param productKey 产品 Key - * @param deviceName 设备名称,可以为 null - * @return 当前上下文实例,用于链式调用 - */ - public DeviceScriptContext withDeviceInfo(String productKey, String deviceName) { - this.productKey = productKey; - this.deviceName = deviceName; - - // 添加到参数中,便于脚本访问 - setParameter("productKey", productKey); - if (StrUtil.isNotEmpty(deviceName)) { - setParameter("deviceName", deviceName); - } - return this; - } - - /** - * 设置设备属性数据 - * - * @param properties 属性数据 - * @return 当前上下文实例,用于链式调用 - */ - public DeviceScriptContext withProperties(Map properties) { - this.properties = properties; - if (MapUtil.isNotEmpty(properties)) { - setParameter("properties", properties); - } - return this; - } - - /** - * 获取设备属性值 - * - * @param key 属性标识符 - * @return 属性值 - */ - public Object getProperty(String key) { - if (MapUtil.isEmpty(properties)) { - return null; - } - return properties.get(key); - } - - /** - * 设置设备属性值 - * - * @param key 属性标识符 - * @param value 属性值 - */ - public void setProperty(String key, Object value) { - if (this.properties == null) { - this.properties = MapUtil.newHashMap(); - setParameter("properties", this.properties); - } - this.properties.put(key, value); - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/ScriptContext.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/ScriptContext.java deleted file mode 100644 index d18644e822..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/context/ScriptContext.java +++ /dev/null @@ -1,47 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.context; - -import java.util.Map; - -/** - * 脚本上下文接口,定义脚本执行所需的上下文环境 - */ -public interface ScriptContext { - - /** - * 获取上下文参数 - * - * @return 上下文参数 - */ - Map getParameters(); - - /** - * 获取上下文函数 - * - * @return 上下文函数 - */ - Map getFunctions(); - - /** - * 设置上下文参数 - * - * @param key 参数名 - * @param value 参数值 - */ - void setParameter(String key, Object value); - - /** - * 获取上下文参数 - * - * @param key 参数名 - * @return 参数值 - */ - Object getParameter(String key); - - /** - * 注册函数 - * - * @param name 函数名称 - * @param function 函数对象 - */ - void registerFunction(String name, Object function); -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/AbstractScriptEngine.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/AbstractScriptEngine.java deleted file mode 100644 index b69aced139..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/AbstractScriptEngine.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.engine; - -import cn.iocoder.yudao.module.iot.script.context.ScriptContext; -import cn.iocoder.yudao.module.iot.script.sandbox.ScriptSandbox; -import lombok.extern.slf4j.Slf4j; - -/** - * 抽象脚本引擎,提供脚本引擎的基本框架 - */ -@Slf4j -public abstract class AbstractScriptEngine implements ScriptEngine { - - /** - * 脚本沙箱,用于提供安全执行环境 - */ - protected final ScriptSandbox sandbox; - - /** - * 构造函数 - * - * @param sandbox 脚本沙箱 - */ - protected AbstractScriptEngine(ScriptSandbox sandbox) { - this.sandbox = sandbox; - } - - @Override - public Object execute(String script, ScriptContext context) { - try { - // 执行前验证脚本安全性 - sandbox.validate(script); - // 执行脚本 - return doExecute(script, context); - } catch (Exception e) { - log.error("执行脚本出错:{}", e.getMessage(), e); - throw new RuntimeException("脚本执行失败:" + e.getMessage(), e); - } - } - - /** - * 执行脚本的具体实现 - * - * @param script 脚本内容 - * @param context 脚本上下文 - * @return 脚本执行结果 - * @throws Exception 执行异常 - */ - protected abstract Object doExecute(String script, ScriptContext context) throws Exception; -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/JsScriptEngine.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/JsScriptEngine.java deleted file mode 100644 index 788be01dab..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/JsScriptEngine.java +++ /dev/null @@ -1,348 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.engine; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.IdUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.module.iot.script.context.ScriptContext; -import cn.iocoder.yudao.module.iot.script.sandbox.ScriptSandbox; -import cn.iocoder.yudao.module.iot.script.util.ScriptUtils; -import lombok.extern.slf4j.Slf4j; -import org.graalvm.polyglot.*; - -import java.nio.file.Path; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -/** - * JavaScript 脚本引擎实现,基于 GraalJS Context API - */ -@Slf4j -public class JsScriptEngine extends AbstractScriptEngine implements AutoCloseable { - - /** - * JavaScript 引擎类型 - */ - public static final String TYPE = "js"; - - /** - * 脚本语言类型 - */ - private static final String LANGUAGE_ID = "js"; - - /** - * GraalJS 上下文 - */ - private final Context context; - - /** - * 脚本源代码缓存 - */ - private final Map sourceCache = new ConcurrentHashMap<>(); - - /** - * 脚本缓存的最大数量 - */ - private static final int MAX_CACHE_SIZE = 1000; - - /** - * 构造函数 - * - * @param sandbox JavaScript 沙箱 - */ - public JsScriptEngine(ScriptSandbox sandbox) { - super(sandbox); - - // 创建安全的主机访问配置 - HostAccess hostAccess = HostAccess.newBuilder() - .allowPublicAccess(true) // 允许访问公共方法和字段 - .allowArrayAccess(true) // 允许数组访问 - .allowListAccess(true) // 允许 List 访问 - .allowMapAccess(true) // 允许 Map 访问 - .build(); - - // 创建隔离的临时目录路径 - // TODO @haohao:貌似没用到? - Path tempDirectory = Path.of(System.getProperty("java.io.tmpdir"), "graaljs-" + IdUtil.fastSimpleUUID()); - - // 初始化 GraalJS 上下文 - this.context = Context.newBuilder(LANGUAGE_ID) - .allowHostAccess(hostAccess) // 使用安全的主机访问配置 - .allowHostClassLookup(className -> false) // 禁止查找 Java 类 - .allowIO(false) // 禁止文件 IO - .allowNativeAccess(false) // 禁止本地访问 - .allowCreateThread(false) // 禁止创建线程 - .allowEnvironmentAccess(EnvironmentAccess.NONE) // 禁止环境变量访问 - .allowExperimentalOptions(false) // 禁止实验性选项 - .option("js.ecmascript-version", "2021") // 使用最新的 ECMAScript 标准 - .option("js.foreign-object-prototype", "false") // 禁用外部对象原型 - .option("js.nashorn-compat", "false") // 关闭 Nashorn 兼容模式以获得更好性能 - .build(); - } - - @Override - protected Object doExecute(String script, ScriptContext context) throws Exception { - if (StrUtil.isBlank(script)) { - return null; - } - - try { - // 绑定上下文变量 - bindContextVariables(context); - - // 从缓存获取或创建脚本源 - Source source = getOrCreateSource(script); - - // 执行脚本并捕获结果,添加超时控制 - // TODO @haohao:通过线程池 + future 会好点? - Value result; - Thread executionThread = Thread.currentThread(); - Thread watchdogThread = new Thread(() -> { - try { - // 等待 5 秒 - TimeUnit.SECONDS.sleep(5); - // 如果执行线程还在运行,中断它 - if (executionThread.isAlive()) { - log.warn("脚本执行超时,强制中断"); - executionThread.interrupt(); - } - } catch (InterruptedException ignored) { - // 忽略中断 - } - }); - - watchdogThread.setDaemon(true); - watchdogThread.start(); - - try { - result = this.context.eval(source); - } finally { - watchdogThread.interrupt(); // 确保看门狗线程停止 - } - - // 转换结果为 Java 对象 - return convertResultToJava(result); - } catch (PolyglotException e) { - handleScriptException(e, script); - throw e; - } - } - - /** - * 绑定上下文变量 - * - * @param context 脚本上下文 - */ - private void bindContextVariables(ScriptContext context) { - Value bindings = this.context.getBindings(LANGUAGE_ID); - - // 添加上下文参数 - if (MapUtil.isNotEmpty(context.getParameters())) { - context.getParameters().forEach(bindings::putMember); - } - - // 添加上下文函数 - if (MapUtil.isNotEmpty(context.getFunctions())) { - context.getFunctions().forEach(bindings::putMember); - } - - // 添加工具类 - bindings.putMember("utils", ScriptUtils.getInstance()); - - // 添加日志对象 - bindings.putMember("log", log); - - // 添加控制台输出(限制并重定向到日志) - AtomicReference consoleBuffer = new AtomicReference<>(new StringBuilder()); - - Value console = this.context.eval(LANGUAGE_ID, "({\n" + - " log: function(msg) { _consoleLog(msg, 'INFO'); },\n" + - " info: function(msg) { _consoleLog(msg, 'INFO'); },\n" + - " warn: function(msg) { _consoleLog(msg, 'WARN'); },\n" + - " error: function(msg) { _consoleLog(msg, 'ERROR'); }\n" + - "})"); - - bindings.putMember("console", console); - - bindings.putMember("_consoleLog", (java.util.function.BiConsumer) (message, level) -> { - String formattedMsg = String.valueOf(message); - switch (level) { - case "INFO": - log.info("Script console: {}", formattedMsg); - break; - case "WARN": - log.warn("Script console: {}", formattedMsg); - break; - case "ERROR": - log.error("Script console: {}", formattedMsg); - break; - default: - log.info("Script console: {}", formattedMsg); - } - - // 将输出添加到缓冲区 - StringBuilder buffer = consoleBuffer.get(); - if (buffer.length() > 10000) { - buffer = new StringBuilder(); - consoleBuffer.set(buffer); - } - buffer.append(formattedMsg).append("\n"); - }); - } - - /** - * 从缓存中获取或创建脚本源 - * - * @param script 脚本内容 - * @return 脚本源 - */ - private Source getOrCreateSource(String script) { - // 如果缓存太大,清理部分缓存 - if (sourceCache.size() > MAX_CACHE_SIZE) { - int itemsToRemove = (int) (MAX_CACHE_SIZE * 0.2); // 清理 20% 的缓存 - sourceCache.keySet().stream() - .limit(itemsToRemove) - .toList() - .forEach(sourceCache::remove); - } - - // 使用脚本的哈希码作为缓存键 - String cacheKey = String.valueOf(script.hashCode()); - - return sourceCache.computeIfAbsent(cacheKey, key -> { - try { - return Source.newBuilder(LANGUAGE_ID, script, "script-" + key + ".js").cached(true).build(); - } catch (Exception e) { - log.error("创建脚本源失败: {}", e.getMessage(), e); - throw new RuntimeException("创建脚本源失败: " + e.getMessage(), e); - } - }); - } - - /** - * 将 GraalJS 结果转换为 Java 对象 - * - * @param result GraalJS 执行结果 - * @return Java 对象 - */ - private Object convertResultToJava(Value result) { - if (result == null || result.isNull()) { - return null; - } - - if (result.isString()) { - return result.asString(); - } - - if (result.isNumber()) { - if (result.fitsInInt()) { - return result.asInt(); - } - if (result.fitsInLong()) { - return result.asLong(); - } - if (result.fitsInFloat()) { - return result.asFloat(); - } - if (result.fitsInDouble()) { - return result.asDouble(); - } - } - - if (result.isBoolean()) { - return result.asBoolean(); - } - - if (result.hasArrayElements()) { - int size = (int) result.getArraySize(); - Object[] array = new Object[size]; - for (int i = 0; i < size; i++) { - array[i] = convertResultToJava(result.getArrayElement(i)); - } - return array; - } - - if (result.hasMembers()) { - Map map = MapUtil.newHashMap(); - for (String key : result.getMemberKeys()) { - map.put(key, convertResultToJava(result.getMember(key))); - } - return map; - } - - if (result.isHostObject()) { - return result.asHostObject(); - } - - // 默认情况下尝试转换为字符串 - return result.toString(); - } - - /** - * 处理脚本执行异常 - * - * @param e 多语言异常 - * @param script 原始脚本 - */ - private void handleScriptException(PolyglotException e, String script) { - if (e.isCancelled()) { - log.error("脚本执行被取消,可能超出资源限制"); - } else if (e.isHostException()) { - Throwable hostException = e.asHostException(); - log.error("脚本执行时发生 Java 异常: {}", hostException.getMessage(), hostException); - } else if (e.isGuestException()) { - if (e.getSourceLocation() != null) { - log.error("脚本执行错误: {} 位于行 {},列 {}", - e.getMessage(), - e.getSourceLocation().getStartLine(), - e.getSourceLocation().getStartColumn()); - - // 尝试显示错误位置上下文 - try { - String[] lines = script.split("\n"); - int lineNumber = e.getSourceLocation().getStartLine(); - if (lineNumber > 0 && lineNumber <= lines.length) { - int contextStart = Math.max(1, lineNumber - 2); - int contextEnd = Math.min(lines.length, lineNumber + 2); - - StringBuilder context = new StringBuilder(); - for (int i = contextStart; i <= contextEnd; i++) { - if (i == lineNumber) { - context.append("> "); // 标记错误行 - } else { - context.append(" "); - } - context.append(i).append(": ").append(lines[i - 1]).append("\n"); - } - log.error("脚本上下文:\n{}", context); - } - } catch (Exception ignored) { - // 忽略上下文显示失败 - } - } else { - log.error("脚本执行错误: {}", e.getMessage()); - } - } else { - log.error("脚本执行时发生未知错误: {}", e.getMessage(), e); - } - } - - @Override - public String getType() { - return TYPE; - } - - @Override - public void close() { - try { - // 清除脚本缓存 - sourceCache.clear(); - - // 关闭 GraalJS 上下文,释放资源 - context.close(true); - } catch (Exception e) { - log.warn("关闭 GraalJS 引擎时发生错误: {}", e.getMessage()); - } - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngine.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngine.java deleted file mode 100644 index 7786aea4d5..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngine.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.engine; - -import cn.iocoder.yudao.module.iot.script.context.ScriptContext; - -/** - * 脚本引擎接口,定义脚本执行的核心功能 - */ -public interface ScriptEngine { - - /** - * 执行脚本 - * - * @param script 脚本内容 - * @param context 脚本上下文 - * @return 脚本执行结果 - */ - Object execute(String script, ScriptContext context); - - /** - * 获取脚本引擎类型 - * - * @return 脚本引擎类型 - */ - String getType(); -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngineFactory.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngineFactory.java deleted file mode 100644 index e6364f8467..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/engine/ScriptEngineFactory.java +++ /dev/null @@ -1,85 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.engine; - -import cn.iocoder.yudao.module.iot.script.sandbox.JsSandbox; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.stereotype.Component; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * 脚本引擎工厂,用于创建和缓存不同类型的脚本引擎,支持资源生命周期管理 - */ -@Slf4j -@Component -public class ScriptEngineFactory implements DisposableBean { - - /** - * 脚本引擎缓存 - */ - private final Map engines = new ConcurrentHashMap<>(); - - /** - * 获取脚本引擎 - * - * @param type 脚本类型 - * @return 脚本引擎 - */ - public ScriptEngine getEngine(String type) { - // 从缓存中获取引擎 - return engines.computeIfAbsent(type, this::createEngine); - } - - /** - * 创建脚本引擎 - * - * @param type 脚本类型 - * @return 脚本引擎 - */ - private ScriptEngine createEngine(String type) { - try { - if (JsScriptEngine.TYPE.equals(type)) { - log.info("创建 GraalJS 脚本引擎"); - return new JsScriptEngine(new JsSandbox()); - } - - log.warn("不支持的脚本类型: {}", type); - return null; - } catch (Exception e) { - log.error("创建脚本引擎 [{}] 失败: {}", type, e.getMessage(), e); - throw new RuntimeException("创建脚本引擎失败: " + e.getMessage(), e); - } - } - - /** - * 释放指定类型的引擎资源 - * - * @param type 脚本类型 - */ - public void releaseEngine(String type) { - ScriptEngine engine = engines.remove(type); - if (engine instanceof AutoCloseable) { - try { - ((AutoCloseable) engine).close(); - log.info("已释放脚本引擎资源: {}", type); - } catch (Exception e) { - log.warn("释放脚本引擎 [{}] 资源时发生错误: {}", type, e.getMessage()); - } - } - } - - /** - * 清理所有引擎资源 - */ - public void releaseAllEngines() { - engines.keySet().forEach(this::releaseEngine); - log.info("已清理所有脚本引擎资源"); - } - - @Override - public void destroy() { - log.info("应用关闭,释放所有脚本引擎资源..."); - releaseAllEngines(); - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/GraalJsExample.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/GraalJsExample.java deleted file mode 100644 index 636f96b72d..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/GraalJsExample.java +++ /dev/null @@ -1,209 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.example; - -import cn.hutool.core.map.MapUtil; -import cn.iocoder.yudao.module.iot.script.context.DefaultScriptContext; -import cn.iocoder.yudao.module.iot.script.context.ScriptContext; -import cn.iocoder.yudao.module.iot.script.service.ScriptService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -// TODO @haohao:搞到 test 里面哈; -/** - * GraalJS 脚本引擎示例 - *

- * 展示了如何使用 GraalJS 脚本引擎的各种功能 - */ -@Slf4j -@Component -public class GraalJsExample { - - @Autowired - private ScriptService scriptService; - - /** - * 执行简单的 JavaScript 脚本 - * - * @return 执行结果 - */ - public Object executeSimpleScript() { - // 简单的脚本内容 - String script = "var result = a + b; result;"; - - // 创建参数 - Map params = MapUtil.newHashMap(); - params.put("a", 10); - params.put("b", 20); - - // 执行脚本 - return scriptService.executeJavaScript(script, params); - } - - /** - * 执行现代 JavaScript 语法(ES6+) - * - * @return 执行结果 - */ - public Object executeModernJavaScript() { - // 使用现代 JavaScript 语法 - String script = "// 使用箭头函数\n" + - "const add = (a, b) => a + b;\n" + - "\n" + - "// 使用解构赋值\n" + - "const {c, d} = params;\n" + - "\n" + - "// 使用模板字符串\n" + - "const result = `计算结果: ${add(c, d)}`;\n" + - "\n" + - "// 使用可选链操作符\n" + - "const value = params?.e?.value ?? 'default';\n" + - "\n" + - "// 返回一个对象\n" + - "({\n" + - " sum: add(c, d),\n" + - " message: result,\n" + - " defaultValue: value\n" + - "})"; - - // 创建参数 - Map params = MapUtil.newHashMap(); - params.put("params", MapUtil.builder() - .put("c", 30) - .put("d", 40) - .build()); - - // 执行脚本 - return scriptService.executeJavaScript(script, params); - } - - /** - * 执行带错误处理的脚本 - * - * @return 执行结果 - */ - public Object executeWithErrorHandling() { - // 包含错误处理的脚本 - String script = "try {\n" + - " // 故意制造错误\n" + - " if (!nonExistentVar) {\n" + - " throw new Error('手动抛出的错误');\n" + - " }\n" + - "} catch (error) {\n" + - " console.error('捕获到错误: ' + error.message);\n" + - " return { success: false, error: error.message };\n" + - "}\n" + - "\n" + - "return { success: true, data: 'No error' };"; - - // 执行脚本 - return scriptService.executeJavaScript(script, MapUtil.newHashMap()); - } - - /** - * 演示超时控制 - * - * @return 执行结果 - */ - public Object executeWithTimeout() { - // 这个脚本会导致无限循环 - String script = "// 无限循环\n" + - "var counter = 0;\n" + - "while(true) {\n" + - " counter++;\n" + - " if (counter % 1000000 === 0) {\n" + - " console.log('Still running: ' + counter);\n" + - " }\n" + - "}\n" + - "return counter;"; - - // 使用 CompletableFuture 和超时控制 - CompletableFuture future = CompletableFuture.supplyAsync(() -> { - try { - return scriptService.executeJavaScript(script, MapUtil.newHashMap()); - } catch (Exception e) { - log.error("脚本执行失败: {}", e.getMessage()); - return "执行失败: " + e.getMessage(); - } - }); - - try { - // 等待结果,最多 10 秒 - return future.get(10, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException e) { - return "执行异常: " + e.getMessage(); - } catch (TimeoutException e) { - future.cancel(true); - return "执行超时,已强制终止"; - } - } - - /** - * 演示 JSON 处理 - * - * @return 执行结果 - */ - public Object executeJsonProcessing() { - // JSON 处理示例 - String script = "// 解析传入的 JSON\n" + - "var data = JSON.parse(jsonString);\n" + - "\n" + - "// 处理 JSON 数据\n" + - "var result = {\n" + - " name: data.name.toUpperCase(),\n" + - " age: data.age + 1,\n" + - " address: data.address || 'Unknown',\n" + - " tags: [...data.tags, 'processed'],\n" + - " timestamp: Date.now()\n" + - "};\n" + - "\n" + - "// 转换回 JSON 字符串\n" + - "JSON.stringify(result);"; - - // 创建上下文 - ScriptContext context = new DefaultScriptContext(); - context.setParameter("jsonString", - "{\"name\":\"test user\",\"age\":25,\"tags\":[\"tag1\",\"tag2\"]}"); - - // 执行脚本 - return scriptService.executeJavaScript(script, context); - } - - /** - * 演示数据转换 - * - * @return 执行结果 - */ - public Object executeDataConversion() { - // 数据转换和处理示例 - String script = "// 使用 utils 工具类进行数据转换\n" + - "var stringValue = utils.isEmpty(input) ? \"默认值\" : input;\n" + - "var numberValue = utils.convert(stringValue, \"number\");\n" + - "\n" + - "// 创建一个复杂数据结构\n" + - "var result = {\n" + - " original: input,\n" + - " stringValue: stringValue,\n" + - " numberValue: numberValue,\n" + - " booleanValue: Boolean(numberValue),\n" + - " isValid: utils.isNotEmpty(input)\n" + - "};\n" + - "\n" + - "// 记录处理结果\n" + - "log.info(\"处理结果: \" + utils.toJson(result));\n" + - "\n" + - "return result;"; - - // 创建参数 - Map params = MapUtil.newHashMap(); - params.put("input", "42"); - - // 执行脚本 - return scriptService.executeJavaScript(script, params); - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/ProductScriptSamples.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/ProductScriptSamples.java deleted file mode 100644 index d091565b8b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/example/ProductScriptSamples.java +++ /dev/null @@ -1,174 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.example; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -/** - * 产品脚本示例类,提供各种类型的产品脚本示例代码 - */ -@Slf4j -@Component -public class ProductScriptSamples { - - /** - * 获取属性解析脚本示例 - * - * @return 属性解析脚本示例代码 - */ - public String getPropertyParserSample() { - return "/**\n" + - " * 属性上报数据解析脚本示例\n" + - " * @param input 设备上报的原始数据\n" + - " * @param productKey 产品标识\n" + - " * @param method 方法类型,固定为 property\n" + - " * @param properties 当前设备的属性数据\n" + - " * @return 解析后的属性数据\n" + - " */\n" + - "function parseProperty(input, productKey) {\n" + - " // 记录日志\n" + - " console.log('开始解析属性数据: ' + input);\n" + - " \n" + - " try {\n" + - " // 假设上报的是 JSON 字符串\n" + - " var data = JSON.parse(input);\n" + - " \n" + - " // 构建属性数据结构\n" + - " var result = {\n" + - " // 属性上报的时间戳,毫秒级\n" + - " timestamp: data.timestamp || Date.now(),\n" + - " // 属性数据\n" + - " params: {}\n" + - " };\n" + - " \n" + - " // 处理属性值\n" + - " if (data.temperature) {\n" + - " result.params.temperature = parseFloat(data.temperature);\n" + - " }\n" + - " \n" + - " if (data.humidity) {\n" + - " result.params.humidity = parseFloat(data.humidity);\n" + - " }\n" + - " \n" + - " console.log('属性解析结果: ' + JSON.stringify(result));\n" + - " return result;\n" + - " } catch (error) {\n" + - " console.error('解析属性数据失败: ' + error.message);\n" + - " throw new Error('解析失败: ' + error.message);\n" + - " }\n" + - "};\n" + - "\n" + - "// 执行解析\n" + - "parseProperty(input, productKey);"; - } - - /** - * 获取事件解析脚本示例 - * - * @return 事件解析脚本示例代码 - */ - public String getEventParserSample() { - return "/**\n" + - " * 事件数据解析脚本示例\n" + - " * @param input 设备上报的原始数据\n" + - " * @param productKey 产品标识\n" + - " * @param method 方法类型,固定为 event\n" + - " * @param identifier 事件标识符\n" + - " * @return 解析后的事件数据\n" + - " */\n" + - "function parseEvent(input, productKey, identifier) {\n" + - " // 记录日志\n" + - " console.log('开始解析事件数据: ' + input);\n" + - " console.log('事件标识符: ' + identifier);\n" + - " \n" + - " try {\n" + - " // 假设上报的是 JSON 字符串\n" + - " var data = JSON.parse(input);\n" + - " \n" + - " // 构建事件数据结构\n" + - " var result = {\n" + - " // 事件标识符\n" + - " identifier: identifier || 'alert',\n" + - " // 事件上报的时间戳,毫秒级\n" + - " timestamp: data.timestamp || Date.now(),\n" + - " // 事件参数\n" + - " params: {}\n" + - " };\n" + - " \n" + - " // 根据不同事件类型处理参数\n" + - " if (result.identifier === 'alert') {\n" + - " result.params.level = data.level || 'info';\n" + - " result.params.message = data.message || '';\n" + - " } else if (result.identifier === 'error') {\n" + - " result.params.code = data.code || 0;\n" + - " result.params.message = data.message || '';\n" + - " }\n" + - " \n" + - " console.log('事件解析结果: ' + JSON.stringify(result));\n" + - " return result;\n" + - " } catch (error) {\n" + - " console.error('解析事件数据失败: ' + error.message);\n" + - " throw new Error('解析失败: ' + error.message);\n" + - " }\n" + - "};\n" + - "\n" + - "// 执行解析\n" + - "parseEvent(input, productKey, identifier);"; - } - - /** - * 获取命令编码脚本示例 - * - * @return 命令编码脚本示例代码 - */ - public String getCommandEncoderSample() { - return "/**\n" + - " * 命令数据编码脚本示例\n" + - " * @param input 平台下发的命令数据\n" + - " * @param productKey 产品标识\n" + - " * @param method 方法类型,固定为 command\n" + - " * @param cmdParams 命令参数\n" + - " * @return 编码后的命令数据\n" + - " */\n" + - "function encodeCommand(input, productKey, cmdParams) {\n" + - " // 记录日志\n" + - " console.log('开始编码命令数据: ' + input);\n" + - " console.log('命令参数: ' + JSON.stringify(cmdParams));\n" + - " \n" + - " try {\n" + - " // 输入可能是 JSON 字符串或对象\n" + - " var data = typeof input === 'string' ? JSON.parse(input) : input;\n" + - " \n" + - " // 获取命令名称和值\n" + - " var cmdName = cmdParams.cmdName || '';\n" + - " var cmdValue = cmdParams.cmdValue;\n" + - " \n" + - " // 构建设备可识别的命令格式\n" + - " var result = {\n" + - " cmd: cmdName,\n" + - " value: cmdValue,\n" + - " timestamp: Date.now()\n" + - " };\n" + - " \n" + - " // 根据不同命令类型构建参数\n" + - " if (cmdName === 'setValue') {\n" + - " // 无需额外处理\n" + - " } else if (cmdName === 'control') {\n" + - " result.mode = data.mode || 'auto';\n" + - " result.action = data.action || 'start';\n" + - " }\n" + - " \n" + - " // 转换为设备能识别的格式(此处以 JSON 字符串为例)\n" + - " var encodedResult = JSON.stringify(result);\n" + - " \n" + - " console.log('命令编码结果: ' + encodedResult);\n" + - " return encodedResult;\n" + - " } catch (error) {\n" + - " console.error('编码命令数据失败: ' + error.message);\n" + - " throw new Error('编码失败: ' + error.message);\n" + - " }\n" + - "};\n" + - "\n" + - "// 执行编码\n" + - "encodeCommand(input, productKey, cmdParams);"; - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/package-info.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/package-info.java deleted file mode 100644 index 0ec0c14e07..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * IoT 脚本模块,提供脚本引擎、执行环境和沙箱功能,支持 JavaScript 脚本的执行 - */ -package cn.iocoder.yudao.module.iot.script; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/JsSandbox.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/JsSandbox.java deleted file mode 100644 index 0483b35053..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/JsSandbox.java +++ /dev/null @@ -1,330 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.sandbox; - -import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; -import lombok.extern.slf4j.Slf4j; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * JavaScript 沙箱实现,提供脚本安全性验证 - */ -@Slf4j -public class JsSandbox implements ScriptSandbox { - - /** - * JavaScript 沙箱类型 - */ - public static final String TYPE = "js"; - - /** - * 不安全的关键字列表 - */ - private final List unsafeKeywords = new ArrayList<>(); - - /** - * 可能导致高资源消耗的关键字 - */ - private final List highResourceKeywords = new ArrayList<>(); - - /** - * 不安全的包/类访问模式 - */ - private final List unsafePatterns = new ArrayList<>(); - - /** - * 递归或循环嵌套深度检测模式 - */ - private final List recursionPatterns = new ArrayList<>(); - - /** - * 允许的脚本最大长度(字节) - */ - private static final int MAX_SCRIPT_LENGTH = 100 * 1024; // 100KB - - /** - * 脚本安全验证超时时间(毫秒) - */ - private static final long VALIDATION_TIMEOUT = 1000; // 1秒 - - /** - * 构造函数,初始化不安全的关键字和模式 - */ - public JsSandbox() { - // 初始化 Java 相关的不安全关键字 - // TODO @haohao:可以使用 addAll 哈。 - Arrays.asList( - "java.lang.System", - "java.io", - "java.net", - "java.nio", - "java.security", - "java.rmi", - "java.lang.reflect", - "java.sql", - "javax.sql", - "javax.naming", - "javax.script", - "javax.tools", - "org.omg", - "org.graalvm.polyglot", - "sun.", - "javafx.", - "Packages.", - "com.sun.", - "com.oracle.").forEach(unsafeKeywords::add); - - // GraalJS 特有的不安全关键字 - Arrays.asList( - "Polyglot.import", - "Polyglot.eval", - "Java.type", - "allowHostAccess", - "allowNativeAccess", - "allowIO", - "allowHostClassLoading", - "allowAllAccess", - "allowExperimentalOptions", - "Context.Builder", - "Context.create", - "Context.getCurrent", - "Context.newBuilder", - "__proto__", - "__defineGetter__", - "__defineSetter__", - "__lookupGetter__", - "__lookupSetter__", - "__noSuchMethod__", - "constructor.constructor", - "Object.constructor").forEach(unsafeKeywords::add); - - // 可能导致高资源消耗的关键字 - Arrays.asList( - "while(true)", - "for(;;)", - "do{", - "BigInt", - "Promise.all", - "setTimeout", - "setInterval", - "new Array(", - "Array(", - "new ArrayBuffer(", - ".repeat(", - ".forEach(", - ".map(", - ".reduce(").forEach(highResourceKeywords::add); - - // 初始化不安全的模式 - // 系统访问和进程执行 - unsafePatterns.add(Pattern.compile("java\\.lang\\.Runtime")); - unsafePatterns.add(Pattern.compile("java\\.lang\\.ProcessBuilder")); - unsafePatterns.add(Pattern.compile("java\\.lang\\.reflect")); - - // 特殊对象和操作 - unsafePatterns.add(Pattern.compile("Packages")); - unsafePatterns.add(Pattern.compile("JavaImporter")); - unsafePatterns.add(Pattern.compile("load\\s*\\(")); - unsafePatterns.add(Pattern.compile("loadWithNewGlobal\\s*\\(")); - unsafePatterns.add(Pattern.compile("exit\\s*\\(")); - unsafePatterns.add(Pattern.compile("quit\\s*\\(")); - unsafePatterns.add(Pattern.compile("eval\\s*\\(")); - - // GraalJS 特有的不安全模式 - unsafePatterns.add(Pattern.compile("Polyglot\\.")); - unsafePatterns.add(Pattern.compile("Java\\.type\\s*\\(")); - unsafePatterns.add(Pattern.compile("Context\\.")); - unsafePatterns.add(Pattern.compile("Engine\\.")); - - // 原型污染检测 - unsafePatterns.add(Pattern.compile("(?:Object|Array|String|Number|Boolean|Function|RegExp|Date)\\.prototype")); - unsafePatterns.add(Pattern.compile("\\['constructor'\\]")); - unsafePatterns.add(Pattern.compile("\\[\"constructor\"\\]")); - unsafePatterns.add(Pattern.compile("\\['__proto__'\\]")); - unsafePatterns.add(Pattern.compile("\\[\"__proto__\"\\]")); - - // 检测可能导致无限递归或循环的模式 - recursionPatterns.add(Pattern.compile("for\\s*\\([^\\)]*\\)\\s*\\{[^\\}]*for\\s*\\(")); // 嵌套循环 - recursionPatterns.add(Pattern.compile("while\\s*\\([^\\)]*\\)\\s*\\{[^\\}]*while\\s*\\(")); // 嵌套 while - recursionPatterns.add(Pattern.compile("function\\s+[a-zA-Z0-9_$]+\\s*\\([^\\)]*\\)\\s*\\{[^\\}]*\\1\\s*\\(")); // 递归函数调用 - } - - @Override - public boolean validate(String script) { - if (StrUtil.isBlank(script)) { - return true; - } - - // 检查脚本长度 - if (script.length() > MAX_SCRIPT_LENGTH) { - log.warn("脚本长度超过限制: {} > {}", script.length(), MAX_SCRIPT_LENGTH); - return false; - } - - // 使用超时机制进行验证 - final boolean[] result = {true}; - Thread validationThread = new Thread(() -> { - // 检查不安全的关键字 - if (containsUnsafeKeywords(script)) { - result[0] = false; - return; - } - - // 检查不安全的模式 - if (matchesUnsafePatterns(script)) { - result[0] = false; - return; - } - - // 检查可能导致高资源消耗的构造 - if (containsHighResourcePatterns(script)) { - log.warn("脚本包含可能导致高资源消耗的构造,需要注意"); - // 不直接拒绝,而是记录警告 - } - - // 分析脚本复杂度 - analyzeScriptComplexity(script); - }); - - validationThread.start(); - try { - validationThread.join(VALIDATION_TIMEOUT); - if (validationThread.isAlive()) { - validationThread.interrupt(); - log.warn("脚本安全验证超时"); - return false; - } - } catch (InterruptedException e) { - log.warn("脚本安全验证被中断"); - return false; - } - - return result[0]; - } - - @Override - public String getType() { - return TYPE; - } - - /** - * 检查脚本是否包含不安全的关键字 - * - * @param script 脚本内容 - * @return 是否包含不安全的关键字 - */ - private boolean containsUnsafeKeywords(String script) { - if (CollUtil.isEmpty(unsafeKeywords)) { - return false; - } - - for (String keyword : unsafeKeywords) { - if (script.contains(keyword)) { - log.warn("脚本包含不安全的关键字: {}", keyword); - return true; - } - } - return false; - } - - /** - * 检查脚本是否匹配不安全的模式 - * - * @param script 脚本内容 - * @return 是否匹配不安全的模式 - */ - private boolean matchesUnsafePatterns(String script) { - if (CollUtil.isEmpty(unsafePatterns)) { - return false; - } - - for (Pattern pattern : unsafePatterns) { - Matcher matcher = pattern.matcher(script); - if (matcher.find()) { - log.warn("脚本匹配到不安全的模式: {}", pattern.pattern()); - return true; - } - } - return false; - } - - /** - * 检查脚本是否包含可能导致高资源消耗的模式 - * - * @param script 脚本内容 - * @return 是否包含高资源消耗模式 - */ - private boolean containsHighResourcePatterns(String script) { - if (CollUtil.isEmpty(highResourceKeywords)) { - return false; - } - - boolean result = false; - for (String pattern : highResourceKeywords) { - if (script.contains(pattern)) { - log.warn("脚本包含高资源消耗模式: {}", pattern); - result = true; - } - } - - // 还要检查递归或嵌套循环模式 - for (Pattern pattern : recursionPatterns) { - Matcher matcher = pattern.matcher(script); - if (matcher.find()) { - log.warn("脚本包含嵌套循环或递归调用: {}", pattern.pattern()); - result = true; - } - } - - return result; - } - - /** - * 分析脚本复杂度 - * - * @param script 脚本内容 - */ - private void analyzeScriptComplexity(String script) { - // 计算循环和条件语句的数量 - int forCount = countOccurrences(script, "for("); - forCount += countOccurrences(script, "for ("); - - int whileCount = countOccurrences(script, "while("); - whileCount += countOccurrences(script, "while ("); - - int doWhileCount = countOccurrences(script, "do{"); - doWhileCount += countOccurrences(script, "do {"); - - int funcCount = countOccurrences(script, "function"); - - // 记录复杂度评估 - if (forCount + whileCount + doWhileCount > 10) { - log.warn("脚本循环结构过多: for={}, while={}, do-while={}", forCount, whileCount, doWhileCount); - } - - if (funcCount > 20) { - log.warn("脚本函数定义过多: {}", funcCount); - } - } - - /** - * 计算字符串出现次数 - * - * @param source 源字符串 - * @param substring 子字符串 - * @return 出现次数 - */ - private int countOccurrences(String source, String substring) { - int count = 0; - int index = 0; - while ((index = source.indexOf(substring, index)) != -1) { - count++; - index += substring.length(); - } - return count; - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/ScriptSandbox.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/ScriptSandbox.java deleted file mode 100644 index 3064bec393..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/sandbox/ScriptSandbox.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.sandbox; - -/** - * 脚本沙箱接口,提供脚本安全性验证 - */ -public interface ScriptSandbox { - - /** - * 验证脚本内容是否安全 - * - * @param script 脚本内容 - * @return 脚本是否安全 - */ - boolean validate(String script); - - /** - * 获取沙箱类型 - * - * @return 沙箱类型 - */ - String getType(); -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptService.java deleted file mode 100644 index 1988e5c151..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptService.java +++ /dev/null @@ -1,58 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.service; - -import cn.iocoder.yudao.module.iot.script.context.ScriptContext; - -import java.util.Map; - -/** - * 脚本服务接口,定义脚本执行的核心功能 - */ -public interface ScriptService { - - /** - * 执行脚本 - * - * @param scriptType 脚本类型(如 js、groovy 等) - * @param script 脚本内容 - * @param context 脚本上下文 - * @return 脚本执行结果 - */ - Object executeScript(String scriptType, String script, ScriptContext context); - - /** - * 执行脚本 - * - * @param scriptType 脚本类型(如 js、groovy 等) - * @param script 脚本内容 - * @param params 脚本参数 - * @return 脚本执行结果 - */ - Object executeScript(String scriptType, String script, Map params); - - /** - * 执行 JavaScript 脚本 - * - * @param script 脚本内容 - * @param context 脚本上下文 - * @return 脚本执行结果 - */ - Object executeJavaScript(String script, ScriptContext context); - - /** - * 执行 JavaScript 脚本 - * - * @param script 脚本内容 - * @param params 脚本参数 - * @return 脚本执行结果 - */ - Object executeJavaScript(String script, Map params); - - /** - * 验证脚本内容是否安全 - * - * @param scriptType 脚本类型 - * @param script 脚本内容 - * @return 脚本是否安全 - */ - boolean validateScript(String scriptType, String script); -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptServiceImpl.java deleted file mode 100644 index 044e55e3db..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/service/ScriptServiceImpl.java +++ /dev/null @@ -1,111 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.service; - -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.module.iot.script.context.DefaultScriptContext; -import cn.iocoder.yudao.module.iot.script.context.ScriptContext; -import cn.iocoder.yudao.module.iot.script.engine.JsScriptEngine; -import cn.iocoder.yudao.module.iot.script.engine.ScriptEngine; -import cn.iocoder.yudao.module.iot.script.engine.ScriptEngineFactory; -import cn.iocoder.yudao.module.iot.script.sandbox.JsSandbox; -import cn.iocoder.yudao.module.iot.script.sandbox.ScriptSandbox; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.Map; - -/** - * 脚本服务实现类 - */ -@Slf4j -@Service -public class ScriptServiceImpl implements ScriptService { - - @Autowired - private ScriptEngineFactory engineFactory; - - @Override - public Object executeScript(String scriptType, String script, ScriptContext context) { - if (StrUtil.isBlank(scriptType) || StrUtil.isBlank(script)) { - return null; - } - - ScriptEngine engine = engineFactory.getEngine(scriptType); - if (engine == null) { - log.error("找不到脚本引擎: {}", scriptType); - throw new RuntimeException("不支持的脚本类型: " + scriptType); - } - - try { - return engine.execute(script, context); - } catch (Exception e) { - // TODO @haohao:最好打印一些参数;下面类似的也是 - log.error("执行脚本失败: {}", e.getMessage(), e); - throw new RuntimeException("执行脚本失败: " + e.getMessage(), e); - } - } - - @Override - public Object executeScript(String scriptType, String script, Map params) { - ScriptContext context = createContext(params); - return executeScript(scriptType, script, context); - } - - @Override - public Object executeJavaScript(String script, ScriptContext context) { - return executeScript(JsScriptEngine.TYPE, script, context); - } - - @Override - public Object executeJavaScript(String script, Map params) { - return executeScript(JsScriptEngine.TYPE, script, params); - } - - @Override - public boolean validateScript(String scriptType, String script) { - if (StrUtil.isBlank(scriptType) || StrUtil.isBlank(script)) { - return true; - } - - ScriptSandbox sandbox = getSandbox(scriptType); - if (sandbox == null) { - log.warn("找不到对应的脚本沙箱: {}", scriptType); - return false; - } - - try { - return sandbox.validate(script); - } catch (Exception e) { - log.error("验证脚本安全性失败: {}", e.getMessage(), e); - return false; - } - } - - /** - * 根据脚本类型获取对应的沙箱实现 - * - * @param scriptType 脚本类型 - * @return 沙箱实现 - */ - private ScriptSandbox getSandbox(String scriptType) { - if (JsScriptEngine.TYPE.equals(scriptType)) { - return new JsSandbox(); - } - return null; - } - - /** - * 根据参数创建脚本上下文 - * - * @param params 参数 - * @return 脚本上下文 - */ - private ScriptContext createContext(Map params) { - ScriptContext context = new DefaultScriptContext(); - if (MapUtil.isNotEmpty(params)) { - params.forEach(context::setParameter); - } - return context; - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/util/ScriptUtils.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/util/ScriptUtils.java deleted file mode 100644 index bb6af5d34b..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/script/util/ScriptUtils.java +++ /dev/null @@ -1,159 +0,0 @@ -package cn.iocoder.yudao.module.iot.script.util; - -import cn.hutool.core.convert.Convert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONUtil; -import lombok.extern.slf4j.Slf4j; - -import java.util.Map; - -/** - * 脚本工具类,提供给脚本执行环境使用的工具方法 - */ -@Slf4j -public class ScriptUtils { - - /** - * 单例实例 - */ - private static final ScriptUtils INSTANCE = new ScriptUtils(); - - /** - * 获取单例实例 - * - * @return 工具类实例 - */ - public static ScriptUtils getInstance() { - return INSTANCE; - } - - // TODO @haohao:使用 lombok 简化掉 - private ScriptUtils() { - // 私有构造函数 - } - - /** - * 字符串是否为空 - * - * @param str 字符串 - * @return 是否为空 - */ - public boolean isEmpty(String str) { - return StrUtil.isEmpty(str); - } - - /** - * 字符串是否不为空 - * - * @param str 字符串 - * @return 是否不为空 - */ - public boolean isNotEmpty(String str) { - return StrUtil.isNotEmpty(str); - } - - /** - * 将对象转为 JSON 字符串 - * - * @param obj 对象 - * @return JSON 字符串 - */ - public String toJson(Object obj) { - return JSONUtil.toJsonStr(obj); - } - - /** - * 将 JSON 字符串转为 Map - * - * @param json JSON 字符串 - * @return Map 对象 - */ - public Map parseJson(String json) { - if (StrUtil.isEmpty(json)) { - return MapUtil.newHashMap(); - } - try { - return JSONUtil.toBean(json, Map.class); - } catch (Exception e) { - log.error("JSON 解析失败: {}", json, e); - return MapUtil.newHashMap(); - } - } - - /** - * 类型转换 - * - * @param value 值 - * @param type 目标类型 - * @param 泛型 - * @return 转换后的值 - */ - public T convert(Object value, Class type) { - return Convert.convert(type, value); - } - - /** - * 从 Map 中获取值 - * - * @param map Map 对象 - * @param key 键 - * @return 值 - */ - public Object get(Map map, String key) { - return MapUtil.get(map, key, Object.class); - } - - /** - * 从 Map 中获取字符串 - * - * @param map Map 对象 - * @param key 键 - * @return 字符串值 - */ - public String getString(Map map, String key) { - return MapUtil.getStr(map, key); - } - - /** - * 从 Map 中获取整数 - * - * @param map Map 对象 - * @param key 键 - * @return 整数值 - */ - public Integer getInt(Map map, String key) { - return MapUtil.getInt(map, key); - } - - /** - * 从 Map 中获取布尔值 - * - * @param map Map 对象 - * @param key 键 - * @return 布尔值 - */ - public Boolean getBool(Map map, String key) { - return MapUtil.getBool(map, key); - } - - /** - * 从 Map 中获取双精度浮点数 - * - * @param map Map 对象 - * @param key 键 - * @return 双精度浮点数值 - */ - public Double getDouble(Map map, String key) { - return MapUtil.getDouble(map, key); - } - - /** - * 获取当前时间戳(毫秒) - * - * @return 时间戳 - */ - public long currentTimeMillis() { - return System.currentTimeMillis(); - } -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptService.java deleted file mode 100644 index 87486aaa6c..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptService.java +++ /dev/null @@ -1,82 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.product; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptPageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptSaveReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptTestReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptTestRespVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductScriptDO; -import jakarta.validation.Valid; - -import java.util.List; - -/** - * IoT 产品脚本信息 Service 接口 - * - * @author 芋道源码 - */ -public interface IotProductScriptService { - - /** - * 创建IoT 产品脚本信息 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createProductScript(@Valid IotProductScriptSaveReqVO createReqVO); - - /** - * 更新IoT 产品脚本信息 - * - * @param updateReqVO 更新信息 - */ - void updateProductScript(@Valid IotProductScriptSaveReqVO updateReqVO); - - /** - * 删除IoT 产品脚本信息 - * - * @param id 编号 - */ - void deleteProductScript(Long id); - - /** - * 获得IoT 产品脚本信息 - * - * @param id 编号 - * @return IoT 产品脚本信息 - */ - IotProductScriptDO getProductScript(Long id); - - /** - * 获得IoT 产品脚本信息分页 - * - * @param pageReqVO 分页查询 - * @return IoT 产品脚本信息分页 - */ - PageResult getProductScriptPage(IotProductScriptPageReqVO pageReqVO); - - /** - * 获取产品的脚本列表 - * - * @param productId 产品ID - * @return 脚本列表 - */ - List getProductScriptListByProductId(Long productId); - - /** - * 测试产品脚本 - * - * @param testReqVO 测试请求 - * @return 测试结果 - */ - IotProductScriptTestRespVO testProductScript(@Valid IotProductScriptTestReqVO testReqVO); - - /** - * 更新产品脚本状态 - * - * @param id 脚本ID - * @param status 状态 - */ - void updateProductScriptStatus(Long id, Integer status); - -} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptServiceImpl.java deleted file mode 100644 index 803e0047e9..0000000000 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptServiceImpl.java +++ /dev/null @@ -1,234 +0,0 @@ -package cn.iocoder.yudao.module.iot.service.product; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptPageReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptSaveReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptTestReqVO; -import cn.iocoder.yudao.module.iot.controller.admin.product.vo.script.IotProductScriptTestRespVO; -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductScriptDO; -import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductScriptMapper; -import cn.iocoder.yudao.module.iot.script.context.DeviceScriptContext; -import cn.iocoder.yudao.module.iot.script.service.ScriptService; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import jakarta.annotation.Resource; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.validation.annotation.Validated; - -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS; -import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_SCRIPT_NOT_EXISTS; - -// TODO @芋艿:后续再 review 哈! -/** - * IoT 产品脚本信息 Service 实现类 - * - * @author 芋道源码 - */ -@Service -@Validated -@Slf4j -public class IotProductScriptServiceImpl implements IotProductScriptService { - - @Resource - private IotProductScriptMapper productScriptMapper; - - @Resource - private IotProductService productService; - - @Resource - private ScriptService scriptService; - - @Override - public Long createProductScript(IotProductScriptSaveReqVO createReqVO) { - // 验证产品是否存在 - validateProductExists(createReqVO.getProductId()); - - // 插入 - IotProductScriptDO productScript = BeanUtils.toBean(createReqVO, IotProductScriptDO.class); - // 初始化版本为1 - productScript.setVersion(1); - // 初始化测试相关字段 - productScript.setLastTestResult(null); - productScript.setLastTestTime(null); - productScriptMapper.insert(productScript); - // 返回 - return productScript.getId(); - } - - @Override - public void updateProductScript(IotProductScriptSaveReqVO updateReqVO) { - // 校验存在 - validateProductScriptExists(updateReqVO.getId()); - - // 获取旧的记录,保留版本号和测试信息 - IotProductScriptDO oldScript = getProductScript(updateReqVO.getId()); - - // 更新 - IotProductScriptDO updateObj = BeanUtils.toBean(updateReqVO, IotProductScriptDO.class); - // 更新版本号 - updateObj.setVersion(oldScript.getVersion() + 1); - // 保留测试相关信息 - updateObj.setLastTestTime(oldScript.getLastTestTime()); - updateObj.setLastTestResult(oldScript.getLastTestResult()); - productScriptMapper.updateById(updateObj); - } - - @Override - public void deleteProductScript(Long id) { - // 校验存在 - validateProductScriptExists(id); - // 删除 - productScriptMapper.deleteById(id); - } - - private void validateProductScriptExists(Long id) { - if (productScriptMapper.selectById(id) == null) { - throw exception(PRODUCT_SCRIPT_NOT_EXISTS); - } - } - - private void validateProductExists(Long productId) { - IotProductDO product = productService.getProduct(productId); - if (product == null) { - throw exception(PRODUCT_NOT_EXISTS); - } - } - - @Override - public IotProductScriptDO getProductScript(Long id) { - return productScriptMapper.selectById(id); - } - - @Override - public PageResult getProductScriptPage(IotProductScriptPageReqVO pageReqVO) { - return productScriptMapper.selectPage(pageReqVO); - } - - @Override - public List getProductScriptListByProductId(Long productId) { - return productScriptMapper.selectList(new LambdaQueryWrapper() - .eq(IotProductScriptDO::getProductId, productId) - .orderByDesc(IotProductScriptDO::getId)); - } - - @Override - public IotProductScriptTestRespVO testProductScript(IotProductScriptTestReqVO testReqVO) { - long startTime = System.currentTimeMillis(); - - try { - // 验证产品是否存在 - validateProductExists(testReqVO.getProductId()); - - // 根据ID获取已保存的脚本(如果有) - IotProductScriptDO existingScript = null; - if (testReqVO.getId() != null) { - existingScript = getProductScript(testReqVO.getId()); - } - - // 创建测试上下文 - IotProductDO product = productService.getProduct(testReqVO.getProductId()); - DeviceScriptContext context = new DeviceScriptContext(); - - // 设置设备上下文(使用产品信息,测试时无具体设备) - context.withDeviceInfo(product.getProductKey(), null); - - // 设置输入参数 - Map params = new HashMap<>(); - params.put("input", testReqVO.getTestInput()); - params.put("productKey", product.getProductKey()); - params.put("scriptType", testReqVO.getScriptType()); - - // 根据脚本类型设置特定参数 - switch (testReqVO.getScriptType()) { - case 1: // PROPERTY_PARSER - params.put("method", "property"); - // 添加一些模拟的属性数据 - Map properties = new HashMap<>(); - properties.put("temp", 25.5); - properties.put("humidity", 60); - context.withProperties(properties); - break; - case 2: // EVENT_PARSER - params.put("method", "event"); - params.put("identifier", "default"); - // 添加事件数据 - Map eventParams = new HashMap<>(); - eventParams.put("timestamp", System.currentTimeMillis()); - params.put("eventParams", eventParams); - break; - case 3: // COMMAND_ENCODER - params.put("method", "command"); - // 添加命令参数 - Map cmdParams = new HashMap<>(); - cmdParams.put("cmdName", "setValue"); - cmdParams.put("cmdValue", 100); - params.put("cmdParams", cmdParams); - break; - default: - // 默认不添加额外参数 - } - - // 添加所有参数到上下文 - for (Map.Entry entry : params.entrySet()) { - context.setParameter(entry.getKey(), entry.getValue()); - } - - // 执行脚本 - Object result = scriptService.executeScript( - testReqVO.getScriptLanguage(), - testReqVO.getScriptContent(), - context); - - // 更新测试结果(如果是已保存的脚本) - if (existingScript != null) { - IotProductScriptDO updateObj = new IotProductScriptDO(); - updateObj.setId(existingScript.getId()); - updateObj.setLastTestTime(LocalDateTime.now()); - updateObj.setLastTestResult(1); // 1表示成功 - productScriptMapper.updateById(updateObj); - } - - long executionTime = System.currentTimeMillis() - startTime; - return IotProductScriptTestRespVO.success(result, executionTime); - - } catch (Exception e) { - log.error("[testProductScript][测试脚本异常]", e); - - // 如果是已保存的脚本,更新测试失败状态 - if (testReqVO.getId() != null) { - try { - IotProductScriptDO updateObj = new IotProductScriptDO(); - updateObj.setId(testReqVO.getId()); - updateObj.setLastTestTime(LocalDateTime.now()); - updateObj.setLastTestResult(0); // 0表示失败 - productScriptMapper.updateById(updateObj); - } catch (Exception ex) { - log.error("[testProductScript][更新脚本测试结果异常]", ex); - } - } - - long executionTime = System.currentTimeMillis() - startTime; - return IotProductScriptTestRespVO.error(e.getMessage(), executionTime); - } - } - - @Override - public void updateProductScriptStatus(Long id, Integer status) { - // 校验存在 - validateProductScriptExists(id); - - // 更新状态 - IotProductScriptDO updateObj = new IotProductScriptDO(); - updateObj.setId(id); - updateObj.setStatus(status); - productScriptMapper.updateById(updateObj); - } -} \ No newline at end of file diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index fe0cca16ab..85e385ce94 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -69,13 +69,13 @@ spring: url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true username: root password: 123456 -# tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!) -# url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro -# driver-class-name: com.taosdata.jdbc.rs.RestfulDriver -# username: root -# password: taosdata -# druid: -# validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL + tdengine: # IoT 数据库(需要 IoT 物联网再开启噢!) + url: jdbc:TAOS-RS://127.0.0.1:6041/ruoyi_vue_pro + driver-class-name: com.taosdata.jdbc.rs.RestfulDriver + username: root + password: taosdata + druid: + validation-query: SELECT SERVER_STATUS() # TDengine 数据源的有效性检查 SQL # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 data: