diff --git a/iot-data/iot-ts-temporal-service/pom.xml b/iot-data/iot-ts-temporal-service/pom.xml
new file mode 100644
index 00000000..7f335626
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/pom.xml
@@ -0,0 +1,97 @@
+
+
+ 4.0.0
+
+ cc.iotkit
+ iot-data
+ 0.4.2-SNAPSHOT
+
+
+ iot-ts-temporal-service
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+ org.springframework
+ spring-context
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+
+ org.mapstruct
+ mapstruct
+
+
+
+ cn.hutool
+ hutool-http
+
+
+
+
+
+ cc.iotkit
+ iot-data-cache
+
+
+
+
+ org.jooq
+ jooq-meta
+ 3.14.15
+
+
+
+ org.jooq
+ jooq
+ 3.14.15
+
+
+
+ org.postgresql
+ postgresql
+ 42.5.4
+
+
+ org.springframework
+ spring-jdbc
+
+
+
+ cc.iotkit
+ iot-model
+
+
+
+ cc.iotkit
+ iot-model
+
+
+ cc.iotkit
+ iot-temporal-service
+
+
+
+
\ No newline at end of file
diff --git a/iot-data/iot-ts-temporal-service/readme.md b/iot-data/iot-ts-temporal-service/readme.md
new file mode 100644
index 00000000..e84fc038
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/readme.md
@@ -0,0 +1,5 @@
+### 时序数据库服务接口的TimescaleDB实现
+
+postgrep 14
+
+
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/config/Constants.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/config/Constants.java
new file mode 100644
index 00000000..5a6bb3f6
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/config/Constants.java
@@ -0,0 +1,27 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.config;
+
+public interface Constants {
+
+ /**
+ * 根据产品key获取产品属性超级表名
+ */
+ static String getProductPropertySTableName(String productKey) {
+ return String.format("product_property_%s", productKey.toLowerCase());
+ }
+
+ /**
+ * 根据deviceId获取设备属性表名
+ */
+ static String getDevicePropertyTableName(String deviceId) {
+ return String.format("device_property_%s", deviceId.toLowerCase());
+ }
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/config/TsDatasourceConfig.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/config/TsDatasourceConfig.java
new file mode 100644
index 00000000..2b8c5979
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/config/TsDatasourceConfig.java
@@ -0,0 +1,43 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.config;
+
+import cc.iotkit.temporal.ts.dao.TsTemplate;
+import com.zaxxer.hikari.HikariDataSource;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class TsDatasourceConfig {
+
+ @Value("${spring.ts-datasource.url}")
+ private String url;
+
+ @Value("${spring.ts-datasource.driverClassName}")
+ private String driverClassName;
+
+ @Value("${spring.ts-datasource.username}")
+ private String username;
+
+ @Value("${spring.ts-datasource.password}")
+ private String password;
+
+ @Bean("tsJdbcTemplate")
+ public TsTemplate tdJdbcTemplate() {
+ HikariDataSource dataSource = new HikariDataSource();
+ dataSource.setJdbcUrl(url);
+ dataSource.setUsername(username);
+ dataSource.setPassword(password);
+ dataSource.setDriverClassName(driverClassName);
+ return new TsTemplate(dataSource);
+ }
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dao/TsTemplate.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dao/TsTemplate.java
new file mode 100644
index 00000000..bb9c3163
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dao/TsTemplate.java
@@ -0,0 +1,19 @@
+package cc.iotkit.temporal.ts.dao;
+
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import javax.sql.DataSource;
+
+public class TsTemplate extends JdbcTemplate {
+
+ public TsTemplate() {
+ }
+
+ public TsTemplate(DataSource dataSource) {
+ super(dataSource);
+ }
+
+ public TsTemplate(DataSource dataSource, boolean lazyInit) {
+ super(dataSource, lazyInit);
+ }
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/DbField.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/DbField.java
new file mode 100644
index 00000000..c9d7443e
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/DbField.java
@@ -0,0 +1,15 @@
+package cc.iotkit.temporal.ts.dm;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DbField {
+ private String name;
+ private String type;
+
+ private int length;
+}
\ No newline at end of file
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/FieldParser.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/FieldParser.java
new file mode 100644
index 00000000..55e081a2
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/FieldParser.java
@@ -0,0 +1,118 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.dm;
+
+
+import cc.iotkit.model.product.ThingModel;
+import org.jooq.DataType;
+import org.jooq.Field;
+import org.jooq.impl.DSL;
+import org.jooq.impl.SQLDataType;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class FieldParser {
+
+ /**
+ * 物模型到td数据类型映射
+ */
+ private static final Map TYPE_MAPPING = Map.of(
+ "int32", SQLDataType.INTEGER,
+ "float", SQLDataType.FLOAT,
+ "bool", SQLDataType.BOOLEAN,
+ "enum", SQLDataType.INTEGER,
+ "text", SQLDataType.NVARCHAR,
+ "date", SQLDataType.DATE
+ );
+
+ /**
+ * td数据类型到物模型映射
+ */
+
+ private static final Map DB2TYPE_MAPPING = Map.of(
+ "int",SQLDataType.INTEGER,
+ "float", SQLDataType.FLOAT,
+ "bool", SQLDataType.BOOLEAN,
+ "char",SQLDataType.NVARCHAR,
+ "date", SQLDataType.DATE,
+ "timestamptz", SQLDataType.TIMESTAMPWITHTIMEZONE
+ );
+
+
+ private static DataType getFieldType(final String type) {
+ Set keys = DB2TYPE_MAPPING.keySet();
+ String lowerCase = type.toLowerCase();
+ for(String key:keys){
+ if(lowerCase.contains(key)){
+ return DB2TYPE_MAPPING.get(key);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 将物模型字段转换为td字段
+ */
+ public static TsField parse(ThingModel.Property property) {
+ String filedName = property.getIdentifier().toLowerCase();
+ ThingModel.DataType dataType = property.getDataType();
+ String type = dataType.getType();
+
+ //将物模型字段类型映射为td字段类型
+ DataType fType = TYPE_MAPPING.get(type);
+ Object specs = dataType.getSpecs();
+ int len = -1;
+ if (specs instanceof Map) {
+ Object objLen = ((Map, ?>) specs).get("length");
+ if (objLen != null) {
+ len = Integer.parseInt(objLen.toString());
+ }
+ }
+
+ return new TsField(filedName, fType, len);
+ }
+
+ /**
+ * 获取物模型中的字段列表
+ */
+ public static List parse(ThingModel 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) -> {
+
+ return new TsField(
+ r.getName(),
+ getFieldType(r.getType()).length(r.getLength()),r.getLength());
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * 获取字段字义
+ */
+ public static Field getFieldDefine(TsField field) {
+ int length = field.getLength();
+ DataType type = field.getType();
+
+ if(length>0){
+ type.length(length);
+ }
+ return DSL.field(field.getName(),type);
+
+ }
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/TableManager.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/TableManager.java
new file mode 100644
index 00000000..0cb13a3d
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/TableManager.java
@@ -0,0 +1,161 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.dm;
+
+import org.jooq.*;
+import org.jooq.conf.ParamType;
+import org.jooq.impl.DSL;
+import org.jooq.impl.SQLDataType;
+import org.jooq.tools.StringUtils;
+
+import java.util.List;
+
+import static org.jooq.impl.DSL.*;
+
+public class TableManager {
+
+ private static final DSLContext sqlBuilder = DSL.using(SQLDialect.POSTGRES);
+
+ public static DSLContext getSqlBuilder() {
+ return sqlBuilder;
+ }
+
+ /**
+ * 获取创建表sql
+ */
+ public static String getCreateSTableSql(String tbName, List fields) {
+ if (fields.size() == 0) {
+ return null;
+ }
+
+ CreateTableColumnStep tableColumnStep = sqlBuilder.createTable(tbName)
+ .column("time", SQLDataType.TIMESTAMPWITHTIMEZONE.nullable(false))
+ .column(field("device_id", SQLDataType.NCHAR.length(50).nullable(false)));
+
+ //生成字段片段
+
+ for (TsField field : fields) {
+ tableColumnStep.column(FieldParser.getFieldDefine(field));
+
+ }
+
+
+ return tableColumnStep.getSQL(ParamType.INLINED);
+
+ }
+
+ public static String getCreateSTableIndexSql(String tbName, String partitionCol) {
+ //根据时间和设备纬度分区
+ String sql = null;
+ if(StringUtils.isBlank(partitionCol)){
+ // 只根据时间分区
+ sql= String.format(" SELECT create_hypertable('%s', 'time') ;", tbName);
+ }else{
+ sql= String.format(" SELECT * FROM create_hypertable('%s', 'time'," +
+ " partitioning_column => '%s'," +
+ " number_partitions => 4" +
+ ") ;", tbName, partitionCol );
+
+ }
+
+ return sql;
+
+ }
+
+ public static String getCreateTableIndexSql(String tbName) {
+
+ CreateIndexIncludeStep step = sqlBuilder.createIndexIfNotExists(tbName + "_index").on(
+ table(name(tbName)),
+ field(name("device_id")),
+ field(name("time")).desc());
+
+ return step.getSQL(ParamType.INLINED);
+
+ }
+
+ /**
+ * 取正确的表名
+ *
+ * @param name 表象
+ */
+ public static String rightTbName(String name) {
+ return name.toLowerCase().replace("-", "_");
+ }
+
+ /**
+ * 获取表详情的sql
+ */
+ public static String getDescTableSql(String tbName) {
+
+
+ String sql =String.format( " select a.attname as name," +
+ " t.typname as type, " +
+ "a.attlen as length," +
+ " case when a.attnotnull='t' then '1' else '0' end as nullable," +
+ " case when b.pk='t' then '1' else '0' end as isPk " +
+ "from pg_class e, pg_attribute a left join pg_type t on a.atttypid = t.oid " +
+ "left join (select pg_constraint.conname,pg_constraint.contype,pg_attribute.attname as pk " +
+ "from pg_constraint " +
+ " inner join pg_class on pg_constraint.conrelid = pg_class.oid" +
+ " inner join pg_attribute on pg_attribute.attrelid = pg_class.oid " +
+ " and pg_attribute.attnum = any(pg_constraint.conkey) where contype='p')" +
+ " b on a.attname=b.pk where e.relname = '%s'" +
+ " and a.attnum > 0 and a.attrelid = e.oid and t.typname is not null ;",tbName);
+
+ return sql;
+
+
+
+ }
+
+ /**
+ * 获取添加字段sql
+ */
+ public static String getAddSTableColumnSql(String tbName, List fields) {
+
+ AlterTableStep alterTableStep = sqlBuilder.alterTable(tbName);
+
+ AlterTableFinalStep addStep = null;
+ for (TsField o : fields) {
+ addStep = alterTableStep.add(FieldParser.getFieldDefine(o));
+ }
+ return addStep.getSQL();
+ }
+
+ /**
+ * 获取修改字段sql
+ */
+ public static String getModifySTableColumnSql(String tbName, List fields) {
+ AlterTableStep alterTableStep = sqlBuilder.alterTable(tbName);
+ AlterTableFinalStep step = null;
+ for (TsField o : fields) {
+ Field fieldDefine = FieldParser.getFieldDefine(o);
+ step = alterTableStep.alterColumn(o.getName()).set(fieldDefine.getDataType());
+ }
+ return step.getSQL();
+ }
+
+ /**
+ * 获取删除字段sql
+ */
+ public static String getDropSTableColumnSql(String tbName, List fields) {
+
+ AlterTableStep alterTableStep = sqlBuilder.alterTable(tbName);
+
+
+ AlterTableFinalStep step = null;
+ for (TsField o : fields) {
+ step = alterTableStep.dropColumnIfExists(FieldParser.getFieldDefine(o));
+ }
+
+ return step.getSQL();
+ }
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/TsField.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/TsField.java
new file mode 100644
index 00000000..72734d80
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/dm/TsField.java
@@ -0,0 +1,24 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.dm;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.jooq.DataType;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TsField {
+ private String name;
+ private DataType type;
+ private int length;
+}
\ No newline at end of file
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsDeviceProperty.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsDeviceProperty.java
new file mode 100644
index 00000000..ebc9f32f
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsDeviceProperty.java
@@ -0,0 +1,31 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TsDeviceProperty {
+
+ private Date time;
+
+ private String deviceId;
+
+ private String name;
+
+ private Object value;
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsRuleLog.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsRuleLog.java
new file mode 100644
index 00000000..59d5f2dd
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsRuleLog.java
@@ -0,0 +1,33 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TsRuleLog {
+
+ private Date time;
+
+ private String ruleId;
+
+ private String state1;
+
+ private String content;
+
+ private Boolean success;
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsTaskLog.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsTaskLog.java
new file mode 100644
index 00000000..a2546658
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsTaskLog.java
@@ -0,0 +1,29 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TsTaskLog {
+
+ private Long time;
+
+ private String taskId;
+
+ private String content;
+
+ private Boolean success;
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsThingModelMessage.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsThingModelMessage.java
new file mode 100644
index 00000000..fe6b6c73
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsThingModelMessage.java
@@ -0,0 +1,45 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TsThingModelMessage {
+
+ private Date time;
+
+ private String mid;
+
+ private String deviceId;
+
+ private String productKey;
+
+ private String deviceName;
+
+ private String uid;
+
+ private String type;
+
+ private String identifier;
+
+ private int code;
+
+ private String data;
+
+ private Long reportTime;
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsTimeData.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsTimeData.java
new file mode 100644
index 00000000..c98b4097
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsTimeData.java
@@ -0,0 +1,36 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * 统计的时间数据
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TsTimeData {
+
+ /**
+ * 时间
+ */
+ private Date time;
+
+ /**
+ * 数据值
+ */
+ private Object data;
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsVirtualDeviceLog.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsVirtualDeviceLog.java
new file mode 100644
index 00000000..f1488b65
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/model/TsVirtualDeviceLog.java
@@ -0,0 +1,33 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TsVirtualDeviceLog {
+
+ private Date time;
+
+ private String virtualDeviceId;
+
+ private String virtualDeviceName;
+
+ private int deviceTotal;
+
+ private String result;
+
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/service/DbStructureDataImpl.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/service/DbStructureDataImpl.java
new file mode 100644
index 00000000..7984e665
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/service/DbStructureDataImpl.java
@@ -0,0 +1,207 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.service;
+
+import cc.iotkit.common.utils.JsonUtil;
+import cc.iotkit.model.product.ThingModel;
+import cc.iotkit.temporal.IDbStructureData;
+import cc.iotkit.temporal.ts.config.Constants;
+import cc.iotkit.temporal.ts.dao.TsTemplate;
+import cc.iotkit.temporal.ts.dm.DbField;
+import cc.iotkit.temporal.ts.dm.FieldParser;
+import cc.iotkit.temporal.ts.dm.TableManager;
+import cc.iotkit.temporal.ts.dm.TsField;
+import lombok.extern.slf4j.Slf4j;
+import org.jooq.CreateTableColumnStep;
+import org.jooq.DSLContext;
+import org.jooq.SQLDialect;
+import org.jooq.impl.DSL;
+import org.jooq.impl.SQLDataType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Service
+public class DbStructureDataImpl implements IDbStructureData {
+
+ @Autowired
+ private TsTemplate tsTemplate;
+
+ /**
+ * 根据物模型创建超级表
+ */
+ @Override
+ public void defineThingModel(ThingModel thingModel) {
+ //获取物模型中的属性定义
+ List fields = FieldParser.parse(thingModel);
+ String tbName = Constants.getProductPropertySTableName(thingModel.getProductKey());
+ String sql = TableManager.getCreateSTableSql(tbName,
+ fields);
+ if (sql == null) {
+ return;
+ }
+ System.out.println(sql);
+ tsTemplate.execute(sql);
+
+ createHypertable(tbName, "device_id");
+
+ }
+
+ private void createHypertable(String tbName, String partitionCol) {
+ String createSTableIndexSql = TableManager.getCreateSTableIndexSql(tbName, partitionCol);
+ try {
+ System.out.println(createSTableIndexSql);
+
+ tsTemplate.execute(createSTableIndexSql);
+ } catch (Exception e) {
+ log.info("createHypertable error:{}", e.getMessage());
+ }
+
+ }
+
+ /**
+ * 根据物模型更新超级表结构
+ */
+ @Override
+ public void updateThingModel(ThingModel thingModel) {
+ //获取旧字段信息
+ String tbName = Constants.getProductPropertySTableName(thingModel.getProductKey());
+ String sql = TableManager.getDescTableSql(tbName);
+ if (sql == null) {
+ return;
+ }
+
+ tsTemplate.execute(sql);
+
+ List fieldsInDb = tsTemplate.query(sql, new BeanPropertyRowMapper(DbField.class));
+
+ List newFields = FieldParser.parse(thingModel);
+ List oldFields = FieldParser.parse(fieldsInDb);
+
+ //对比差异
+
+ //找出修改的字段
+ 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.size() > 0) {
+ sql = TableManager.getModifySTableColumnSql(tbName, modifyFields);
+ log.info("modify column:{}", sql);
+
+ tsTemplate.execute(sql);
+
+ }
+
+ //找出新增的字段
+ List addFields = newFields.stream().filter((f) -> oldFields.stream()
+ .noneMatch(old -> old.getName().equals(f.getName())))
+ .collect(Collectors.toList());
+ if (addFields.size() > 0) {
+ sql = TableManager.getAddSTableColumnSql(tbName, addFields);
+ log.info("add column:{}", sql);
+
+ tsTemplate.execute(sql);
+ }
+
+
+ //找出删除的字段
+ List dropFields = oldFields.stream().filter((f) ->
+ !"time".equals(f.getName()) &&
+ !"device_id".equals(f.getName()) && newFields.stream()
+ //字段名不是time且没有相同字段名的
+ .noneMatch(n -> n.getName().equals(f.getName())))
+ .collect(Collectors.toList());
+ if (dropFields.size() > 0) {
+
+ sql = TableManager.getDropSTableColumnSql(tbName, dropFields);
+ log.info("drop column:{}", sql);
+ tsTemplate.execute(sql);
+
+ }
+ }
+
+ /**
+ * 初始化其它数据结构
+ */
+ @Override
+ @PostConstruct
+ public void initDbStructure() {
+ //创建规则日志表
+ DSLContext dslBuilder = DSL.using(SQLDialect.POSTGRES);
+
+ CreateTableColumnStep ruleLogStep = dslBuilder.createTableIfNotExists("rule_log")
+ .column("time", SQLDataType.TIMESTAMPWITHTIMEZONE.nullable(false))
+ .column("state1", SQLDataType.INTEGER)
+ .column("content", SQLDataType.VARCHAR(1024))
+ .column("success", SQLDataType.BOOLEAN)
+ .column("rule_id", SQLDataType.VARCHAR(50).nullable(false));
+ String sql = ruleLogStep.getSQL();
+ System.out.println(sql);
+ tsTemplate.execute(sql);
+ // 按时间和rule_id分区
+ createHypertable("rule_log", "rule_id");
+
+
+ CreateTableColumnStep taskLogStep = dslBuilder.createTableIfNotExists("task_log")
+ .column("time", SQLDataType.TIMESTAMPWITHTIMEZONE.nullable(false))
+ .column("content", SQLDataType.VARCHAR(1024))
+ .column("success", SQLDataType.BOOLEAN)
+ .column("task_id", SQLDataType.VARCHAR(50).nullable(false));
+ String taskLogsql = taskLogStep.getSQL();
+
+ System.out.println(taskLogsql);
+ tsTemplate.execute(taskLogsql);
+ // 按时间和task_id分区
+ createHypertable("task_log", "task_id");
+
+ CreateTableColumnStep thingModelStep = dslBuilder.createTableIfNotExists("thing_model_message")
+ .column("time", SQLDataType.TIMESTAMPWITHTIMEZONE.nullable(false))
+ .column("mid", SQLDataType.NCHAR(50))
+ .column("product_key", SQLDataType.NCHAR(50))
+ .column("device_name", SQLDataType.NCHAR(50))
+ .column("uid", SQLDataType.NCHAR(50))
+ .column("type", SQLDataType.NCHAR(20))
+ .column("identifier", SQLDataType.NVARCHAR(50))
+ .column("code", SQLDataType.INTEGER)
+ .column("data", SQLDataType.NVARCHAR(1024))
+ .column("report_time", SQLDataType.NVARCHAR(1024))
+ .column("device_id", SQLDataType.NCHAR(50));
+
+ String thingModelsql = thingModelStep.getSQL();
+
+ System.out.println(thingModelsql);
+ tsTemplate.execute(thingModelsql);
+ createHypertable("thing_model_message", "device_id");
+
+ //创建虚拟设备日志表
+ CreateTableColumnStep virtualStep = dslBuilder.createTableIfNotExists("virtual_device_log")
+ .column("time", SQLDataType.TIMESTAMPWITHTIMEZONE.nullable(false))
+ .column("virtual_device_name", SQLDataType.NCHAR(50))
+ .column("device_total", SQLDataType.INTEGER)
+ .column("result", SQLDataType.NVARCHAR(1024))
+ .column("virtual_device_id", SQLDataType.NCHAR(50));
+
+ String virtualsql = virtualStep.getSQL();
+ System.out.println(virtualsql);
+
+ tsTemplate.execute(virtualsql);
+ createHypertable("virtual_device_log", "virtual_device_id");
+
+ }
+}
diff --git a/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/service/DevicePropertyDataImpl.java b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/service/DevicePropertyDataImpl.java
new file mode 100644
index 00000000..c414ac59
--- /dev/null
+++ b/iot-data/iot-ts-temporal-service/src/main/java/cc/iotkit/temporal/ts/service/DevicePropertyDataImpl.java
@@ -0,0 +1,105 @@
+/*
+ * +----------------------------------------------------------------------
+ * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
+ * +----------------------------------------------------------------------
+ * | Licensed 未经许可不能去掉「奇特物联」相关版权
+ * +----------------------------------------------------------------------
+ * | Author: xw2sy@163.com
+ * +----------------------------------------------------------------------
+ */
+package cc.iotkit.temporal.ts.service;
+
+import cc.iotkit.data.IDeviceInfoData;
+import cc.iotkit.model.device.DeviceInfo;
+import cc.iotkit.model.device.message.DeviceProperty;
+import cc.iotkit.temporal.IDevicePropertyData;
+import cc.iotkit.temporal.ts.config.Constants;
+import cc.iotkit.temporal.ts.dao.TsTemplate;
+import cc.iotkit.temporal.ts.model.TsDeviceProperty;
+import lombok.extern.slf4j.Slf4j;
+import org.jooq.Condition;
+import org.jooq.Field;
+import org.jooq.InsertValuesStepN;
+import org.jooq.Record;
+import org.jooq.conf.ParamType;
+import org.jooq.impl.DSL;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.jooq.impl.DSL.field;
+
+@Slf4j
+@Service
+public class DevicePropertyDataImpl implements IDevicePropertyData {
+
+ @Autowired
+ private TsTemplate tsTemplate;
+ @Autowired
+ @Qualifier("deviceInfoDataCache")
+ private IDeviceInfoData deviceInfoData;
+
+ @Override
+ public List findDevicePropertyHistory(String deviceId, String name, long start, long end) {
+ DeviceInfo device = deviceInfoData.findByDeviceId(deviceId);
+
+ String tbName = Constants.getProductPropertySTableName(device.getProductKey());
+ Condition con = field("time").greaterOrEqual(new Date(start)).and(field("time").lessOrEqual(new Date(end)))
+ .and(DSL.field("device_id").eq(deviceId));
+ String sql = DSL.select(DSL.field("time"), DSL.field("device_id"), DSL.field(name.toLowerCase()).as("value"))
+ .from(tbName).where(con)
+ .getSQL(ParamType.INLINED);
+
+
+ List list = tsTemplate.query(sql, new BeanPropertyRowMapper<>(TsDeviceProperty.class));
+
+
+ return list.stream().map(
+ o->{
+ DeviceProperty deviceProperty = new DeviceProperty();
+ BeanUtils.copyProperties(o,deviceProperty);
+ deviceProperty.setTime(o.getTime().getTime());
+ return deviceProperty;
+ }
+ ).collect(Collectors.toList());
+
+ }
+
+ @Override
+ public void addProperties(String deviceId, Map properties, long time) {
+ DeviceInfo device = deviceInfoData.findByDeviceId(deviceId);
+ if (device == null) {
+ return;
+ }
+ //获取设备旧属性
+ Map oldProperties = deviceInfoData.getProperties(deviceId);
+ //用新属性覆盖
+ oldProperties.putAll(properties);
+
+ List> fields = new ArrayList<>();
+ List