Commit 3524bfd3b3e1d43d48860e330bf7f098f5763ea4

Authored by puhui999
2 parents 1f9af15e 7b64b7fc

Merge remote-tracking branch 'yudao/feature/iot' into iot

# Conflicts:
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotProductThingModelServiceImpl.java
Showing 31 changed files with 508 additions and 461 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/thingmodel/IotDataSpecsDataTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.enums.thingmodel;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * IoT 数据定义的数据类型枚举类
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@AllArgsConstructor
  12 +@Getter
  13 +public enum IotDataSpecsDataTypeEnum {
  14 +
  15 + INT("int"),
  16 + FLOAT("float"),
  17 + DOUBLE("double"),
  18 + ENUM("enum"),
  19 + BOOL("bool"),
  20 + TEXT("text"),
  21 + DATE("date"),
  22 + STRUCT("struct"),
  23 + ARRAY("array");
  24 +
  25 + private final String dataType;
  26 +
  27 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java
1 1 package cn.iocoder.yudao.module.iot.api.device;
2 2  
3   -import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
  3 +import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
4 4 import org.springframework.stereotype.Service;
5 5 import org.springframework.validation.annotation.Validated;
6 6  
... ... @@ -14,7 +14,7 @@ import javax.annotation.Resource;
14 14 public class DeviceDataApiImpl implements DeviceDataApi {
15 15  
16 16 @Resource
17   - private IotDeviceDataService deviceDataService;
  17 + private IotDevicePropertyDataService deviceDataService;
18 18  
19 19 @Override
20 20 public void saveDeviceData(String productKey, String deviceName, String message) {
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceDataController.java
... ... @@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDevi
7 7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
9 9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO;
10   -import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
  10 +import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
11 11 import io.swagger.v3.oas.annotations.Operation;
12 12 import io.swagger.v3.oas.annotations.tags.Tag;
13 13 import jakarta.annotation.Resource;
... ... @@ -29,7 +29,7 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
29 29 public class IotDeviceDataController {
30 30  
31 31 @Resource
32   - private IotDeviceDataService deviceDataService;
  32 + private IotDevicePropertyDataService deviceDataService;
33 33  
34 34 // TODO @浩浩:这里的 /latest-list,包括方法名。
35 35 @GetMapping("/latest")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/ThingModelProperty.java
... ... @@ -31,7 +31,8 @@ public class ThingModelProperty {
31 31 private String description;
32 32 /**
33 33 * 云端可以对该属性进行的操作类型
34   - * 关联枚举 {@link IotProductThingModelAccessModeEnum}
  34 + *
  35 + * 枚举 {@link IotProductThingModelAccessModeEnum}
35 36 */
36 37 private String accessMode;
37 38 /**
... ... @@ -43,6 +44,8 @@ public class ThingModelProperty {
43 44 private Boolean required;
44 45 /**
45 46 * 数据类型,与 dataSpecs 的 dataType 保持一致
  47 + *
  48 + * 枚举 {@link cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum}
46 49 */
47 50 private String dataType;
48 51 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thingmodel/model/dataType/ThingModelDateOrTextDataSpecs.java
... ... @@ -20,7 +20,7 @@ public class ThingModelDateOrTextDataSpecs extends ThingModelDataSpecs {
20 20 * 数据长度,单位为字节。取值不能超过 2048。
21 21 * 当 dataType 为 text 时,需传入该参数。
22 22 */
23   - private Long length;
  23 + private Integer length;
24 24 /**
25 25 * 默认值,可选参数,用于存储默认值。
26 26 */
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/FieldParser.java
... ... @@ -16,6 +16,7 @@ public class FieldParser {
16 16 /**
17 17 * 物模型到td数据类型映射
18 18 */
  19 + @Deprecated
19 20 private static final HashMap<String, String> TYPE_MAPPING = new HashMap<>() {
20 21 {
21 22 put("INT", "INT");
... ... @@ -74,6 +75,7 @@ public class FieldParser {
74 75 /**
75 76 * 获取字段字义
76 77 */
  78 + @Deprecated
77 79 public static String getFieldDefine(TdFieldDO field) {
78 80 return "`" + field.getFieldName() + "`" + " "
79 81 + (field.getDataLength() > 0 ? String.format("%s(%d)", field.getDataType(), field.getDataLength())
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/SelectDO.java
... ... @@ -2,13 +2,12 @@ package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
2 2  
3 3 import lombok.Data;
4 4  
5   -import java.util.Set;
6   -
7 5 // TODO @haohao:类似这个,其实可以参考 mybatis plus,querywrapper,搞个 TdEngineQueryWrapper。这样看起来会更好懂。
8 6 /**
9 7 * 查询DO
10 8 */
11 9 @Data
  10 +@Deprecated
12 11 public class SelectDO {
13 12  
14 13 // TODO @haoha:database 是个单词
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/SelectVisualDO.java
... ... @@ -6,6 +6,7 @@ import java.util.Map;
6 6  
7 7 // TODO @haohao:类似 SelectDO 的想法,只是它是返回。ps:貌似可以在 tdengine 里面,创建一个 query 包,放这种比较特殊的查询和结果对象。dataobject 更多还是实际存储的结构化的 do
8 8 @Data
  9 +@Deprecated
9 10 public class SelectVisualDO {
10 11  
11 12 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TagsSelectDO.java
... ... @@ -6,6 +6,7 @@ import lombok.Data;
6 6 * tags查询DO
7 7 */
8 8 @Data
  9 +@Deprecated
9 10 public class TagsSelectDO {
10 11  
11 12 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdFieldDO.java
... ... @@ -5,6 +5,7 @@ import lombok.Builder;
5 5 import lombok.Data;
6 6 import lombok.NoArgsConstructor;
7 7  
  8 +// TODO 芋艿:看看是不是后续简化掉。
8 9 /**
9 10 * TD 引擎的字段
10 11 */
... ... @@ -12,6 +13,7 @@ import lombok.NoArgsConstructor;
12 13 @NoArgsConstructor
13 14 @AllArgsConstructor
14 15 @Builder
  16 +@Deprecated
15 17 public class TdFieldDO {
16 18  
17 19 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdResponse.java
... ... @@ -12,6 +12,7 @@ import java.util.List;
12 12 @Data
13 13 @NoArgsConstructor
14 14 @AllArgsConstructor
  15 +@Deprecated
15 16 public class TdResponse {
16 17  
17 18 public static final int CODE_SUCCESS = 0;
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdRestApi.java
... ... @@ -13,6 +13,7 @@ import org.springframework.stereotype.Service;
13 13 */
14 14 @Slf4j
15 15 @Service
  16 +@Deprecated // TODO 芋艿:貌似没用到
16 17 public class TdRestApi {
17 18  
18 19 @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdTableDO.java
... ... @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor;
7 7  
8 8 import java.util.List;
9 9  
  10 +@Deprecated
10 11 /**
11 12 * TD 引擎的数据库
12 13 */
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/thingmodel/IotProductThingModelDO.java
... ... @@ -16,6 +16,7 @@ import lombok.Builder;
16 16 import lombok.Data;
17 17 import lombok.NoArgsConstructor;
18 18  
  19 +// TODO @huihui:IotProductThingModelDO => IotThingModelDO
19 20 /**
20 21 * IoT 产品物模型功能 DO
21 22 * <p>
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDevicePageReqVO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  9 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
9 10 import org.apache.ibatis.annotations.Mapper;
10 11  
11 12 import java.util.List;
... ... @@ -47,7 +48,8 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; {
47 48 }
48 49  
49 50 default IotDeviceDO selectByDeviceKey(String deviceKey) {
50   - return selectOne(IotDeviceDO::getDeviceKey, deviceKey);
  51 + return selectOne(new LambdaQueryWrapper<IotDeviceDO>()
  52 + .apply("LOWER(device_key) = {0}", deviceKey.toLowerCase()));
51 53 }
52 54  
53 55 default List<IotDeviceDO> selectList(Integer deviceType) {
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  8 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
8 9 import org.apache.ibatis.annotations.Mapper;
9 10  
10 11 /**
... ... @@ -23,7 +24,8 @@ public interface IotProductMapper extends BaseMapperX&lt;IotProductDO&gt; {
23 24 }
24 25  
25 26 default IotProductDO selectByProductKey(String productKey) {
26   - return selectOne(IotProductDO::getProductKey, productKey);
  27 + return selectOne(new LambdaQueryWrapper<IotProductDO>()
  28 + .apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
27 29 }
28 30  
29 31 }
30 32 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/IotDevicePropertyDataMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.tdengine;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
  6 +import cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation.TDengineDS;
  7 +import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +import org.apache.ibatis.annotations.Param;
  10 +
  11 +import java.util.ArrayList;
  12 +import java.util.List;
  13 +import java.util.stream.Collectors;
  14 +
  15 +@Mapper
  16 +@TDengineDS
  17 +@InterceptorIgnore(tenantLine = "true") // 避免 SQL 解析,因为 JSqlParser 对 TDengine 的 SQL 解析会报错
  18 +public interface IotDevicePropertyDataMapper {
  19 +
  20 + List<TDengineTableField> getProductPropertySTableFieldList(@Param("productKey") String productKey);
  21 +
  22 + void createProductPropertySTable(@Param("productKey") String productKey,
  23 + @Param("fields") List<TDengineTableField> fields);
  24 +
  25 + @SuppressWarnings("SimplifyStreamApiCallChains") // 保持 JDK8 兼容性
  26 + default void alterProductPropertySTable(String productKey,
  27 + List<TDengineTableField> oldFields,
  28 + List<TDengineTableField> newFields) {
  29 + oldFields.removeIf(field -> TDengineTableField.FIELD_TS.equals(field.getField())
  30 + || TDengineTableField.FIELD_DEVICE_KEY.equals(field.getField()));
  31 + List<TDengineTableField> addFields = newFields.stream().filter( // 新增的字段
  32 + newField -> oldFields.stream().noneMatch(oldField -> oldField.getField().equals(newField.getField())))
  33 + .collect(Collectors.toList());
  34 + List<TDengineTableField> dropFields = oldFields.stream().filter( // 删除的字段
  35 + oldField -> newFields.stream().noneMatch(n -> n.getField().equals(oldField.getField())))
  36 + .collect(Collectors.toList());
  37 + List<TDengineTableField> modifyTypeFields = new ArrayList<>(); // 变更类型的字段
  38 + List<TDengineTableField> modifyLengthFields = new ArrayList<>(); // 变更长度的字段
  39 + newFields.forEach(newField -> {
  40 + TDengineTableField oldField = CollUtil.findOne(oldFields, field -> field.getField().equals(newField.getField()));
  41 + if (oldField == null) {
  42 + return;
  43 + }
  44 + if (ObjectUtil.notEqual(oldField.getType(), newField.getType())) {
  45 + modifyTypeFields.add(newField);
  46 + return;
  47 + }
  48 + if (newField.getLength()!= null) {
  49 + if (newField.getLength() > oldField.getLength()) {
  50 + modifyLengthFields.add(newField);
  51 + } else if (newField.getLength() < oldField.getLength()) {
  52 + // 特殊:TDengine 长度修改时,只允许变长,所以此时认为是修改类型
  53 + modifyTypeFields.add(newField);
  54 + }
  55 + }
  56 + });
  57 +
  58 + // 执行
  59 + addFields.forEach(field -> alterProductPropertySTableAddField(productKey, field));
  60 + dropFields.forEach(field -> alterProductPropertySTableDropField(productKey, field));
  61 + modifyLengthFields.forEach(field -> alterProductPropertySTableModifyField(productKey, field));
  62 + modifyTypeFields.forEach(field -> {
  63 + alterProductPropertySTableDropField(productKey, field);
  64 + alterProductPropertySTableAddField(productKey, field);
  65 + });
  66 + }
  67 +
  68 + void alterProductPropertySTableAddField(@Param("productKey") String productKey,
  69 + @Param("field") TDengineTableField field);
  70 +
  71 + void alterProductPropertySTableModifyField(@Param("productKey") String productKey,
  72 + @Param("field") TDengineTableField field);
  73 +
  74 + void alterProductPropertySTableDropField(@Param("productKey") String productKey,
  75 + @Param("field") TDengineTableField field);
  76 +
  77 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineDDLMapper.java
... ... @@ -96,7 +96,6 @@ public interface TdEngineDDLMapper {
96 96 @TenantIgnore
97 97 void modifyColumnWidthForSuperTable(TdTableDO superTable);
98 98  
99   -
100 99 /**
101 100 * 修改超级表 - 为超级表添加标签
102 101 *
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/emq/service/EmqxServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.emq.service;
2 2  
3   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4   -import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
  3 +import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
5 4 import jakarta.annotation.Resource;
6 5 import lombok.extern.slf4j.Slf4j;
7 6 import org.eclipse.paho.client.mqttv3.MqttClient;
... ... @@ -21,7 +20,7 @@ import org.springframework.stereotype.Service;
21 20 public class EmqxServiceImpl implements EmqxService {
22 21  
23 22 @Resource
24   - private IotDeviceDataService iotDeviceDataService;
  23 + private IotDevicePropertyDataService iotDeviceDataService;
25 24  
26 25 // TODO 多线程处理消息
27 26 @Override
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/TDengineTableField.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.tdengine.core;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Data;
  5 +import lombok.NoArgsConstructor;
  6 +
  7 +/**
  8 + * TDEngine 表字段
  9 + */
  10 +@Data
  11 +@NoArgsConstructor
  12 +@AllArgsConstructor
  13 +public class TDengineTableField {
  14 +
  15 + /**
  16 + * 字段名 - TDengine 默认 ts 字段,默认会被 TDengine 创建
  17 + */
  18 + public static final String FIELD_TS = "ts";
  19 +
  20 + /**
  21 + * 字段名 - 我们系统定义的 device_key 字段,非 TDengine 默认字段
  22 + */
  23 + public static final String FIELD_DEVICE_KEY = "device_key";
  24 +
  25 + public static final String TYPE_TINYINT = "TINYINT";
  26 + public static final String TYPE_INT = "INT";
  27 + public static final String TYPE_FLOAT = "FLOAT";
  28 + public static final String TYPE_DOUBLE = "DOUBLE";
  29 + public static final String TYPE_BOOL = "BOOL";
  30 + public static final String TYPE_NCHAR = "NCHAR";
  31 + public static final String TYPE_TIMESTAMP = "TIMESTAMP";
  32 +
  33 + /**
  34 + * 注释 - TAG 字段
  35 + */
  36 + public static final String NOTE_TAG = "TAG";
  37 +
  38 + /**
  39 + * 字段名
  40 + */
  41 + private String field;
  42 +
  43 + /**
  44 + * 字段类型
  45 + */
  46 + private String type;
  47 +
  48 + /**
  49 + * 字段长度
  50 + */
  51 + private Integer length;
  52 +
  53 + /**
  54 + * 注释
  55 + */
  56 + private String note;
  57 +
  58 + public TDengineTableField(String field, String type) {
  59 + this.field = field;
  60 + this.type = type;
  61 + }
  62 +
  63 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/core/annotation/TDengineDS.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.tdengine.core.annotation;
  2 +
  3 +import com.baomidou.dynamic.datasource.annotation.DS;
  4 +
  5 +import java.lang.annotation.*;
  6 +
  7 +/**
  8 + * TDEngine 数据源
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Target({ElementType.TYPE, ElementType.METHOD})
  13 +@Retention(RetentionPolicy.RUNTIME)
  14 +@Documented
  15 +@DS("tdengine")
  16 +public @interface TDengineDS {
  17 +}
0 18 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/tdengine/package-info.java 0 → 100644
  1 +/**
  2 + * iot 模块的 tdengine 拓展封装
  3 + */
  4 +package cn.iocoder.yudao.module.iot.framework.tdengine;
0 5 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.service.device;
2   -
3   -import cn.hutool.core.date.DateUtil;
4   -import cn.hutool.core.util.StrUtil;
5   -import cn.hutool.json.JSONObject;
6   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
8   -import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
9   -import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
10   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
11   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
12   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
13   -import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
14   -import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
15   -import cn.iocoder.yudao.module.iot.enums.IotConstants;
16   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
17   -import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
18   -import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
19   -import jakarta.annotation.Resource;
20   -import jakarta.validation.Valid;
21   -import lombok.extern.slf4j.Slf4j;
22   -import org.springframework.beans.factory.annotation.Value;
23   -import org.springframework.stereotype.Service;
24   -
25   -import java.time.ZoneId;
26   -import java.util.ArrayList;
27   -import java.util.HashMap;
28   -import java.util.List;
29   -import java.util.Map;
30   -
31   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
32   -
33   -@Slf4j
34   -@Service
35   -public class IotDeviceDataServiceImpl implements IotDeviceDataService {
36   -
37   - @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
38   - private String url;
39   -
40   - @Resource
41   - private IotDeviceService deviceService;
42   - @Resource
43   - private IotThingModelMessageService thingModelMessageService;
44   - @Resource
45   - private IotProductThingModelService thingModelService;
46   - @Resource
47   - private TdEngineDMLMapper tdEngineDMLMapper;
48   -
49   - @Resource
50   - private DeviceDataRedisDAO deviceDataRedisDAO;
51   -
52   - @Override
53   - public void saveDeviceData(String productKey, String deviceName, String message) {
54   - // 1. 根据产品 key 和设备名称,获得设备信息
55   - IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName);
56   - // 2. 解析消息,保存数据
57   - JSONObject jsonObject = new JSONObject(message);
58   - log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject);
59   - ThingModelMessage thingModelMessage = ThingModelMessage.builder()
60   - .id(jsonObject.getStr("id"))
61   - .sys(jsonObject.get("sys"))
62   - .method(jsonObject.getStr("method"))
63   - .params(jsonObject.get("params"))
64   - .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time"))
65   - .productKey(productKey)
66   - .deviceName(deviceName)
67   - .deviceKey(device.getDeviceKey())
68   - .build();
69   - thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
70   - }
71   -
72   - @Override
73   - public List<IotDeviceDataDO> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
74   - List<IotDeviceDataDO> list = new ArrayList<>();
75   - // 1. 获取设备信息
76   - IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
77   - // 2. 获取设备属性最新数据
78   - List<IotProductThingModelDO> thingModelList = thingModelService.getProductThingModelListByProductKey(device.getProductKey());
79   - thingModelList = filterList(thingModelList, thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType()
80   - .equals(thingModel.getType()));
81   -
82   - // 3. 过滤标识符和属性名称
83   - if (deviceDataReqVO.getIdentifier() != null) {
84   - thingModelList = filterList(thingModelList, thingModel -> thingModel.getIdentifier()
85   - .toLowerCase().contains(deviceDataReqVO.getIdentifier().toLowerCase()));
86   - }
87   - if (deviceDataReqVO.getName() != null) {
88   - thingModelList = filterList(thingModelList, thingModel -> thingModel.getName()
89   - .toLowerCase().contains(deviceDataReqVO.getName().toLowerCase()));
90   - }
91   - // 4. 获取设备属性最新数据
92   - thingModelList.forEach(thingModel -> {
93   - IotDeviceDataDO deviceData = deviceDataRedisDAO.get(device.getProductKey(), device.getDeviceName(), thingModel.getIdentifier());
94   - if (deviceData == null) {
95   - deviceData = new IotDeviceDataDO();
96   - deviceData.setProductKey(device.getProductKey());
97   - deviceData.setDeviceName(device.getDeviceName());
98   - deviceData.setIdentifier(thingModel.getIdentifier());
99   - deviceData.setDeviceId(deviceDataReqVO.getDeviceId());
100   - deviceData.setThingModelId(thingModel.getId());
101   - deviceData.setName(thingModel.getName());
102   - deviceData.setDataType(thingModel.getProperty().getDataType());
103   - }
104   - list.add(deviceData);
105   - });
106   - return list;
107   - }
108   -
109   - @Override
110   - public PageResult<Map<String, Object>> getHistoryDeviceProperties(IotDeviceDataPageReqVO deviceDataReqVO) {
111   - PageResult<Map<String, Object>> pageResult = new PageResult<>();
112   - // 1. 获取设备信息
113   - IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
114   - // 2. 获取设备属性历史数据
115   - SelectVisualDO selectVisualDO = new SelectVisualDO();
116   - selectVisualDO.setDataBaseName(getDatabaseName());
117   - selectVisualDO.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()));
118   - selectVisualDO.setFieldName(deviceDataReqVO.getIdentifier());
119   - selectVisualDO.setStartTime(DateUtil.date(deviceDataReqVO.getTimes()[0].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
120   - selectVisualDO.setEndTime(DateUtil.date(deviceDataReqVO.getTimes()[1].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
121   - Map<String, Object> params = new HashMap<>();
122   - params.put("rows", deviceDataReqVO.getPageSize());
123   - params.put("page", (deviceDataReqVO.getPageNo() - 1) * deviceDataReqVO.getPageSize());
124   - selectVisualDO.setParams(params);
125   - pageResult.setList(tdEngineDMLMapper.selectHistoryDataList(selectVisualDO));
126   - pageResult.setTotal(tdEngineDMLMapper.selectHistoryCount(selectVisualDO));
127   - return pageResult;
128   - }
129   -
130   - private String getDatabaseName() {
131   - return StrUtil.subAfter(url, "/", true);
132   - }
133   -
134   - private static String getDeviceTableName(String productKey, String deviceName) {
135   - return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey, deviceName);
136   - }
137   -
138   -}
139 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataService.java
... ... @@ -9,11 +9,18 @@ import java.util.List;
9 9 import java.util.Map;
10 10  
11 11 /**
12   - * IoT 设备数据 Service 接口
  12 + * IoT 设备【属性】数据 Service 接口
13 13 *
14 14 * @author 芋道源码
15 15 */
16   -public interface IotDeviceDataService {
  16 +public interface IotDevicePropertyDataService {
  17 +
  18 + /**
  19 + * 定义设备属性数据的结构
  20 + *
  21 + * @param productId 产品编号
  22 + */
  23 + void defineDevicePropertyData(Long productId);
17 24  
18 25 /**
19 26 * 保存设备数据
... ... @@ -40,4 +47,5 @@ public interface IotDeviceDataService {
40 47 * @return 设备属性历史数据
41 48 */
42 49 PageResult<Map<String, Object>> getHistoryDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO);
  50 +
43 51 }
44 52 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDevicePropertyDataServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.device;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.date.DateUtil;
  5 +import cn.hutool.core.map.MapUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.hutool.json.JSONObject;
  8 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  9 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
  10 +import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.dataType.ThingModelDateOrTextDataSpecs;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  12 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
  13 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  14 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
  15 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
  16 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
  17 +import cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper;
  18 +import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
  19 +import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
  20 +import cn.iocoder.yudao.module.iot.enums.IotConstants;
  21 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotDataSpecsDataTypeEnum;
  22 +import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
  23 +import cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField;
  24 +import cn.iocoder.yudao.module.iot.service.product.IotProductService;
  25 +import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
  26 +import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
  27 +import jakarta.annotation.Resource;
  28 +import jakarta.validation.Valid;
  29 +import lombok.extern.slf4j.Slf4j;
  30 +import org.springframework.beans.factory.annotation.Value;
  31 +import org.springframework.stereotype.Service;
  32 +
  33 +import java.time.ZoneId;
  34 +import java.util.ArrayList;
  35 +import java.util.HashMap;
  36 +import java.util.List;
  37 +import java.util.Map;
  38 +
  39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  40 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
  41 +
  42 +/**
  43 + * IoT 设备【属性】数据 Service 实现类
  44 + *
  45 + * @author 芋道源码
  46 + */
  47 +@Service
  48 +@Slf4j
  49 +public class IotDevicePropertyDataServiceImpl implements IotDevicePropertyDataService {
  50 +
  51 + /**
  52 + * 物模型的数据类型,与 TDengine 数据类型的映射关系
  53 + */
  54 + private static final Map<String, String> TYPE_MAPPING = MapUtil.<String, String>builder()
  55 + .put(IotDataSpecsDataTypeEnum.INT.getDataType(), TDengineTableField.TYPE_INT)
  56 + .put(IotDataSpecsDataTypeEnum.FLOAT.getDataType(), TDengineTableField.TYPE_FLOAT)
  57 + .put(IotDataSpecsDataTypeEnum.DOUBLE.getDataType(), TDengineTableField.TYPE_DOUBLE)
  58 + .put(IotDataSpecsDataTypeEnum.ENUM.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
  59 + .put( IotDataSpecsDataTypeEnum.BOOL.getDataType(), TDengineTableField.TYPE_TINYINT) // TODO 芋艿:为什么要映射为 TINYINT 的说明?
  60 + .put(IotDataSpecsDataTypeEnum.TEXT.getDataType(), TDengineTableField.TYPE_NCHAR)
  61 + .put(IotDataSpecsDataTypeEnum.DATE.getDataType(), TDengineTableField.TYPE_TIMESTAMP)
  62 + .put(IotDataSpecsDataTypeEnum.STRUCT.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
  63 + .put(IotDataSpecsDataTypeEnum.ARRAY.getDataType(), TDengineTableField.TYPE_NCHAR) // TODO 芋艿:怎么映射!!!!
  64 + .build();
  65 +
  66 + @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
  67 + private String url;
  68 +
  69 + @Resource
  70 + private IotDeviceService deviceService;
  71 + @Resource
  72 + private IotThingModelMessageService thingModelMessageService;
  73 + @Resource
  74 + private IotProductThingModelService thingModelService;
  75 + @Resource
  76 + private IotProductService productService;
  77 +
  78 + @Resource
  79 + private TdEngineDMLMapper tdEngineDMLMapper;
  80 +
  81 + @Resource
  82 + private DeviceDataRedisDAO deviceDataRedisDAO;
  83 +
  84 + @Resource
  85 + private IotDevicePropertyDataMapper devicePropertyDataMapper;
  86 +
  87 + @Override
  88 + public void defineDevicePropertyData(Long productId) {
  89 + // 1.1 查询产品和物模型
  90 + IotProductDO product = productService.validateProductExists(productId);
  91 + List<IotProductThingModelDO> thingModels = filterList(thingModelService.getProductThingModelListByProductId(productId),
  92 + thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType().equals(thingModel.getType()));
  93 + // 1.2 解析 DB 里的字段
  94 + List<TDengineTableField> oldFields = new ArrayList<>();
  95 + try {
  96 + oldFields.addAll(devicePropertyDataMapper.getProductPropertySTableFieldList(product.getProductKey()));
  97 + } catch (Exception e) {
  98 + if (!e.getMessage().contains("Table does not exist")) {
  99 + throw e;
  100 + }
  101 + }
  102 +
  103 + // 2.1 情况一:如果是新增的时候,需要创建表
  104 + List<TDengineTableField> newFields = buildTableFieldList(thingModels);
  105 + if (CollUtil.isEmpty(oldFields)) {
  106 + if (CollUtil.isEmpty(newFields)) {
  107 + log.info("[defineDevicePropertyData][productId({}) 没有需要定义的属性]", productId);
  108 + return;
  109 + }
  110 + newFields.add(0, new TDengineTableField(TDengineTableField.FIELD_TS, TDengineTableField.TYPE_TIMESTAMP));
  111 + devicePropertyDataMapper.createProductPropertySTable(product.getProductKey(), newFields);
  112 + return;
  113 + }
  114 + // 2.2 情况二:如果是修改的时候,需要更新表
  115 + devicePropertyDataMapper.alterProductPropertySTable(product.getProductKey(), oldFields, newFields);
  116 + }
  117 +
  118 + private List<TDengineTableField> buildTableFieldList(List<IotProductThingModelDO> thingModels) {
  119 + return convertList(thingModels, thingModel -> {
  120 + TDengineTableField field = new TDengineTableField(
  121 + StrUtil.toUnderlineCase(thingModel.getIdentifier()), // TDengine 字段默认都是小写
  122 + TYPE_MAPPING.get(thingModel.getProperty().getDataType()));
  123 + if (thingModel.getProperty().getDataType().equals(IotDataSpecsDataTypeEnum.TEXT.getDataType())) {
  124 + field.setLength(((ThingModelDateOrTextDataSpecs) thingModel.getProperty().getDataSpecs()).getLength());
  125 + }
  126 + return field;
  127 + });
  128 + }
  129 +
  130 + @Override
  131 + public void saveDeviceData(String productKey, String deviceName, String message) {
  132 + // 1. 根据产品 key 和设备名称,获得设备信息
  133 + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceName(productKey, deviceName);
  134 + // 2. 解析消息,保存数据
  135 + JSONObject jsonObject = new JSONObject(message);
  136 + log.info("[saveDeviceData][productKey({}) deviceName({}) data({})]", productKey, deviceName, jsonObject);
  137 + ThingModelMessage thingModelMessage = ThingModelMessage.builder()
  138 + .id(jsonObject.getStr("id"))
  139 + .sys(jsonObject.get("sys"))
  140 + .method(jsonObject.getStr("method"))
  141 + .params(jsonObject.get("params"))
  142 + .time(jsonObject.getLong("time") == null ? System.currentTimeMillis() : jsonObject.getLong("time"))
  143 + .productKey(productKey)
  144 + .deviceName(deviceName)
  145 + .deviceKey(device.getDeviceKey())
  146 + .build();
  147 + thingModelMessageService.saveThingModelMessage(device, thingModelMessage);
  148 + }
  149 +
  150 + @Override
  151 + public List<IotDeviceDataDO> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
  152 + List<IotDeviceDataDO> list = new ArrayList<>();
  153 + // 1. 获取设备信息
  154 + IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
  155 + // 2. 获取设备属性最新数据
  156 + List<IotProductThingModelDO> thingModelList = thingModelService.getProductThingModelListByProductKey(device.getProductKey());
  157 + thingModelList = filterList(thingModelList, thingModel -> IotProductThingModelTypeEnum.PROPERTY.getType()
  158 + .equals(thingModel.getType()));
  159 +
  160 + // 3. 过滤标识符和属性名称
  161 + if (deviceDataReqVO.getIdentifier() != null) {
  162 + thingModelList = filterList(thingModelList, thingModel -> thingModel.getIdentifier()
  163 + .toLowerCase().contains(deviceDataReqVO.getIdentifier().toLowerCase()));
  164 + }
  165 + if (deviceDataReqVO.getName() != null) {
  166 + thingModelList = filterList(thingModelList, thingModel -> thingModel.getName()
  167 + .toLowerCase().contains(deviceDataReqVO.getName().toLowerCase()));
  168 + }
  169 + // 4. 获取设备属性最新数据
  170 + thingModelList.forEach(thingModel -> {
  171 + IotDeviceDataDO deviceData = deviceDataRedisDAO.get(device.getProductKey(), device.getDeviceName(), thingModel.getIdentifier());
  172 + if (deviceData == null) {
  173 + deviceData = new IotDeviceDataDO();
  174 + deviceData.setProductKey(device.getProductKey());
  175 + deviceData.setDeviceName(device.getDeviceName());
  176 + deviceData.setIdentifier(thingModel.getIdentifier());
  177 + deviceData.setDeviceId(deviceDataReqVO.getDeviceId());
  178 + deviceData.setThingModelId(thingModel.getId());
  179 + deviceData.setName(thingModel.getName());
  180 + deviceData.setDataType(thingModel.getProperty().getDataType());
  181 + }
  182 + list.add(deviceData);
  183 + });
  184 + return list;
  185 + }
  186 +
  187 + @Override
  188 + public PageResult<Map<String, Object>> getHistoryDeviceProperties(IotDeviceDataPageReqVO deviceDataReqVO) {
  189 + PageResult<Map<String, Object>> pageResult = new PageResult<>();
  190 + // 1. 获取设备信息
  191 + IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
  192 + // 2. 获取设备属性历史数据
  193 + SelectVisualDO selectVisualDO = new SelectVisualDO();
  194 + selectVisualDO.setDataBaseName(getDatabaseName());
  195 + selectVisualDO.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()));
  196 + selectVisualDO.setFieldName(deviceDataReqVO.getIdentifier());
  197 + selectVisualDO.setStartTime(DateUtil.date(deviceDataReqVO.getTimes()[0].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
  198 + selectVisualDO.setEndTime(DateUtil.date(deviceDataReqVO.getTimes()[1].atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()).getTime());
  199 + Map<String, Object> params = new HashMap<>();
  200 + params.put("rows", deviceDataReqVO.getPageSize());
  201 + params.put("page", (deviceDataReqVO.getPageNo() - 1) * deviceDataReqVO.getPageSize());
  202 + selectVisualDO.setParams(params);
  203 + pageResult.setList(tdEngineDMLMapper.selectHistoryDataList(selectVisualDO));
  204 + pageResult.setTotal(tdEngineDMLMapper.selectHistoryCount(selectVisualDO));
  205 + return pageResult;
  206 + }
  207 +
  208 + private String getDatabaseName() {
  209 + return StrUtil.subAfter(url, "/", true);
  210 + }
  211 +
  212 + private static String getDeviceTableName(String productKey, String deviceName) {
  213 + return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey, deviceName);
  214 + }
  215 +
  216 +}
0 217 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
... ... @@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
9 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 10 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
11 11 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
  12 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
12 13 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
13 14 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
14 15 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
... ... @@ -60,9 +61,11 @@ public class IotDeviceServiceImpl implements IotDeviceService {
60 61 throw exception(PRODUCT_NOT_EXISTS);
61 62 }
62 63 // 1.2 校验设备标识是否唯一
63   - if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
64   - throw exception(DEVICE_KEY_EXISTS);
65   - }
  64 + TenantUtils.executeIgnore(() -> {
  65 + if (deviceMapper.selectByDeviceKey(createReqVO.getDeviceKey()) != null) {
  66 + throw exception(PRODUCT_KEY_EXISTS);
  67 + }
  68 + });
66 69 // 1.3 校验设备名称在同一产品下是否唯一
67 70 if (deviceMapper.selectByProductKeyAndDeviceName(product.getProductKey(), createReqVO.getDeviceKey()) != null) {
68 71 throw exception(DEVICE_NAME_EXISTS);
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
... ... @@ -2,13 +2,14 @@ package cn.iocoder.yudao.module.iot.service.product;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
5 6 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductPageReqVO;
6 7 import cn.iocoder.yudao.module.iot.controller.admin.product.vo.product.IotProductSaveReqVO;
7 8 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
8 9 import cn.iocoder.yudao.module.iot.dal.mysql.product.IotProductMapper;
9 10 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
  11 +import cn.iocoder.yudao.module.iot.service.device.IotDevicePropertyDataService;
10 12 import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
11   -import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
12 13 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
13 14 import jakarta.annotation.Resource;
14 15 import org.springframework.context.annotation.Lazy;
... ... @@ -34,18 +35,22 @@ public class IotProductServiceImpl implements IotProductService {
34 35 private IotProductMapper productMapper;
35 36  
36 37 @Resource
37   - @Lazy // 延迟加载,解决循环依赖
38   - private IotProductThingModelService thingModelFunctionService;
39   - @Resource
40 38 @Lazy // 延迟加载,解决循环依赖
41 39 private IotThingModelMessageService thingModelMessageService;
  40 + @Resource
  41 + @Lazy // 延迟加载,解决循环依赖
  42 + private IotDevicePropertyDataService devicePropertyDataService;
42 43  
43 44 @Override
44 45 public Long createProduct(IotProductSaveReqVO createReqVO) {
45   - // 1. 生成 ProductKey
46   - if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
47   - throw exception(PRODUCT_KEY_EXISTS);
48   - }
  46 + // 1. 校验 ProductKey
  47 + TenantUtils.executeIgnore(() -> {
  48 + // 为什么忽略租户?避免多个租户之间,productKey 重复,导致 TDengine 设备属性表重复
  49 + if (productMapper.selectByProductKey(createReqVO.getProductKey()) != null) {
  50 + throw exception(PRODUCT_KEY_EXISTS);
  51 + }
  52 + });
  53 +
49 54 // 2. 插入
50 55 IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class)
51 56 .setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus());
... ... @@ -119,8 +124,8 @@ public class IotProductServiceImpl implements IotProductService {
119 124 // 3. 产品是发布状态
120 125 if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getStatus())) {
121 126 // 3.1 创建产品超级表数据模型
122   - thingModelFunctionService.createSuperTableDataModel(id);
123   - // 3.2 创建物模型日志超级表数据模型
  127 + devicePropertyDataService.defineDevicePropertyData(id);
  128 + // 3.2 创建物模型日志超级表数据模型 TODO 待定:message 要不要分;
124 129 thingModelMessageService.createSuperTable(id);
125 130 }
126 131 productMapper.updateById(updateObj);
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotSuperTableService.java deleted
1   -package cn.iocoder.yudao.module.iot.service.tdengine;
2   -
3   -
4   -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
5   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
6   -
7   -import java.util.List;
8   -
9   -/**
10   - * IoT 超级表服务,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
11   - */
12   -public interface IotSuperTableService {
13   -
14   - /**
15   - * 创建超级表数据模型
16   - */
17   - void createSuperTableDataModel(IotProductDO product, List<IotProductThingModelDO> thingModelList);
18   -
19   -}
20 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotSuperTableServiceImpl.java deleted
1   -package cn.iocoder.yudao.module.iot.service.tdengine;
2   -
3   -import cn.hutool.core.bean.BeanUtil;
4   -import cn.hutool.core.collection.CollUtil;
5   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelProperty;
6   -import cn.iocoder.yudao.module.iot.controller.admin.thingmodel.model.ThingModelRespVO;
7   -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
8   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
9   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
10   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
11   -import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
12   -import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
13   -import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
14   -import jakarta.annotation.Resource;
15   -import lombok.extern.slf4j.Slf4j;
16   -import org.springframework.beans.factory.annotation.Value;
17   -import org.springframework.stereotype.Service;
18   -
19   -import java.util.*;
20   -import java.util.stream.Collectors;
21   -
22   -/**
23   - * IoT 超级表服务实现类,负责根据物模型创建和更新超级表,以及创建超级表的子表等操作。
24   - */
25   -@Service
26   -@Slf4j
27   -public class IotSuperTableServiceImpl implements IotSuperTableService {
28   -
29   - @Resource
30   - private TdEngineDDLMapper tdEngineDDLMapper;
31   -
32   - @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
33   - private String url;
34   -
35   - @Override
36   - public void createSuperTableDataModel(IotProductDO product, List<IotProductThingModelDO> thingModelList) {
37   - ThingModelRespVO thingModel = buildThingModel(product, thingModelList);
38   -
39   - if (thingModel.getModel() == null || CollUtil.isEmpty(thingModel.getModel().getProperties())) {
40   - log.warn("物模型属性列表为空,不创建超级表");
41   - return;
42   - }
43   -
44   - String superTableName = getSuperTableName(product.getDeviceType(), product.getProductKey());
45   - String databaseName = getDatabaseName();
46   -
47   - List<Map<String, Object>> results = tdEngineDDLMapper.showSuperTables(new TdTableDO(databaseName, superTableName));
48   - int tableExists = results == null || results.isEmpty() ? 0 : results.size();
49   - if (tableExists > 0) {
50   - updateSuperTable(thingModel, product.getDeviceType());
51   - } else {
52   - createSuperTable(thingModel, product.getDeviceType());
53   - }
54   - }
55   -
56   - /**
57   - * 创建超级表
58   - */
59   - private void createSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
60   - // 解析物模型,获取字段列表
61   - List<TdFieldDO> schemaFields = new ArrayList<>();
62   - schemaFields.add(TdFieldDO.builder()
63   - .fieldName("time")
64   - .dataType("TIMESTAMP")
65   - .build());
66   - schemaFields.addAll(FieldParser.parse(thingModel));
67   -
68   - // 设置超级表的标签
69   - List<TdFieldDO> tagsFields = List.of(
70   - TdFieldDO.builder().fieldName("product_key").dataType("NCHAR").dataLength(64).build(),
71   - TdFieldDO.builder().fieldName("device_key").dataType("NCHAR").dataLength(64).build(),
72   - TdFieldDO.builder().fieldName("device_name").dataType("NCHAR").dataLength(64).build(),
73   - TdFieldDO.builder().fieldName("device_type").dataType("INT").build()
74   - );
75   -
76   - // 获取超级表的名称和数据库名称
77   - String superTableName = getSuperTableName(deviceType, thingModel.getProductKey());
78   - String databaseName = getDatabaseName();
79   -
80   - // 创建超级表
81   - tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
82   - }
83   -
84   - /**
85   - * 更新超级表
86   - */
87   - private void updateSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
88   - String superTableName = getSuperTableName(deviceType, thingModel.getProductKey());
89   - try {
90   - List<TdFieldDO> oldFields = getTableFields(superTableName);
91   - List<TdFieldDO> newFields = FieldParser.parse(thingModel);
92   -
93   - updateTableFields(superTableName, oldFields, newFields);
94   - } catch (Exception e) {
95   - log.error("更新物模型超级表失败: {}", e.getMessage(), e);
96   - }
97   - }
98   -
99   - /**
100   - * 获取表的字段信息
101   - */
102   - private List<TdFieldDO> getTableFields(String tableName) {
103   - List<Map<String, Object>> tableDescription = tdEngineDDLMapper.describeSuperTable(new TdTableDO(getDatabaseName(), tableName));
104   - if (CollUtil.isEmpty(tableDescription)) {
105   - return Collections.emptyList();
106   - }
107   -
108   - return tableDescription.stream()
109   - .filter(map -> !"TAG".equals(map.get("note")))
110   - .filter(map -> !"time".equals(map.get("field")))
111   - .map(map -> TdFieldDO.builder()
112   - .fieldName((String) map.get("field"))
113   - .dataType((String) map.get("type"))
114   - .dataLength((Integer) map.get("length"))
115   - .build())
116   - .collect(Collectors.toList());
117   - }
118   -
119   - /**
120   - * 更新表的字段,包括新增、修改和删除字段
121   - */
122   - private void updateTableFields(String tableName, List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
123   - String databaseName = getDatabaseName();
124   -
125   - // 获取新增、修改、删除的字段
126   - List<TdFieldDO> addFields = getAddFields(oldFields, newFields);
127   - List<TdFieldDO> modifyFields = getModifyFields(oldFields, newFields);
128   - List<TdFieldDO> dropFields = getDropFields(oldFields, newFields);
129   -
130   - // 添加新增字段
131   - if (CollUtil.isNotEmpty(addFields)) {
132   - for (TdFieldDO addField : addFields) {
133   - tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
134   - .dataBaseName(databaseName)
135   - .superTableName(tableName)
136   - .column(addField)
137   - .build());
138   - }
139   - }
140   - // 删除旧字段
141   - if (CollUtil.isNotEmpty(dropFields)) {
142   - for (TdFieldDO dropField : dropFields) {
143   - tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
144   - .dataBaseName(databaseName)
145   - .superTableName(tableName)
146   - .column(dropField)
147   - .build());
148   - }
149   - }
150   - // 修改字段(先删除再添加)
151   - if (CollUtil.isNotEmpty(modifyFields)) {
152   - for (TdFieldDO modifyField : modifyFields) {
153   - tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
154   - .dataBaseName(databaseName)
155   - .superTableName(tableName)
156   - .column(modifyField)
157   - .build());
158   - tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
159   - .dataBaseName(databaseName)
160   - .superTableName(tableName)
161   - .column(modifyField)
162   - .build());
163   - }
164   - }
165   - }
166   -
167   - /**
168   - * 获取需要新增的字段
169   - */
170   - private List<TdFieldDO> getAddFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
171   - Set<String> oldFieldNames = oldFields.stream()
172   - .map(TdFieldDO::getFieldName)
173   - .collect(Collectors.toSet());
174   - return newFields.stream()
175   - .filter(f -> !oldFieldNames.contains(f.getFieldName()))
176   - .collect(Collectors.toList());
177   - }
178   -
179   - /**
180   - * 获取需要修改的字段
181   - */
182   - private List<TdFieldDO> getModifyFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
183   - Map<String, TdFieldDO> oldFieldMap = oldFields.stream()
184   - .collect(Collectors.toMap(TdFieldDO::getFieldName, f -> f));
185   -
186   - return newFields.stream()
187   - .filter(f -> {
188   - TdFieldDO oldField = oldFieldMap.get(f.getFieldName());
189   - return oldField != null && (
190   - !oldField.getDataType().equals(f.getDataType()) ||
191   - !Objects.equals(oldField.getDataLength(), f.getDataLength())
192   - );
193   - })
194   - .collect(Collectors.toList());
195   - }
196   -
197   - /**
198   - * 获取需要删除的字段
199   - */
200   - private List<TdFieldDO> getDropFields(List<TdFieldDO> oldFields, List<TdFieldDO> newFields) {
201   - Set<String> newFieldNames = newFields.stream()
202   - .map(TdFieldDO::getFieldName)
203   - .collect(Collectors.toSet());
204   - return oldFields.stream()
205   - .filter(f -> !"time".equals(f.getFieldName()))
206   - .filter(f -> !newFieldNames.contains(f.getFieldName()))
207   - .collect(Collectors.toList());
208   - }
209   -
210   - /**
211   - * 构建物模型
212   - */
213   - private ThingModelRespVO buildThingModel(IotProductDO product, List<IotProductThingModelDO> thingModelList) {
214   - ThingModelRespVO thingModel = new ThingModelRespVO();
215   - thingModel.setId(product.getId());
216   - thingModel.setProductKey(product.getProductKey());
217   -
218   - List<ThingModelProperty> properties = thingModelList.stream()
219   - .filter(item -> IotProductThingModelTypeEnum.PROPERTY.equals(
220   - IotProductThingModelTypeEnum.valueOfType(item.getType())))
221   - .map(this::buildThingModelProperty)
222   - .collect(Collectors.toList());
223   -
224   - ThingModelRespVO.Model model = new ThingModelRespVO.Model();
225   - model.setProperties(properties);
226   - thingModel.setModel(model);
227   -
228   - return thingModel;
229   - }
230   -
231   - /**
232   - * 构建物模型属性
233   - */
234   - private ThingModelProperty buildThingModelProperty(IotProductThingModelDO thingModel) {
235   - ThingModelProperty property = BeanUtil.copyProperties(thingModel, ThingModelProperty.class);
236   - property.setDataType(thingModel.getProperty().getDataType());
237   - return property;
238   - }
239   -
240   - /**
241   - * 获取数据库名称
242   - */
243   - private String getDatabaseName() {
244   - int index = url.lastIndexOf("/");
245   - return index != -1 ? url.substring(index + 1) : url;
246   - }
247   -
248   - /**
249   - * 获取超级表名称
250   - */
251   - private String getSuperTableName(Integer deviceType, String productKey) {
252   - String prefix = switch (deviceType) {
253   - case 1 -> "gateway_sub_";
254   - case 2 -> "gateway_";
255   - default -> "device_";
256   - };
257   - return (prefix + productKey).toLowerCase();
258   - }
259   -
260   -}
261 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotProductThingModelServiceImpl.java
... ... @@ -16,7 +16,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotProductThingModelMapp
16 16 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
17 17 import cn.iocoder.yudao.module.iot.enums.thingmodel.*;
18 18 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
19   -import cn.iocoder.yudao.module.iot.service.tdengine.IotSuperTableService;
20 19 import jakarta.annotation.Resource;
21 20 import lombok.extern.slf4j.Slf4j;
22 21 import org.springframework.stereotype.Service;
... ... @@ -44,8 +43,6 @@ public class IotProductThingModelServiceImpl implements IotProductThingModelServ
44 43  
45 44 @Resource
46 45 private IotProductService productService;
47   - @Resource
48   - private IotSuperTableService superTableService;
49 46  
50 47 @Override
51 48 @Transactional(rollbackFor = Exception.class)
... ... @@ -75,18 +72,6 @@ public class IotProductThingModelServiceImpl implements IotProductThingModelServ
75 72 }
76 73  
77 74 @Override
78   - public void createSuperTableDataModel(Long productId) {
79   - // 1. 查询产品
80   - IotProductDO product = productService.getProduct(productId);
81   -
82   - // 2. 查询产品的物模型功能列表
83   - List<IotProductThingModelDO> thingModelList = productThingModelMapper.selectListByProductId(productId);
84   -
85   - // 3. 生成 TDengine 的数据模型
86   - superTableService.createSuperTableDataModel(product, thingModelList);
87   - }
88   -
89   - @Override
90 75 @Transactional(rollbackFor = Exception.class)
91 76 public void updateProductThingModel(IotProductThingModelSaveReqVO updateReqVO) {
92 77 // 1. 校验功能是否存在
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/device/IotDevicePropertyDataMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper
  3 + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4 + "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5 +<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.IotDevicePropertyDataMapper">
  6 +
  7 + <select id="getProductPropertySTableFieldList" resultType="cn.iocoder.yudao.module.iot.framework.tdengine.core.TDengineTableField">
  8 + DESCRIBE product_property_${productKey}
  9 + </select>
  10 +
  11 + <update id="createProductPropertySTable">
  12 + CREATE STABLE product_property_${productKey}
  13 + <foreach item="field" collection="fields" separator="," open="(" close=")">
  14 + ${field.field} ${field.type}
  15 + <if test="field.length != null and field.length > 0">
  16 + (${field.length})
  17 + </if>
  18 + </foreach>
  19 + TAGS (
  20 + device_key NCHAR(50)
  21 + )
  22 + </update>
  23 +
  24 + <update id="alterProductPropertySTableAddField">
  25 + ALTER STABLE product_property_${productKey}
  26 + ADD COLUMN ${field.field} ${field.type}
  27 + <if test="field.length != null and field.length > 0">
  28 + (${field.length})
  29 + </if>
  30 + </update>
  31 +
  32 + <update id="alterProductPropertySTableModifyField">
  33 + ALTER STABLE product_property_${productKey}
  34 + MODIFY COLUMN ${field.field} ${field.type}
  35 + <if test="field.length != null and field.length > 0">
  36 + (${field.length})
  37 + </if>
  38 + </update>
  39 +
  40 + <update id="alterProductPropertySTableDropField">
  41 + ALTER STABLE product_property_${productKey}
  42 + DROP COLUMN ${field.field}
  43 + </update>
  44 +
  45 +</mapper>
0 46 \ No newline at end of file
... ...