From 90d358a90e5df6060b1d3ce21ea51f80f0130ba4 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 4 Oct 2026 15:08:21 +0800 Subject: [PATCH] fix(iot): 仅同步指定产品的物模型表结构 --- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http | 2 +- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java | 4 ++-- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java | 4 ++-- yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java | 26 +++++++------------------- yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/IotProductSyncTest.java | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql | 7 ++++++- 6 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/IotProductSyncTest.java diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http index e4e2589..49f54fb 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http @@ -1,5 +1,5 @@ ### 请求 /iot/product/sync-property-table 接口 => 成功 -POST {{baseUrl}}/iot/product/sync-property-table +POST {{baseUrl}}/iot/product/sync-property-table?id=1 Content-Type: application/json tenant-id: {{adminTenantId}} Authorization: Bearer {{token}} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java index 130c45d..679f779 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java @@ -144,8 +144,8 @@ public class IotProductController { @PostMapping("/sync-property-table") @Operation(summary = "同步产品属性表结构到 TDengine") @PreAuthorize("@ss.hasPermission('iot:product:update')") - public CommonResult syncProductPropertyTable() { - productService.syncProductPropertyTable(); + public CommonResult syncProductPropertyTable(@RequestParam("id") Long id) { + productService.syncProductPropertyTable(id); return success(true); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java index f31961c..87aa61b 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java @@ -152,8 +152,8 @@ public interface IotProductService { /** * 同步产品的 TDengine 表结构 * - * 目的:当 MySQL 和 TDengine 不同步时,强制将已发布产品的表结构同步到 TDengine 中 + * 目的:当 MySQL 和 TDengine 不同步时,强制将指定产品的表结构同步到 TDengine 中 */ - void syncProductPropertyTable(); + void syncProductPropertyTable(Long id); } \ 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/IotProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java index 4c8a789..f184563 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java @@ -181,24 +181,12 @@ public class IotProductServiceImpl implements IotProductService { } @Override - public void syncProductPropertyTable() { - // 1. 获取所有已发布的产品 - List products = productMapper.selectListByStatus( - IotProductStatusEnum.PUBLISHED.getStatus()); - log.info("[syncProductPropertyTable][开始同步,已发布产品数量({})]", products.size()); - - // 2. 遍历同步 TDengine 表结构(创建产品超级表数据模型) - int successCount = 0; - for (IotProductDO product : products) { - try { - devicePropertyDataService.defineDevicePropertyData(product.getId()); - successCount++; - log.info("[syncProductPropertyTable][产品({}/{}) 同步成功]", product.getId(), product.getName()); - } catch (Exception e) { - log.error("[syncProductPropertyTable][产品({}/{}) 同步失败]", product.getId(), product.getName(), e); - } - } - log.info("[syncProductPropertyTable][同步完成,成功({}/{})个]", successCount, products.size()); + public void syncProductPropertyTable(Long id) { + // 1. 校验产品存在 + validateProductExists(id); + + // 2. 同步指定产品的 TDengine 表结构 + devicePropertyDataService.defineDevicePropertyData(id); } @Override @@ -212,4 +200,4 @@ public class IotProductServiceImpl implements IotProductService { } } -} \ No newline at end of file +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/IotProductSyncTest.java b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/IotProductSyncTest.java new file mode 100644 index 0000000..32bde0f --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/IotProductSyncTest.java @@ -0,0 +1,92 @@ +package cn.iocoder.yudao.module.iot.service.product; + +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; +import cn.iocoder.yudao.module.iot.controller.admin.product.IotProductController; +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; +import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper; +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; +import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.web.bind.annotation.RequestParam; + +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId; +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * {@link IotProductServiceImpl} 的产品表结构同步单元测试 + * + * @author 芋道源码 + */ +@Import(IotProductServiceImpl.class) +public class IotProductSyncTest extends BaseDbUnitTest { + + @Resource + private IotProductServiceImpl productService; + + @Resource + private IotProductMapper productMapper; + + @MockitoBean + private IotDeviceService deviceService; + @MockitoBean + private IotDevicePropertyService devicePropertyDataService; + + @Test + public void testSyncProductPropertyTable_success() { + // mock 数据 + IotProductDO dbProduct = randomPojo(IotProductDO.class); + productMapper.insert(dbProduct); + IotProductDO otherProduct = randomPojo(IotProductDO.class); + productMapper.insert(otherProduct); + // 准备参数 + Long id = dbProduct.getId(); + + // 调用 + productService.syncProductPropertyTable(id); + // 断言:只同步指定产品 + verify(devicePropertyDataService).defineDevicePropertyData(id); + verifyNoMoreInteractions(devicePropertyDataService); + } + + @Test + public void testSyncProductPropertyTable_productNotExists() { + // 准备参数 + Long id = randomLongId(); + + // 调用,并断言异常 + assertServiceException(() -> productService.syncProductPropertyTable(id), PRODUCT_NOT_EXISTS); + verifyNoInteractions(devicePropertyDataService); + } + + @Test + public void testSyncProductPropertyTable_syncFailed() { + // mock 数据 + IotProductDO dbProduct = randomPojo(IotProductDO.class); + productMapper.insert(dbProduct); + // 准备参数 + Long id = dbProduct.getId(); + // mock 同步失败 + RuntimeException failure = new RuntimeException("TDengine sync failed"); + doThrow(failure).when(devicePropertyDataService).defineDevicePropertyData(id); + + // 调用,并断言异常 + assertSame(failure, assertThrows(RuntimeException.class, () -> productService.syncProductPropertyTable(id))); + } + + @Test + public void testController_syncProductPropertyTable_idRequired() throws Exception { + // 调用:读取请求参数定义 + RequestParam parameter = IotProductController.class.getDeclaredMethod("syncProductPropertyTable", Long.class) + .getParameters()[0].getAnnotation(RequestParam.class); + // 断言 + assertEquals("id", parameter.value()); + assertTrue(parameter.required()); + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql index 0bd2ad2..8cf6c9d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql +++ b/yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql @@ -18,7 +18,12 @@ CREATE TABLE IF NOT EXISTS "iot_product" ( "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, "name" varchar(255) NOT NULL DEFAULT '', "product_key" varchar(100) NOT NULL DEFAULT '', - "protocol_type" tinyint NOT NULL DEFAULT '0', + "product_secret" varchar(100) DEFAULT NULL, + "register_enabled" bit DEFAULT NULL, + "icon" varchar(255) DEFAULT NULL, + "pic_url" varchar(255) DEFAULT NULL, + "protocol_type" varchar(100) DEFAULT NULL, + "serialize_type" varchar(100) DEFAULT NULL, "category_id" bigint DEFAULT NULL, "description" varchar(500) DEFAULT NULL, "data_format" tinyint NOT NULL DEFAULT '0', -- libgit2 0.21.4