Bladeren bron

【新增功能】 集成 tdengine

安浩浩 9 maanden geleden
bovenliggende
commit
05c5482715
37 gewijzigde bestanden met toevoegingen van 2016 en 3 verwijderingen
  1. 7 0
      yudao-dependencies/pom.xml
  2. 4 0
      yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml
  3. 25 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/BaseEntity.java
  4. 35 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/DeviceDataExportExcelDto.java
  5. 19 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/DeviceDataVo.java
  6. 72 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/Fields.java
  7. 68 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/FieldsVo.java
  8. 71 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/IotSequential.java
  9. 25 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/MessageCountVo.java
  10. 39 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/ProductSuperTableModel.java
  11. 37 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/SelectDto.java
  12. 38 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/SuperTableDto.java
  13. 33 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/TableDto.java
  14. 33 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/TagsSelectDao.java
  15. 73 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/Weather.java
  16. 58 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/visual/SelectVisualDto.java
  17. 9 0
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductFunctionTypeEnum.java
  18. 5 0
      yudao-module-iot/yudao-module-iot-biz/pom.xml
  19. 2 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java
  20. 51 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelRespVO.java
  21. 80 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/FieldParser.java
  22. 36 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TableData.java
  23. 155 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TableManager.java
  24. 29 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdField.java
  25. 26 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdResponse.java
  26. 55 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdRestApi.java
  27. 25 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TimeData.java
  28. 74 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineMapper.java
  29. 38 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/aspect/TaosAspect.java
  30. 15 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
  31. 29 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataService.java
  32. 168 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java
  33. 141 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineService.java
  34. 93 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineServiceImpl.java
  35. 6 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionService.java
  36. 18 0
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java
  37. 324 0
      yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdEngineMapper.xml

+ 7 - 0
yudao-dependencies/pom.xml

@@ -33,6 +33,7 @@
         <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>
         <kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
         <opengauss.jdbc.version>5.0.2</opengauss.jdbc.version>
+        <taos.version>3.3.3</taos.version>
         <!-- 消息队列 -->
         <rocketmq-spring.version>2.3.0</rocketmq-spring.version>
         <!-- 服务保障相关 -->
@@ -253,6 +254,12 @@
                 <version>${kingbase.jdbc.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>com.taosdata.jdbc</groupId>
+                <artifactId>taos-jdbcdriver</artifactId>
+                <version>${taos.version}</version>
+            </dependency>
+
             <!-- Job 定时任务相关 -->
             <dependency>
                 <groupId>cn.iocoder.boot</groupId>

+ 4 - 0
yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml

@@ -63,6 +63,10 @@
             <artifactId>opengauss-jdbc</artifactId>
             <optional>true</optional>
         </dependency>
+        <dependency>
+            <groupId>com.taosdata.jdbc</groupId>
+            <artifactId>taos-jdbcdriver</artifactId>
+        </dependency>
 
         <dependency>
             <groupId>com.alibaba</groupId>

+ 25 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/BaseEntity.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+/**
+ * @ClassDescription: tdEngine的基础实体类
+ * @ClassName: BaseEntity
+ * @Author: fxlinks
+ * @Date: 2021-12-30 14:39:25
+ * @Version 1.0
+ */
+@Data
+public class BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 数据库名称
+     */
+    private String dataBaseName;
+
+    /**
+     * 超级表名称
+     */
+    private String superTableName;
+}

+ 35 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/DeviceDataExportExcelDto.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+/**
+ * 设备数据导出 excel DTO
+ */
+@Data
+public class DeviceDataExportExcelDto {
+
+    /**
+     * 设备标识
+     */
+    private String deviceKey;
+
+    /**
+     * 导出形式 1 单个参数导出 2 全部参数导出
+     */
+    private String exportType;
+
+    /**
+     * 导出开始时间
+     */
+    private String exportBeginTime;
+
+    /**
+     * 导出结束时间
+     */
+    private String exportEndTime;
+
+    /**
+     * 导出参数,空则导出全部
+     */
+    private String exportParameter;
+}

+ 19 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/DeviceDataVo.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+/**
+ * @ClassDescription: 查询可视化所需入参对象
+ * @ClassName: SelectDto
+ * @Author: andyz
+ * @Date: 2022-07-29 14:12:26
+ * @Version 1.0
+ */
+@Data
+public class DeviceDataVo {
+
+
+    private String deviceId;
+
+    private Long lastTime;
+}

+ 72 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/Fields.java

@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+/**
+ * @ClassDescription: 建表的字段实体类
+ * @ClassName: Fields
+ * @Author: fxlinks
+ * @Date: 2021-12-28 09:09:04
+ * @Version 1.0
+ */
+@Data
+public class Fields {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字段名称
+     */
+    private String fieldName;
+
+    /**
+     * 字段值
+     */
+    private Object fieldValue;
+
+    /**
+     * 字段数据类型
+     */
+//    private DataTypeEnum dataType;
+
+    /**
+     * 字段字节大小
+     */
+    private Integer size;
+
+    public Fields() {
+    }
+
+    public Fields(String fieldName, String dataType, Integer size) {
+//        this.fieldName = fieldName;
+//        //根据规则匹配字段数据类型
+//        switch (dataType.toLowerCase()) {
+//            case ("json"):
+//                this.dataType = DataTypeEnum.JSON;
+//                this.size = size;
+//                break;
+//            case ("string"):
+//                this.dataType = DataTypeEnum.NCHAR;
+//                this.size = size;
+//                break;
+//            case ("binary"):
+//                this.dataType = DataTypeEnum.BINARY;
+//                this.size = size;
+//                break;
+//            case ("int"):
+//                this.dataType = DataTypeEnum.INT;
+//                break;
+//            case ("bool"):
+//                this.dataType = DataTypeEnum.BOOL;
+//                break;
+//            case ("decimal"):
+//                this.dataType = DataTypeEnum.DOUBLE;
+//                break;
+//            case ("timestamp"):
+//                if ("eventTime".equals(fieldName)) {
+//                    this.fieldName = "eventTime";
+//                }
+//                this.dataType = DataTypeEnum.TIMESTAMP;
+//                break;
+//        }
+    }
+}

+ 68 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/FieldsVo.java

@@ -0,0 +1,68 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @ClassDescription: 建表的字段实体类的vo类
+ * @ClassName: FieldsVo
+ * @Author: fxlinks
+ * @Date: 2021-12-28 11:30:06
+ * @Version 1.0
+ */
+@Data
+public class FieldsVo {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字段名称
+     */
+    private String fieldName;
+
+    /**
+     * 字段数据类型
+     */
+    private String dataType;
+
+    /**
+     * 字段字节大小
+     */
+    private Integer size;
+
+    /**
+     * @param fields 字段实体类
+     * @return FieldsVo 字段实体vo类
+     * @MethodDescription 字段实体类转为vo类
+     * @author fx
+     * @Date 2021/12/28 13:48
+     */
+    public static FieldsVo fieldsTranscoding(Fields fields) throws SQLException {
+//        if (StringUtils.isBlank(fields.getFieldName()) || fields.getDataType() == null) {
+//            throw new SQLException("invalid operation: fieldName or dataType can not be null");
+//        }
+//        FieldsVo fieldsVo = new FieldsVo();
+//        fieldsVo.setFieldName(fields.getFieldName());
+//        fieldsVo.setDataType(fields.getDataType().getDataType());
+//        fieldsVo.setSize(fields.getSize());
+//        return fieldsVo;
+        return null;
+    }
+
+    /**
+     * @param fieldsList 字段实体类集合
+     * @return List<FieldsVo> 字段实体vo类集合
+     * @MethodDescription 字段实体类集合转为vo类集合
+     * @author fx
+     * @Date 2021/12/28 14:00
+     */
+    public static List<FieldsVo> fieldsTranscoding(List<Fields> fieldsList) throws SQLException {
+        List<FieldsVo> fieldsVoList = new ArrayList<>();
+        for (Fields fields : fieldsList) {
+            fieldsVoList.add(fieldsTranscoding(fields));
+        }
+        return fieldsVoList;
+    }
+}

