|
|
@@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|
9
|
9
|
import org.springframework.stereotype.Service;
|
|
10
|
10
|
|
|
11
|
11
|
import java.nio.charset.StandardCharsets;
|
|
|
12
|
+import java.math.BigDecimal;
|
|
12
|
13
|
import java.sql.*;
|
|
13
|
14
|
import java.time.ZoneOffset;
|
|
14
|
15
|
import java.util.*;
|
|
|
@@ -48,8 +49,11 @@ public class TdEngineService {
|
|
48
|
49
|
// 每批次最大列数(防止 TDengine 行超限)
|
|
49
|
50
|
private static final int MAX_COLUMNS_PER_INSERT = 100;
|
|
50
|
51
|
|
|
51
|
|
- // 默认 VARCHAR 字段长度限制
|
|
52
|
|
- private static final int DEFAULT_VARCHAR_LENGTH = 128;
|
|
|
52
|
+ // 默认 VARCHAR 字段长度限制(用于未提供实际长度时的兜底)
|
|
|
53
|
+ private static final int DEFAULT_VARCHAR_LENGTH = 16;
|
|
|
54
|
+
|
|
|
55
|
+ // TDengine 数据保留天数(超过此天数的数据自动删除)
|
|
|
56
|
+ private static final int DATA_RETENTION_DAYS = 180;
|
|
53
|
57
|
|
|
54
|
58
|
// 东八区时区偏移(避免重复创建)
|
|
55
|
59
|
private static final ZoneOffset ZONE_OFFSET_8 = ZoneOffset.of("+8");
|
|
|
@@ -126,7 +130,7 @@ public class TdEngineService {
|
|
126
|
130
|
if (name == null || name.isEmpty()) {
|
|
127
|
131
|
return "`unknown`";
|
|
128
|
132
|
}
|
|
129
|
|
- return "`" + name.replace("`", "") + "`";
|
|
|
133
|
+ return "`" + name.replaceAll("`", "") + "`";
|
|
130
|
134
|
}
|
|
131
|
135
|
|
|
132
|
136
|
private boolean isValidFieldName(String name) {
|
|
|
@@ -228,17 +232,17 @@ public class TdEngineService {
|
|
228
|
232
|
// ==========================================
|
|
229
|
233
|
// 初始化表结构(按列存储,无 ext_data)
|
|
230
|
234
|
// ==========================================
|
|
231
|
|
- private void initTableStructure(String dbName, String superTableName, String table)
|
|
|
235
|
+ private void initTableStructure(String dbName, String superTableName, String table, String controllerId)
|
|
232
|
236
|
throws SQLException {
|
|
233
|
237
|
|
|
234
|
238
|
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
|
235
|
239
|
stmt.setQueryTimeout(10);
|
|
236
|
240
|
|
|
237
|
|
- stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + wrapName(dbName));
|
|
|
241
|
+ stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS " + wrapName(dbName) + " KEEP " + DATA_RETENTION_DAYS);
|
|
238
|
242
|
|
|
239
|
|
- // 创建超级表:固定 ts + surfacename,无 ext_data 列
|
|
|
243
|
+ // 创建超级表:固定 ts + controller_id 列(TDengine 要求至少 2 列)
|
|
240
|
244
|
String stableSql = String.format(
|
|
241
|
|
- "CREATE STABLE IF NOT EXISTS %s.%s (ts TIMESTAMP, surfacename VARCHAR(64)) TAGS (location VARCHAR(255))",
|
|
|
245
|
+ "CREATE STABLE IF NOT EXISTS %s.%s (ts TIMESTAMP, controller_id BIGINT) TAGS (location VARCHAR(64))",
|
|
242
|
246
|
wrapName(dbName),
|
|
243
|
247
|
wrapName(superTableName)
|
|
244
|
248
|
);
|
|
|
@@ -248,17 +252,17 @@ public class TdEngineService {
|
|
248
|
252
|
String key = getStableKey(dbName, superTableName);
|
|
249
|
253
|
Set<String> fixedCols = new HashSet<>();
|
|
250
|
254
|
fixedCols.add("ts");
|
|
251
|
|
- fixedCols.add("surfacename");
|
|
|
255
|
+ fixedCols.add("controller_id");
|
|
252
|
256
|
stableColumnCache.put(key, fixedCols);
|
|
253
|
257
|
|
|
254
|
|
- // 创建子表
|
|
|
258
|
+ // 创建子表,使用 controllerId 作为 tag
|
|
255
|
259
|
String tableSql = String.format(
|
|
256
|
260
|
"CREATE TABLE IF NOT EXISTS %s.%s USING %s.%s TAGS ('%s')",
|
|
257
|
261
|
wrapName(dbName),
|
|
258
|
262
|
wrapName(table),
|
|
259
|
263
|
wrapName(dbName),
|
|
260
|
264
|
wrapName(superTableName),
|
|
261
|
|
- escapeValue(superTableName)
|
|
|
265
|
+ escapeValue(controllerId)
|
|
262
|
266
|
);
|
|
263
|
267
|
stmt.executeUpdate(tableSql);
|
|
264
|
268
|
}
|
|
|
@@ -267,26 +271,26 @@ public class TdEngineService {
|
|
267
|
271
|
// ==========================================
|
|
268
|
272
|
// 批量插入(按列存储)
|
|
269
|
273
|
// ==========================================
|
|
270
|
|
- public void insertBatch(String dbName, String superTable, String table, List<Map<String, Object>> dataList)
|
|
|
274
|
+ public void insertBatch(String dbName, String superTable, String controllerId, List<Map<String, Object>> dataList)
|
|
271
|
275
|
throws SQLException {
|
|
272
|
276
|
|
|
273
|
277
|
if (dataList == null || dataList.isEmpty()) {
|
|
274
|
|
- log.debug("insertBatch 收到空数据,直接返回 | dbName={}, table={}", dbName, table);
|
|
|
278
|
+ log.debug("insertBatch 收到空数据,直接返回 | dbName={}, table={}", dbName, controllerId);
|
|
275
|
279
|
return;
|
|
276
|
280
|
}
|
|
277
|
281
|
|
|
278
|
282
|
Map<String, String> columnTypes = collectColumnTypes(dataList);
|
|
279
|
283
|
|
|
280
|
284
|
// 所有数据都走拆分逻辑:按首字符 UTF-8 值模 10 分组到不同的超级表
|
|
281
|
|
- splitAndInsertToMultipleStables(dbName, superTable, table, dataList, columnTypes);
|
|
|
285
|
+ splitAndInsertToMultipleStables(dbName, superTable, controllerId, dataList, columnTypes);
|
|
282
|
286
|
|
|
283
|
|
- log.info("批量写入完成: {} | 条数: {}", table, dataList.size());
|
|
|
287
|
+ log.info("批量写入完成: {} | 条数: {}", controllerId, dataList.size());
|
|
284
|
288
|
}
|
|
285
|
289
|
|
|
286
|
290
|
/**
|
|
287
|
291
|
* 根据列 key 首字符的 UTF-8 值拆分到多个超级表
|
|
288
|
292
|
*/
|
|
289
|
|
- private void splitAndInsertToMultipleStables(String dbName, String stableName, String tableName,
|
|
|
293
|
+ private void splitAndInsertToMultipleStables(String dbName, String stableName, String controllerId,
|
|
290
|
294
|
List<Map<String, Object>> dataList,
|
|
291
|
295
|
Map<String, String> columnTypes) throws SQLException {
|
|
292
|
296
|
|
|
|
@@ -304,7 +308,7 @@ public class TdEngineService {
|
|
304
|
308
|
int groupId = group.getKey();
|
|
305
|
309
|
// 超级表名 = superTableName + groupId (例如 charge + 3 = charge3)
|
|
306
|
310
|
String newStableName = stableName+"_"+groupId;
|
|
307
|
|
- String newTableName = stableName+"_"+groupId+"_"+tableName;
|
|
|
311
|
+ String newTableName = stableName+"_"+groupId+"_"+controllerId;
|
|
308
|
312
|
|
|
309
|
313
|
// 筛选出该组列的数据
|
|
310
|
314
|
List<Map<String, Object>> filteredData = filterDataByColumnGroup(dataList, group.getValue().keySet());
|
|
|
@@ -313,7 +317,7 @@ public class TdEngineService {
|
|
313
|
317
|
int batchSize = DEFAULT_BATCH_SIZE;
|
|
314
|
318
|
for (int i = 0; i < filteredData.size(); i += batchSize) {
|
|
315
|
319
|
List<Map<String, Object>> batch = filteredData.subList(i, Math.min(i + batchSize, filteredData.size()));
|
|
316
|
|
- insertBatchInternal(dbName, newStableName, newTableName, batch);
|
|
|
320
|
+ insertBatchInternal(dbName, newStableName, newTableName, controllerId, batch);
|
|
317
|
321
|
}
|
|
318
|
322
|
log.info("分组插入完成: stable={}, table={}, 列数={}, 数据条数={}",
|
|
319
|
323
|
newStableName, newTableName, group.getValue().size(), filteredData.size());
|
|
|
@@ -374,11 +378,36 @@ public class TdEngineService {
|
|
374
|
378
|
}
|
|
375
|
379
|
|
|
376
|
380
|
/**
|
|
|
381
|
+ * 收集数据中所有动态列的最大字符串长度(用于动态 VARCHAR 长度)
|
|
|
382
|
+ */
|
|
|
383
|
+ private Map<String, Integer> collectColumnMaxLengths(List<Map<String, Object>> dataList) {
|
|
|
384
|
+ Map<String, Integer> maxLengths = new HashMap<>();
|
|
|
385
|
+ for (Map<String, Object> data : dataList) {
|
|
|
386
|
+ if (data == null) {
|
|
|
387
|
+ continue;
|
|
|
388
|
+ }
|
|
|
389
|
+ for (Map.Entry<String, Object> entry : data.entrySet()) {
|
|
|
390
|
+ String key = entry.getKey();
|
|
|
391
|
+ if (!isValidFieldName(key) || isReservedColumn(key)) {
|
|
|
392
|
+ continue;
|
|
|
393
|
+ }
|
|
|
394
|
+ Object value = entry.getValue();
|
|
|
395
|
+ if (value != null) {
|
|
|
396
|
+ int len = value.toString().length();
|
|
|
397
|
+ maxLengths.merge(key, len, Math::max);
|
|
|
398
|
+ }
|
|
|
399
|
+ }
|
|
|
400
|
+ }
|
|
|
401
|
+ log.debug("收集到的列最大长度: {}", maxLengths);
|
|
|
402
|
+ return maxLengths;
|
|
|
403
|
+ }
|
|
|
404
|
+
|
|
|
405
|
+ /**
|
|
377
|
406
|
* 构建批量插入 SQL
|
|
378
|
407
|
*/
|
|
379
|
408
|
private String buildInsertSql(String dbName, String table, String superTableName,
|
|
380
|
|
- Map<String, String> columnTypes, List<Map<String, Object>> dataList) {
|
|
381
|
|
- return buildInsertSql(dbName, table, superTableName, columnTypes, dataList, null);
|
|
|
409
|
+ Map<String, String> columnTypes, List<Map<String, Object>> dataList, String controllerId) {
|
|
|
410
|
+ return buildInsertSql(dbName, table, superTableName, columnTypes, dataList, null, controllerId);
|
|
382
|
411
|
}
|
|
383
|
412
|
|
|
384
|
413
|
/**
|
|
|
@@ -386,7 +415,7 @@ public class TdEngineService {
|
|
386
|
415
|
*/
|
|
387
|
416
|
private String buildInsertSql(String dbName, String table, String superTableName,
|
|
388
|
417
|
Map<String, String> columnTypes, List<Map<String, Object>> dataList,
|
|
389
|
|
- Long customTs) {
|
|
|
418
|
+ Long customTs, String controllerId) {
|
|
390
|
419
|
if (columnTypes.isEmpty()) {
|
|
391
|
420
|
return null;
|
|
392
|
421
|
}
|
|
|
@@ -399,7 +428,7 @@ public class TdEngineService {
|
|
399
|
428
|
|
|
400
|
429
|
StringBuilder sql = new StringBuilder();
|
|
401
|
430
|
sql.append("INSERT INTO ").append(wrapName(dbName)).append(".").append(wrapName(table))
|
|
402
|
|
- .append(" (ts, surfacename");
|
|
|
431
|
+ .append(" (ts, controller_id");
|
|
403
|
432
|
|
|
404
|
433
|
for (String col : columnTypes.keySet()) {
|
|
405
|
434
|
sql.append(", ").append(wrapName(col));
|
|
|
@@ -412,7 +441,7 @@ public class TdEngineService {
|
|
412
|
441
|
continue;
|
|
413
|
442
|
}
|
|
414
|
443
|
|
|
415
|
|
- sql.append("(").append(tsValue).append(", '").append(escapeValue(superTableName)).append("'");
|
|
|
444
|
+ sql.append("(").append(tsValue).append(", '").append(escapeValue(controllerId)).append("'");
|
|
416
|
445
|
for (Map.Entry<String, String> entry : columnTypes.entrySet()) {
|
|
417
|
446
|
String col = entry.getKey();
|
|
418
|
447
|
Object value = data.get(col);
|
|
|
@@ -435,15 +464,15 @@ public class TdEngineService {
|
|
435
|
464
|
/**
|
|
436
|
465
|
* 内部方法:插入一批数据(支持一次重试)
|
|
437
|
466
|
*/
|
|
438
|
|
- private void insertBatchInternal(String dbName, String superTableName, String table,
|
|
|
467
|
+ private void insertBatchInternal(String dbName, String superTableName, String table,String controllerId,
|
|
439
|
468
|
List<Map<String, Object>> dataList) throws SQLException {
|
|
440
|
|
- insertBatchInternal(dbName, superTableName, table, dataList, false);
|
|
|
469
|
+ insertBatchInternal(dbName, superTableName, table, controllerId,dataList,false);
|
|
441
|
470
|
}
|
|
442
|
471
|
|
|
443
|
|
- private void insertBatchInternal(String dbName, String superTableName, String table,
|
|
|
472
|
+ private void insertBatchInternal(String dbName, String superTableName, String table,String controllerId,
|
|
444
|
473
|
List<Map<String, Object>> dataList, boolean isRetry) throws SQLException {
|
|
445
|
474
|
|
|
446
|
|
- ensureTableExists(dbName, superTableName, table);
|
|
|
475
|
+ ensureTableExists(dbName, superTableName, table,controllerId);
|
|
447
|
476
|
|
|
448
|
477
|
Map<String, String> columnTypes = collectColumnTypes(dataList);
|
|
449
|
478
|
if (columnTypes.isEmpty()) {
|
|
|
@@ -451,6 +480,8 @@ public class TdEngineService {
|
|
451
|
480
|
return;
|
|
452
|
481
|
}
|
|
453
|
482
|
|
|
|
483
|
+ Map<String, Integer> columnMaxLengths = collectColumnMaxLengths(dataList);
|
|
|
484
|
+
|
|
454
|
485
|
log.info("收集到的列类型: {}", columnTypes);
|
|
455
|
486
|
Set<String> existingColumns = getStableColumns(dbName, superTableName);
|
|
456
|
487
|
log.info("超级表已有列: {}", existingColumns);
|
|
|
@@ -468,7 +499,7 @@ public class TdEngineService {
|
|
468
|
499
|
return;
|
|
469
|
500
|
}
|
|
470
|
501
|
|
|
471
|
|
- ensureColumnsExist(dbName, superTableName, columnTypes);
|
|
|
502
|
+ ensureColumnsExist(dbName, superTableName, columnTypes, columnMaxLengths);
|
|
472
|
503
|
|
|
473
|
504
|
existingColumns = getStableColumns(dbName, superTableName);
|
|
474
|
505
|
if (existingColumns.size() >= MAX_COLUMNS_PER_STABLE) {
|
|
|
@@ -477,7 +508,7 @@ public class TdEngineService {
|
|
477
|
508
|
return;
|
|
478
|
509
|
}
|
|
479
|
510
|
|
|
480
|
|
- String sql = buildInsertSql(dbName, table, superTableName, columnTypes, dataList);
|
|
|
511
|
+ String sql = buildInsertSql(dbName, table, superTableName, columnTypes, dataList, controllerId);
|
|
481
|
512
|
if (sql == null) {
|
|
482
|
513
|
log.warn("insertBatchInternal SQL 构建为空,跳过插入 | dbName={}, table={}", dbName, table);
|
|
483
|
514
|
return;
|
|
|
@@ -503,7 +534,7 @@ public class TdEngineService {
|
|
503
|
534
|
subColumnTypes.put(col, columnTypes.get(col));
|
|
504
|
535
|
}
|
|
505
|
536
|
|
|
506
|
|
- String subSql = buildInsertSql(dbName, table, superTableName, subColumnTypes, dataList, unifiedTs);
|
|
|
537
|
+ String subSql = buildInsertSql(dbName, table, superTableName, subColumnTypes, dataList, unifiedTs, controllerId);
|
|
507
|
538
|
if (subSql != null) {
|
|
508
|
539
|
stmt.executeUpdate(subSql);
|
|
509
|
540
|
insertedCount += dataList.size();
|
|
|
@@ -517,8 +548,8 @@ public class TdEngineService {
|
|
517
|
548
|
if (!isRetry && e.getMessage().contains("Table does not exist")) {
|
|
518
|
549
|
log.warn("表不存在,重建并重试: {}", table);
|
|
519
|
550
|
clearStableColumnCache();
|
|
520
|
|
- initTableStructure(dbName, superTableName, table);
|
|
521
|
|
- insertBatchInternal(dbName, superTableName, table, dataList, true);
|
|
|
551
|
+ initTableStructure(dbName, superTableName, table, controllerId);
|
|
|
552
|
+ insertBatchInternal(dbName, superTableName, table, controllerId, dataList, true);
|
|
522
|
553
|
return;
|
|
523
|
554
|
}
|
|
524
|
555
|
throw new SQLException("批量写入 SQL 失败: " + table + " | 错误: " + e.getMessage(), e);
|
|
|
@@ -526,10 +557,10 @@ public class TdEngineService {
|
|
526
|
557
|
}
|
|
527
|
558
|
|
|
528
|
559
|
/**
|
|
529
|
|
- * 判断是否为保留列(ts, surfacename)
|
|
|
560
|
+ * 判断是否为保留列(ts)
|
|
530
|
561
|
*/
|
|
531
|
562
|
private boolean isReservedColumn(String columnName) {
|
|
532
|
|
- return "ts".equalsIgnoreCase(columnName) || "surfacename".equalsIgnoreCase(columnName);
|
|
|
563
|
+ return "ts".equalsIgnoreCase(columnName) || "controller_id".equalsIgnoreCase(columnName);
|
|
533
|
564
|
}
|
|
534
|
565
|
|
|
535
|
566
|
/**
|
|
|
@@ -545,7 +576,7 @@ public class TdEngineService {
|
|
545
|
576
|
if (value instanceof Integer || value instanceof Long) {
|
|
546
|
577
|
return "BIGINT";
|
|
547
|
578
|
}
|
|
548
|
|
- if (value instanceof Float || value instanceof Double) {
|
|
|
579
|
+ if (value instanceof Float || value instanceof Double || value instanceof BigDecimal) {
|
|
549
|
580
|
return "DOUBLE";
|
|
550
|
581
|
}
|
|
551
|
582
|
// 时间类型(Date, Timestamp, LocalDateTime 等)
|
|
|
@@ -559,7 +590,7 @@ public class TdEngineService {
|
|
559
|
590
|
/**
|
|
560
|
591
|
* 根据 TdEngine 列类型获取创建列的 SQL 类型
|
|
561
|
592
|
*/
|
|
562
|
|
- private String getColumnTypeForDDL(String tdType) {
|
|
|
593
|
+ private String getColumnTypeForDDL(String tdType, Integer maxLen) {
|
|
563
|
594
|
switch (tdType) {
|
|
564
|
595
|
case "BOOL":
|
|
565
|
596
|
case "BIGINT":
|
|
|
@@ -568,7 +599,8 @@ public class TdEngineService {
|
|
568
|
599
|
return tdType;
|
|
569
|
600
|
case "VARCHAR":
|
|
570
|
601
|
default:
|
|
571
|
|
- return "VARCHAR(" + DEFAULT_VARCHAR_LENGTH + ")";
|
|
|
602
|
+ int len = (maxLen != null && maxLen > 0) ? maxLen + 5 : DEFAULT_VARCHAR_LENGTH;
|
|
|
603
|
+ return "VARCHAR(" + len + ")";
|
|
572
|
604
|
}
|
|
573
|
605
|
}
|
|
574
|
606
|
|
|
|
@@ -612,20 +644,16 @@ public class TdEngineService {
|
|
612
|
644
|
}
|
|
613
|
645
|
}
|
|
614
|
646
|
|
|
615
|
|
- // 字符串类型校验长度
|
|
616
|
|
- if (strValue.length() > DEFAULT_VARCHAR_LENGTH) {
|
|
617
|
|
- log.debug("字段值超长,截断存储 | 列: {} | 值长度: {} | 最大: {} | 截断后: {}...",
|
|
618
|
|
- columnName, strValue.length(), DEFAULT_VARCHAR_LENGTH, strValue.substring(0, DEFAULT_VARCHAR_LENGTH));
|
|
619
|
|
- strValue = strValue.substring(0, DEFAULT_VARCHAR_LENGTH);
|
|
620
|
|
- }
|
|
|
647
|
+ // 字符串类型:使用实际值长度 + 5 冗余,不截断
|
|
|
648
|
+ // (TDengine VARCHAR 实际限制在创建列时已保证充足)
|
|
621
|
649
|
return "'" + escapeValue(strValue) + "'";
|
|
622
|
650
|
}
|
|
623
|
651
|
|
|
624
|
652
|
/**
|
|
625
|
653
|
* 确保列存在,如有新列则 ALTER 添加(根据类型添加对应列)
|
|
626
|
654
|
*/
|
|
627
|
|
- private void ensureColumnsExist(String dbName, String superTableName, Map<String, String> columnTypes)
|
|
628
|
|
- throws SQLException {
|
|
|
655
|
+ private void ensureColumnsExist(String dbName, String superTableName, Map<String, String> columnTypes,
|
|
|
656
|
+ Map<String, Integer> columnMaxLengths) throws SQLException {
|
|
629
|
657
|
|
|
630
|
658
|
Set<String> existingColumns = getStableColumns(dbName, superTableName);
|
|
631
|
659
|
|
|
|
@@ -641,6 +669,7 @@ public class TdEngineService {
|
|
641
|
669
|
for (Map.Entry<String, String> entry : columnTypes.entrySet()) {
|
|
642
|
670
|
String col = entry.getKey();
|
|
643
|
671
|
String colType = entry.getValue();
|
|
|
672
|
+ Integer maxLen = columnMaxLengths != null ? columnMaxLengths.get(col) : null;
|
|
644
|
673
|
|
|
645
|
674
|
// 再次检查列数上限
|
|
646
|
675
|
if (existingColumns.size() >= MAX_COLUMNS_PER_STABLE) {
|
|
|
@@ -650,7 +679,7 @@ public class TdEngineService {
|
|
650
|
679
|
}
|
|
651
|
680
|
|
|
652
|
681
|
if (!existingColumns.contains(col)) {
|
|
653
|
|
- String ddlType = getColumnTypeForDDL(colType);
|
|
|
682
|
+ String ddlType = getColumnTypeForDDL(colType, maxLen);
|
|
654
|
683
|
String alterSql = String.format(
|
|
655
|
684
|
"ALTER STABLE %s.%s ADD COLUMN %s %s",
|
|
656
|
685
|
wrapName(dbName),
|
|
|
@@ -684,7 +713,7 @@ public class TdEngineService {
|
|
684
|
713
|
/**
|
|
685
|
714
|
* 确保表存在
|
|
686
|
715
|
*/
|
|
687
|
|
- private void ensureTableExists(String dbName, String superTableName, String table)
|
|
|
716
|
+ private void ensureTableExists(String dbName, String superTableName, String table,String controllerId)
|
|
688
|
717
|
throws SQLException {
|
|
689
|
718
|
|
|
690
|
719
|
if (!isValidTableName(dbName) || !isValidTableName(superTableName) || !isValidTableName(table)) {
|
|
|
@@ -702,7 +731,7 @@ public class TdEngineService {
|
|
702
|
731
|
try (ResultSet rs = pStmt.executeQuery()) {
|
|
703
|
732
|
if (!rs.next()) {
|
|
704
|
733
|
log.info("超级表不存在,创建: {}.{}", dbName, superTableName);
|
|
705
|
|
- initTableStructure(dbName, superTableName, table);
|
|
|
734
|
+ initTableStructure(dbName, superTableName, table, controllerId);
|
|
706
|
735
|
return;
|
|
707
|
736
|
}
|
|
708
|
737
|
}
|
|
|
@@ -747,6 +776,96 @@ public class TdEngineService {
|
|
747
|
776
|
log.info("清除了 TdEngine 超级表结构缓存");
|
|
748
|
777
|
}
|
|
749
|
778
|
|
|
|
779
|
+ /**
|
|
|
780
|
+ * 根据 controllerId、deviceId 查询 TDengine 最新数据
|
|
|
781
|
+ * @param controllerId 控制器ID
|
|
|
782
|
+ * @param deviceId 设备ID
|
|
|
783
|
+ * @param key 列名,不传则返回所有列
|
|
|
784
|
+ */
|
|
|
785
|
+ public Object queryLatestData(String controllerId, String deviceId, String key) throws SQLException {
|
|
|
786
|
+ // 构建 dbName 前缀
|
|
|
787
|
+ String dbNamePrefix = "pe_iot_" + controllerId.substring(0, 2);
|
|
|
788
|
+
|
|
|
789
|
+ // 遍历 10 个分组查找数据
|
|
|
790
|
+ for (int groupId = 0; groupId < 10; groupId++) {
|
|
|
791
|
+ // 表名格式: {deviceId}_{groupId}_{controllerId} (与 insertBatch 保持一致)
|
|
|
792
|
+ String tableName = deviceId + "_" + groupId + "_" + controllerId;
|
|
|
793
|
+
|
|
|
794
|
+ // 检查表是否存在
|
|
|
795
|
+ if (!isTableExists(dbNamePrefix, tableName)) {
|
|
|
796
|
+ continue;
|
|
|
797
|
+ }
|
|
|
798
|
+
|
|
|
799
|
+ // 构建查询 SQL
|
|
|
800
|
+ String colPart = (key != null && !key.trim().isEmpty()) ? wrapName(key) : "*";
|
|
|
801
|
+ String sql = String.format("SELECT %s FROM %s.%s ORDER BY ts DESC LIMIT 1",
|
|
|
802
|
+ colPart, wrapName(dbNamePrefix), wrapName(tableName));
|
|
|
803
|
+
|
|
|
804
|
+ try (Connection conn = getConnection();
|
|
|
805
|
+ Statement stmt = conn.createStatement();
|
|
|
806
|
+ ResultSet rs = stmt.executeQuery(sql)) {
|
|
|
807
|
+
|
|
|
808
|
+ if (rs.next()) {
|
|
|
809
|
+ if (key != null && !key.trim().isEmpty()) {
|
|
|
810
|
+ // 返回指定列的值
|
|
|
811
|
+ return rs.getObject(1);
|
|
|
812
|
+ } else {
|
|
|
813
|
+ // 返回整行数据
|
|
|
814
|
+ java.util.Map<String, Object> row = new java.util.LinkedHashMap<>();
|
|
|
815
|
+ ResultSetMetaData metaData = rs.getMetaData();
|
|
|
816
|
+ int columnCount = metaData.getColumnCount();
|
|
|
817
|
+ for (int i = 1; i <= columnCount; i++) {
|
|
|
818
|
+ row.put(metaData.getColumnLabel(i), rs.getObject(i));
|
|
|
819
|
+ }
|
|
|
820
|
+ return row;
|
|
|
821
|
+ }
|
|
|
822
|
+ }
|
|
|
823
|
+ } catch (SQLException e) {
|
|
|
824
|
+ // 列不存在,继续查找下一个分组
|
|
|
825
|
+ if (e.getMessage().contains("Invalid column")) {
|
|
|
826
|
+ log.debug("表 {} 中无列 {},继续查找其他分组", tableName, key);
|
|
|
827
|
+ continue;
|
|
|
828
|
+ }
|
|
|
829
|
+ throw e;
|
|
|
830
|
+ }
|
|
|
831
|
+ }
|
|
|
832
|
+ return null;
|
|
|
833
|
+ }
|
|
|
834
|
+
|
|
|
835
|
+ /**
|
|
|
836
|
+ * 检查表是否存在
|
|
|
837
|
+ */
|
|
|
838
|
+ private boolean isTableExists(String dbName, String tableName) throws SQLException {
|
|
|
839
|
+ // 先检查数据库是否存在
|
|
|
840
|
+ String checkDbSql = "SHOW DATABASES";
|
|
|
841
|
+ try (Connection conn = getConnection();
|
|
|
842
|
+ Statement stmt = conn.createStatement()) {
|
|
|
843
|
+ stmt.setQueryTimeout(5);
|
|
|
844
|
+ boolean dbExists = false;
|
|
|
845
|
+ try (ResultSet rs = stmt.executeQuery(checkDbSql)) {
|
|
|
846
|
+ while (rs.next()) {
|
|
|
847
|
+ if (dbName.equals(rs.getString(1))) {
|
|
|
848
|
+ dbExists = true;
|
|
|
849
|
+ break;
|
|
|
850
|
+ }
|
|
|
851
|
+ }
|
|
|
852
|
+ }
|
|
|
853
|
+ if (!dbExists) {
|
|
|
854
|
+ return false;
|
|
|
855
|
+ }
|
|
|
856
|
+ // 检查表是否存在
|
|
|
857
|
+ String sql = String.format("SHOW %s.TABLES", dbName);
|
|
|
858
|
+ try (ResultSet rs = stmt.executeQuery(sql)) {
|
|
|
859
|
+ while (rs.next()) {
|
|
|
860
|
+ if (tableName.equals(rs.getString(1))) {
|
|
|
861
|
+ return true;
|
|
|
862
|
+ }
|
|
|
863
|
+ }
|
|
|
864
|
+ }
|
|
|
865
|
+ return false;
|
|
|
866
|
+ }
|
|
|
867
|
+ }
|
|
|
868
|
+
|
|
750
|
869
|
public void close() {
|
|
751
|
870
|
log.info("关闭 TdEngine 服务...");
|
|
752
|
871
|
if (dataSource != null) {
|