From e26903b06ba68e56712e4c65a9f05128b0fd9cf2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=AE=89=E6=B5=A9=E6=B5=A9?= <1036606149@qq.com>
Date: Sun, 23 Mar 2025 21:55:08 +0800
Subject: [PATCH] =?UTF-8?q?=E3=80=90=E5=8A=9F=E8=83=BD=E6=96=B0=E5=A2=9E?=
=?UTF-8?q?=E3=80=91IoT:=20=E5=A2=9E=E5=8A=A0=E4=BA=A7=E5=93=81=E8=84=9A?=
=?UTF-8?q?=E6=9C=AC=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8C=85?=
=?UTF-8?q?=E6=8B=AC=E5=A2=9E=E5=88=A0=E6=94=B9=E6=9F=A5=E5=8F=8A=E6=B5=8B?=
=?UTF-8?q?=E8=AF=95=E6=8E=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../module/iot/enums/ErrorCodeConstants.java | 3 +
yudao-module-iot/yudao-module-iot-biz/pom.xml | 9 +-
.../product/IotProductScriptController.java | 99 ++++++++
.../vo/script/IotProductScriptPageReqVO.java | 46 ++++
.../vo/script/IotProductScriptRespVO.java | 63 +++++
.../vo/script/IotProductScriptSaveReqVO.java | 42 ++++
.../vo/script/IotProductScriptTestReqVO.java | 35 +++
.../vo/script/IotProductScriptTestRespVO.java | 39 ++++
.../IotProductScriptUpdateStatusReqVO.java | 19 ++
.../product/IotProductScriptDO.java | 72 ++++++
.../mysql/product/IotProductScriptMapper.java | 31 +++
.../product/IotProductScriptService.java | 82 +++++++
.../product/IotProductScriptServiceImpl.java | 219 ++++++++++++++++++
13 files changed, 758 insertions(+), 1 deletion(-)
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductScriptController.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptPageReqVO.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptRespVO.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptSaveReqVO.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestReqVO.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestRespVO.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptUpdateStatusReqVO.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductScriptDO.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductScriptMapper.java
create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptService.java
create 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-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
index c719aeaa28..a19b800061 100644
--- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
+++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
@@ -75,4 +75,7 @@ public interface ErrorCodeConstants {
// ========== IoT 规则场景(场景联动) 1-050-011-000 ==========
ErrorCode RULE_SCENE_NOT_EXISTS = new ErrorCode(1_050_011_000, "IoT 规则场景(场景联动)不存在");
+ // ========== IoT 产品脚本信息 1-050-012-000 ==========
+ ErrorCode PRODUCT_SCRIPT_NOT_EXISTS = new ErrorCode(1_050_012_000, "IoT 产品脚本信息不存在");
+
}
\ No newline at end of file
diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml
index 8721e4de93..c5a968207f 100644
--- a/yudao-module-iot/yudao-module-iot-biz/pom.xml
+++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -69,6 +69,13 @@
yudao-spring-boot-starter-excel
+
+
+ cn.iocoder.boot
+ yudao-module-iot-plugin-script
+ ${revision}
+
+
org.apache.rocketmq
@@ -87,7 +94,7 @@
- org.pf4j
+ org.pf4j
pf4j-spring
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
new file mode 100644
index 0000000000..7e95ea2e0e
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductScriptController.java
@@ -0,0 +1,99 @@
+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.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;
+
+ @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);
+ }
+}
\ 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
new file mode 100644
index 0000000000..73df10a617
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptPageReqVO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+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 = "脚本类型(property_parser=属性解析,event_parser=事件解析,command_encoder=命令编码)", example = "2")
+ private String scriptType;
+
+ @Schema(description = "脚本语言")
+ private String scriptLanguage;
+
+ @Schema(description = "状态(0=禁用 1=启用)", example = "2")
+ 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
new file mode 100644
index 0000000000..1530b1f07d
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptRespVO.java
@@ -0,0 +1,63 @@
+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 = "脚本类型(property_parser=属性解析,event_parser=事件解析,command_encoder=命令编码)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("脚本类型(property_parser=属性解析,event_parser=事件解析,command_encoder=命令编码)")
+ private String scriptType;
+
+ @Schema(description = "脚本内容", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("脚本内容")
+ private String scriptContent;
+
+ @Schema(description = "脚本语言", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("脚本语言")
+ private String scriptLanguage;
+
+ @Schema(description = "状态(0=禁用 1=启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("状态(0=禁用 1=启用)")
+ 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
new file mode 100644
index 0000000000..05d685b4ac
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptSaveReqVO.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;
+
+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 = "脚本类型(property_parser=属性解析,event_parser=事件解析,command_encoder=命令编码)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotEmpty(message = "脚本类型(property_parser=属性解析,event_parser=事件解析,command_encoder=命令编码)不能为空")
+ private String scriptType;
+
+ @Schema(description = "脚本内容", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "脚本内容不能为空")
+ private String scriptContent;
+
+ @Schema(description = "脚本语言", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotEmpty(message = "脚本语言不能为空")
+ private String scriptLanguage;
+
+ @Schema(description = "状态(0=禁用 1=启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "状态(0=禁用 1=启用)不能为空")
+ 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
new file mode 100644
index 0000000000..e571b7c044
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;
+
+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 = "property_parser")
+ @NotEmpty(message = "脚本类型不能为空")
+ private String 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
new file mode 100644
index 0000000000..3dec9f6988
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptTestRespVO.java
@@ -0,0 +1,39 @@
+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
new file mode 100644
index 0000000000..823224abc8
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/script/IotProductScriptUpdateStatusReqVO.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.iot.controller.admin.product.vo.script;
+
+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 = "状态(0=禁用 1=启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+ @NotNull(message = "状态不能为空")
+ 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
new file mode 100644
index 0000000000..6b973e6529
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductScriptDO.java
@@ -0,0 +1,72 @@
+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;
+
+/**
+ * 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
new file mode 100644
index 0000000000..96c5ababdf
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductScriptMapper.java
@@ -0,0 +1,31 @@
+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/service/product/IotProductScriptService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptService.java
new file mode 100644
index 0000000000..87486aaa6c
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptService.java
@@ -0,0 +1,82 @@
+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
new file mode 100644
index 0000000000..d5a4aac72f
--- /dev/null
+++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductScriptServiceImpl.java
@@ -0,0 +1,219 @@
+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.plugin.script.context.PluginScriptContext;
+import cn.iocoder.yudao.module.iot.plugin.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;
+
+/**
+ * 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());
+ }
+
+ // 创建测试上下文
+ PluginScriptContext context = new PluginScriptContext();
+ IotProductDO product = productService.getProduct(testReqVO.getProductId());
+
+ // 设置设备上下文(使用产品信息,没有具体设备)
+ context.withDeviceContext(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 "property_parser":
+ params.put("method", "property");
+ break;
+ case "event_parser":
+ params.put("method", "event");
+ params.put("identifier", "default");
+ break;
+ case "command_encoder":
+ params.put("method", "command");
+ 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