+ 71 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/IotSequential.java

@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.sql.Timestamp;
+
+public class IotSequential extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS" , timezone = "GMT+8")
+    private Timestamp statetime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS" , timezone = "GMT+8")
+    private Timestamp endtime;
+
+    private String deviceid;
+
+    private String eventtime;
+
+    private String serviceid;
+
+    private String devices;
+
+    public String getDeviceid() {
+        return deviceid;
+    }
+
+    public void setDeviceid(String deviceid) {
+        this.deviceid = deviceid;
+    }
+
+    public String getEventtime() {
+        return eventtime;
+    }
+
+    public void setEventtime(String eventtime) {
+        this.eventtime = eventtime;
+    }
+
+    public String getServiceid() {
+        return serviceid;
+    }
+
+    public void setServiceid(String serviceid) {
+        this.serviceid = serviceid;
+    }
+
+    public String getDevices() {
+        return devices;
+    }
+
+    public void setDevices(String devices) {
+        this.devices = devices;
+    }
+
+    public Timestamp getStatetime() {
+        return statetime;
+    }
+
+    public void setStatetime(Timestamp statetime) {
+        this.statetime = statetime;
+    }
+
+    public Timestamp getEndtime() {
+        return endtime;
+    }
+
+    public void setEndtime(Timestamp endtime) {
+        this.endtime = endtime;
+    }
+}

+ 25 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/MessageCountVo.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 统计的时间数据
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MessageCountVo {
+
+    /**
+     * 时间
+     */
+    private String time;
+
+    /**
+     * 数据值
+     */
+    private Object data;
+
+}

+ 39 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/ProductSuperTableModel.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.sql.Timestamp;
+import java.util.HashMap;
+import java.util.Optional;
+
+/**
+ * @Description: 产品超级表模型
+ * @Author: fx
+ * @Website: http://www.sxfxck.com
+ * @CreateDate: 2022/1/1$ 19:37$
+ * @UpdateUser: fx
+ * @UpdateDate: 2022/1/1$ 19:37$
+ * @UpdateRemark: 修改内容
+ * @Version: V1.0
+ */
+@Data
+public class ProductSuperTableModel {
+    private static final long serialVersionUID = 1L;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS" , timezone = "GMT+8")
+    private Timestamp ts;
+
+    private String superTableName;
+
+    /**
+     * columnsName,columnsProperty
+     */
+    private HashMap<Optional, Optional> columns;
+
+    /**
+     * tagsName,tagsProperty
+     */
+    private HashMap<Optional, Optional> tags;
+
+}

+ 37 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/SelectDto.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+import java.util.Set;
+
+/**
+ * @ClassDescription: 查询所需入参对象
+ * @ClassName: SelectDto
+ * @Author: fxlinks
+ * @Date: 2022-01-07 14:12:26
+ * @Version 1.0
+ */
+@Data
+public class SelectDto {
+
+    //    @NotBlank(message = "invalid operation: dataBaseName can not be empty")
+    private String dataBaseName;
+
+//    @NotBlank(message = "invalid operation: tableName can not be empty")
+    private String tableName;
+
+    //    @NotBlank(message = "invalid operation: fieldName can not be empty")
+    private String fieldName;
+
+    //    @NotNull(message = "invalid operation: startTime can not be null")
+    private Long startTime;
+
+    //    @NotNull(message = "invalid operation: endTime can not be null")
+    private Long endTime;
+
+    private String type;
+
+    private Set<Integer> orgIds;
+
+    private String deviceId;
+}

+ 38 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/SuperTableDto.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @ClassDescription: 创建超级表需要的入参的实体类
+ * @ClassName: SuperTableDto
+ * @Author: fxlinks
+ * @Date: 2021-12-28 15:03:45
+ * @Version 1.0
+ */
+@Data
+public class SuperTableDto extends BaseEntity {
+
+    /**
+     * 超级表的表结构(业务相关)
+     * 第一个字段的数据类型必须为timestamp
+     * 字符相关数据类型必须指定大小
+     * 字段名称和字段数据类型不能为空
+     */
+//    @NotEmpty(message = "invalid operation: schemaFields can not be empty")
+    private List<Fields> schemaFields;
+
+    /**
+     * 超级表的标签字段,可以作为子表在超级表里的标识
+     * 字符相关数据类型必须指定大小
+     * 字段名称和字段数据类型不能为空
+     */
+//    @NotEmpty(message = "invalid operation: tagsFields can not be empty")
+    private List<Fields> tagsFields;
+
+    /**
+     * 字段信息对象,超级表添加列时使用该属性
+     */
+    private Fields fields;
+}

+ 33 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/TableDto.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @ClassDescription: 创建超级表的子表需要的入参的实体类
+ * @ClassName: TableDto
+ * @Author: fxlinks
+ * @Date: 2021-12-30 14:42:47
+ * @Version 1.0
+ */
+@Data
+public class TableDto extends BaseEntity {
+
+    /**
+     * 超级表普通列字段的值
+     * 值需要与创建超级表时普通列字段的数据类型对应上
+     */
+    private List<Fields> schemaFieldValues;
+
+    /**
+     * 超级表标签字段的值
+     * 值需要与创建超级表时标签字段的数据类型对应上
+     */
+    private List<Fields> tagsFieldValues;
+
+    /**
+     * 表名称
+     */
+    private String tableName;
+}

+ 33 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/TagsSelectDao.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import lombok.Data;
+
+
+/**
+ * @program: fxlinks
+ * @description: 标签查询模型
+ * @packagename: com.fx.tdengine.api.domain.rule
+ * @author: fx
+ * @e-mainl: 13733918655@163.com
+ * @date: 2022-07-27 18:40
+ **/
+@Data
+public class TagsSelectDao {
+
+//    @NotBlank(message = "invalid operation: dataBaseName can not be empty")
+    private String dataBaseName;
+
+//    @NotBlank(message = "invalid operation: stableName can not be empty")
+    private String stableName;
+
+//    @NotBlank(message = "invalid operation: tagsName can not be empty")
+    private String tagsName;
+
+    //    @NotNull(message = "invalid operation: startTime can not be null")
+    private Long startTime;
+
+    //    @NotNull(message = "invalid operation: endTime can not be null")
+    private Long endTime;
+
+
+}

+ 73 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/Weather.java

