【优化功能】 优化 tdengine 操作数据库相关代码

This commit is contained in:
安浩浩 2024-11-09 23:44:05 +08:00
parent 9b30d5d355
commit 89fb71e857
35 changed files with 416 additions and 953 deletions

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.iot.enums;
/**
* Iot 常量
*
* @author 芋道源码
*/
public interface IotConstants {
/**
* 获取设备表名
* <p>
* 格式为 device_{productKey}_{deviceName}
*/
String DEVICE_TABLE_NAME_FORMAT = "device_%s_%s";
/**
* 获取产品属性超级表名 - 网关子设备
* <p>
* 格式为 gateway_sub_{productKey}
*/
String GATEWAY_SUB_STABLE_NAME_FORMAT = "gateway_sub_%s";
/**
* 获取产品属性超级表名 - 网关
* <p>
* 格式为 gateway_{productKey}
*/
String GATEWAY_STABLE_NAME_FORMAT = "gateway_%s";
/**
* 获取产品属性超级表名 - 设备
* <p>
* 格式为 device_{productKey}
*/
String DEVICE_STABLE_NAME_FORMAT = "device_%s";
}

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.device;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataRespVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotTimeDataRespVO;
@ -31,19 +31,17 @@ public class IotDeviceDataController {
@Resource
private IotDeviceDataService deviceDataService;
// TODO @haohao是不是叫 get-latest 就好了因为 data 已经在 url 里了哈
@GetMapping("/latest-data")
@GetMapping("/latest")
@Operation(summary = "获取设备属性最新数据")
public CommonResult<List<IotDeviceDataRespVO>> getDevicePropertiesLatestData(@Valid IotDeviceDataReqVO deviceDataReqVO) {
List<IotDeviceDataDO> list = deviceDataService.getDevicePropertiesLatestData(deviceDataReqVO);
public CommonResult<List<IotDeviceDataRespVO>> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
List<IotDeviceDataDO> list = deviceDataService.getLatestDeviceProperties(deviceDataReqVO);
return success(BeanUtils.toBean(list, IotDeviceDataRespVO.class));
}
// TODO @haohao是不是叫 /history-data => page
@GetMapping("/history-data")
@GetMapping("/history")
@Operation(summary = "获取设备属性历史数据")
public CommonResult<PageResult<IotTimeDataRespVO>> getDevicePropertiesHistoryData(@Valid IotDeviceDataReqVO deviceDataReqVO) {
PageResult<Map<String, Object>> list = deviceDataService.getDevicePropertiesHistoryData(deviceDataReqVO);
public CommonResult<PageResult<IotTimeDataRespVO>> getHistoryDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
PageResult<Map<String, Object>> list = deviceDataService.getHistoryDeviceProperties(deviceDataReqVO);
return success(BeanUtils.toBean(list, IotTimeDataRespVO.class));
}

View File

@ -10,10 +10,9 @@ import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
// TODO @haohaoIotDeviceDataPageReqVO
@Schema(description = "管理后台 - IoT 设备数据 Request VO")
@Data
public class IotDeviceDataReqVO extends PageParam {
public class IotDeviceDataPageReqVO extends PageParam {
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "177")
private Long deviceId;

View File

@ -1,9 +1,6 @@
package cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;

View File

@ -1,15 +1,12 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.device;
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
@ -23,39 +20,52 @@ import java.time.LocalDateTime;
@AllArgsConstructor
public class IotDeviceDataDO {
// TODO @haohao每个字段的关联关系可以 @ 下哈
/**
* 设备编号
* <p>
* 关联 {@link IotDeviceDO#getId()}
*/
private Long deviceId;
/**
* 物模型编号
* <p>
* 关联 {@link IotThinkModelFunctionDO#getId()}
*/
private Long thinkModelFunctionId;
/**
* 产品标识
* <p>
* 关联 {@link IotProductDO#getProductKey()}
*/
private String productKey;
/**
* 设备名称
* <p>
* 冗余 {@link IotDeviceDO#getDeviceName()}
*/
private String deviceName;
/**
* 属性标识符
* <p>
* 关联 {@link IotThinkModelFunctionDO#getIdentifier()}
*/
private String identifier;
/**
* 属性名称
* <p>
* 关联 {@link IotThinkModelFunctionDO#getName()}
*/
private String name;
/**
* 数据类型
* <p>
* 关联 {@link IotThinkModelFunctionDO#getProperty()#getDataType()}
*/
private String dataType;

View File

@ -42,12 +42,6 @@ public class SelectDO {
*/
private String type;
// TODO @haohao这个字段是啥哈
/**
* 查询条件
*/
private Set<Integer> orgIds;
/**
* 设备ID
*/

View File

@ -27,14 +27,17 @@ public class SelectVisualDO {
* 查询类型0历史数据1实时数据2聚合数据
*/
private int type;
/**
* 查询的数据量
*/
private int num;
/**
* 聚合函数
*/
private String aggregate;
/**
* 统计间隔数字+s/m/h/d
* 比如1s,1m,1h,1d代表1秒1分钟1小时1天

View File

@ -1,159 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.dataobject.tdengine;
import java.util.List;
// TODO @haohao这个还有用哇
/**
* TableManager 类用于管理 TDengine 表的创建删除和结构信息获取
*/
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<TdFieldDO> fields, TdFieldDO... tags) {
if (fields.isEmpty()) {
return null;
}
// 生成字段片段
StringBuilder sbField = new StringBuilder("time TIMESTAMP,");
for (TdFieldDO field : fields) {
sbField.append(FieldParser.getFieldDefine(field));
sbField.append(",");
}
sbField.deleteCharAt(sbField.length() - 1);
String fieldFrag = sbField.toString();
// 生成tag
StringBuilder sbTag = new StringBuilder();
for (TdFieldDO 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<TdFieldDO> fields) {
if (fields.size() == 0) {
return null;
}
//生成字段片段
StringBuilder sbField = new StringBuilder("time timestamp,");
for (TdFieldDO 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<TdFieldDO> fields) {
StringBuilder sbAdd = new StringBuilder();
for (TdFieldDO 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<TdFieldDO> fields) {
StringBuilder sbModify = new StringBuilder();
for (TdFieldDO 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<TdFieldDO> fields) {
StringBuilder sbDrop = new StringBuilder();
for (TdFieldDO field : fields) {
sbDrop.append(String.format(ALTER_STABLE_DROP_COL_TPL,
tbName,
field.getFieldName()
));
}
return sbDrop.toString();
}
}

View File

@ -1,19 +1,30 @@
package cn.iocoder.yudao.module.iot.dal.tdengine;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
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;
/**
* TD 引擎的超级表 Mapper
* 专门处理 DDL数据定义语言操作包含所有的数据库和表的定义操作例如创建数据库创建超级表创建子表等
*/
@Mapper
@DS("tdengine")
public interface TdEngineSuperTableMapper {
public interface TdEngineDDLMapper {
/**
* 创建数据库
* SQLCREATE DATABASE [IF NOT EXISTS] db_name [database_options];
*
* @param dataBaseName 数据库名称
*/
@TenantIgnore
void createDatabase(@Param("dataBaseName") String dataBaseName);
/**
* 创建超级表
@ -25,7 +36,7 @@ public interface TdEngineSuperTableMapper {
* columns 列信息
* tags 标签信息
*/
@InterceptorIgnore(tenantLine = "true")
@TenantIgnore
void createSuperTable(TdTableDO superTable);
/**
@ -36,7 +47,7 @@ public interface TdEngineSuperTableMapper {
* dataBaseName 数据库名称
* superTableName 超级表名称
*/
@InterceptorIgnore(tenantLine = "true")
@TenantIgnore
List<Map<String, Object>> showSuperTables(TdTableDO superTable);
/**
@ -47,7 +58,7 @@ public interface TdEngineSuperTableMapper {
* dataBaseName 数据库名称
* superTableName 超级表名称
*/
@InterceptorIgnore(tenantLine = "true")
@TenantIgnore
List<Map<String, Object>> describeSuperTable(TdTableDO superTable);
/**
@ -59,7 +70,7 @@ public interface TdEngineSuperTableMapper {
* superTableName 超级表名称
* column 列信息
*/
@InterceptorIgnore(tenantLine = "true")
@TenantIgnore
void addColumnForSuperTable(TdTableDO superTable);
/**
@ -71,7 +82,7 @@ public interface TdEngineSuperTableMapper {
* superTableName 超级表名称
* column 列信息
*/
@InterceptorIgnore(tenantLine = "true")
@TenantIgnore
void dropColumnForSuperTable(TdTableDO superTable);
/**
@ -83,7 +94,7 @@ public interface TdEngineSuperTableMapper {
* superTableName 超级表名称
* column 列信息
*/
@InterceptorIgnore(tenantLine = "true")
@TenantIgnore
void modifyColumnWidthForSuperTable(TdTableDO superTable);
@ -95,7 +106,7 @@ public interface TdEngineSuperTableMapper {
* superTableName 超级表名称
* tag 标签信息
*/
@InterceptorIgnore(tenantLine = "true")
@TenantIgnore
void addTagForSuperTable(TdTableDO superTable);
/**
@ -106,6 +117,33 @@ public interface TdEngineSuperTableMapper {
* superTableName 超级表名称
* tag 标签信息
*/
@InterceptorIgnore(tenantLine = "true")
@TenantIgnore
void dropTagForSuperTable(TdTableDO superTable);
}
/**
* 创建子表 - 创建子表
* SQLCREATE TABLE [IF NOT EXISTS] tb_name USING stb_name TAGS (tag_value1, ...);
*
* @param table 表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* tableName 子表名称
* tags TAG 字段
*/
@TenantIgnore
void createTable(TdTableDO table);
/**
* 创建子表 - 创建子表并指定标签的值
* SQLCREATE TABLE [IF NOT EXISTS] tb_name USING stb_name (tag_name1, ...) TAGS (tag_value1, ...);
*
* @param table 表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* tableName 子表名称
* tags TAG 字段
*/
@TenantIgnore
void createTableWithTags(TdTableDO table);
}

View File

@ -0,0 +1,103 @@
package cn.iocoder.yudao.module.iot.dal.tdengine;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TagsSelectDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
/**
* 专门处理 TD Engine DML数据操作语言操作处理所有的数据查询和写入操作如插入数据查询数据等
*/
@Mapper
@DS("tdengine")
public interface TdEngineDMLMapper {
/**
* 插入数据 - 指定列插入数据
*
* @param table 数据
* dataBaseName 数据库名
* tableName 表名
* columns
*/
@TenantIgnore
void insertData(TdTableDO table);
/**
* 根据时间戳查询数据
*
* @param selectDO 查询条件
* @return 查询结果列表
*/
@TenantIgnore
List<Map<String, Object>> selectByTimestamp(SelectDO selectDO);
/**
* 根据时间戳获取数据条数
*
* @param selectDO 查询条件
* @return 数据条数
*/
@TenantIgnore
Map<String, Long> selectCountByTimestamp(SelectDO selectDO);
/**
* 获取最新数据
*
* @param selectDO 查询条件
* @return 最新数据
*/
@TenantIgnore
Map<String, Object> selectOneLastData(SelectDO selectDO);
/**
* 获取历史数据列表
*
* @param selectVisualDO 查询条件
* @return 历史数据列表
*/
@TenantIgnore
List<Map<String, Object>> selectHistoryDataList(SelectVisualDO selectVisualDO);
/**
* 获取实时数据列表
*
* @param selectVisualDO 查询条件
* @return 实时数据列表
*/
@TenantIgnore
List<Map<String, Object>> selectRealtimeDataList(SelectVisualDO selectVisualDO);
/**
* 获取聚合数据列表
*
* @param selectVisualDO 查询条件
* @return 聚合数据列表
*/
@TenantIgnore
List<Map<String, Object>> selectAggregateDataList(SelectVisualDO selectVisualDO);
/**
* 根据标签获取最新数据列表
*
* @param tagsSelectDO 查询条件
* @return 最新数据列表
*/
@TenantIgnore
List<Map<String, Object>> selectLastDataListByTags(TagsSelectDO tagsSelectDO);
/**
* 获取历史数据条数
*
* @param selectVisualDO 查询条件
* @return 数据条数
*/
@TenantIgnore
Long selectHistoryCount(SelectVisualDO selectVisualDO);
}

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.apache.ibatis.annotations.Mapper;
/**
* TD 引擎的数据写入 Mapper
*/
@Mapper
@DS("tdengine")
public interface TdEngineDataWriterMapper {
/**
* 插入数据 - 指定列插入数据
*
* @param table 数据
* dataBaseName 数据库名
* tableName 表名
* columns
*/
@InterceptorIgnore(tenantLine = "true")
void insertData(TdTableDO table);
}

View File

@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.tdengine;
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;
// TODO @haohaoInterceptorIgnore 忽略租户可以在每个方法上添加 @TenantIgnore
/**
* TD 引擎的数据库 Mapper
*/
@Mapper
@DS("tdengine")
public interface TdEngineDatabaseMapper {
/**
* 创建数据库
* SQLCREATE DATABASE [IF NOT EXISTS] db_name [database_options];
*
* @param dataBaseName 数据库名称
*/
@InterceptorIgnore(tenantLine = "true")
void createDatabase(@Param("dataBaseName") String dataBaseName);
}

View File

@ -1,86 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TagsSelectDO;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
/**
* TD 引擎的查询 Mapper
*/
@Mapper
@DS("tdengine")
public interface TdEngineQueryMapper {
/**
* 根据时间戳查询数据
*
* @param selectDO 查询条件
* @return 查询结果
*/
List<Map<String, Object>> selectByTimestamp(SelectDO selectDO);
// TODO @haohao最好方法的命名和数据库操作的保持一直get => select然后 selectList or selectOne
/**
* 根据时间戳获取数据条数
*
* @param selectDO 查询条件
* @return 数据条数
*/
Map<String, Long> getCountByTimestamp(SelectDO selectDO);
/**
* 获取最新数据
*
* @param selectDO 查询条件
* @return 最新数据
*/
Map<String, Object> getLastData(SelectDO selectDO);
/**
* 获取历史数据
*
* @param selectVisualDO 查询条件
* @return 历史数据列表
*/
@InterceptorIgnore(tenantLine = "true")
List<Map<String, Object>> getHistoryData(SelectVisualDO selectVisualDO);
/**
* 获取实时数据
*
* @param selectVisualDO 查询条件
* @return 实时数据列表
*/
List<Map<String, Object>> getRealtimeData(SelectVisualDO selectVisualDO);
/**
* 获取聚合数据
*
* @param selectVisualDO 查询条件
* @return 聚合数据列表
*/
List<Map<String, Object>> getAggregateData(SelectVisualDO selectVisualDO);
/**
* 根据标签获取最新数据
*
* @param tagsSelectDO 查询条件
* @return 最新数据列表
*/
List<Map<String, Object>> getLastDataByTags(TagsSelectDO tagsSelectDO);
/**
* 获取历史数据条数
*
* @param selectVisualDO 查询条件
* @return 数据条数
*/
@InterceptorIgnore(tenantLine = "true")
Long getHistoryCount(SelectVisualDO selectVisualDO);
}

View File

@ -1,41 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import org.apache.ibatis.annotations.Mapper;
/**
* TD 引擎的表 Mapper
*/
@Mapper
@DS("tdengine")
public interface TdEngineTableMapper {
/**
* 创建子表 - 创建子表
* SQLCREATE TABLE [IF NOT EXISTS] tb_name USING stb_name TAGS (tag_value1, ...);
*
* @param table 表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* tableName 子表名称
* tags TAG 字段
*/
@InterceptorIgnore(tenantLine = "true")
void createTable(TdTableDO table);
/**
* 创建子表 - 创建子表并指定标签的值
* SQLCREATE TABLE [IF NOT EXISTS] tb_name USING stb_name (tag_name1, ...) TAGS (tag_value1, ...);
*
* @param table 表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* tableName 子表名称
* tags TAG 字段
*/
@InterceptorIgnore(tenantLine = "true")
void createTableWithTags(TdTableDO table);
}

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import jakarta.validation.Valid;
@ -31,7 +31,7 @@ public interface IotDeviceDataService {
* @param deviceId 设备编号
* @return 设备属性最新数据
*/
List<IotDeviceDataDO> getDevicePropertiesLatestData(@Valid IotDeviceDataReqVO deviceId);
List<IotDeviceDataDO> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceId);
/**
* 获得设备属性历史数据
@ -39,5 +39,5 @@ public interface IotDeviceDataService {
* @param deviceDataReqVO 设备属性历史数据 Request VO
* @return 设备属性历史数据
*/
PageResult<Map<String, Object>> getDevicePropertiesHistoryData(@Valid IotDeviceDataReqVO deviceDataReqVO);
PageResult<Map<String, Object>> getHistoryDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO);
}

View File

@ -1,18 +1,20 @@
package cn.iocoder.yudao.module.iot.service.device;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataReqVO;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.deviceData.IotDeviceDataPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
import cn.iocoder.yudao.module.iot.enums.IotConstants;
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
import cn.iocoder.yudao.module.iot.service.tdengine.IotThingModelMessageService;
import cn.iocoder.yudao.module.iot.service.tdengine.TdEngineQueryService;
import cn.iocoder.yudao.module.iot.service.thinkmodelfunction.IotThinkModelFunctionService;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
@ -40,7 +42,7 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
@Resource
private IotThinkModelFunctionService thinkModelFunctionService;
@Resource
private TdEngineQueryService tdEngineQueryService;
private TdEngineDMLMapper tdEngineDMLMapper;
@Resource
private DeviceDataRedisDAO deviceDataRedisDAO;
@ -66,7 +68,7 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
}
@Override
public List<IotDeviceDataDO> getDevicePropertiesLatestData(@Valid IotDeviceDataReqVO deviceDataReqVO) {
public List<IotDeviceDataDO> getLatestDeviceProperties(@Valid IotDeviceDataPageReqVO deviceDataReqVO) {
List<IotDeviceDataDO> list = new ArrayList<>();
// 1. 获取设备信息
IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
@ -106,7 +108,7 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
}
@Override
public PageResult<Map<String, Object>> getDevicePropertiesHistoryData(IotDeviceDataReqVO deviceDataReqVO) {
public PageResult<Map<String, Object>> getHistoryDeviceProperties(IotDeviceDataPageReqVO deviceDataReqVO) {
PageResult<Map<String, Object>> pageResult = new PageResult<>();
// 1. 获取设备信息
IotDeviceDO device = deviceService.getDevice(deviceDataReqVO.getDeviceId());
@ -121,17 +123,17 @@ public class IotDeviceDataServiceImpl implements IotDeviceDataService {
params.put("rows", deviceDataReqVO.getPageSize());
params.put("page", (deviceDataReqVO.getPageNo() - 1) * deviceDataReqVO.getPageSize());
selectVisualDO.setParams(params);
pageResult.setList(tdEngineQueryService.getHistoryData(selectVisualDO));
pageResult.setTotal(tdEngineQueryService.getHistoryCount(selectVisualDO));
pageResult.setList(tdEngineDMLMapper.selectHistoryDataList(selectVisualDO));
pageResult.setTotal(tdEngineDMLMapper.selectHistoryCount(selectVisualDO));
return pageResult;
}
private String getDatabaseName() {
return url.substring(url.lastIndexOf("/") + 1);
return StrUtil.subAfter(url, "/", true);
}
private static String getDeviceTableName(String productKey, String deviceName) {
return String.format("device_%s_%s", productKey.toLowerCase(), deviceName.toLowerCase());
return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey, deviceName);
}
}
}

View File

@ -19,7 +19,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Objects;
import java.util.UUID;
@ -137,10 +139,13 @@ public class IotDeviceServiceImpl implements IotDeviceService {
* @return 生成的 MQTT Password
*/
private String generateMqttPassword() {
// TODO @haohao后续优化在实际应用中建议使用更安全的方法生成 MQTT Password如加密或哈希
return UUID.randomUUID().toString();
SecureRandom secureRandom = new SecureRandom();
byte[] passwordBytes = new byte[32]; // 256 位的随机数
secureRandom.nextBytes(passwordBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(passwordBytes);
}
/**
* 生成唯一的 DeviceName
*
@ -214,30 +219,28 @@ public class IotDeviceServiceImpl implements IotDeviceService {
@Override
public void updateDeviceStatus(IotDeviceStatusUpdateReqVO updateReqVO) {
// 1. 校验存在
// TODO @haohao这里的 iotDeviceDO => device一个是去掉 iot一个是去掉 DO 后缀这样简洁一点
IotDeviceDO iotDeviceDO = validateDeviceExists(updateReqVO.getId());
IotDeviceDO device = validateDeviceExists(updateReqVO.getId());
// 2.1 更新状态和更新时间
IotDeviceDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
// TODO @haohao下面几个状态的处理可以考虑 if else if这样看起来会有层次感哈
// 2.2.1 以前是未激活现在是上线设置设备激活时间
// TODO @haohao这里可以使用 ObjectUtils.equalsAny 类似这种哈
if (Objects.equals(iotDeviceDO.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus())
&& Objects.equals(updateObj.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
updateObj.setActiveTime(LocalDateTime.now());
}
// 2.2.2 如果是上线设置上线时间
if (Objects.equals(updateObj.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
updateObj.setLastOnlineTime(LocalDateTime.now());
}
// 2.2.3 如果是离线设置离线时间
if (Objects.equals(updateObj.getStatus(), IotDeviceStatusEnum.OFFLINE.getStatus())) {
updateObj.setLastOfflineTime(LocalDateTime.now());
IotDeviceDO updateDevice = BeanUtils.toBean(updateReqVO, IotDeviceDO.class);
// 2.2 更新状态相关时间
if (Objects.equals(device.getStatus(), IotDeviceStatusEnum.INACTIVE.getStatus())
&& Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
// 从未激活到在线设置激活时间和最后上线时间
updateDevice.setActiveTime(LocalDateTime.now());
updateDevice.setLastOnlineTime(LocalDateTime.now());
} else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.ONLINE.getStatus())) {
// 如果是上线设置最后上线时间
updateDevice.setLastOnlineTime(LocalDateTime.now());
} else if (Objects.equals(updateDevice.getStatus(), IotDeviceStatusEnum.OFFLINE.getStatus())) {
// 如果是离线设置最后离线时间
updateDevice.setLastOfflineTime(LocalDateTime.now());
}
// 2.3 设置状态更新时间
updateObj.setStatusLastUpdateTime(LocalDateTime.now());
updateDevice.setStatusLastUpdateTime(LocalDateTime.now());
// 2.4 更新到数据库
deviceMapper.updateById(updateObj);
deviceMapper.updateById(updateDevice);
}
@Override

View File

@ -15,4 +15,4 @@ public interface IotSuperTableService {
* 创建超级表数据模型
*/
void createSuperTableDataModel(IotProductDO product, List<IotThinkModelFunctionDO> functionList);
}
}

View File

@ -9,6 +9,8 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@ -26,7 +28,7 @@ import java.util.stream.Collectors;
public class IotSuperTableServiceImpl implements IotSuperTableService {
@Resource
private TdEngineSuperTableService tdEngineSuperTableService;
private TdEngineDDLMapper tdEngineDDLMapper;
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
private String url;
@ -42,9 +44,10 @@ public class IotSuperTableServiceImpl implements IotSuperTableService {
String superTableName = getSuperTableName(product.getDeviceType(), product.getProductKey());
String databaseName = getDatabaseName();
Integer tableExists = tdEngineSuperTableService.checkSuperTableExists(new TdTableDO(databaseName, superTableName));
if (tableExists != null && tableExists > 0) {
List<Map<String, Object>> results = tdEngineDDLMapper.showSuperTables(new TdTableDO(databaseName, superTableName));
int tableExists = results == null || results.isEmpty() ? 0 : results.size();
if (tableExists > 0) {
updateSuperTable(thingModel, product.getDeviceType());
} else {
createSuperTable(thingModel, product.getDeviceType());
@ -76,7 +79,7 @@ public class IotSuperTableServiceImpl implements IotSuperTableService {
String databaseName = getDatabaseName();
// 创建超级表
tdEngineSuperTableService.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
}
/**
@ -98,7 +101,7 @@ public class IotSuperTableServiceImpl implements IotSuperTableService {
* 获取表的字段信息
*/
private List<TdFieldDO> getTableFields(String tableName) {
List<Map<String, Object>> tableDescription = tdEngineSuperTableService.describeSuperTable(new TdTableDO(getDatabaseName(), tableName));
List<Map<String, Object>> tableDescription = tdEngineDDLMapper.describeSuperTable(new TdTableDO(getDatabaseName(), tableName));
if (CollUtil.isEmpty(tableDescription)) {
return Collections.emptyList();
}
@ -127,32 +130,38 @@ public class IotSuperTableServiceImpl implements IotSuperTableService {
// 添加新增字段
if (CollUtil.isNotEmpty(addFields)) {
tdEngineSuperTableService.addColumnsForSuperTable(TdTableDO.builder()
.dataBaseName(databaseName)
.superTableName(tableName)
.columns(addFields)
.build());
for (TdFieldDO addField : addFields) {
tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
.dataBaseName(databaseName)
.superTableName(tableName)
.column(addField)
.build());
}
}
// 删除旧字段
if (CollUtil.isNotEmpty(dropFields)) {
tdEngineSuperTableService.dropColumnsForSuperTable(TdTableDO.builder()
.dataBaseName(databaseName)
.superTableName(tableName)
.columns(dropFields)
.build());
for (TdFieldDO dropField : dropFields) {
tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
.dataBaseName(databaseName)
.superTableName(tableName)
.column(dropField)
.build());
}
}
// 修改字段先删除再添加
if (CollUtil.isNotEmpty(modifyFields)) {
tdEngineSuperTableService.dropColumnsForSuperTable(TdTableDO.builder()
.dataBaseName(databaseName)
.superTableName(tableName)
.columns(modifyFields)
.build());
tdEngineSuperTableService.addColumnsForSuperTable(TdTableDO.builder()
.dataBaseName(databaseName)
.superTableName(tableName)
.columns(addFields)
.build());
for (TdFieldDO modifyField : modifyFields) {
tdEngineDDLMapper.dropColumnForSuperTable(TdTableDO.builder()
.dataBaseName(databaseName)
.superTableName(tableName)
.column(modifyField)
.build());
tdEngineDDLMapper.addColumnForSuperTable(TdTableDO.builder()
.dataBaseName(databaseName)
.superTableName(tableName)
.column(modifyField)
.build());
}
}
}

View File

@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
@ -11,6 +13,9 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
import cn.iocoder.yudao.module.iot.dal.dataobject.thinkmodelfunction.IotThinkModelFunctionDO;
import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
import cn.iocoder.yudao.module.iot.enums.IotConstants;
import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
import cn.iocoder.yudao.module.iot.enums.product.IotProductFunctionTypeEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
@ -30,6 +35,14 @@ import java.util.stream.Collectors;
@Service
public class IotThingModelMessageServiceImpl implements IotThingModelMessageService {
private static final String TAG_NOTE = "TAG";
private static final String NOTE = "note";
private static final String TIME = "time";
private static final String DEVICE_KEY = "device_key";
private static final String DEVICE_NAME = "device_name";
private static final String PRODUCT_KEY = "product_key";
private static final String DEVICE_TYPE = "device_type";
@Value("${spring.datasource.dynamic.datasource.tdengine.url}")
private String url;
@ -38,11 +51,9 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
@Resource
private IotDeviceService iotDeviceService;
@Resource
private TdEngineTableService tdEngineTableService;
private TdEngineDDLMapper tdEngineDDLMapper;
@Resource
private TdEngineSuperTableService tdEngineSuperTableService;
@Resource
private TdEngineDataWriterService tdEngineDataWriterService;
private TdEngineDMLMapper tdEngineDMLMapper;
@Resource
private DeviceDataRedisDAO deviceDataRedisDAO;
@ -51,62 +62,64 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
@Override
@TenantIgnore
public void saveThingModelMessage(IotDeviceDO device, ThingModelMessage thingModelMessage) {
// 判断设备状态如果为未激活状态创建数据表
// 1. 判断设备状态如果为未激活状态创建数据表并更新设备状态
if (IotDeviceStatusEnum.INACTIVE.getStatus().equals(device.getStatus())) {
// 创建设备数据表
createDeviceTable(device.getDeviceType(), device.getProductKey(), device.getDeviceName(), device.getDeviceKey());
// 更新设备状态
// TODO @haohao下面可以考虑链式调用iotDeviceService.updateDeviceStatus(new IotDeviceStatusUpdateReqVO().setid().setstatus())
IotDeviceStatusUpdateReqVO updateReqVO = new IotDeviceStatusUpdateReqVO();
updateReqVO.setId(device.getId());
updateReqVO.setStatus(IotDeviceStatusEnum.ONLINE.getStatus());
iotDeviceService.updateDeviceStatus(updateReqVO);
iotDeviceService.updateDeviceStatus(new IotDeviceStatusUpdateReqVO()
.setId(device.getId())
.setStatus(IotDeviceStatusEnum.ONLINE.getStatus()));
}
// TODO @haohao这个变量可以和 过滤并收集有效的属性字段 那块因为关联度高一点
// 获取设备属性
// 2. 获取设备属性并进行物模型校验过滤非物模型属性
Map<String, Object> params = thingModelMessage.dataToMap();
// 物模型校验过滤非物模型属性
List<IotThinkModelFunctionDO> functionList = iotThinkModelFunctionService
.getThinkModelFunctionListByProductKey(thingModelMessage.getProductKey())
.stream()
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.getType().equals(function.getType()))
.toList();
List<IotThinkModelFunctionDO> functionList = getValidFunctionList(thingModelMessage.getProductKey());
if (functionList.isEmpty()) {
return;
}
// 获取属性标识符集合
// TODO @haohao这个变量可以和 过滤并收集有效的属性字段 那块因为关联度高一点另外可以使用 CollectionUtilsconvertSet
Set<String> propertyIdentifiers = functionList.stream()
.map(IotThinkModelFunctionDO::getIdentifier)
.collect(Collectors.toSet());
// 3. 过滤并收集有效的属性字段缓存设备属性
List<TdFieldDO> schemaFieldValues = filterAndCollectValidFields(params, functionList, device, thingModelMessage.getTime());
if (schemaFieldValues.size() == 1) { // 仅有时间字段无需保存
return;
}
// 4. 构建并保存设备属性数据
tdEngineDMLMapper.insertData(TdTableDO.builder()
.dataBaseName(getDatabaseName())
.tableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()))
.columns(schemaFieldValues)
.build());
}
private List<IotThinkModelFunctionDO> getValidFunctionList(String productKey) {
return iotThinkModelFunctionService
.getThinkModelFunctionListByProductKey(productKey)
.stream()
.filter(function -> IotProductFunctionTypeEnum.PROPERTY.getType().equals(function.getType()))
.toList();
}
private List<TdFieldDO> filterAndCollectValidFields(Map<String, Object> params, List<IotThinkModelFunctionDO> functionList, IotDeviceDO device, Long time) {
// 1. 获取属性标识符集合
Set<String> propertyIdentifiers = CollectionUtils.convertSet(functionList, IotThinkModelFunctionDO::getIdentifier);
// 2. 构建属性标识符和属性的映射
Map<String, IotThinkModelFunctionDO> functionMap = functionList.stream()
.collect(Collectors.toMap(IotThinkModelFunctionDO::getIdentifier, function -> function));
// 过滤并收集有效的属性字段
// 3. 过滤并收集有效的属性字段
List<TdFieldDO> schemaFieldValues = new ArrayList<>();
schemaFieldValues.add(new TdFieldDO("time", thingModelMessage.getTime()));
schemaFieldValues.add(new TdFieldDO(TIME, time));
params.forEach((key, val) -> {
if (propertyIdentifiers.contains(key)) {
schemaFieldValues.add(new TdFieldDO(key.toLowerCase(), val));
// 缓存设备属性
// TODO @haohao这个缓存的写入可以使用的时候 cache 被动读
setDeviceDataCache(device, functionMap.get(key), val, thingModelMessage.getTime());
setDeviceDataCache(device, functionMap.get(key), val, time);
}
});
// TODO @haohao疑问为什么 1 不继续哈
if (schemaFieldValues.size() == 1) {
return;
}
// 构建并保存设备属性数据
tdEngineDataWriterService.insertData(TdTableDO.builder().build()
.setDataBaseName(getDatabaseName())
.setTableName(getDeviceTableName(device.getProductKey(), device.getDeviceName()))
.setColumns(schemaFieldValues));
return schemaFieldValues;
}
/**
@ -132,7 +145,6 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
deviceDataRedisDAO.set(deviceData);
}
// TODO @haohao实现没问题哈这个方法的空行有点多逻辑分块上没这么明显看看能不能改下
/**
* 创建设备数据表
*
@ -142,36 +154,37 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
* @param deviceKey 设备 Key
*/
private void createDeviceTable(Integer deviceType, String productKey, String deviceName, String deviceKey) {
// 1. 获取超级表名和数据库名
String superTableName = getProductPropertySTableName(deviceType, productKey);
String dataBaseName = getDatabaseName();
List<Map<String, Object>> maps = tdEngineSuperTableService.describeSuperTable(new TdTableDO(dataBaseName, superTableName));
// 2. 获取超级表的结构信息
List<Map<String, Object>> maps = tdEngineDDLMapper.describeSuperTable(new TdTableDO(dataBaseName, superTableName));
List<TdFieldDO> tagsFieldValues = new ArrayList<>();
if (maps != null) {
// TODO @haohao一些字符串是不是可以枚举起来哈
// TODO @haohao这种过滤的常用的可以考虑用 CollectionUtils.filterList一些常用的 stream 操作适合封装哈
List<Map<String, Object>> taggedNotesList = maps.stream()
.filter(map -> "TAG".equals(map.get("note")))
.toList();
// 2.1 过滤出 TAG 类型的字段
List<Map<String, Object>> taggedNotesList = CollectionUtils.filterList(maps, map -> TAG_NOTE.equals(map.get(NOTE)));
// 2.2 解析字段信息
tagsFieldValues = FieldParser.parse(taggedNotesList.stream()
.map(map -> List.of(map.get("field"), map.get("type"), map.get("length")))
.collect(Collectors.toList()));
// 2.3 设置 TAG 字段的值
for (TdFieldDO tagsFieldValue : tagsFieldValues) {
switch (tagsFieldValue.getFieldName()) {
case "product_key" -> tagsFieldValue.setFieldValue(productKey);
case "device_key" -> tagsFieldValue.setFieldValue(deviceKey);
case "device_name" -> tagsFieldValue.setFieldValue(deviceName);
case "device_type" -> tagsFieldValue.setFieldValue(deviceType);
case PRODUCT_KEY -> tagsFieldValue.setFieldValue(productKey);
case DEVICE_KEY -> tagsFieldValue.setFieldValue(deviceKey);
case DEVICE_NAME -> tagsFieldValue.setFieldValue(deviceName);
case DEVICE_TYPE -> tagsFieldValue.setFieldValue(deviceType);
}
}
}
// 创建设备数据表
// 3. 创建设备数据表
String tableName = getDeviceTableName(productKey, deviceName);
tdEngineTableService.createTable(TdTableDO.builder().build()
tdEngineDDLMapper.createTable(TdTableDO.builder().build()
.setDataBaseName(dataBaseName)
.setSuperTableName(superTableName)
.setTableName(tableName)
@ -184,8 +197,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
* @return 数据库名称
*/
private String getDatabaseName() {
// TODO @haohao可以使用 StrUtil.subAftetLast 这种方法
return url.substring(url.lastIndexOf("/") + 1);
return StrUtil.subAfter(url, "/", true);
}
/**
@ -198,9 +210,9 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
private static String getProductPropertySTableName(Integer deviceType, String productKey) {
// TODO @haohao枚举下会好点哈
return switch (deviceType) {
case 1 -> String.format("gateway_sub_%s", productKey).toLowerCase();
case 2 -> String.format("gateway_%s", productKey).toLowerCase();
default -> String.format("device_%s", productKey).toLowerCase();
case 1 -> String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
case 2 -> String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
default -> String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
};
}
@ -212,7 +224,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
* @return 设备表名
*/
private static String getDeviceTableName(String productKey, String deviceName) {
return String.format("device_%s_%s", productKey.toLowerCase(), deviceName.toLowerCase());
return String.format(IotConstants.DEVICE_TABLE_NAME_FORMAT, productKey.toLowerCase(), deviceName.toLowerCase());
}
}

View File

@ -1,19 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
/**
* TD 引擎的数据写入 Service 接口
*/
public interface TdEngineDataWriterService {
/**
* 插入数据 - 指定列插入数据
*
* @param table 数据
* dataBaseName 数据库名
* tableName 表名
* columns
*/
void insertData(TdTableDO table);
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDataWriterMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
/**
* TD 引擎的数据写入 Service 实现类
*/
@Service
public class TdEngineDataWriterServiceImpl implements TdEngineDataWriterService {
@Resource
private TdEngineDataWriterMapper tdEngineDataWriterMapper;
@Override
public void insertData(TdTableDO table) {
tdEngineDataWriterMapper.insertData(table);
}
}

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
/**
* TD 引擎的数据库 Service 接口
*/
public interface TdEngineDatabaseService {
/**
* 创建数据库
*
* @param dataBaseName 数据库名称
*/
void createDatabase(String dataBaseName);
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDatabaseMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* TD 引擎的数据库 Service 实现类
*/
@Service
@Slf4j
public class TdEngineDatabaseServiceImpl implements TdEngineDatabaseService {
@Resource
private TdEngineDatabaseMapper tdEngineDatabaseMapper;
@Override
public void createDatabase(String dataBaseName) {
tdEngineDatabaseMapper.createDatabase(dataBaseName);
}
}

View File

@ -1,28 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
import java.util.List;
import java.util.Map;
/**
* TD 引擎的查询 Service 接口
*/
public interface TdEngineQueryService {
/**
* 获取历史数据
*
* @param selectVisualDO 查询条件
* @return 历史数据列表
*/
List<Map<String, Object>> getHistoryData(SelectVisualDO selectVisualDO);
/**
* 获取历史数据条数
*
* @param selectVisualDO 查询条件
* @return 数据条数
*/
Long getHistoryCount(SelectVisualDO selectVisualDO);
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineQueryMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
public class TdEngineQueryServiceImpl implements TdEngineQueryService {
@Resource
private TdEngineQueryMapper tdEngineQueryMapper;
@Override
public List<Map<String, Object>> getHistoryData(SelectVisualDO selectVisualDO) {
return tdEngineQueryMapper.getHistoryData(selectVisualDO);
}
@Override
public Long getHistoryCount(SelectVisualDO selectVisualDO) {
return tdEngineQueryMapper.getHistoryCount(selectVisualDO);
}
}

View File

@ -1,123 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import java.util.List;
import java.util.Map;
/**
* TD 引擎的超级表 Service 接口
*/
public interface TdEngineSuperTableService {
/**
* 创建超级表
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* columns 列信息
* tags 标签信息
*/
void createSuperTable(TdTableDO superTable);
/**
* 查看超级表 - 显示当前数据库下的所有超级表信息
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
*/
List<Map<String, Object>> showSuperTables(TdTableDO superTable);
/**
* 查看超级表 - 获取超级表的结构信息
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
*/
List<Map<String, Object>> describeSuperTable(TdTableDO superTable);
/**
* 修改超级表 - 增加列
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* column 列信息
*/
void addColumnForSuperTable(TdTableDO superTable);
/**
* 修改超级表 - 删除列
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* column 列信息
*/
void dropColumnForSuperTable(TdTableDO superTable);
/**
* 修改超级表 - 修改列宽
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* column 列信息
*/
void modifyColumnWidthForSuperTable(TdTableDO superTable);
/**
* 修改超级表 - 为超级表添加标签
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* tag 标签信息
*/
void addTagForSuperTable(TdTableDO superTable);
/**
* 修改超级表 - 为超级表删除标签
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* tag 标签信息
*/
void dropTagForSuperTable(TdTableDO superTable);
/**
* 检查超级表是否存在
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* @return 超级表数量
*/
Integer checkSuperTableExists(TdTableDO superTable);
/**
* 为超级表添加列
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* columns 列信息
*/
void addColumnsForSuperTable(TdTableDO superTable);
/**
* 为超级表删除列
*
* @param superTable 超级表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* columns 列信息
*/
void dropColumnsForSuperTable(TdTableDO superTable);
}

View File

@ -1,90 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineSuperTableMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* TD 引擎的超级表 Service 实现类
*/
@Slf4j
@Service
public class TdEngineSuperTableServiceImpl implements TdEngineSuperTableService {
@Resource
private TdEngineSuperTableMapper tdEngineSuperTableMapper;
@Override
public void createSuperTable(TdTableDO superTable) {
tdEngineSuperTableMapper.createSuperTable(superTable);
}
@Override
public List<Map<String, Object>> showSuperTables(TdTableDO superTable) {
return tdEngineSuperTableMapper.showSuperTables(superTable);
}
@Override
public List<Map<String, Object>> describeSuperTable(TdTableDO superTable) {
return tdEngineSuperTableMapper.describeSuperTable(superTable);
}
@Override
public void addColumnForSuperTable(TdTableDO superTable) {
tdEngineSuperTableMapper.addColumnForSuperTable(superTable);
}
@Override
public void dropColumnForSuperTable(TdTableDO superTable) {
tdEngineSuperTableMapper.dropColumnForSuperTable(superTable);
}
@Override
public void modifyColumnWidthForSuperTable(TdTableDO superTable) {
tdEngineSuperTableMapper.modifyColumnWidthForSuperTable(superTable);
}
@Override
public void addTagForSuperTable(TdTableDO superTable) {
tdEngineSuperTableMapper.addTagForSuperTable(superTable);
}
@Override
public void dropTagForSuperTable(TdTableDO superTable) {
tdEngineSuperTableMapper.dropTagForSuperTable(superTable);
}
@Override
public Integer checkSuperTableExists(TdTableDO superTable) {
List<Map<String, Object>> results = tdEngineSuperTableMapper.showSuperTables(superTable);
return results == null || results.isEmpty() ? 0 : results.size();
}
@Override
public void addColumnsForSuperTable(TdTableDO superTable) {
for (TdFieldDO column : superTable.getColumns()) {
tdEngineSuperTableMapper.addColumnForSuperTable(TdTableDO.builder()
.dataBaseName(superTable.getDataBaseName())
.superTableName(superTable.getSuperTableName())
.column(column)
.build());
}
}
@Override
public void dropColumnsForSuperTable(TdTableDO superTable) {
for (TdFieldDO column : superTable.getColumns()) {
tdEngineSuperTableMapper.dropColumnForSuperTable(TdTableDO.builder()
.dataBaseName(superTable.getDataBaseName())
.superTableName(superTable.getSuperTableName())
.column(column)
.build());
}
}
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
/**
* TD 引擎的表 Service 接口
*/
public interface TdEngineTableService {
/**
* 创建表 - 创建超级表的子表
*
* @param table 表信息
* dataBaseName 数据库名称
* superTableName 超级表名称
* tableName 子表名称
* tags TAG 字段
*/
void createTable(TdTableDO table);
}

View File

@ -1,23 +0,0 @@
package cn.iocoder.yudao.module.iot.service.tdengine;
import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineTableMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* TD 引擎的表 Service 实现类
*/
@Slf4j
@Service
public class TdEngineTableServiceImpl implements TdEngineTableService {
@Resource
private TdEngineTableMapper tdEngineTableMapper;
@Override
public void createTable(TdTableDO table) {
tdEngineTableMapper.createTable(table);
}
}

View File

@ -2,7 +2,12 @@
<!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.TdEngineSuperTableMapper">
<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper">
<!-- 创建数据库 -->
<update id="createDatabase" parameterType="String">
CREATE DATABASE IF NOT EXISTS ${dataBaseName}
</update>
<!-- 创建超级表 -->
<update id="createSuperTable">
@ -68,4 +73,29 @@
ALTER STABLE ${dataBaseName}.${superTableName} DROP TAG ${tag.fieldName}
</update>
</mapper>
<!-- 创建子表 -->
<update id="createTable">
CREATE TABLE IF NOT EXISTS ${dataBaseName}.${tableName}
USING ${dataBaseName}.${superTableName}
TAGS
<foreach item="item" collection="tags" separator=","
open="(" close=")">
#{item.fieldValue}
</foreach>
</update>
<!-- 创建子表,带有 TAGS -->
<update id="createTableWithTags">
CREATE TABLE IF NOT EXISTS ${dataBaseName}.${tableName}
USING ${dataBaseName}.${superTableName}
<foreach item="item" collection="tags" separator="," open="(" close=")">
#{item.fieldName}
</foreach>
TAGS
<foreach item="item" collection="tags" separator=","
open="(" close=")">
#{item.fieldValue}
</foreach>
</update>
</mapper>

View File

@ -2,7 +2,21 @@
<!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.TdEngineQueryMapper">
<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper">
<!-- 插入数据 -->
<insert id="insertData">
INSERT INTO ${dataBaseName}.${tableName}
<foreach item="item" collection="columns" separator=","
open="(" close=")">
${item.fieldName}
</foreach>
VALUES
<foreach item="item" collection="columns" separator=","
open="(" close=")">
#{item.fieldValue}
</foreach>
</insert>
<!-- 根据时间戳查询数据 -->
<select id="selectByTimestamp" parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectDO"
@ -12,7 +26,7 @@
</select>
<!-- 获取时间范围内的数据条数 -->
<select id="getCountByTimestamp" parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectDO"
<select id="selectCountByTimestamp" parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectDO"
resultType="java.util.Map">
SELECT COUNT(0) AS count
FROM ${dataBaseName}.${tableName}
@ -20,14 +34,14 @@
</select>
<!-- 获取最新数据 -->
<select id="getLastData" resultType="java.util.Map">
<select id="selectOneLastData" resultType="java.util.Map">
SELECT LAST(time), *
FROM ${tableName}
WHERE device_id = #{deviceId}
</select>
<!-- 根据标签获取最新数据 -->
<select id="getLastDataByTags" parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TagsSelectDO"
<select id="selectLastDataListByTags" parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TagsSelectDO"
resultType="Map">
SELECT LAST(*)
FROM ${dataBaseName}.${stableName}
@ -35,7 +49,7 @@
</select>
<!-- 获取历史数据 -->
<select id="getHistoryData" resultType="java.util.Map"
<select id="selectHistoryDataList" resultType="java.util.Map"
parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO">
SELECT ${fieldName} AS data, time
FROM ${dataBaseName}.${tableName}
@ -46,14 +60,14 @@
</select>
<!-- 获取实时数据 -->
<select id="getRealtimeData" resultType="java.util.Map"
<select id="selectRealtimeDataList" resultType="java.util.Map"
parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO">
SELECT ${fieldName}, time
FROM ${dataBaseName}.${tableName}
</select>
<!-- 获取聚合数据 -->
<select id="getAggregateData" resultType="java.util.Map"
<select id="selectAggregateDataList" resultType="java.util.Map"
parameterType="cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.SelectVisualDO">
SELECT ${aggregate}(${fieldName})
FROM ${dataBaseName}.${tableName}
@ -62,11 +76,11 @@
</select>
<!-- 获取历史数据条数 -->
<select id="getHistoryCount" resultType="java.lang.Long">
<select id="selectHistoryCount" resultType="java.lang.Long">
SELECT COUNT(time)
FROM ${dataBaseName}.${tableName}
WHERE time BETWEEN #{startTime} AND #{endTime}
AND ${fieldName} IS NOT NULL
</select>
</mapper>
</mapper>

View File

@ -1,21 +0,0 @@
<?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.TdEngineDataWriterMapper">
<!-- 插入数据 -->
<insert id="insertData">
INSERT INTO ${dataBaseName}.${tableName}
<foreach item="item" collection="columns" separator=","
open="(" close=")">
${item.fieldName}
</foreach>
VALUES
<foreach item="item" collection="columns" separator=","
open="(" close=")">
#{item.fieldValue}
</foreach>
</insert>
</mapper>

View File

@ -1,12 +0,0 @@
<?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.TdEngineDatabaseMapper">
<!-- 创建数据库 -->
<update id="createDatabase" parameterType="String">
CREATE DATABASE IF NOT EXISTS ${dataBaseName}
</update>
</mapper>

View File

@ -1,32 +0,0 @@
<?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.TdEngineTableMapper">
<!-- 创建子表 -->
<update id="createTable">
CREATE TABLE IF NOT EXISTS ${dataBaseName}.${tableName}
USING ${dataBaseName}.${superTableName}
TAGS
<foreach item="item" collection="tags" separator=","
open="(" close=")">
#{item.fieldValue}
</foreach>
</update>
<!-- 创建子表,带有 TAGS -->
<update id="createTableWithTags">
CREATE TABLE IF NOT EXISTS ${dataBaseName}.${tableName}
USING ${dataBaseName}.${superTableName}
<foreach item="item" collection="tags" separator="," open="(" close=")">
#{item.fieldName}
</foreach>
TAGS
<foreach item="item" collection="tags" separator=","
open="(" close=")">
#{item.fieldValue}
</foreach>
</update>
</mapper>