diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java index a8df4909c..02301e2f7 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/exception/enums/GlobalErrorCodeConstants.java @@ -35,7 +35,7 @@ public interface GlobalErrorCodeConstants { // ========== 自定义错误段 ========== ErrorCode REPEATED_REQUESTS = new ErrorCode(900, "重复请求,请稍后重试"); // 重复请求 ErrorCode DEMO_DENY = new ErrorCode(901, "演示模式,禁止写操作"); - ErrorCode DEVICE_CONTACT_MODEL_NOT_EXISTS = new ErrorCode(902, "查询不到该设备"); + ErrorCode DEVICE_CONTACT_MODEL_NOT_EXISTS = new ErrorCode(902, "查询不到该点位"); ErrorCode UNKNOWN = new ErrorCode(999, "未知错误"); diff --git a/yudao-module-common/yudao-module-common-biz/src/main/java/cn/iocoder/yudao/module/common/service/qrcordrecord/QrcodeRecordServiceImpl.java b/yudao-module-common/yudao-module-common-biz/src/main/java/cn/iocoder/yudao/module/common/service/qrcordrecord/QrcodeRecordServiceImpl.java index cd511acf7..5faf7bb29 100644 --- a/yudao-module-common/yudao-module-common-biz/src/main/java/cn/iocoder/yudao/module/common/service/qrcordrecord/QrcodeRecordServiceImpl.java +++ b/yudao-module-common/yudao-module-common-biz/src/main/java/cn/iocoder/yudao/module/common/service/qrcordrecord/QrcodeRecordServiceImpl.java @@ -149,10 +149,11 @@ public class QrcodeRecordServiceImpl implements QrcodeRecordService { ); // 2. 组装二维码内容(统一扫码入口) - String qrContent = "{\"type\":\"" + bizType.getCode() - + "\",\"id\":" + bizId - + (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "") - + "}"; +// String qrContent = "{\"type\":\"" + bizType.getCode() +// + "\",\"id\":" + bizId +// + (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "") +// + "}"; + String qrContent = bizType.getCode() + "-" + bizId; // 3. 生成二维码PNG byte[] pngBytes = buildQrPng(qrContent, width, height); @@ -212,10 +213,13 @@ public class QrcodeRecordServiceImpl implements QrcodeRecordService { .last("limit 1") ); - String barcodeContent = "{\"type\":\"" + bizType.getCode() - + "\",\"id\":" + bizId - + (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "") - + "}"; +// String barcodeContent = "{\"type\":\"" + bizType.getCode() +// + "\",\"id\":" + bizId +// + (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "") +// + "}"; + + String barcodeContent = bizType.getCode() + "-" + bizId; + int barcodeWidth = 600; int barcodeHeight = 180; @@ -509,10 +513,13 @@ public class QrcodeRecordServiceImpl implements QrcodeRecordService { ); // 2. 组装内容 - String content = "{\"type\":\"" + bizType.getCode() - + "\",\"id\":" + bizId - + (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "") - + "}"; +// String content = "{\"type\":\"" + bizType.getCode() +// + "\",\"id\":" + bizId +// + (StrUtil.isNotBlank(bizCode) ? ",\"code\":\"" + bizCode + "\"" : "") +// + "}"; + + String content = bizType.getCode() + "-" + bizId; + // 3. 生成图片(按类型) byte[] pngBytes; diff --git a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index 180bdae78..bbc66ba99 100644 --- a/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -84,6 +84,7 @@ public interface ErrorCodeConstants { ErrorCode TABLE_CREATION_FAILED = new ErrorCode(1_004_000_008, "TDengine 表创建失败"); ErrorCode COLOUMN_CREATION_FAILED = new ErrorCode(1_004_000_008, "TDengine 列创建失败"); + ErrorCode COLUMN_RENAME_FAILED = new ErrorCode(1_004_000_008, "列名修改失败"); } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java index e54959650..8c939f45e 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/DeviceServiceImpl.java @@ -152,11 +152,13 @@ public class DeviceServiceImpl implements DeviceService { device.setProtocol(deviceModelDO != null ? deviceModelDO.getProtocol() : ""); //租户ID device.setTenantId("1"); + device.setProtocol(StringUtils.isBlank(device.getProtocol()) || device.getProtocol() == null ? "MQTT":device.getProtocol()); + deviceMapper.insert(device); - if (createReqVO.getDeviceModelId()!=null){ - insertTemplatePoint(createReqVO, device); - } + + insertTemplatePoint(createReqVO, device); + return device; } @@ -166,21 +168,21 @@ public class DeviceServiceImpl implements DeviceService { lambdaQueryWrapper.eq(DeviceModelAttributeDO::getDeviceModelId, createReqVO.getDeviceModelId()).orderByDesc(DeviceModelAttributeDO::getId); List deviceModelAttributeDOS = deviceModelAttributeMapper.selectList(lambdaQueryWrapper); - if (deviceModelAttributeDOS.isEmpty()) { - throw exception(DEVICE_MODEL_ATTRIBUTE_NOT_EXISTS); - } - List contactModelList = new ArrayList<>(); - for (DeviceModelAttributeDO attributeDO : deviceModelAttributeDOS) { - DeviceContactModelDO contactModel = new DeviceContactModelDO(); - BeanUtils.copyProperties(attributeDO, contactModel); - contactModel.setId(null); - contactModel.setDeviceId(device.getId()); - contactModel.setCreateTime(LocalDateTime.now()); - contactModel.setUpdateTime(LocalDateTime.now()); - contactModelList.add(contactModel); + if (!deviceModelAttributeDOS.isEmpty()) { + for (DeviceModelAttributeDO attributeDO : deviceModelAttributeDOS) { + DeviceContactModelDO contactModel = new DeviceContactModelDO(); + BeanUtils.copyProperties(attributeDO, contactModel); + contactModel.setId(null); + contactModel.setDeviceId(device.getId()); + contactModel.setCreateTime(LocalDateTime.now()); + contactModel.setUpdateTime(LocalDateTime.now()); + contactModelList.add(contactModel); + } + deviceContactModelMapper.insertBatch(contactModelList); } - deviceContactModelMapper.insertBatch(contactModelList); + + //创建时序数据库 // createTDengine(device.getId()); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java index bb6532f15..8ebade904 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/TDengineService.java @@ -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> columns = jdbcTemplate.queryForList(sql); + log.info("查询到 {} 列", columns.size()); + + // 打印所有列 + for (int i = 0; i < columns.size(); i++) { + Map 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 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 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 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 diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelServiceImpl.java index f6b2f8d80..9eb79c2f2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/devicecontactmodel/DeviceContactModelServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.devicecontactmodel; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.iot.dal.dataobject.devicecontactmodel.DeviceContactModelDO; import cn.iocoder.yudao.module.iot.dal.dataobject.devicemodelattribute.DeviceModelAttributeDO; import cn.iocoder.yudao.module.iot.dal.mysql.deviceattributetype.DeviceAttributeTypeMapper; @@ -7,6 +8,8 @@ import cn.iocoder.yudao.module.iot.service.device.TDengineService; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.springframework.stereotype.Service; import javax.annotation.Resource; +import javax.validation.constraints.NotEmpty; + import org.springframework.validation.annotation.Validated; import cn.iocoder.yudao.module.iot.controller.admin.devicecontactmodel.vo.*; @@ -15,7 +18,10 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.iot.dal.mysql.devicecontactmodel.DeviceContactModelMapper; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.concurrent.ThreadLocalRandom; import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.DEVICE_CONTACT_MODEL_NOT_EXISTS; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -52,6 +58,11 @@ public class DeviceContactModelServiceImpl implements DeviceContactModelService throw exception(DEVICE_MODEL_POINT_CODE_EXISTS); } + // 2. 检查attributeCode是否符合TDengine列名规则 + if (StrUtil.isNotBlank(createReqVO.getAttributeCode())) { + tDengineService.validateColumnName(createReqVO.getAttributeCode()); + } + // 插入 DeviceContactModelDO deviceContactModel = BeanUtils.toBean(createReqVO, DeviceContactModelDO.class); // deviceContactModel.setTypeName(deviceAttributeTypeMapper.selectById(createReqVO.getAttributeCode()).getName()); @@ -78,11 +89,45 @@ public class DeviceContactModelServiceImpl implements DeviceContactModelService for (Long id : ids) { // 校验存在 validateDeviceContactModelExists(id); + + DeviceContactModelDO deviceContactModelDO = deviceContactModelMapper.selectById(id); + if (deviceContactModelDO == null) { + continue; + } + + Long deviceId = deviceContactModelDO.getDeviceId(); + String oldColumnName = deviceContactModelDO.getAttributeCode(); + + if (deviceId != null && StrUtil.isNotBlank(oldColumnName)) { + // 生成带序号的历史列名 + String newColumnName = generateHistoryColumnName(deviceId, oldColumnName); + + // 重命名列 + tDengineService.renameTDColumn(deviceId, oldColumnName, newColumnName); + } + // 删除 deviceContactModelMapper.deleteById(id); } } + /** + * 生成带序号的历史列名 + * 格式: DEL_{原列名}_{日期}_{序号} + * 示例: DEL_temperature_20260327_1 + */ + private String generateHistoryColumnName(Long deviceId, String originalName) { + String date = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")); + + // 获取当前序号 + int sequence = tDengineService.calculateSequence(deviceId, originalName, date); + + return String.format("DEL_%s_%s_%d", originalName, date, sequence); + } + + + + private void validateDeviceContactModelExists(Long id) { if (deviceContactModelMapper.selectById(id) == null) { throw exception(DEVICE_CONTACT_MODEL_NOT_EXISTS);