@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.iot.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.sql.Timestamp;
+
+public class Weather {
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS" , timezone = "GMT+8")
+    private Timestamp ts;
+    private Float temperature;
+    private Float humidity;
+    private String location;
+    private String note;
+    private int groupId;
+
+    public Weather() {
+    }
+
+    public Weather(Timestamp ts, float temperature, float humidity) {
+        this.ts = ts;
+        this.temperature = temperature;
+        this.humidity = humidity;
+    }
+
+    public Timestamp getTs() {
+        return ts;
+    }
+
+    public void setTs(Timestamp ts) {
+        this.ts = ts;
+    }
+
+    public Float getTemperature() {
+        return temperature;
+    }
+
+    public void setTemperature(Float temperature) {
+        this.temperature = temperature;
+    }
+
+    public Float getHumidity() {
+        return humidity;
+    }
+
+    public void setHumidity(Float humidity) {
+        this.humidity = humidity;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+    public int getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(int groupId) {
+        this.groupId = groupId;
+    }
+
+    public String getNote() {
+        return note;
+    }
+
+    public void setNote(String note) {
+        this.note = note;
+    }
+}

+ 58 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/visual/SelectVisualDto.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.iot.domain.visual;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * @ClassDescription: 查询可视化所需入参对象
+ * @ClassName: SelectDto
+ * @Author: andyz
+ * @Date: 2022-07-29 14:12:26
+ * @Version 1.0
+ */
+@Data
+public class SelectVisualDto {
+
+//    @NotBlank(message = "invalid operation: tableName can not be empty")
+    private String dataBaseName;
+
+//    @NotBlank(message = "invalid operation: tableName can not be empty")
+    private String tableName;
+
+//    @NotBlank(message = "invalid operation: fieldName can not be empty") //属性
+    private String fieldName;
+    /**
+     * 查询类型,0历史数据,1实时数据,2聚合数据
+     */
+    private int type;
+    /**
+     * 查询的数据量
+     */
+    private int num;
+    /**
+     * 聚合函数
+     */
+    private String aggregate;
+    /**
+     * 统计间隔数字+s/m/h/d
+     * 比如1s,1m,1h,1d代表1秒,1分钟,1小时,1天
+     */
+    private String interval;
+    //    @NotNull(message = "invalid operation: startTime can not be null")
+    private Long startTime;
+
+    //    @NotNull(message = "invalid operation: endTime can not be null")
+    private Long endTime;
+
+    /**
+     * 请求参数
+     */
+    private Map<String, Object> params;
+
+    private String sql;
+
+    private String deviceId;
+
+    private Long lastTime;
+}

+ 9 - 0
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductFunctionTypeEnum.java

@@ -30,6 +30,15 @@ public enum IotProductFunctionTypeEnum implements IntArrayValuable {
      */
     private final String description;
 
+    public static IotProductFunctionTypeEnum valueOf(Integer type) {
+        for (IotProductFunctionTypeEnum value : values()) {
+            if (value.getType().equals(type)) {
+                return value;
+            }
+        }
+        return null;
+    }
+
     @Override
     public int[] array() {
         return ARRAYS;

+ 5 - 0
yudao-module-iot/yudao-module-iot-biz/pom.xml

@@ -42,6 +42,11 @@
             <artifactId>yudao-spring-boot-starter-mybatis</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-spring-boot-starter-redis</artifactId>
+        </dependency>
+
         <!-- Test 测试相关 -->
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 2 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java

@@ -83,11 +83,10 @@ public class IotProductController {
         return success(BeanUtils.toBean(pageResult, IotProductRespVO.class));
     }
 
-    // TODO @haohao:改成 simple-list 哈
-    @GetMapping("/list-all-simple")
+    @GetMapping("/simple-list")
     @Operation(summary = "获得所有产品列表")
     @PreAuthorize("@ss.hasPermission('iot:product:query')")
-    public CommonResult<List<IotProductSimpleRespVO>> listAllSimpleProducts() {
+    public CommonResult<List<IotProductSimpleRespVO>> getSimpleProductList() {
         List<IotProductDO> list = productService.getProductList();
         return success(BeanUtils.toBean(list, IotProductSimpleRespVO.class));
     }

+ 51 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelRespVO.java

@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel;
+
+import lombok.*;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString
+public class ThingModelRespVO {
+
+    /**
+     * 产品编号
+     */
+    private Long id;
+
+    /**
+     * 产品标识
+     */
+    private String productKey;
+
+    /**
+     * 物模型
+     */
+    private Model model;
+
+    /**
+     * 物模型
+     */
+    @Data
+    public static class Model {
+
+        /**
+         * 属性列表
+         */
+        private List<ThingModelProperty> properties;
+
+        /**
+         * 服务列表
+         */
+        private List<ThingModelService> services;
+
+        /**
+         * 事件列表
+         */
+        private List<ThingModelEvent> events;
+    }
+}

+ 80 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/FieldParser.java

@@ -0,0 +1,80 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+
+
+import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty;
+import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelRespVO;
+import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.dataType.ThingModelDataType;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class FieldParser {
+
+    /**
+     * 物模型到td数据类型映射
+     */
+    private static final HashMap<String, String> TYPE_MAPPING = new HashMap<>() {{
+        put("INT", "INT");
+        put("FLOAT", "FLOAT");
+        put("DOUBLE", "DOUBLE");
+        put("BOOL", "BOOL");
+        put("ENUM", "NCHAR");
+        put("TEXT", "NCHAR");
+        put("DATE", "NCHAR");
+    }};
+
+
+    /**
+     * 将物模型字段转换为td字段
+     *
+     * @param property 物模型属性
+     * @return TdField对象
+     */
+    public static TdField parse(ThingModelProperty property) {
+        String fieldName = property.getIdentifier().toLowerCase();
+        ThingModelDataType type = property.getDataType();
+
+        // 将物模型字段类型映射为td字段类型
+        String fType = TYPE_MAPPING.get(type.getType().toUpperCase());
+
+        int len = -1;
+        // 如果字段类型为NCHAR,默认长度为64
+        if ("NCHAR".equals(fType)) {
+            len = 64;
+        }
+
+        return new TdField(fieldName, fType, len);
+    }
+
+    /**
+     * 获取物模型中的字段列表
+     */
+    public static List<TdField> parse(ThingModelRespVO thingModel) {
+        return thingModel.getModel().getProperties().stream().map(FieldParser::parse).collect(Collectors.toList());
+    }
+
+    /**
+     * 将从库中查出来的字段信息转换为td字段对象
+     */
+    public static List<TdField> parse(List rows) {
+        return (List<TdField>) rows.stream().map((r) -> {
+            List row = (List) r;
+            String type = row.get(1).toString().toUpperCase();
+            return new TdField(
+                    row.get(0).toString(),
+                    type,
+                    type.equals("NCHAR") ? Integer.parseInt(row.get(2).toString()) : -1);
+        }).collect(Collectors.toList());
+    }
+
+    /**
+     * 获取字段字义
+     */
+    public static String getFieldDefine(TdField field) {
+        return "`" + field.getName() + "`" + " " + (field.getLength() > 0 ?
+                String.format("%s(%d)", field.getType(), field.getLength())
+                : field.getType());
+    }
+
+}

+ 36 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TableData.java

@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class TableData {
+
+    /**
+     * 超级表普通列字段的名称
+     */
+    private List<String> schemaFieldList;
+
+    /**
+     * 超级表标签字段的值
+     * 值需要与创建超级表时标签字段的数据类型对应上
+     */
+    private List<Object> tagsValueList;
+
+    /**
+     * 超级表普通列字段的值
+     * 值需要与创建超级表时普通列字段的数据类型对应上
+     */
+    private List<Object> schemaValueList;
+
+    /**
+     * 表名称
+     */
+    private String tableName;
+
+    /**
+     * 超级表名称
+     */
+    private String superTableName;
+}

+ 155 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TableManager.java

@@ -0,0 +1,155 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+
+import java.util.List;
+
+public class TableManager {
+
+    /**
+     * 创建超级表模板(含存在判断)
+     */
+    private static final String CREATE_STABLE_INE_TPL = "CREATE STABLE IF NOT EXISTS %s (%s) TAGS (%s);";
+
+    /**
+     * 删除超级表
+     */
+    private static final String DROP_STABLE_TPL = "DROP STABLE IF EXISTS %s;";
+
+    /**
+     * 获取表的结构信息
+     */
+    private static final String DESC_TB_TPL = "DESCRIBE %s;";
+
+    /**
+     * 超级表增加列
+     */
+    private static final String ALTER_STABLE_ADD_COL_TPL = "ALTER STABLE %s ADD COLUMN %s;";
+
+    /**
+     * 超级表修改列
+     */
+    private static final String ALTER_STABLE_MODIFY_COL_TPL = "ALTER STABLE %s MODIFY COLUMN %s;";
+
+    /**
+     * 超级表删除列
+     */
+    private static final String ALTER_STABLE_DROP_COL_TPL = "ALTER STABLE %s DROP COLUMN %s;";
+
+    /**
+     * 创建普通表模板(含存在判断)
+     */
+    private static final String CREATE_CTABLE_INE_TPL = "CREATE TABLE IF NOT EXISTS %s (%s)";
+
+    /**
+     * 获取创建表sql
+     */
+    public static String getCreateSTableSql(String tbName, List<TdField> fields, TdField... tags) {
+        if (fields.isEmpty()) {
+            return null;
+        }
+
+        // 生成字段片段
+        StringBuilder sbField = new StringBuilder("time TIMESTAMP,");
+
+        for (TdField field : fields) {
+            sbField.append(FieldParser.getFieldDefine(field));
+            sbField.append(",");
+        }
+        sbField.deleteCharAt(sbField.length() - 1);
+
+        String fieldFrag = sbField.toString();
+
+        // 生成tag
+        StringBuilder sbTag = new StringBuilder();
+        for (TdField tag : tags) {
+            sbTag.append(FieldParser.getFieldDefine(tag))
+                    .append(",");
+        }
+        sbTag.deleteCharAt(sbTag.length() - 1);
+
+        return String.format(CREATE_STABLE_INE_TPL, tbName, fieldFrag, sbTag);
+
+    }
+
+    /**
+     * 获取创建普通表sql
+     */
+    public static String getCreateCTableSql(String tbName, List<TdField> fields) {
+        if (fields.size() == 0) {
+            return null;
+        }
+
+        //生成字段片段
+        StringBuilder sbField = new StringBuilder("time timestamp,");
+
+        for (TdField field : fields) {
+            sbField.append(FieldParser.getFieldDefine(field));
+            sbField.append(",");
+        }
+        sbField.deleteCharAt(sbField.length() - 1);
+
+        String fieldFrag = sbField.toString();
+
+        return String.format(CREATE_CTABLE_INE_TPL, tbName, fieldFrag);
+
+    }
+
+
+    /**
+     * 取正确的表名
+     *
+     * @param name 表象
+     */
+    public static String rightTbName(String name) {
+        return name.toLowerCase().replace("-" , "_");
+    }
+
+    /**
+     * 获取表详情的sql
+     */
+    public static String getDescTableSql(String tbName) {
+        return String.format(DESC_TB_TPL, tbName);
+    }
+
+    /**
+     * 获取添加字段sql
+     */
+    public static String getAddSTableColumnSql(String tbName, List<TdField> fields) {
+        StringBuilder sbAdd = new StringBuilder();
+        for (TdField field : fields) {
+            sbAdd.append(String.format(ALTER_STABLE_ADD_COL_TPL,
+                    tbName,
+                    FieldParser.getFieldDefine(field)
+            ));
+        }
+        return sbAdd.toString();
+    }
+
+    /**
+     * 获取修改字段sql
+     */
+    public static String getModifySTableColumnSql(String tbName, List<TdField> fields) {
+        StringBuilder sbModify = new StringBuilder();
+        for (TdField field : fields) {
+            sbModify.append(String.format(ALTER_STABLE_MODIFY_COL_TPL,
+                    tbName,
+                    FieldParser.getFieldDefine(field)
+            ));
+        }
+        return sbModify.toString();
+    }
+
+    /**
+     * 获取删除字段sql
+     */
+    public static String getDropSTableColumnSql(String tbName, List<TdField> fields) {
+        StringBuilder sbDrop = new StringBuilder();
+        for (TdField field : fields) {
+            sbDrop.append(String.format(ALTER_STABLE_DROP_COL_TPL,
+                    tbName,
+                    field.getName()
+            ));
+        }
+        return sbDrop.toString();
+    }
+
+}

+ 29 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdField.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * TD 引擎的字段
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TdField {
+
+    /**
+     * 字段名称
+     */
+    private String name;
+
+    /**
+     * 字段类型
+     */
+    private String type;
+
+    /**
+     * 字段长度
+     */
+    private int length;
+}

+ 26 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdResponse.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TdResponse {
+
+    public static final int CODE_SUCCESS = 0;
+    public static final int CODE_TB_NOT_EXIST = 866;
+
+    private String status;
+
+    private int code;
+
+    private String desc;
+
+    //[["time","TIMESTAMP",8,""],["powerstate","TINYINT",1,""],["brightness","INT",4,""],["deviceid","NCHAR",32,"TAG"]]
+    private List data;
+
+}

+ 55 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdRestApi.java

@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class TdRestApi {
+
+    @Value("${spring.datasource.dynamic.datasource.master.url}")
+    private String url;
+
+    @Value("${spring.datasource.dynamic.datasource.master.username}")
+    private String username;
+
+    @Value("${spring.datasource.dynamic.datasource.master.password}")
+    private String password;
+
+    private String getRestApiUrl() {
+        //jdbc:TAOS-RS://127.0.0.1:6041/iotkit?xxxx
+        String restUrl = url.replace("jdbc:TAOS-RS://" , "")
+                .replaceAll("\\?.*" , "");
+        // /rest/sql/iotkit
+        int idx = restUrl.lastIndexOf("/");
+        //127.0.0.1:6041/rest/sql/iotkit
+        return String.format("%s/rest/sql/%s" , restUrl.substring(0, idx), restUrl.substring(idx + 1));
+    }
+
+
+    /**
+     * 新建td api请求对象
+     */
+    public HttpRequest newApiRequest(String sql) {
+        return HttpRequest
+                .post(getRestApiUrl())
+                .body(sql)
+                .basicAuth(username, password);
+    }
+
+    /**
+     * 执行sql
+     */
+    public TdResponse execSql(String sql) {
+        log.info("exec td sql:{}" , sql);
+        HttpRequest request = newApiRequest(sql);
+        HttpResponse response = request.execute();
+        return JSONUtil.toBean(response.body(), TdResponse.class);
+    }
+
+
+}

+ 25 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TimeData.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 统计的时间数据
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TimeData {
+
+    /**
+     * 时间
+     */
+    private long time;
+
+    /**
+     * 数据值
+     */
+    private Object data;
+
+}

+ 74 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineMapper.java

@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.iot.dal.tdengine;
+
+import cn.iocoder.yudao.module.iot.domain.FieldsVo;
+import cn.iocoder.yudao.module.iot.domain.SelectDto;
+import cn.iocoder.yudao.module.iot.domain.TableDto;
+import cn.iocoder.yudao.module.iot.domain.TagsSelectDao;
+import cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto;
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+@DS("tdengine")
+public interface TdEngineMapper {
+
+    void createDatabase(@Param("dataBaseName") String dataBaseName);
+
+    void createSuperTable(@Param("schemaFields") List<FieldsVo> schemaFields,
+                          @Param("tagsFields") List<FieldsVo> tagsFields,
+                          @Param("dataBaseName") String dataBaseName,
+                          @Param("superTableName") String superTableName);
+
+    void createTable(TableDto tableDto);
+
+    void insertData(TableDto tableDto);
+
+    List<Map<String, Object>> selectByTimestamp(SelectDto selectDto);
+
+    void addColumnForSuperTable(@Param("superTableName") String superTableName,
+                                @Param("fieldsVo") FieldsVo fieldsVo);
+
+    void dropColumnForSuperTable(@Param("superTableName") String superTableName,
+                                 @Param("fieldsVo") FieldsVo fieldsVo);
+
+    void addTagForSuperTable(@Param("superTableName") String superTableName,
+                             @Param("fieldsVo") FieldsVo fieldsVo);
+
+    void dropTagForSuperTable(@Param("superTableName") String superTableName,
+                              @Param("fieldsVo") FieldsVo fieldsVo);
+
+    Map<String, Long> getCountByTimestamp(SelectDto selectDto);
+
+    /**
+     * 检查表是否存在
+     *
+     * @param dataBaseName 数据库名称
+     * @param tableName    表名称
+     */
+    Integer checkTableExists(@Param("dataBaseName") String dataBaseName, @Param("tableName") String tableName);
+
+    Map<String, Object> getLastData(SelectDto selectDto);
+
+    List<Map<String, Object>> getHistoryData(SelectVisualDto selectVisualDto);
+
+    List<Map<String, Object>> getRealtimeData(SelectVisualDto selectVisualDto);
+
+    List<Map<String, Object>> getAggregateData(SelectVisualDto selectVisualDto);
+
+    List<Map<String, Object>> getLastDataByTags(TagsSelectDao tagsSelectDao);
+
+    /**
+     * 创建超级表
+     *
+     * @param sql sql
+     * @return 返回值
+     */
+    @InterceptorIgnore(tenantLine = "true")
+    Integer createSuperTableDevice(String sql);
+
+}

+ 38 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/aspect/TaosAspect.java

@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.iot.framework.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.stereotype.Component;
+
+import java.sql.Timestamp;
+import java.util.Map;
+
+/**
+ * TaosAspect 是一个处理 Taos 数据库返回值的切面。
+ */
+@Aspect
+@Component
+@Slf4j
+public class TaosAspect {
+
+    @Around("execution(java.util.Map<String,Object> cn.iocoder.yudao.module.iot.dal.tdengine.*.*(..))")
+    public Object handleType(ProceedingJoinPoint joinPoint) {
+        Map<String, Object> result = null;
+        try {
+            result = (Map<String, Object>) joinPoint.proceed();
+            result.replaceAll((key, value) -> {
+                if (value instanceof byte[]) {
+                    return new String((byte[]) value);
+                } else if (value instanceof Timestamp) {
+                    return ((Timestamp) value).getTime();
+                }
+                return value;
+            });
+        } catch (Throwable e) {
+            log.error("TaosAspect handleType error", e);
+        }
+        return result;
+    }
+}

+ 15 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java

@@ -8,8 +8,13 @@ import cn.iocoder.yudao.module.iot.controller.admin.product.vo.IotProductSaveReq
 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.enums.product.IotProductStatusEnum;
+import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService;
+import com.baomidou.dynamic.datasource.annotation.DSTransactional;
 import jakarta.annotation.Resource;
+
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.List;
@@ -31,6 +36,10 @@ public class IotProductServiceImpl implements IotProductService {
     @Resource
     private IotProductMapper productMapper;
 
+    @Resource
+    @Lazy
+    private IotThinkModelFunctionService thinkModelFunctionService;
+
     @Override
     public Long createProduct(IotProductSaveReqVO createReqVO) {
         // 1. 生成 ProductKey
@@ -106,11 +115,17 @@ public class IotProductServiceImpl implements IotProductService {
     }
 
     @Override
+    @DSTransactional(rollbackFor = Exception.class)
     public void updateProductStatus(Long id, Integer status) {
         // 1. 校验存在
         validateProductExists(id);
         // 2. 更新
         IotProductDO updateObj = IotProductDO.builder().id(id).status(status).build();
+        // 3. 产品是发布状态
+        if (Objects.equals(status, IotProductStatusEnum.PUBLISHED.getType())) {
+            // 3.1 创建超级表数据模型
+            thinkModelFunctionService.createSuperTableDataModel(id);
+        }
         productMapper.updateById(updateObj);
     }
 

+ 29 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataService.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.iot.service.tdengine;
+
+
+import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
+
+import java.util.List;
+
+/**
+ * 数据结构接口
+ */
+public interface IotDbStructureDataService {
+
+    /**
+     * 创建物模型定义
+     */
+    void createSuperTable(ThingModelRespVO thingModel, Integer deviceType);
+
+    /**
+     * 更新物模型定义
+     */
+    void updateSuperTable(ThingModelRespVO thingModel, Integer deviceType);
+
+    /**
+     * 创建超级表数据模型
+     */
+    void createSuperTableDataModel(IotProductDO product, List<IotThinkModelFunctionDO> functionList);
+}

+ 168 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java

@@ -0,0 +1,168 @@
+package cn.iocoder.yudao.module.iot.service.tdengine;
+
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelProperty;
+import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingModel.ThingModelRespVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.*;
+import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
+import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineMapper;
+import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class IotDbStructureDataServiceImpl implements IotDbStructureDataService {
+
+    @Resource
+    private TdEngineMapper tdEngineMapper;
+
+    @Resource
+    private TdRestApi tdRestApi;
+
+    @Override
+    public void createSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
+        // 获取物模型中的属性定义
+        List<TdField> fields = FieldParser.parse(thingModel);
+        String tbName = getProductPropertySTableName(deviceType, thingModel.getProductKey());
+
+        // 生成创建超级表的 SQL
+        String sql = TableManager.getCreateSTableSql(tbName, fields, new TdField("device_id", "NCHAR", 64));
+        if (sql == null) {
+            log.warn("生成的 SQL 为空,无法创建超级表");
+            return;
+        }
+        log.info("执行 SQL: {}", sql);
+
+        // 执行 SQL 创建超级表
+        tdEngineMapper.createSuperTableDevice(sql);
+    }
+
+    @Override
+    public void updateSuperTable(ThingModelRespVO thingModel, Integer deviceType) {
+        try {
+            // 获取旧字段信息
+            String tbName = getProductPropertySTableName(deviceType, thingModel.getProductKey());
+            String sql = TableManager.getDescTableSql(tbName);
+            TdResponse response = tdRestApi.execSql(sql);
+            if (response.getCode() != TdResponse.CODE_SUCCESS) {
+                throw new RuntimeException("获取表描述错误: " + JSONUtil.toJsonStr(response));
+            }
+
+            List<TdField> oldFields = FieldParser.parse(response.getData());
+            List<TdField> newFields = FieldParser.parse(thingModel);
+
+            // 找出新增的字段
+            List<TdField> addFields = newFields.stream()
+                    .filter(f -> oldFields.stream().noneMatch(old -> old.getName().equals(f.getName())))
+                    .collect(Collectors.toList());
+            if (!addFields.isEmpty()) {
+                sql = TableManager.getAddSTableColumnSql(tbName, addFields);
+                response = tdRestApi.execSql(sql);
+                if (response.getCode() != TdResponse.CODE_SUCCESS) {
+                    throw new RuntimeException("添加表字段错误: " + JSONUtil.toJsonStr(response));
+                }
+            }
+
+            // 找出修改的字段
+            List<TdField> modifyFields = newFields.stream()
+                    .filter(f -> oldFields.stream().anyMatch(old ->
+                            old.getName().equals(f.getName()) &&
+                                    (!old.getType().equals(f.getType()) || old.getLength() != f.getLength())))
+                    .collect(Collectors.toList());
+            if (!modifyFields.isEmpty()) {
+                sql = TableManager.getModifySTableColumnSql(tbName, modifyFields);
+                response = tdRestApi.execSql(sql);
+                if (response.getCode() != TdResponse.CODE_SUCCESS) {
+                    throw new RuntimeException("修改表字段错误: " + JSONUtil.toJsonStr(response));
+                }
+            }
+
+            // 找出删除的字段
+            List<TdField> dropFields = oldFields.stream()
+                    .filter(f -> !"time".equals(f.getName()) && !"device_id".equals(f.getName()) &&
+                            newFields.stream().noneMatch(n -> n.getName().equals(f.getName())))
+                    .collect(Collectors.toList());
+            if (!dropFields.isEmpty()) {
+                sql = TableManager.getDropSTableColumnSql(tbName, dropFields);
+                response = tdRestApi.execSql(sql);
+                if (response.getCode() != TdResponse.CODE_SUCCESS) {
+                    throw new RuntimeException("删除表字段错误: " + JSONUtil.toJsonStr(response));
+                }
+            }
+        } catch (Throwable e) {
+            log.error("更新物模型超级表失败", e);
+        }
+    }
+
+    @Override
+    public void createSuperTableDataModel(IotProductDO product, List<IotThinkModelFunctionDO> functionList) {
+        // 1. 生成 ThingModelRespVO
+        ThingModelRespVO thingModel = new ThingModelRespVO();
+        thingModel.setId(product.getId());
+        thingModel.setProductKey(product.getProductKey());
+
+        // 1.1 设置属性、服务和事件
+        ThingModelRespVO.Model model = new ThingModelRespVO.Model();
+        List<ThingModelProperty> properties = new ArrayList<>();
+
+        // 1.2 遍历功能列表并分类
+        for (IotThinkModelFunctionDO function : functionList) {
+            if (Objects.requireNonNull(IotProductFunctionTypeEnum.valueOf(function.getType())) == IotProductFunctionTypeEnum.PROPERTY) {
+                ThingModelProperty property = new ThingModelProperty();
+                property.setIdentifier(function.getIdentifier());
+                property.setName(function.getName());
+                property.setDescription(function.getDescription());
+                property.setDataType(function.getProperty().getDataType());
+                properties.add(property);
+            }
+        }
+
+        // 1.3 判断属性列表是否为空
+        if (properties.isEmpty()) {
+            log.warn("物模型属性列表为空,不创建超级表");
+            return;
+        }
+
+        model.setProperties(properties);
+        thingModel.setModel(model);
+
+        // 2. 判断是否已经创建,如果已经创建则进行更新
+        String tbName = getProductPropertySTableName(product.getDeviceType(), product.getProductKey());
+        Integer iot = tdEngineMapper.checkTableExists("ruoyi_vue_pro", tbName);
+        if (iot != null && iot > 0) {
+            // 3. 更新
+            updateSuperTable(thingModel, product.getDeviceType());
+        } else {
+            // 4. 创建
+            createSuperTable(thingModel, product.getDeviceType());
+        }
+    }
+
+    /**
+     * 根据产品key获取产品属性超级表名
+     */
+    static String getProductPropertySTableName(Integer deviceType, String productKey) {
+        if (deviceType == 1) {
+            return String.format("gateway_sub_" + productKey).toLowerCase();
+        } else if (deviceType == 2) {
+            return String.format("gateway_" + productKey).toLowerCase();
+        } else {
+            return String.format("device_" + productKey).toLowerCase();
+        }
+    }
+
+    /**
+     * 根据deviceId获取设备属性表名
+     */
+    static String getDevicePropertyTableName(String deviceType, String productKey, String deviceKey) {
+        return String.format(deviceType + "_" + productKey + "_" + deviceKey).toLowerCase();
+    }
+}

+ 141 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineService.java

@@ -0,0 +1,141 @@
+package cn.iocoder.yudao.module.iot.service.tdengine;
+
+import cn.iocoder.yudao.module.iot.domain.FieldsVo;
+import cn.iocoder.yudao.module.iot.domain.SelectDto;
+import cn.iocoder.yudao.module.iot.domain.TableDto;
+import cn.iocoder.yudao.module.iot.domain.TagsSelectDao;
+import cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * TdEngineService
+ */
+public interface TdEngineService {
+
+    /**
+     * 创建数据库
+     *
+     * @param dataBaseName 数据库名称
+     * @throws Exception 异常
+     */
+    void createDateBase(String dataBaseName) throws Exception;
+
+    /**
+     * 创建超级表
+     *
+     * @param schemaFields   schema字段
+     * @param tagsFields     tags字段
+     * @param dataBaseName   数据库名称
+     * @param superTableName 超级表名称
+     * @throws Exception 异常
+     */
+    void createSuperTable(List<FieldsVo> schemaFields, List<FieldsVo> tagsFields, String dataBaseName,
+                          String superTableName) throws Exception;
+
+    /**
+     * 创建表
+     *
+     * @param tableDto 表信息
+     * @throws Exception 异常
+     */
+    void createTable(TableDto tableDto) throws Exception;
+
+    /**
+     * 插入数据
+     *
+     * @param tableDto 表信息
+     * @throws Exception 异常
+     */
+    void insertData(TableDto tableDto) throws Exception;
+
+    /**
+     * 根据时间戳查询数据
+     *
+     * @param selectDto 查询条件
+     * @return 数据
+     * @throws Exception 异常
+     */
+    List<Map<String, Object>> selectByTimesTamp(SelectDto selectDto) throws Exception;
+
+    /**
+     * 为超级表添加列
+     *
+     * @param superTableName 超级表名称
+     * @param fieldsVo       字段信息
+     * @throws Exception 异常
+     */
+    void addColumnForSuperTable(String superTableName, FieldsVo fieldsVo) throws Exception;
+
+    /**
+     * 为超级表删除列
+     *
+     * @param superTableName 超级表名称
+     * @param fieldsVo       字段信息
+     * @throws Exception 异常
+     */
+    void dropColumnForSuperTable(String superTableName, FieldsVo fieldsVo) throws Exception;
+
+    /**
+     * 为超级表添加tag
+     */
+    Long getCountByTimesTamp(SelectDto selectDto) throws Exception;
+
+    /**
+     * 检查表是否存在
+     *
+     * @return 1存在 0不存在
+     * @throws Exception 异常
+     */
+    Integer checkTableExists(SelectDto selectDto) throws Exception;
+
+    /**
+     * 初始化超级表
+     *
+     * @param msg 消息
+     * @throws Exception 异常
+     */
+    void initSTableFrame(String msg) throws Exception;
+
+    /**
+     * 获取最新数据
+     *
+     * @param selectDto 查询条件
+     * @return 数据
+     * @throws Exception 异常
+     */
+    Map<String, Object> getLastData(SelectDto selectDto) throws Exception;
+
+    /**
+     * 根据tag查询最新数据
+     *
+     * @param tagsSelectDao 查询条件
+     * @return 数据
+     */
+    Map<String, Map<String, Object>> getLastDataByTags(TagsSelectDao tagsSelectDao);
+
+    /**
+     * 获取历史数据
+     *
+     * @param selectVisualDto 查询条件
+     * @return 数据
+     */
+    List<Map<String, Object>> getHistoryData(SelectVisualDto selectVisualDto);
+
+    /**
+     * 获取实时数据
+     *
+     * @param selectVisualDto 查询条件
+     * @return 数据
+     */
+    List<Map<String, Object>> getRealtimeData(SelectVisualDto selectVisualDto);
+
+    /**
+     * 获取聚合数据
+     *
+     * @param selectVisualDto 查询条件
+     * @return 数据
+     */
+    List<Map<String, Object>> getAggregateData(SelectVisualDto selectVisualDto);
+}

+ 93 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineServiceImpl.java

@@ -0,0 +1,93 @@
+package cn.iocoder.yudao.module.iot.service.tdengine;
+
+import cn.iocoder.yudao.module.iot.domain.FieldsVo;
+import cn.iocoder.yudao.module.iot.domain.SelectDto;
+import cn.iocoder.yudao.module.iot.domain.TableDto;
+import cn.iocoder.yudao.module.iot.domain.TagsSelectDao;
+import cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Slf4j
+public class TdEngineServiceImpl implements TdEngineService {
+
+
+    @Override
+    public void createDateBase(String dataBaseName) {
+
+    }
+
+    @Override
+    public void createSuperTable(List<FieldsVo> schemaFields, List<FieldsVo> tagsFields, String dataBaseName, String superTableName) {
+
+    }
+
+    @Override
+    public void createTable(TableDto tableDto) {
+
+    }
+
+    @Override
+    public void insertData(TableDto tableDto) {
+
+    }
+
+    @Override
+    public List<Map<String, Object>> selectByTimesTamp(SelectDto selectDto) {
+        return List.of();
+    }
+
+    @Override
+    public void addColumnForSuperTable(String superTableName, FieldsVo fieldsVo) {
+
+    }
+
+    @Override
+    public void dropColumnForSuperTable(String superTableName, FieldsVo fieldsVo) {
+
+    }
+
+    @Override
+    public Long getCountByTimesTamp(SelectDto selectDto) {
+        return 0L;
+    }
+
+    @Override
+    public Integer checkTableExists(SelectDto selectDto) {
+        return 0;
+    }
+
+    @Override
+    public void initSTableFrame(String msg) {
+
+    }
+
+    @Override
+    public Map<String, Object> getLastData(SelectDto selectDto) {
+        return Map.of();
+    }
+
+    @Override
+    public Map<String, Map<String, Object>> getLastDataByTags(TagsSelectDao tagsSelectDao) {
+        return Map.of();
+    }
+
+    @Override
+    public List<Map<String, Object>> getHistoryData(SelectVisualDto selectVisualDto) {
+        return List.of();
+    }
+
+    @Override
+    public List<Map<String, Object>> getRealtimeData(SelectVisualDto selectVisualDto) {
+        return List.of();
+    }
+
+    @Override
+    public List<Map<String, Object>> getAggregateData(SelectVisualDto selectVisualDto) {
+        return List.of();
+    }
+}

+ 6 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionService.java

@@ -62,4 +62,10 @@ public interface IotThinkModelFunctionService {
      */
     PageResult<IotThinkModelFunctionDO> getThinkModelFunctionPage(IotThinkModelFunctionPageReqVO pageReqVO);
 
+    /**
+     * 创建超级表数据模型
+     *
+     * @param productId 产品编号
+     */
+    void createSuperTableDataModel(Long productId);
 }

+ 18 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java

@@ -15,10 +15,13 @@ import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.thingMode
 import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionPageReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.thinkmodelfunction.vo.IotThinkModelFunctionSaveReqVO;
 import cn.iocoder.yudao.module.iot.convert.thinkmodelfunction.IotThinkModelFunctionConvert;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
 import cn.iocoder.yudao.module.iot.dal.mysql.thinkmodelfunction.IotThinkModelFunctionMapper;
 import cn.iocoder.yudao.module.iot.enums.product.IotAccessModeEnum;
 import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
+import cn.iocoder.yudao.module.iot.service.tdengine.IotDbStructureDataService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
@@ -45,6 +48,11 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe
     @Resource
     private IotThinkModelFunctionMapper thinkModelFunctionMapper;
 
+    @Resource
+    private IotProductService productService;
+    @Resource
+    private IotDbStructureDataService dbStructureDataService;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createThinkModelFunction(IotThinkModelFunctionSaveReqVO createReqVO) {
@@ -162,6 +170,16 @@ public class IotThinkModelFunctionServiceImpl implements IotThinkModelFunctionSe
         return thinkModelFunctionMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public void createSuperTableDataModel(Long productId) {
+        // 1. 查询产品
+        IotProductDO product = productService.getProduct(productId);
+        // 2. 查询产品的物模型功能列表
+        List<IotThinkModelFunctionDO> functionList = thinkModelFunctionMapper.selectListByProductId(productId);
+        // 3. 生成 TDengine 的数据模型
+        dbStructureDataService.createSuperTableDataModel(product, functionList);
+    }
+
     /**
      * 创建默认的事件和服务
      */

+ 324 - 0
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdEngineMapper.xml

@@ -0,0 +1,324 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+
+<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineMapper">
+
+    <update id="createDatabase" parameterType="String">
+        create database if not exists #{dataBaseName}
+    </update>
+
+    <update id="createSuperTable">
+        create table if not exists #{dataBaseName}.#{superTableName}
+        <foreach item="item" collection="schemaFields" separator=","
+                 open="(" close=")" index="">
+            <if test="item.fieldName != null || item.fieldName != ''">
+                ${item.fieldName}
+            </if>
+            <if test="item.dataType != null || item.dataType != ''">
+                <choose>
+                    <when test="item.dataType == 'timestamp'">
+                        timestamp
+                    </when>
+                    <when test="item.dataType == 'tinyint'">
+                        tinyint
+                    </when>
+                    <when test="item.dataType == 'smallint'">
+                        smallint
+                    </when>
+                    <when test="item.dataType == 'int'">
+                        int
+                    </when>
+                    <when test="item.dataType == 'bigint'">
+                        bigint
+                    </when>
+                    <when test="item.dataType == 'float'">
+                        float
+                    </when>
+                    <when test="item.dataType == 'double'">
+                        double
+                    </when>
+                    <when test="item.dataType == 'binary'">
+                        binary
+                    </when>
+                    <when test="item.dataType == 'nchar'">
+                        nchar
+                    </when>
+                    <when test="item.dataType == 'bool'">
+                        bool
+                    </when>
+                    <when test="item.dataType == 'json'">
+                        json
+                    </when>
+                </choose>
+            </if>
+            <if test="item.size != null">
+                (#{item.size})
+            </if>
+        </foreach>
+        tags
+        <!--tdEngine不支持动态tags里的数据类型,只能使用choose标签比对-->
+        <foreach item="item" collection="tagsFields" separator=","
+                 open="(" close=")" index="">
+            <if test="item.fieldName != null || item.fieldName != ''">
+                ${item.fieldName}
+            </if>
+            <if test="item.dataType != null || item.dataType != ''">
+                <choose>
+                    <when test="item.dataType == 'timestamp'">
+                        timestamp
+                    </when>
+                    <when test="item.dataType == 'tinyint'">
+                        tinyint
+                    </when>
+                    <when test="item.dataType == 'smallint'">
+                        smallint
+                    </when>
+                    <when test="item.dataType == 'int'">
+                        int
+                    </when>
+                    <when test="item.dataType == 'bigint'">
+                        bigint
+                    </when>
+                    <when test="item.dataType == 'float'">
+                        float
+                    </when>
+                    <when test="item.dataType == 'double'">
+                        double
+                    </when>
+                    <when test="item.dataType == 'binary'">
+                        binary
+                    </when>
+                    <when test="item.dataType == 'nchar'">
+                        nchar
+                    </when>
+                    <when test="item.dataType == 'bool'">
+                        bool
+                    </when>
+                    <when test="item.dataType == 'json'">
+                        json
+                    </when>
+                </choose>
+            </if>
+            <if test="item.size != null">
+                (#{item.size})
+            </if>
+        </foreach>
+    </update>
+
+    <update id="createTable">
+        create table
+        if not exists #{dataBaseName}.#{tableName}
+        using #{dataBaseName}.#{superTableName}
+        tags
+        <foreach item="item" collection="tagsFieldValues" separator=","
+                 open="(" close=")" index="">
+            #{item.fieldValue}
+        </foreach>
+    </update>
+
+    <insert id="insertData">
+        insert into #{dataBaseName}.#{tableName}
+        <foreach item="item" collection="schemaFieldValues" separator=","
+                 open="(" close=")" index="">
+            #{item.fieldName}
+        </foreach>
+        using #{dataBaseName}.#{superTableName}
+        tags
+        <foreach item="item" collection="tagsFieldValues" separator=","
+                 open="(" close=")" index="">
+            #{item.fieldValue}
+        </foreach>
+        values
+        <foreach item="item" collection="schemaFieldValues" separator=","
+                 open="(" close=")" index="">
+            #{item.fieldValue}
+        </foreach>
+    </insert>
+
+
+    <select id="selectByTimestamp" parameterType="cn.iocoder.yudao.module.iot.domain.SelectDto"
+            resultType="Map">
+        select * from #{dataBaseName}.#{tableName}
+        <!--查询这里不能使用#{}占位符的方式,使用这种方式,tdEngine不识别为列名,只能使用${}占位的方式-->
+        <!--因为tdEngine引擎一次只执行一条sql,所以有效预防了sql的注入,且该服务该接口为内部调用,所以可以使用${}-->
+        where ${fieldName}
+        between #{startTime} and #{endTime}
+    </select>
+
+    <update id="addColumnForSuperTable">
+        ALTER
+        STABLE
+        #{superTableName}
+        ADD
+        COLUMN
+        <if test="fieldsVo.fieldName != null || fieldsVo.fieldName != ''">
+            #{fieldsVo.fieldName}
+        </if>
+        <if test="fieldsVo.dataType != null || fieldsVo.dataType != ''">
+            <choose>
+                <when test="fieldsVo.dataType == 'timestamp'">
+                    timestamp
+                </when>
+                <when test="fieldsVo.dataType == 'tinyint'">
+                    tinyint
+                </when>
+                <when test="fieldsVo.dataType == 'smallint'">
+                    smallint
+                </when>
+                <when test="fieldsVo.dataType == 'int'">
+                    int
+                </when>
+                <when test="fieldsVo.dataType == 'bigint'">
+                    bigint
+                </when>
+                <when test="fieldsVo.dataType == 'float'">
+                    float
+                </when>
+                <when test="fieldsVo.dataType == 'double'">
+                    double
+                </when>
+                <when test="fieldsVo.dataType == 'binary'">
+                    binary
+                </when>
+                <when test="fieldsVo.dataType == 'nchar'">
+                    nchar
+                </when>
+                <when test="fieldsVo.dataType == 'bool'">
+                    bool
+                </when>
+                <when test="fieldsVo.dataType == 'json'">
+                    json
+                </when>
+            </choose>
+        </if>
+        <if test="fieldsVo.size != null">
+            (
+            #{fieldsVo.size}
+            )
+        </if>
+    </update>
+
+    <update id="dropColumnForSuperTable">
+        ALTER
+        STABLE
+        #{superTableName}
+        DROP
+        COLUMN
+        <if test="fieldsVo.fieldName != null || fieldsVo.fieldName != ''">
+            #{fieldsVo.fieldName}
+        </if>
+    </update>
+
+    <update id="addTagForSuperTable">
+        ALTER
+        STABLE
+        #{superTableName}
+        ADD
+        TAG
+        <if test="fieldsVo.fieldName != null || fieldsVo.fieldName != ''">
+            #{fieldsVo.fieldName}
+        </if>
+        <if test="fieldsVo.dataType != null || fieldsVo.dataType != ''">
+            <choose>
+                <when test="fieldsVo.dataType == 'timestamp'">
+                    timestamp
+                </when>
+                <when test="fieldsVo.dataType == 'tinyint'">
+                    tinyint
+                </when>
+                <when test="fieldsVo.dataType == 'smallint'">
+                    smallint
+                </when>
+                <when test="fieldsVo.dataType == 'int'">
+                    int
+                </when>
+                <when test="fieldsVo.dataType == 'bigint'">
+                    bigint
+                </when>
+                <when test="fieldsVo.dataType == 'float'">
+                    float
+                </when>
+                <when test="fieldsVo.dataType == 'double'">
+                    double
+                </when>
+                <when test="fieldsVo.dataType == 'binary'">
+                    binary
+                </when>
+                <when test="fieldsVo.dataType == 'nchar'">
+                    nchar
+                </when>
+                <when test="fieldsVo.dataType == 'bool'">
+                    bool
+                </when>
+                <when test="fieldsVo.dataType == 'json'">
+                    json
+                </when>
+            </choose>
+        </if>
+        <if test="fieldsVo.size != null">
+            (
+            #{fieldsVo.size}
+            )
+        </if>
+    </update>
+
+    <update id="dropTagForSuperTable">
+        ALTER
+        STABLE
+        #{superTableName}
+        DROP
+        TAG
+        <if test="fieldsVo.fieldName != null || fieldsVo.fieldName != ''">
+            #{fieldsVo.fieldName}
+        </if>
+    </update>
+
+    <select id="getCountByTimestamp" parameterType="cn.iocoder.yudao.module.iot.domain.SelectDto"
+            resultType="java.util.Map">
+        SELECT count(0) AS count
+        FROM #{dataBaseName}.#{tableName} WHERE ${fieldName} BETWEEN #{startTime} AND #{endTime}
+    </select>
+
+    <select id="checkTableExists" resultType="java.lang.Integer">
+        SELECT COUNT(0)
+        FROM information_schema.ins_tables
+        WHERE db_name = #{dataBaseName}
+          AND table_name = #{tableName}
+    </select>
+
+    <select id="getLastData" resultType="java.util.Map">
+        select last(time), *
+        from #{tableName}
+        where device_id = #{deviceId}
+    </select>
+
+    <select id="getLastDataByTags" parameterType="cn.iocoder.yudao.module.iot.domain.TagsSelectDao"
+            resultType="Map">
+        select last(*)
+        from #{dataBaseName}.#{stableName} group by ${tagsName}
+    </select>
+
+    <select id="getHistoryData" resultType="java.util.Map"
+            parameterType="cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto">
+        SELECT #{fieldName}, ts
+        FROM #{dataBaseName}.#{tableName} WHERE ts BETWEEN #{startTime} AND #{endTime}
+        LIMIT #{num}
+    </select>
+    <select id="getRealtimeData" resultType="java.util.Map"
+            parameterType="cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto">
+        SELECT #{fieldName}, ts
+        FROM #{dataBaseName}.#{tableName} LIMIT #{num}
+    </select>
+    <select id="getAggregateData" resultType="java.util.Map"
+            parameterType="cn.iocoder.yudao.module.iot.domain.visual.SelectVisualDto">
+        SELECT #{aggregate}(${fieldName})
+        FROM #{dataBaseName}.#{tableName} WHERE ts BETWEEN #{startTime} AND #{endTime} interval (${interval})
+        LIMIT #{num}
+    </select>
+
+
+    <insert id="createSuperTableDevice">
+        ${sql}
+    </insert>
+
+</mapper>