diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 924e52eedd..486fe124b3 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -33,6 +33,7 @@ 8.1.3.62 8.6.0 5.0.2 + 3.3.3 2.3.0 @@ -253,6 +254,12 @@ ${kingbase.jdbc.version} + + com.taosdata.jdbc + taos-jdbcdriver + ${taos.version} + + cn.iocoder.boot diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml index 1f04e4a635..e907a2de4d 100644 --- a/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-mybatis/pom.xml @@ -63,6 +63,10 @@ opengauss-jdbc true + + com.taosdata.jdbc + taos-jdbcdriver + com.alibaba diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/BaseEntity.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/BaseEntity.java new file mode 100644 index 0000000000..74a4c8494b --- /dev/null +++ b/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; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/DeviceDataExportExcelDto.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/DeviceDataExportExcelDto.java new file mode 100644 index 0000000000..cedb4d1190 --- /dev/null +++ b/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; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/DeviceDataVo.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/DeviceDataVo.java new file mode 100644 index 0000000000..b9f6fc4127 --- /dev/null +++ b/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; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/Fields.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/Fields.java new file mode 100644 index 0000000000..bbd7bb74e8 --- /dev/null +++ b/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; +// } + } +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/FieldsVo.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/FieldsVo.java new file mode 100644 index 0000000000..554d52149d --- /dev/null +++ b/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 字段实体vo类集合 + * @MethodDescription 字段实体类集合转为vo类集合 + * @author fx + * @Date 2021/12/28 14:00 + */ + public static List fieldsTranscoding(List fieldsList) throws SQLException { + List fieldsVoList = new ArrayList<>(); + for (Fields fields : fieldsList) { + fieldsVoList.add(fieldsTranscoding(fields)); + } + return fieldsVoList; + } +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/IotSequential.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/IotSequential.java new file mode 100644 index 0000000000..349d6d1371 --- /dev/null +++ b/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; + } +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/MessageCountVo.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/MessageCountVo.java new file mode 100644 index 0000000000..e9e93cac01 --- /dev/null +++ b/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; + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/ProductSuperTableModel.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/ProductSuperTableModel.java new file mode 100644 index 0000000000..49887415c7 --- /dev/null +++ b/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 columns; + + /** + * tagsName,tagsProperty + */ + private HashMap tags; + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/SelectDto.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/SelectDto.java new file mode 100644 index 0000000000..381ef3d6f1 --- /dev/null +++ b/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 orgIds; + + private String deviceId; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/SuperTableDto.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/SuperTableDto.java new file mode 100644 index 0000000000..ee80d42cfd --- /dev/null +++ b/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 schemaFields; + + /** + * 超级表的标签字段,可以作为子表在超级表里的标识 + * 字符相关数据类型必须指定大小 + * 字段名称和字段数据类型不能为空 + */ +// @NotEmpty(message = "invalid operation: tagsFields can not be empty") + private List tagsFields; + + /** + * 字段信息对象,超级表添加列时使用该属性 + */ + private Fields fields; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/TableDto.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/TableDto.java new file mode 100644 index 0000000000..f27beb1a69 --- /dev/null +++ b/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 schemaFieldValues; + + /** + * 超级表标签字段的值 + * 值需要与创建超级表时标签字段的数据类型对应上 + */ + private List tagsFieldValues; + + /** + * 表名称 + */ + private String tableName; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/TagsSelectDao.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/TagsSelectDao.java new file mode 100644 index 0000000000..5cb9c5ed92 --- /dev/null +++ b/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; + + +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/Weather.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/Weather.java new file mode 100644 index 0000000000..a4a1e983b8 --- /dev/null +++ b/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; + } +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/visual/SelectVisualDto.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/domain/visual/SelectVisualDto.java new file mode 100644 index 0000000000..1444da9727 --- /dev/null +++ b/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 params; + + private String sql; + + private String deviceId; + + private Long lastTime; +} diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductFunctionTypeEnum.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductFunctionTypeEnum.java index 7a924997a5..b99d2b0938 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductFunctionTypeEnum.java +++ b/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; diff --git a/yudao-module-iot/yudao-module-iot-biz/pom.xml b/yudao-module-iot/yudao-module-iot-biz/pom.xml index e3f93086ae..cb2fd818f9 100644 --- a/yudao-module-iot/yudao-module-iot-biz/pom.xml +++ b/yudao-module-iot/yudao-module-iot-biz/pom.xml @@ -42,6 +42,11 @@ yudao-spring-boot-starter-mybatis + + cn.iocoder.boot + yudao-spring-boot-starter-redis + + cn.iocoder.boot diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java index 5b0ecb27ae..e7f41d91dc 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java @@ -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> listAllSimpleProducts() { + public CommonResult> getSimpleProductList() { List list = productService.getProductList(); return success(BeanUtils.toBean(list, IotProductSimpleRespVO.class)); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelRespVO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/thinkmodelfunction/thingModel/ThingModelRespVO.java new file mode 100644 index 0000000000..6fc48ef4f7 --- /dev/null +++ b/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 properties; + + /** + * 服务列表 + */ + private List services; + + /** + * 事件列表 + */ + private List events; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/FieldParser.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/FieldParser.java new file mode 100644 index 0000000000..0a91e7cf19 --- /dev/null +++ b/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 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 parse(ThingModelRespVO thingModel) { + return thingModel.getModel().getProperties().stream().map(FieldParser::parse).collect(Collectors.toList()); + } + + /** + * 将从库中查出来的字段信息转换为td字段对象 + */ + public static List parse(List rows) { + return (List) 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()); + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TableData.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TableData.java new file mode 100644 index 0000000000..1d42933d4c --- /dev/null +++ b/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 schemaFieldList; + + /** + * 超级表标签字段的值 + * 值需要与创建超级表时标签字段的数据类型对应上 + */ + private List tagsValueList; + + /** + * 超级表普通列字段的值 + * 值需要与创建超级表时普通列字段的数据类型对应上 + */ + private List schemaValueList; + + /** + * 表名称 + */ + private String tableName; + + /** + * 超级表名称 + */ + private String superTableName; +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TableManager.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TableManager.java new file mode 100644 index 0000000000..1bf4ecf646 --- /dev/null +++ b/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 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 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 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 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 fields) { + StringBuilder sbDrop = new StringBuilder(); + for (TdField field : fields) { + sbDrop.append(String.format(ALTER_STABLE_DROP_COL_TPL, + tbName, + field.getName() + )); + } + return sbDrop.toString(); + } + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdField.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdField.java new file mode 100644 index 0000000000..f040a017ad --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdResponse.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdResponse.java new file mode 100644 index 0000000000..380807a1b8 --- /dev/null +++ b/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; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdRestApi.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TdRestApi.java new file mode 100644 index 0000000000..4506383f61 --- /dev/null +++ b/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); + } + + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TimeData.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/tdengine/TimeData.java new file mode 100644 index 0000000000..f7e251ea8a --- /dev/null +++ b/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; + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdEngineMapper.java new file mode 100644 index 0000000000..6a925f4e5a --- /dev/null +++ b/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 schemaFields, + @Param("tagsFields") List tagsFields, + @Param("dataBaseName") String dataBaseName, + @Param("superTableName") String superTableName); + + void createTable(TableDto tableDto); + + void insertData(TableDto tableDto); + + List> 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 getCountByTimestamp(SelectDto selectDto); + + /** + * 检查表是否存在 + * + * @param dataBaseName 数据库名称 + * @param tableName 表名称 + */ + Integer checkTableExists(@Param("dataBaseName") String dataBaseName, @Param("tableName") String tableName); + + Map getLastData(SelectDto selectDto); + + List> getHistoryData(SelectVisualDto selectVisualDto); + + List> getRealtimeData(SelectVisualDto selectVisualDto); + + List> getAggregateData(SelectVisualDto selectVisualDto); + + List> getLastDataByTags(TagsSelectDao tagsSelectDao); + + /** + * 创建超级表 + * + * @param sql sql + * @return 返回值 + */ + @InterceptorIgnore(tenantLine = "true") + Integer createSuperTableDevice(String sql); + +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/aspect/TaosAspect.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/aspect/TaosAspect.java new file mode 100644 index 0000000000..7c9fe70009 --- /dev/null +++ b/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 cn.iocoder.yudao.module.iot.dal.tdengine.*.*(..))") + public Object handleType(ProceedingJoinPoint joinPoint) { + Map result = null; + try { + result = (Map) 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; + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java index 15391f70b3..d0ca092889 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java @@ -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); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataService.java new file mode 100644 index 0000000000..42439e25a2 --- /dev/null +++ b/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 functionList); +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotDbStructureDataServiceImpl.java new file mode 100644 index 0000000000..f8dd6feb5d --- /dev/null +++ b/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 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 oldFields = FieldParser.parse(response.getData()); + List newFields = FieldParser.parse(thingModel); + + // 找出新增的字段 + List 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 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 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 functionList) { + // 1. 生成 ThingModelRespVO + ThingModelRespVO thingModel = new ThingModelRespVO(); + thingModel.setId(product.getId()); + thingModel.setProductKey(product.getProductKey()); + + // 1.1 设置属性、服务和事件 + ThingModelRespVO.Model model = new ThingModelRespVO.Model(); + List 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(); + } +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineService.java new file mode 100644 index 0000000000..b3ae6f8186 --- /dev/null +++ b/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 schemaFields, List 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> 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 getLastData(SelectDto selectDto) throws Exception; + + /** + * 根据tag查询最新数据 + * + * @param tagsSelectDao 查询条件 + * @return 数据 + */ + Map> getLastDataByTags(TagsSelectDao tagsSelectDao); + + /** + * 获取历史数据 + * + * @param selectVisualDto 查询条件 + * @return 数据 + */ + List> getHistoryData(SelectVisualDto selectVisualDto); + + /** + * 获取实时数据 + * + * @param selectVisualDto 查询条件 + * @return 数据 + */ + List> getRealtimeData(SelectVisualDto selectVisualDto); + + /** + * 获取聚合数据 + * + * @param selectVisualDto 查询条件 + * @return 数据 + */ + List> getAggregateData(SelectVisualDto selectVisualDto); +} diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/TdEngineServiceImpl.java new file mode 100644 index 0000000000..70933617f7 --- /dev/null +++ b/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 schemaFields, List tagsFields, String dataBaseName, String superTableName) { + + } + + @Override + public void createTable(TableDto tableDto) { + + } + + @Override + public void insertData(TableDto tableDto) { + + } + + @Override + public List> 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 getLastData(SelectDto selectDto) { + return Map.of(); + } + + @Override + public Map> getLastDataByTags(TagsSelectDao tagsSelectDao) { + return Map.of(); + } + + @Override + public List> getHistoryData(SelectVisualDto selectVisualDto) { + return List.of(); + } + + @Override + public List> getRealtimeData(SelectVisualDto selectVisualDto) { + return List.of(); + } + + @Override + public List> getAggregateData(SelectVisualDto selectVisualDto) { + return List.of(); + } +} \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionService.java index ce8e472f59..3067772e66 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionService.java +++ b/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 getThinkModelFunctionPage(IotThinkModelFunctionPageReqVO pageReqVO); + /** + * 创建超级表数据模型 + * + * @param productId 产品编号 + */ + void createSuperTableDataModel(Long productId); } \ No newline at end of file diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java index 7937869443..1e356c6b7f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thinkmodelfunction/IotThinkModelFunctionServiceImpl.java +++ b/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 functionList = thinkModelFunctionMapper.selectListByProductId(productId); + // 3. 生成 TDengine 的数据模型 + dbStructureDataService.createSuperTableDataModel(product, functionList); + } + /** * 创建默认的事件和服务 */ diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdEngineMapper.xml b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdEngineMapper.xml new file mode 100644 index 0000000000..f722e0130e --- /dev/null +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdEngineMapper.xml @@ -0,0 +1,324 @@ + + + + + + + create database if not exists #{dataBaseName} + + + + create table if not exists #{dataBaseName}.#{superTableName} + + + ${item.fieldName} + + + + + timestamp + + + tinyint + + + smallint + + + int + + + bigint + + + float + + + double + + + binary + + + nchar + + + bool + + + json + + + + + (#{item.size}) + + + tags + + + + ${item.fieldName} + + + + + timestamp + + + tinyint + + + smallint + + + int + + + bigint + + + float + + + double + + + binary + + + nchar + + + bool + + + json + + + + + (#{item.size}) + + + + + + create table + if not exists #{dataBaseName}.#{tableName} + using #{dataBaseName}.#{superTableName} + tags + + #{item.fieldValue} + + + + + insert into #{dataBaseName}.#{tableName} + + #{item.fieldName} + + using #{dataBaseName}.#{superTableName} + tags + + #{item.fieldValue} + + values + + #{item.fieldValue} + + + + + + + + ALTER + STABLE + #{superTableName} + ADD + COLUMN + + #{fieldsVo.fieldName} + + + + + timestamp + + + tinyint + + + smallint + + + int + + + bigint + + + float + + + double + + + binary + + + nchar + + + bool + + + json + + + + + ( + #{fieldsVo.size} + ) + + + + + ALTER + STABLE + #{superTableName} + DROP + COLUMN + + #{fieldsVo.fieldName} + + + + + ALTER + STABLE + #{superTableName} + ADD + TAG + + #{fieldsVo.fieldName} + + + + + timestamp + + + tinyint + + + smallint + + + int + + + bigint + + + float + + + double + + + binary + + + nchar + + + bool + + + json + + + + + ( + #{fieldsVo.size} + ) + + + + + ALTER + STABLE + #{superTableName} + DROP + TAG + + #{fieldsVo.fieldName} + + + + + + + + + + + + + + + + + + ${sql} + + +