Commit 90d358a90e5df6060b1d3ce21ea51f80f0130ba4

Authored by YunaiV
1 parent 4843ff57

fix(iot): 仅同步指定产品的物模型表结构

yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http
1 ### 请求 /iot/product/sync-property-table 接口 => 成功 1 ### 请求 /iot/product/sync-property-table 接口 => 成功
2 -POST {{baseUrl}}/iot/product/sync-property-table 2 +POST {{baseUrl}}/iot/product/sync-property-table?id=1
3 Content-Type: application/json 3 Content-Type: application/json
4 tenant-id: {{adminTenantId}} 4 tenant-id: {{adminTenantId}}
5 Authorization: Bearer {{token}} 5 Authorization: Bearer {{token}}
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 { @@ -144,8 +144,8 @@ public class IotProductController {
144 @PostMapping("/sync-property-table") 144 @PostMapping("/sync-property-table")
145 @Operation(summary = "同步产品属性表结构到 TDengine") 145 @Operation(summary = "同步产品属性表结构到 TDengine")
146 @PreAuthorize("@ss.hasPermission('iot:product:update')") 146 @PreAuthorize("@ss.hasPermission('iot:product:update')")
147 - public CommonResult<Boolean> syncProductPropertyTable() {  
148 - productService.syncProductPropertyTable(); 147 + public CommonResult<Boolean> syncProductPropertyTable(@RequestParam("id") Long id) {
  148 + productService.syncProductPropertyTable(id);
149 return success(true); 149 return success(true);
150 } 150 }
151 151
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 { @@ -152,8 +152,8 @@ public interface IotProductService {
152 /** 152 /**
153 * 同步产品的 TDengine 表结构 153 * 同步产品的 TDengine 表结构
154 * 154 *
155 - * 目的:当 MySQL 和 TDengine 不同步时,强制将已发布产品的表结构同步到 TDengine 中 155 + * 目的:当 MySQL 和 TDengine 不同步时,强制将指定产品的表结构同步到 TDengine 中
156 */ 156 */
157 - void syncProductPropertyTable(); 157 + void syncProductPropertyTable(Long id);
158 158
159 } 159 }
160 \ No newline at end of file 160 \ No newline at end of file
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 { @@ -181,24 +181,12 @@ public class IotProductServiceImpl implements IotProductService {
181 } 181 }
182 182
183 @Override 183 @Override
184 - public void syncProductPropertyTable() {  
185 - // 1. 获取所有已发布的产品  
186 - List<IotProductDO> products = productMapper.selectListByStatus(  
187 - IotProductStatusEnum.PUBLISHED.getStatus());  
188 - log.info("[syncProductPropertyTable][开始同步,已发布产品数量({})]", products.size());  
189 -  
190 - // 2. 遍历同步 TDengine 表结构(创建产品超级表数据模型)  
191 - int successCount = 0;  
192 - for (IotProductDO product : products) {  
193 - try {  
194 - devicePropertyDataService.defineDevicePropertyData(product.getId());  
195 - successCount++;  
196 - log.info("[syncProductPropertyTable][产品({}/{}) 同步成功]", product.getId(), product.getName());  
197 - } catch (Exception e) {  
198 - log.error("[syncProductPropertyTable][产品({}/{}) 同步失败]", product.getId(), product.getName(), e);  
199 - }  
200 - }  
201 - log.info("[syncProductPropertyTable][同步完成,成功({}/{})个]", successCount, products.size()); 184 + public void syncProductPropertyTable(Long id) {
  185 + // 1. 校验产品存在
  186 + validateProductExists(id);
  187 +
  188 + // 2. 同步指定产品的 TDengine 表结构
  189 + devicePropertyDataService.defineDevicePropertyData(id);
202 } 190 }
203 191
204 @Override 192 @Override
@@ -212,4 +200,4 @@ public class IotProductServiceImpl implements IotProductService { @@ -212,4 +200,4 @@ public class IotProductServiceImpl implements IotProductService {
212 } 200 }
213 } 201 }
214 202
215 -}  
216 \ No newline at end of file 203 \ No newline at end of file
  204 +}
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/product/IotProductSyncTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.product;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.product.IotProductController;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  6 +import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
  7 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
  8 +import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
  9 +import jakarta.annotation.Resource;
  10 +import org.junit.jupiter.api.Test;
  11 +import org.springframework.context.annotation.Import;
  12 +import org.springframework.test.context.bean.override.mockito.MockitoBean;
  13 +import org.springframework.web.bind.annotation.RequestParam;
  14 +
  15 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  16 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
  17 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
  18 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PRODUCT_NOT_EXISTS;
  19 +import static org.junit.jupiter.api.Assertions.*;
  20 +import static org.mockito.Mockito.*;
  21 +
  22 +/**
  23 + * {@link IotProductServiceImpl} 的产品表结构同步单元测试
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Import(IotProductServiceImpl.class)
  28 +public class IotProductSyncTest extends BaseDbUnitTest {
  29 +
  30 + @Resource
  31 + private IotProductServiceImpl productService;
  32 +
  33 + @Resource
  34 + private IotProductMapper productMapper;
  35 +
  36 + @MockitoBean
  37 + private IotDeviceService deviceService;
  38 + @MockitoBean
  39 + private IotDevicePropertyService devicePropertyDataService;
  40 +
  41 + @Test
  42 + public void testSyncProductPropertyTable_success() {
  43 + // mock 数据
  44 + IotProductDO dbProduct = randomPojo(IotProductDO.class);
  45 + productMapper.insert(dbProduct);
  46 + IotProductDO otherProduct = randomPojo(IotProductDO.class);
  47 + productMapper.insert(otherProduct);
  48 + // 准备参数
  49 + Long id = dbProduct.getId();
  50 +
  51 + // 调用
  52 + productService.syncProductPropertyTable(id);
  53 + // 断言:只同步指定产品
  54 + verify(devicePropertyDataService).defineDevicePropertyData(id);
  55 + verifyNoMoreInteractions(devicePropertyDataService);
  56 + }
  57 +
  58 + @Test
  59 + public void testSyncProductPropertyTable_productNotExists() {
  60 + // 准备参数
  61 + Long id = randomLongId();
  62 +
  63 + // 调用,并断言异常
  64 + assertServiceException(() -> productService.syncProductPropertyTable(id), PRODUCT_NOT_EXISTS);
  65 + verifyNoInteractions(devicePropertyDataService);
  66 + }
  67 +
  68 + @Test
  69 + public void testSyncProductPropertyTable_syncFailed() {
  70 + // mock 数据
  71 + IotProductDO dbProduct = randomPojo(IotProductDO.class);
  72 + productMapper.insert(dbProduct);
  73 + // 准备参数
  74 + Long id = dbProduct.getId();
  75 + // mock 同步失败
  76 + RuntimeException failure = new RuntimeException("TDengine sync failed");
  77 + doThrow(failure).when(devicePropertyDataService).defineDevicePropertyData(id);
  78 +
  79 + // 调用,并断言异常
  80 + assertSame(failure, assertThrows(RuntimeException.class, () -> productService.syncProductPropertyTable(id)));
  81 + }
  82 +
  83 + @Test
  84 + public void testController_syncProductPropertyTable_idRequired() throws Exception {
  85 + // 调用:读取请求参数定义
  86 + RequestParam parameter = IotProductController.class.getDeclaredMethod("syncProductPropertyTable", Long.class)
  87 + .getParameters()[0].getAnnotation(RequestParam.class);
  88 + // 断言
  89 + assertEquals("id", parameter.value());
  90 + assertTrue(parameter.required());
  91 + }
  92 +}
yudao-module-iot/yudao-module-iot-biz/src/test/resources/sql/create_tables.sql
@@ -18,7 +18,12 @@ CREATE TABLE IF NOT EXISTS &quot;iot_product&quot; ( @@ -18,7 +18,12 @@ CREATE TABLE IF NOT EXISTS &quot;iot_product&quot; (
18 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, 18 "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
19 "name" varchar(255) NOT NULL DEFAULT '', 19 "name" varchar(255) NOT NULL DEFAULT '',
20 "product_key" varchar(100) NOT NULL DEFAULT '', 20 "product_key" varchar(100) NOT NULL DEFAULT '',
21 - "protocol_type" tinyint NOT NULL DEFAULT '0', 21 + "product_secret" varchar(100) DEFAULT NULL,
  22 + "register_enabled" bit DEFAULT NULL,
  23 + "icon" varchar(255) DEFAULT NULL,
  24 + "pic_url" varchar(255) DEFAULT NULL,
  25 + "protocol_type" varchar(100) DEFAULT NULL,
  26 + "serialize_type" varchar(100) DEFAULT NULL,
22 "category_id" bigint DEFAULT NULL, 27 "category_id" bigint DEFAULT NULL,
23 "description" varchar(500) DEFAULT NULL, 28 "description" varchar(500) DEFAULT NULL,
24 "data_format" tinyint NOT NULL DEFAULT '0', 29 "data_format" tinyint NOT NULL DEFAULT '0',