|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
package cn.iocoder.yudao.module.iot.service.device;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.pojo.DeviceEdgeData;
|
|
|
|
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|
|
|
|
import cn.iocoder.yudao.module.iot.controller.admin.device.enums.JavaToTdengineTypeEnum;
|
|
|
|
|
@ -38,8 +39,7 @@ import java.util.*;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
|
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
|
|
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.COLOUMN_CREATION_FAILED;
|
|
|
|
|
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.TABLE_CREATION_FAILED;
|
|
|
|
|
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
|
|
|
|
|
|
|
|
|
|
@Service
|
|
|
|
|
@Slf4j
|
|
|
|
|
@ -751,11 +751,9 @@ public class TDengineService {
|
|
|
|
|
|
|
|
|
|
// 1. 数据库名
|
|
|
|
|
String dbName = "besure_server";
|
|
|
|
|
|
|
|
|
|
// 2. 表名
|
|
|
|
|
String tableName = "d_" + deviceId;
|
|
|
|
|
|
|
|
|
|
// 3. 确保数据库存在
|
|
|
|
|
// 2. 确保数据库存在
|
|
|
|
|
try {
|
|
|
|
|
String createDbSql = "CREATE DATABASE IF NOT EXISTS " + dbName;
|
|
|
|
|
jdbcTemplate.execute(createDbSql);
|
|
|
|
|
@ -765,40 +763,50 @@ public class TDengineService {
|
|
|
|
|
throw exception(TABLE_CREATION_FAILED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 构建列SQL,TDengine必须有 ts
|
|
|
|
|
// 3. 构建列SQL
|
|
|
|
|
StringBuilder columnsSql = new StringBuilder("ts TIMESTAMP");
|
|
|
|
|
|
|
|
|
|
// 5. 遍历 contactModelList 构建列
|
|
|
|
|
// 4. 生成唯一列名
|
|
|
|
|
String uniqueColName = "val_" + deviceId + "_" + System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
// 5. 如果 contactModelList 不为空,添加对应列
|
|
|
|
|
if (contactModelList != null && !contactModelList.isEmpty()) {
|
|
|
|
|
for (DeviceContactModelDO contact : contactModelList) {
|
|
|
|
|
String attributeCode = contact.getAttributeCode();
|
|
|
|
|
String dataType = contact.getDataType();
|
|
|
|
|
|
|
|
|
|
if (attributeCode == null || dataType == null) {
|
|
|
|
|
if (StrUtil.isBlank(attributeCode) || StrUtil.isBlank(dataType)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用枚举获取 TDengine 类型
|
|
|
|
|
String tdType = JavaToTdengineTypeEnum.getTdTypeByJavaType(dataType);
|
|
|
|
|
if (tdType == null) {
|
|
|
|
|
tdType = "DOUBLE"; // 默认使用 DOUBLE
|
|
|
|
|
tdType = "DOUBLE";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 拼接列
|
|
|
|
|
columnsSql.append(", ").append(attributeCode).append(" ").append(tdType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确保至少有一个数据列
|
|
|
|
|
if (columnsSql.toString().equals("ts TIMESTAMP")) {
|
|
|
|
|
columnsSql.append(", ").append(uniqueColName).append(" DOUBLE"); // 使用唯一列名
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 6. contactModelList 为空时,添加默认数据列
|
|
|
|
|
columnsSql.append(", ").append(uniqueColName).append(" DOUBLE"); // 使用唯一列名
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 6. 构建完整 SQL
|
|
|
|
|
String createTableSql = "CREATE TABLE IF NOT EXISTS " + dbName + "." + tableName + " ("
|
|
|
|
|
+ columnsSql.toString() + ")";
|
|
|
|
|
// 7. 构建完整 SQL
|
|
|
|
|
String fullTableName = dbName + "." + tableName;
|
|
|
|
|
String createTableSql = "CREATE TABLE IF NOT EXISTS " + fullTableName +
|
|
|
|
|
" (" + columnsSql.toString() + ")";
|
|
|
|
|
|
|
|
|
|
// 7. 执行创建表
|
|
|
|
|
// 8. 执行创建表
|
|
|
|
|
try {
|
|
|
|
|
jdbcTemplate.execute(createTableSql);
|
|
|
|
|
log.info("TDengine 表创建成功: {}.{}", dbName, tableName);
|
|
|
|
|
log.info("TDengine 表创建成功: {}", fullTableName);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("TDengine 表创建失败: {}.{}", dbName, tableName, e);
|
|
|
|
|
log.error("TDengine 表创建失败: {}", fullTableName, e);
|
|
|
|
|
throw exception(TABLE_CREATION_FAILED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -854,6 +862,8 @@ public class TDengineService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 插入数据
|
|
|
|
|
* @param deviceId
|
|
|
|
|
@ -976,6 +986,284 @@ public class TDengineService {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 修改TDengine表列名
|
|
|
|
|
* @param deviceId 设备ID
|
|
|
|
|
* @param oldColumnName 原列名
|
|
|
|
|
* @param newColumnName 新列名
|
|
|
|
|
*/
|
|
|
|
|
@DS("tdengine")
|
|
|
|
|
public void renameTDColumn(Long deviceId, String oldColumnName, String newColumnName) {
|
|
|
|
|
if (deviceId == null || StrUtil.isBlank(oldColumnName) || StrUtil.isBlank(newColumnName)) {
|
|
|
|
|
log.warn("修改列名参数错误: deviceId={}, oldColumnName={}, newColumnName={}",
|
|
|
|
|
deviceId, oldColumnName, newColumnName);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 1. 验证原列是否存在
|
|
|
|
|
if (!columnExists(deviceId, oldColumnName)) {
|
|
|
|
|
log.warn("原列不存在,无法修改: deviceId={}, column={}", deviceId, oldColumnName);
|
|
|
|
|
throw exception(COLUMN_RENAME_FAILED, "原列 '" + oldColumnName + "' 不存在");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 验证新列名是否已存在
|
|
|
|
|
if (columnExists(deviceId, newColumnName)) {
|
|
|
|
|
log.warn("新列名已存在: deviceId={}, column={}", deviceId, newColumnName);
|
|
|
|
|
throw exception(COLUMN_RENAME_FAILED, "新列名 '" + newColumnName + "' 已存在");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 验证新列名是否为保留关键字
|
|
|
|
|
if (isReservedKeyword(newColumnName)) {
|
|
|
|
|
log.warn("新列名是保留关键字: {}", newColumnName);
|
|
|
|
|
throw exception(COLUMN_RENAME_FAILED, "新列名 '" + newColumnName + "' 是保留关键字");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 表名
|
|
|
|
|
String tableName = "besure_server.d_" + deviceId;
|
|
|
|
|
|
|
|
|
|
// ALTER TABLE RENAME COLUMN SQL
|
|
|
|
|
String alterSql = "ALTER TABLE " + tableName
|
|
|
|
|
+ " RENAME COLUMN " + oldColumnName + " " + newColumnName;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
jdbcTemplate.execute(alterSql);
|
|
|
|
|
log.info("TDengine 表修改列名成功: table={}, oldColumn={}, newColumn={}",
|
|
|
|
|
tableName, oldColumnName, newColumnName);
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 处理特定错误
|
|
|
|
|
String errorMsg = e.getMessage();
|
|
|
|
|
|
|
|
|
|
if (errorMsg != null) {
|
|
|
|
|
if (errorMsg.contains("column not exist") || errorMsg.contains("column does not exist")) {
|
|
|
|
|
log.warn("原列不存在,无法修改: table={}, column={}", tableName, oldColumnName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (errorMsg.contains("duplicate column") || errorMsg.contains("column already exists")) {
|
|
|
|
|
log.warn("新列名已存在: table={}, newColumn={}", tableName, newColumnName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (errorMsg.contains("reserved keyword") || errorMsg.toLowerCase().contains("syntax")) {
|
|
|
|
|
log.warn("新列名包含保留关键字: {}", newColumnName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.error("TDengine 表修改列名失败: table={}, oldColumn={}, newColumn={}",
|
|
|
|
|
tableName, oldColumnName, newColumnName, e);
|
|
|
|
|
throw exception(COLUMN_RENAME_FAILED);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算序号(线程安全版本)- 带详细日志
|
|
|
|
|
*/
|
|
|
|
|
@DS("tdengine")
|
|
|
|
|
public synchronized int calculateSequence(Long deviceId, String originalName, String date) {
|
|
|
|
|
log.info("=== 开始计算序号 ===");
|
|
|
|
|
log.info("参数: deviceId={}, originalName={}, date={}", deviceId, originalName, date);
|
|
|
|
|
|
|
|
|
|
String tableName = "besure_server.d_" + deviceId;
|
|
|
|
|
log.info("表名: {}", tableName);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 检查表是否存在
|
|
|
|
|
boolean exists = tableExists(deviceId);
|
|
|
|
|
log.info("表是否存在: {}", exists);
|
|
|
|
|
|
|
|
|
|
if (!exists) {
|
|
|
|
|
log.info("表不存在,返回 1");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查询所有列
|
|
|
|
|
String sql = "DESC " + tableName;
|
|
|
|
|
log.info("执行SQL: {}", sql);
|
|
|
|
|
|
|
|
|
|
List<Map<String, Object>> columns = jdbcTemplate.queryForList(sql);
|
|
|
|
|
log.info("查询到 {} 列", columns.size());
|
|
|
|
|
|
|
|
|
|
// 打印所有列
|
|
|
|
|
for (int i = 0; i < columns.size(); i++) {
|
|
|
|
|
Map<String, Object> column = columns.get(i);
|
|
|
|
|
String colName = (String) column.get("Field");
|
|
|
|
|
String colType = (String) column.get("Type");
|
|
|
|
|
log.info("列[{}]: {} ({})", i, colName, colType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int maxSequence = 0;
|
|
|
|
|
String prefix = "del_" + originalName + "_" + date + "_";
|
|
|
|
|
log.info("查找前缀: {}", prefix);
|
|
|
|
|
log.info("前缀长度: {}", prefix.length());
|
|
|
|
|
|
|
|
|
|
for (Map<String, Object> column : columns) {
|
|
|
|
|
String colName = (String) column.get("Field");
|
|
|
|
|
log.info("检查列: {}", colName);
|
|
|
|
|
|
|
|
|
|
if (colName != null) {
|
|
|
|
|
log.info("列长度: {}, 是否以前缀开头: {}",
|
|
|
|
|
colName.length(), colName.startsWith(prefix));
|
|
|
|
|
|
|
|
|
|
if (colName.startsWith(prefix)) {
|
|
|
|
|
String seqStr = colName.substring(prefix.length());
|
|
|
|
|
log.info("匹配成功! 提取序号字符串: '{}'", seqStr);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
int seq = Integer.parseInt(seqStr);
|
|
|
|
|
log.info("转换为数字: {}", seq);
|
|
|
|
|
if (seq > maxSequence) {
|
|
|
|
|
maxSequence = seq;
|
|
|
|
|
log.info("更新最大序号为: {}", maxSequence);
|
|
|
|
|
}
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
log.warn("序号不是数字: '{}'", seqStr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int result = maxSequence + 1;
|
|
|
|
|
log.info("最终结果: {} + 1 = {}", maxSequence, result);
|
|
|
|
|
log.info("=== 计算序号结束 ===");
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("计算历史列序号失败,返回1", e);
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查表是否存在(修复版)
|
|
|
|
|
*/
|
|
|
|
|
@DS("tdengine")
|
|
|
|
|
private boolean tableExists(Long deviceId) {
|
|
|
|
|
String tableName = "besure_server.d_" + deviceId;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 方法1:直接尝试查询
|
|
|
|
|
String sql = "SELECT 1 FROM " + tableName + " LIMIT 0";
|
|
|
|
|
jdbcTemplate.execute(sql);
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 如果错误包含"table not exist",说明表不存在
|
|
|
|
|
String errorMsg = e.getMessage();
|
|
|
|
|
if (errorMsg != null && (
|
|
|
|
|
errorMsg.contains("table not exist") ||
|
|
|
|
|
errorMsg.contains("table does not exist") ||
|
|
|
|
|
errorMsg.contains("unknown table") ||
|
|
|
|
|
errorMsg.contains("Table not found"))) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 其他错误,可能是权限问题等
|
|
|
|
|
log.warn("检查表是否存在时出错: table={}, error={}", tableName, errorMsg);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查列是否存在(增强版)
|
|
|
|
|
*/
|
|
|
|
|
@DS("tdengine")
|
|
|
|
|
private boolean columnExists(Long deviceId, String columnName) {
|
|
|
|
|
if (deviceId == null || StrUtil.isBlank(columnName)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String tableName = "besure_server.d_" + deviceId;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 方法1:直接尝试查询该列
|
|
|
|
|
String testSql = "SELECT " + columnName + " FROM " + tableName + " LIMIT 0";
|
|
|
|
|
jdbcTemplate.execute(testSql);
|
|
|
|
|
return true; // 执行成功,说明列存在
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
String errorMsg = e.getMessage();
|
|
|
|
|
|
|
|
|
|
// 判断是否是"列不存在"的错误
|
|
|
|
|
if (errorMsg != null && (
|
|
|
|
|
errorMsg.contains("column not exist") ||
|
|
|
|
|
errorMsg.contains("column does not exist") ||
|
|
|
|
|
errorMsg.contains("Invalid column") ||
|
|
|
|
|
errorMsg.contains("column not found") ||
|
|
|
|
|
errorMsg.contains("unknown column"))) {
|
|
|
|
|
return false; // 列不存在
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 其他错误(如表不存在),记录日志
|
|
|
|
|
log.warn("检查列是否存在时发生未知错误: table={}, column={}, error={}",
|
|
|
|
|
tableName, columnName, errorMsg);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查是否为保留关键字
|
|
|
|
|
*/
|
|
|
|
|
private boolean isReservedKeyword(String columnName) {
|
|
|
|
|
Set<String> reservedWords = new HashSet<>(Arrays.asList(
|
|
|
|
|
"value", "timestamp", "current", "database", "table",
|
|
|
|
|
"user", "password", "select", "insert", "update", "delete",
|
|
|
|
|
"create", "drop", "alter", "show", "describe", "use", "ts"
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
return reservedWords.contains(columnName.toLowerCase());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证列名是否符合TDengine规则
|
|
|
|
|
*/
|
|
|
|
|
public void validateColumnName(String columnName) {
|
|
|
|
|
if (StrUtil.isBlank(columnName)) {
|
|
|
|
|
throw exception(DEVICE_MODEL_POINT_CODE_EXISTS, "列名不能为空");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TDengine 列名规则验证
|
|
|
|
|
// 1. 不能是保留关键字
|
|
|
|
|
Set<String> reservedKeywords = new HashSet<>(Arrays.asList(
|
|
|
|
|
"value", "timestamp", "current", "database", "table", "user", "password",
|
|
|
|
|
"select", "insert", "update", "delete", "create", "drop", "alter",
|
|
|
|
|
"show", "describe", "use", "ts", "now", "current_timestamp"
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
if (reservedKeywords.contains(columnName.toLowerCase())) {
|
|
|
|
|
throw exception(DEVICE_MODEL_POINT_CODE_EXISTS,
|
|
|
|
|
"列名不能使用保留关键字: " + columnName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. 必须以字母开头
|
|
|
|
|
if (!Character.isLetter(columnName.charAt(0))) {
|
|
|
|
|
throw exception(DEVICE_MODEL_POINT_CODE_EXISTS,
|
|
|
|
|
"列名必须以字母开头: " + columnName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 只能包含字母、数字、下划线
|
|
|
|
|
if (!columnName.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) {
|
|
|
|
|
throw exception(DEVICE_MODEL_POINT_CODE_EXISTS,
|
|
|
|
|
"列名只能包含字母、数字和下划线: " + columnName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 长度限制(根据TDengine文档)
|
|
|
|
|
if (columnName.length() > 64) {
|
|
|
|
|
throw exception(DEVICE_MODEL_POINT_CODE_EXISTS,
|
|
|
|
|
"列名长度不能超过64个字符: " + columnName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 5. 不能以下划线开头(虽然不是强制,但避免潜在问题)
|
|
|
|
|
if (columnName.startsWith("_")) {
|
|
|
|
|
throw exception(DEVICE_MODEL_POINT_CODE_EXISTS,
|
|
|
|
|
"列名不能以下划线开头: " + columnName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据deviceId批量查询最新时间
|
|
|
|
|
* @param deviceIds
|
|
|
|
|
|