feat:修改采集设备点位删除逻辑

main
HuangHuiKang 2 weeks ago
parent 0bbac34d67
commit 1b90a076fe

@ -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, "未知错误");

@ -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;

@ -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, "列名修改失败");
}

@ -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<DeviceModelAttributeDO> deviceModelAttributeDOS = deviceModelAttributeMapper.selectList(lambdaQueryWrapper);
if (deviceModelAttributeDOS.isEmpty()) {
throw exception(DEVICE_MODEL_ATTRIBUTE_NOT_EXISTS);
}
List<DeviceContactModelDO> 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());

@ -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. 构建列SQLTDengine必须有 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

@ -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);

Loading…
Cancel
Save