jetlinks-扩展TDengine时序库

jetlinks默认支持TDengine存储数据,但是没有支持作为时序库。

TDengine如果要实现,需要扩展一下。

TDengine

作为时序库, 不讲理论,官方文档很清楚。

官网:时序数据库_实时数据库 - TDengine | 涛思数据

大家自己了解一下即可。主要是讲支持jetlinks的问题。

TDengine注意事项

执行接口

官方提供了2种方式。

(1)api接口

直接作为web服务,访问数据库

(2)客户端驱动

和传统数据库一样,提供各种语言的驱动包

要说明一下的是,jdbc不支持批量参数化sql.

语法

这个数据库的语法检查很严格,尤其是空格,转义要小心。

jetlinks扩展

继承接口即可,我把演示代码贴出来供大家参考。

扩展类:

java 复制代码
package cn.iot.things.tdengine.timeseries;

import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.timeseries.TimeSeriesManager;
import org.jetlinks.community.timeseries.TimeSeriesMetadata;
import org.jetlinks.community.timeseries.TimeSeriesMetric;
import org.jetlinks.community.timeseries.TimeSeriesService;
import org.jetlinks.core.metadata.PropertyMetadata;
import org.jetlinks.core.metadata.types.*;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import java.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import static cn.iot.things.tdengine.timeseries.TDengineHelper.*;

@Slf4j
@Component
public class TdengineTimeSeriesManager implements TimeSeriesManager {

    /**
     * 所有表的tag列
     */
    private  static  final ConcurrentHashMap<String,Map<String,String>> map=new ConcurrentHashMap<>();


    private static final String TD_URL = "jdbc:TAOS-RS://localhost:6041/?";
    private static final String TD_USER = "root";
    private static final String TD_PWD = "taosdata";
    private static final String TD_DB = "jetlinks_db";

    private  static  volatile  boolean db_init=false;
    private static HikariDataSource hikariDs;
    static {
        hikariDs = new HikariDataSource();
        hikariDs.setJdbcUrl(TD_URL.replace("/?","/"+TD_DB));
        hikariDs.setUsername(TD_USER);
        hikariDs.setPassword(TD_PWD);
        hikariDs.setDriverClassName("com.taosdata.jdbc.rs.RestfulDriver");
        hikariDs.setMaximumPoolSize(20);
        hikariDs.setMinimumIdle(2);
        hikariDs.setConnectionTimeout(3000);
        hikariDs.setIdleTimeout(600000);
        hikariDs.setMaxLifetime(1800000);
        hikariDs.addDataSourceProperty("socketTimeout", "5000");
        log.info("TDengine连接池初始化完成(写死配置)");
    }


    private Connection getConnection() throws SQLException {
        Connection conn = hikariDs.getConnection();
        conn.setAutoCommit(true);
        return conn;
    }

    @Override
    public TimeSeriesService getService(TimeSeriesMetric metric) {
        return getService(metric.getId());
    }

    @Override
    public TimeSeriesService getServices(TimeSeriesMetric... metric) {
        if (metric.length == 1) {
            return getService(metric[0].getId());
        }
        throw new UnsupportedOperationException("TDengine暂不支持多metric合并");
    }



    @Override
    public TimeSeriesService getServices(String... metric) {
        if (metric.length == 1) {
            return getService(metric[0]);
        }
        throw new UnsupportedOperationException("TDengine暂不支持多metric合并");
    }

    @Override
    public TimeSeriesService getService(String metric) {
        String tableName = metric;
        TdengineTimeSeriesService.ConnectionFactory factory = new TdengineTimeSeriesService.ConnectionFactory() {
            @Override
            public Connection getConnection() throws SQLException {
                return TdengineTimeSeriesManager.this.getConnection();
            }
        };
        return new TdengineTimeSeriesService(tableName, factory, TD_DB, map,hikariDs);
    }

    /**
     * 创建数据库
     * @throws SQLException
     */
    private void createDatabase() throws SQLException {
        if(db_init){
            return;
        }
        HikariDataSource hikari = new HikariDataSource();
        hikari.setJdbcUrl(TD_URL);
        hikari.setUsername(TD_USER);
        hikari.setPassword(TD_PWD);
        hikari.setDriverClassName("com.taosdata.jdbc.rs.RestfulDriver");
        hikari.setMaximumPoolSize(20);
        Connection conn=hikari.getConnection();
        String createSql = String.format(
            "CREATE DATABASE IF NOT EXISTS %s " +
                "KEEP 365 " +      // 数据保留365天
                "CACHEMODEL 'none' " + // 缓存模式
                "WAL_LEVEL 1",     // WAL级别
            TD_DB
        );

        try (Statement stmt = conn.createStatement()) {
            stmt.executeUpdate(createSql);
        }

        try (Statement stmt = conn.createStatement()) {
            stmt.executeUpdate("use "+TD_DB);
        }
        db_init=true;
        hikari.close();
    }

    @Override
    public Mono<Void> registerMetadata(TimeSeriesMetadata metadata) {
        try {
            createDatabase();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        // 1. 前置校验(强化)
        if (metadata == null || metadata.getMetric() == null || metadata.getMetric().getId() == null) {
            return Mono.error(new IllegalArgumentException("metadata/metric/metric.id 不能为空"));
        }
        String tableName = metadata.getMetric().getId().trim();
        // 过滤表名特殊字符(避免语法错误)
        tableName =getSafeTableName(tableName);
        List<PropertyMetadata> properties = metadata.getProperties() == null ? new ArrayList<>() : metadata.getProperties();

        // 2. 字段分类(严格按规则)
        List<String> dataColumnList = new ArrayList<>(); // 数据列(带类型,如 "provider VARCHAR(255)")
        List<String> tagColumnList = new ArrayList<>();   // TAG列(带类型,如 "`id` VARCHAR(255)")
        Map<String, String> finalTagMap = new HashMap<>();// TAG映射

        // 第一步:筛选JSON字段
        List<PropertyMetadata> jsonFields = new ArrayList<>();
        for (PropertyMetadata prop : properties) {
            String fieldId = escapeIfKeyword(prop.getId());
            if (fieldId == null) continue;
            String tdType = mapToTDengineType(prop.getValueType(), fieldId);
            if ("JSON".equals(tdType)) {
                jsonFields.add(prop);
            }
        }
        boolean hasJson = !jsonFields.isEmpty();

        // 第二步:构建数据列/TAG列(用数组+join,避免空格错误)
        if (hasJson) {
            // 有JSON:所有非JSON字段(含id/Id)为数据列,JSON合并为1个TAG
            for (PropertyMetadata prop : properties) {
                String fieldId = escapeIfKeyword(prop.getId());
                if (fieldId == null) continue;
                String tdType = mapToTDengineType(prop.getValueType(), fieldId);
                if ("JSON".equals(tdType)) continue;
                // 数据列:字段名转义 + 类型,强制空格
                dataColumnList.add(escapeIfKeyword(fieldId) + " " + tdType);
            }
            // JSON TAG:合并字段名
            String jsonTagName = jsonFields.stream()
                                           .map(p -> escapeIfKeyword(p.getId()))
                                           .filter(Objects::nonNull)
                                           .reduce((a, b) -> a + "_" + b)
                                           .orElse("json_meta");
            jsonTagName = escapeIfKeyword(jsonTagName);
            tagColumnList.add(jsonTagName + " JSON");
            finalTagMap.put(jsonTagName, jsonTagName);
        } else {
            // 无JSON:id/Id为TAG,其余为数据列
            for (PropertyMetadata prop : properties) {
                String fieldId = escapeIfKeyword(prop.getId());
                if (fieldId == null) continue;
                String tdType = mapToTDengineType(prop.getValueType(), fieldId);
                boolean isIdField = (fieldId.endsWith("id") || fieldId.endsWith("Id"))&&!tagNo(fieldId.toLowerCase());

                if (isIdField) {
                    // TAG列:转义 + 类型,强制空格
                    String tagColumn = escapeIfKeyword(fieldId) + " " + tdType;
                    tagColumnList.add(tagColumn);
                    finalTagMap.put(fieldId, escapeIfKeyword(fieldId));
                } else {
                    // 数据列:转义 + 类型,强制空格
                    dataColumnList.add(escapeIfKeyword(fieldId) + " " + tdType);
                }
            }
            // 无JSON无id/Id:默认TAG
            if (tagColumnList.isEmpty()) {
                tagColumnList.add("id BIGINT");
                finalTagMap.put("id", "id");
            }
        }

        // 3. 构建最终SQL(强制语法规范)
        // 数据列:固定ts TIMESTAMP + 动态列,用", "连接(逗号+空格)
        List<String> finalDataColumns = new ArrayList<>();
        finalDataColumns.add("ts TIMESTAMP"); // 基础时间列
        finalDataColumns.addAll(dataColumnList);
        String dataColumnsStr = String.join(", ", finalDataColumns);

        // TAG列:用", "连接
        String tagColumnsStr = String.join(", ", tagColumnList);

        // 最终SQL:强制关键字前后空格,避免拼接错误
        String createSql = String.format(
            "CREATE STABLE IF NOT EXISTS %s.%s (%s) TAGS (%s);",
            TD_DB,
            tableName,
            dataColumnsStr,
            tagColumnsStr
        );

        // 4. 执行SQL(响应式+资源关闭+日志)
        String finalTableName = tableName;
        return Mono.fromRunnable(() -> {
                       try (Connection conn = getConnection();
                            Statement stmt = conn.createStatement()) {

                           // 打印格式化后的SQL,便于排查
                           System.out.println("=== 格式化后的SQL ===");
                           System.out.println(createSql);
                           System.out.println("====================");

                           stmt.execute(createSql);
                           map.put(TD_DB + "." + metadata, finalTagMap);
                           System.out.println("超级表 " + finalTableName + " 创建成功");

                       } catch (SQLException e) {
                           throw new RuntimeException(
                               "创建超级表失败\nSQL:" + createSql + "\n错误:" + e.getMessage(),
                               e
                           );
                       }
                   })
                   .then(Mono.empty());
    }
}

服务类:

java 复制代码
package cn.iot.things.tdengine.timeseries;

import com.alibaba.fastjson.JSON;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.hswebframework.ezorm.core.param.QueryParam;
import org.hswebframework.web.api.crud.entity.PagerResult;
import org.jetlinks.community.timeseries.TimeSeriesData;
import org.jetlinks.community.timeseries.TimeSeriesService;
import org.jetlinks.community.timeseries.query.*;
import org.jetlinks.community.timeseries.utils.TimeSeriesUtils;
import org.jetlinks.core.metadata.types.*;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.sql.*;
import java.util.*;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static cn.iot.things.tdengine.timeseries.TDengineHelper.*;


/**

 */
@Slf4j
public class TdengineTimeSeriesService implements TimeSeriesService {

    private static final String TD_DB ="jetlinks_db" ;
    // ========== 核心配置(一次配置,终身受用) ==========
    private final String metric;                // 超级表名
    private final ConnectionFactory connFactory;// 连接工厂
    private final String dbName;                // TDengine 数据库名
    private final Map<String, CacheValue<Boolean>> tableCache = new ConcurrentHashMap<>(); // 子表存在性缓存(避免重复查)



    private static final int TD_TABLE_NAME_MAX_LEN = 64; // TDengine 3.7.8 表名最大长度
    private static final String TD_TABLE_NAME_REGEX = "[^a-zA-Z0-9_]"; // 仅允许字母/数字/下划线
    private static final long CACHE_EXPIRE_SECONDS = 300; // 表缓存过期时间(5分钟)
  private  static  final  ConcurrentHashMap<String,String> mapCheck=new ConcurrentHashMap<>();//检查古过的表
   private static  final  Map<String,BatchProcessor> mapBatch=new ConcurrentHashMap<>();

   private static  final  Map<String,Integer> mapNUm=new ConcurrentHashMap<>();
    private final HikariDataSource hikariDs;
    private static class CacheValue<T> {
        T value;
        long expireTime;

        CacheValue(T value) {
            this.value = value;
            this.expireTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(CACHE_EXPIRE_SECONDS);
        }

        boolean isExpired() {
            return System.currentTimeMillis() > expireTime;
        }
    }

    private  final ConcurrentHashMap<String,Map<String,String>> map;


    @FunctionalInterface
    public interface ConnectionFactory {
        Connection getConnection() throws SQLException;
    }

    /**
     * 场景1:系统表(无标签列)
     * @param metric 超级表名
     * @param connFactory 连接工厂
     * @param dbName 数据库名
     */
    public TdengineTimeSeriesService(String metric, ConnectionFactory connFactory, String dbName, ConcurrentHashMap<String, Map<String, String>> map, HikariDataSource hikariDs) {
        this(metric, connFactory, null, false, "", dbName, hikariDs, map);
    }



    /**
     * 全量自定义(通用场景)
     */
    public TdengineTimeSeriesService(String metric, ConnectionFactory connFactory, String tagColumn, boolean requireTag, String defaultTagValue, String dbName, HikariDataSource hikariDs, ConcurrentHashMap<String, Map<String, String>> map) {
        this.metric = getSafeTableName(metric);
        this.connFactory = connFactory;

        this.dbName = dbName;
        this.hikariDs = hikariDs;
        this.map = map;
    }


    @Override
    public Mono<Void> commit(Publisher<TimeSeriesData> data) {
        return Flux.from(data)
                   .flatMap(this::commitSingle, 1) // 单线程串行入库,避免并发问题
                   .retry(1) // 仅重试1次,避免死循环
                   .then()
                   .onErrorResume(e -> {
                       log.error("批量单条入库失败(超级表:{})", metric, e);
                       return Mono.empty();
                   });
    }

    @Override
    public Mono<Void> commit(TimeSeriesData data) {
        return commitSingle(data);
    }


    private Mono<Void> commitSingle(TimeSeriesData data) {
        if (data == null || data.getData() == null) {
            log.warn("空数据跳过入库(超级表:{})", metric);
            return Mono.empty();
        }
       check(data);
        if(mapBatch.containsKey(metric)){
           BatchProcessor processor= mapBatch.get(metric);
           processor.submit(data);
           return  Mono.empty();
        }
        else {
            int num= mapNUm.getOrDefault(metric,1);
            num++;
            mapNUm.put(metric,num);
            if(num>20){
                BatchProcessor processor= new BatchProcessor(metric,20,3,map.get(metric),hikariDs);
                mapBatch.put(metric,processor);
                processor.submit(data);
                return  Mono.empty();
            }
        }

        // 串行执行:避免并发抢占连接池
        return Mono.fromRunnable(() -> {
                       // Connection 用 try-with-resources 自动释放
                       try (Connection conn = connFactory.getConnection()) {
                           // 禁用自动提交(可选,提升性能)
                           conn.setAutoCommit(true);
                           insertData(conn,  data);
                       } catch (SQLException e) {
                           try {
                               Connection conn = connFactory.getConnection();
                               checkAddColumn(data,conn);
                               conn.close();
                           } catch (SQLException ex) {
                               throw new RuntimeException(ex);
                           }

                           throw new RuntimeException("TDengine 入库失败", e);
                       }
                   })
                   .subscribeOn(Schedulers.boundedElastic())
                   // 异常时释放资源,避免连接泄漏
                   .doOnError(e -> log.error("入库异常,资源已释放", e))
                   .then()
                   .onErrorResume(e -> {
                       log.error("入库失败(时间戳:{})", data.getTimestamp(), e);
                       return Mono.empty();
                   });
    }


    /**
     * 检测表是否存在
     * @return
     * @throws SQLException
     */
    private boolean checkSuperTableExists() throws SQLException {
        String checkSql = String.format("SHOW %s.TABLES", dbName);
        try (Connection conn = connFactory.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(checkSql)) {
            while (rs.next()) {
                String tableName = rs.getString("table_name");
                if (tableName == null) {
                    tableName = rs.getString(1); // 尝试第一列
                }
                if (metric.equals(tableName)) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * 管理安全表
     * @param tableName
     * @param data
     * @return
     */
    private  String getTdTableNameRegex(String tableName,TimeSeriesData data) {
        if(map.containsKey(tableName)){
          Map<String,String> coloumns=  map.get(tableName);
          StringBuilder builder = new StringBuilder();
          builder.append("ts");
          for (String col : coloumns.keySet()) {
              //查找一下
              if(data.getData().containsKey(col)){
                  builder.append("_").append(data.getData().get(col));
              }
              else {
                  Map<String,Object> objectMap=data.getData();
                  for (String key : objectMap.keySet()) {
                      if(key.toLowerCase().equals(col.toLowerCase())){
                          builder.append("_").append(objectMap.get(key));
                      }
                  }
              }

          }
            // 仅保留字母、数字、下划线,其余替换为下划线
            String child=builder.toString();
            String cleanName = child.trim().replaceAll("[^a-zA-Z0-9_]", "_");
            // 确保以字母/下划线开头(避免数字开头)
            if (!cleanName.isEmpty() && Character.isDigit(cleanName.charAt(0))) {
                cleanName = "t_" + cleanName;
            }
            return cleanName;

        }
        return  "db001";
    }

    /**
     * 修改添加列
     * @param data
     * @param con
     */
    private void checkAddColumn(TimeSeriesData data,Connection con)  {
        if(mapCheck.containsKey(metric)){
            return;
        }
        //同时查询结构
        String sqlTag="DESCRIBE jetlinks_db."+metric;

        try {
            Statement stm = con.createStatement();
            ResultSet resultSet = null;
            resultSet = stm.executeQuery(sqlTag);
            HashMap<String,String> tmp=new HashMap<>();
            while (resultSet.next()) {
                String column = resultSet.getString("field");
                tmp.put(column.toLowerCase(), column);
            }
            Map<String,Object> map=   data.getData();
            Map<String,Object> coloumns=new HashMap<>();
            for(Map.Entry<String, Object> entry:map.entrySet()) {
                if(!tmp.containsKey(entry.getKey().toLowerCase())) {
                    coloumns.put(entry.getKey(), entry.getValue());
                }
            }
            //
            if(!coloumns.isEmpty()){
                //有需要添加的列
                StringBuilder builder = new StringBuilder();
                builder.append("ALTER STABLE  jetlinks_db."+metric);
                builder.append(" ADD COLUMN ");
                for(Map.Entry<String, Object> entry:coloumns.entrySet()) {
                    builder.append(entry.getKey()).append(" ").append(mapClassToTDengineType(entry.getValue().getClass().getName())).append(",");
                }
                builder.deleteCharAt(builder.length()-1);
                stm=  con.createStatement();
                stm.executeUpdate(builder.toString());
            }

         mapCheck.put(metric,"");
          stm.close();


        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }


    /**
     * 检测超级表存在否则创建
     * @param data
     */
    private  void check(TimeSeriesData data) {

        String name = metric;
        if (map.containsKey(name)) {
            return;
        }
        String sql = "SHOW jetlinks_db.STABLES LIKE '"+name+"'";
        try {
        Connection con =  connFactory.getConnection();
        Statement stm = null;
        stm = con.createStatement();
        ResultSet resultSet = stm.executeQuery(sql);
        boolean exists = resultSet.next();
            if (exists) {
                //同时查询结构
                String sqlTag="DESCRIBE jetlinks_db."+name;
                stm=con.createStatement();
                resultSet=  stm.executeQuery(sqlTag);
                HashMap<String,String> tmp=new HashMap<>();
                while (resultSet.next()) {
                   String column= resultSet.getString("field");
                   String value= resultSet.getString("note");
                   if("TAG".equals(value)){
                       tmp.put(column,column);
                   }
                }
                map.put(name, tmp);

            } else {
                //创建表
                createTable(data,con);
            }

            stm.close();
            con.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }

    }




    /**
     * 创建表
     * @param data
     * @param connection
     * @throws SQLException
     */
    private void createTable(TimeSeriesData data, Connection connection) throws SQLException {
        // 1. 前置校验:避免空指针和无效参数
        if (data == null || data.getData() == null || connection == null) {
            throw new IllegalArgumentException("参数不能为空:data/connection/data.getData()");
        }
        if (metric == null || metric.trim().isEmpty()) {
            throw new IllegalArgumentException("表名(metric)不能为空");
        }

        System.out.println("正在创建," + metric + "," + data.getData().size());
        String safeTableName = metric.trim();
        Map<String, Object> properties = data.getData();
        StringBuilder builder = new StringBuilder();

        // 2. 初始化创建语句:规范空格,避免语法错误
        builder.append("CREATE STABLE IF NOT EXISTS ")
               .append(TD_DB).append(".").append(safeTableName)
               .append(" (ts TIMESTAMP"); // 先加ts,后续用逗号拼接,避免末尾逗号问题

        // 3. 分离字段:普通字段/ID类Tag/JSON类Tag(待合并)
        Map<String, Object> idTag = new HashMap<>(); // id/Id结尾的Tag(非JSON)
        Map<String, Object> jsonTagFields = new HashMap<>(); // 待合并的JSON Tag字段
        Map<String, String> finalTagMap = new HashMap<>(); // 最终Tag名(转义后)

        for (Map.Entry<String, Object> property : properties.entrySet()) {
            String fieldName = property.getKey();
            Object fieldValue = property.getValue();

            // 空值处理:默认按VARCHAR(512)映射为普通字段
            if (fieldValue == null) {
                builder.append(", ").append(escapeIfKeyword(fieldName)).append(" VARCHAR(512)");
                continue;
            }

            // 3.1 识别ID类Tag(id/Id结尾,非JSON)
            if ((fieldName.endsWith("id") || fieldName.endsWith("Id"))&&tagNo(fieldName.toLowerCase())) {
                idTag.put(fieldName, fieldValue);
                continue;
            }

            // 3.2 识别JSON类型字段(需合并为一个Tag)
            String typeName = getSimpleClassName(fieldValue.getClass());
            String tdType = mapClassToTDengineType(typeName);
            if ("JSON".equals(tdType)) {
                jsonTagFields.put(fieldName, fieldValue); // 暂存,后续合并
                continue;
            }

            // 3.3 普通字段:拼接到STABLE列定义
            builder.append(", ").append(escapeIfKeyword(fieldName)).append(" ").append(tdType);
        }

        // 3.4 处理ID类Tag:拼接到普通字段后(作为列,非Tag)
        if (!idTag.isEmpty()) {
            for (Map.Entry<String, Object> entry : idTag.entrySet()) {
                String fieldName = entry.getKey();
                Object fieldValue = entry.getValue();
                String typeName = fieldValue == null ? "String" : getSimpleClassName(fieldValue.getClass());
                String tdType = mapClassToTDengineType(typeName);
                builder.append(", ").append(escapeIfKeyword(fieldName)).append(" ").append(tdType);
            }
        }

        // 4. 闭合列定义,开始拼接Tag(核心:合并JSON Tag)
        builder.append(") TAGS (");

        // 4.1 合并多个JSON Tag字段为一个
        String mergedJsonTagName = "";
        String mergedJsonTagValue = "{}"; // 默认空JSON
        if (!jsonTagFields.isEmpty()) {
            // 拼接Tag名称:字段名用下划线连接(如 "field1_field2")
            mergedJsonTagName = String.join("_", jsonTagFields.keySet());
            // 转义Tag名称(避免关键字冲突)
            mergedJsonTagName = escapeIfKeyword(mergedJsonTagName);
            // 合并字段值为JSON字符串
            mergedJsonTagValue = JSON.toJSONString(jsonTagFields);
            // 拼接到Tag定义:仅一个JSON Tag
            builder.append(mergedJsonTagName).append(" JSON");
            // 记录最终Tag名
            finalTagMap.put(mergedJsonTagName, mergedJsonTagName);
        }

        // 4.2 补充ID类Tag(若有,拼接到JSON Tag后)
        if (!idTag.isEmpty() && !jsonTagFields.isEmpty()) {
            builder.append(", "); // 有JSON Tag时加逗号分隔
        }
        for (Map.Entry<String, Object> entry : idTag.entrySet()) {
            String fieldName = entry.getKey();
            Object fieldValue = entry.getValue();
            String typeName = fieldValue == null ? "String" : getSimpleClassName(fieldValue.getClass());
            String tdType = mapClassToTDengineType(typeName);
            String escapedName = escapeIfKeyword(fieldName);
            builder.append(escapedName).append(" ").append(tdType);
            finalTagMap.put(fieldName, escapedName);
            // 多个ID Tag时加逗号(最后一个手动移除)
            if (entry != idTag.entrySet().toArray()[idTag.size() - 1]) {
                builder.append(", ");
            }
        }

        // 4.3 无任何Tag时:默认添加id BIGINT(兼容空Tag场景)
        if (jsonTagFields.isEmpty() && idTag.isEmpty()) {
            builder.append("id BIGINT");
            finalTagMap.put("id", "id");
        }

        // 5. 闭合Tag定义,补全语句
        builder.append(");");

        // 6. 执行SQL:自动关闭资源,避免泄漏
        try (Statement stmt = connection.createStatement()) {
            System.out.println("执行创建表SQL:" + builder.toString());
            stmt.execute(builder.toString());
            // 记录表名与Tag映射
            map.put(TD_DB + "." + safeTableName, finalTagMap);
        } catch (SQLException e) {
            System.err.println("创建表失败,SQL:" + builder.toString());
            System.err.println("合并后的JSON Tag名称:" + mergedJsonTagName);
            System.err.println("合并后的JSON Tag值:" + mergedJsonTagValue);
            throw new SQLException("创建TDengine超级表失败:" + e.getMessage(), e);
        }
    }



    /**
     * 入库
     * @param conn
     * @param data
     * @throws SQLException
     */
    private void insertData(Connection conn, TimeSeriesData data) throws SQLException {
        Map<String, Object> dataMap = data.getData();
        Map<String, String> tags = map.get(metric);
        dataMap
            .put("ts", new Timestamp(data.getTimestamp()));

        // 获取配置的超级表名(可以根据metric映射)
        String superTable = metric;
        // 生成子表名(基于tags或时间戳)
        String childTable =getTdTableNameRegex(superTable,data);

        // 构建列名和占位符
        List<String> columns = new ArrayList<>(dataMap.keySet());
        List<String> placeholders = new ArrayList<>();
        // 调试:打印所有数据
        log.info("=== 插入数据调试信息 ===");
        log.info("metric: {}", metric);
        log.info("子表名: {}", childTable);
        log.info("tags配置: {}", tags);
        log.info("dataMap内容: {}", dataMap);
        for (int i = 0; i < columns.size(); i++) placeholders.add("?");
        List<String> dataColumns = new ArrayList<>();
        // 构建标签部分
        List<String> tagColumns = new ArrayList<>(tags.values());
        if(tagColumns.size()==1){
            //判断JSON合并
            if(tagColumns.get(0).contains("_")&&!dataMap.containsKey(tagColumns.get(0))&&!dataMap.containsKey(tagColumns.get(0).toLowerCase())){
                //JSON
                Map<String,Object> tmp=new HashMap<>();
                String[] cols=tagColumns.get(0).split("_");
                for(int j=0;j<cols.length;j++) {
                    if (dataMap.containsKey(cols[j])) {
                        tmp.put(cols[j], dataMap.get(cols[j]));
                        dataMap.remove(cols[j]);
                    } else if (dataMap.containsKey(cols[j].toLowerCase())) {
                        tmp.put(cols[j], dataMap.get(cols[j].toLowerCase()));
                        dataMap.remove(cols[j]);
                    }
                }
                //

                dataMap.put(tagColumns.get(0).toLowerCase(),tmp);//直接替换
            }
        }
        List<String> tagPlaceholders = new ArrayList<>();
        for (String column : columns) {
            // 检查是否是标签字段(注意大小写)
            boolean isTag = false;
            for (String tagCol : tagColumns) {
                if (column.equalsIgnoreCase(tagCol)) {
                    isTag = true;
                    break;
                }
            }

            if (!isTag) {
                dataColumns.add(column);
            }
        }
        for (int i = 0; i < tagColumns.size(); i++) tagPlaceholders.add("?");

        // 构建插入SQL:使用USING子句自动建表
        String insertSql = buildInsertSql(dbName, childTable, superTable,
                                          dataColumns
            , tagColumns, tagPlaceholders);
        log.info("生成的SQL: {}", insertSql);
       // log.info("预计参数数量: {} (1子表 + {}标签 + {}字段)", totalParams, tagColumns.size(), columns.size());

        try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
            int idx = 1;

            // 1. 设置子表名
            pstmt
                .setString(idx++, childTable);

            // 2. 设置标签值
            for (String tagCol : tagColumns) {
                Object tagVal =dataMap.get(tagCol.replaceAll("'",""));

                setPreparedStatementValue(pstmt, idx++, tagVal);
            }

            // 3. 设置数据字段值
            for (String col : dataColumns) {
                Object val = dataMap.get(col);
                setPreparedStatementValue(pstmt, idx++, val);
            }
            pstmt
                .executeUpdate();
        }
    }

    /**
     * 构建插入SQL语句
     */
    private String buildInsertSql(String dbName, String childTable, String superTable,
                                  List<String> columns, List<String> tagColumns,
                                  List<String> tagPlaceholders) {
        StringBuilder sql = new StringBuilder();

        // 基本格式:INSERT INTO 子表名 USING 超级表名 TAGS (标签值) (字段列) VALUES (字段值)

        sql.append("INSERT INTO ? USING ")
           .append(dbName).append(".").append(superTable)
           .append(" TAGS (");

        // 添加标签占位符
        sql
            .append(String.join(",", tagPlaceholders))
            .append(") (")
            .append(String.join(",", columns))
            .append(") VALUES (")
            .append(String.join(",", Collections.nCopies(columns.size(), "?")))
            .append(")");

        return sql.toString();
    }


    /**
     * 设置PreparedStatement的值(通用方法)
     */
    private void setPreparedStatementValue(PreparedStatement pstmt, int index, Object value)
        throws SQLException {
        if (value == null) {
            pstmt.setNull(index, Types.VARCHAR);
        } else if (value instanceof Integer) {
            pstmt.setInt(index, (Integer) value);
        } else if (value instanceof Long) {
            pstmt.setLong(index, (Long) value);
        } else if (value instanceof Double) {
            pstmt.setDouble(index, (Double) value);
        } else if (value instanceof Float) {
            pstmt.setFloat(index, (Float) value);
        } else if (value instanceof Boolean) {
            pstmt.setBoolean(index, (Boolean) value);
        } else if (value instanceof Timestamp) {
            pstmt.setTimestamp(index, (Timestamp) value);
        } else if (value instanceof Date) {
            java.sql.Date date=new java.sql.Date(((Date)value).getTime());
            pstmt.setDate(index, date);
        } else if (value instanceof byte[]) {
            pstmt.setBytes(index, (byte[]) value);
        } else if (value instanceof String) {
            String str = (String) value;
            if(str.length() > 512){
                //截断
                str = str.substring(0, 512 - 3) + "...";
            }
            pstmt.setString(index, str);
        } else {
            // 其他类型转为JSON字符串
            try {
                pstmt.setString(index, JSON.toJSONString(value));
            } catch (Exception e) {
                pstmt.setString(index, value.toString());
            }
        }
    }




    @Override
    public Flux<TimeSeriesData> query(QueryParam queryParam) {
        return Mono.fromCallable(() -> checkSuperTableExists())
                   .flatMapMany(exists -> {
                       if (!exists) return Flux.empty();
                       return buildQuerySql(queryParam)
                           .flatMapMany(sql -> executeQuery(sql));
                   })
                   .onErrorResume(e -> {
                       log.error("查询失败(超级表:{})", metric, e);
                       return Flux.empty();
                   });
    }

    @Override
    public Flux<TimeSeriesData> multiQuery(Collection<QueryParam> queries) {
        return Flux.fromIterable(queries).flatMap(this::query, 4);
    }

    @Override
    public Mono<Integer> count(QueryParam queryParam) {
        return Mono.fromCallable(() -> checkSuperTableExists())
                   .flatMap(exists -> {
                       if (!exists) return Mono.just(0);
                       return buildCountSql(queryParam)
                           .flatMap(this::executeCount);
                   })
                   .onErrorReturn(0);
    }
    // 补全 buildCountSql 方法(放在类内,与 buildQuerySql 方法同级)
    private Mono<QuerySql> buildCountSql(QueryParam queryParam) {
        return Mono.fromCallable(() -> {
            // 构建 TDengine 3.7.8 兼容的 COUNT 查询 SQL
            StringBuilder sql = new StringBuilder("SELECT COUNT(1) FROM ").append(dbName).append(".").append(metric);
            List<String> conditions = new ArrayList<>();
            List<Object> params = new ArrayList<>();

            // 处理查询条件(与查询逻辑一致,保证条件过滤后计数准确)
            if (queryParam.getTerms() != null) {
                queryParam.getTerms().forEach(term -> {
                    if (term.getColumn() != null && term.getValue() != null) {
                        String col = term.getColumn();
                        Object val = term.getValue();
                        if ("ts".equals(col) || "timestamp".equals(col)) {
                            if (val instanceof Object[]) {
                                Object[] range = (Object[]) val;
                                conditions.add("ts >= ? AND ts <= ?");
                                params.add(range[0]);
                                params.add(range[1]);
                            } else {
                                conditions.add("ts = ?");
                                params.add(val);
                            }
                        } else {
                            conditions.add(col + " = ?");
                            params.add(val);
                        }
                    }
                });
            }

            // 拼接 WHERE 条件
            if (!conditions.isEmpty()) {
                sql.append(" WHERE ").append(String.join(" AND ", conditions));
            }

            // 返回封装后的 SQL + 参数
            return new QuerySql(sql.toString(), params);
        }).subscribeOn(Schedulers.boundedElastic()); // 与其他 SQL 构建方法保持一致的线程调度
    }
    @Override
    public Flux<AggregationData> aggregation(AggregationQueryParam param) {
        return Mono.fromCallable(() -> checkSuperTableExists())
                   .flatMapMany(exists -> {
                       if (!exists) return Flux.empty();
                       ProcessedAggregation processed = preProcessAggregation(param);
                       return buildAggregationSql(param, processed.getDbColumns())
                           .flatMapMany(sql -> executeAggregation(sql, param, processed));
                   })
                   .onErrorResume(e -> {
                       log.error("聚合失败(超级表:{})", metric, e);
                       return Flux.empty();
                   });
    }

    @Override
    public Mono<PagerResult<TimeSeriesData>> queryPager(QueryParam queryParam) {
        return TimeSeriesService.super.queryPager(queryParam);
    }

    @Override
    public Mono<Void> save(Publisher<TimeSeriesData> data) {
        return commit(data);
    }



    private Mono<QuerySql> buildQuerySql(QueryParam param) {
        return Mono.fromCallable(() -> {
            StringBuilder sql = new StringBuilder("SELECT * FROM ").append(dbName).append(".").append(metric);
            List<String> conditions = new ArrayList<>();
            List<Object> params = new ArrayList<>();

            // 时间条件(3.7.8 时序查询核心)
            if (param.getTerms() != null) {
                param.getTerms().forEach(term -> {
                    if (term.getColumn() != null && term.getValue() != null) {
                        String col = term.getColumn();
                        Object val = term.getValue();
                        if ("ts".equals(col) || "timestamp".equals(col)) {
                            if (val instanceof Object[]) {
                                Object[] range = (Object[]) val;
                                conditions.add("ts >= ? AND ts <= ?");
                                params.add(range[0]);
                                params.add(range[1]);
                            } else {
                                conditions.add("ts = ?");
                                params.add(val);
                            }
                        } else {
                            conditions.add(col + " = ?");
                            params.add(val);
                        }
                    }
                });
            }

            if (!conditions.isEmpty()) {
                sql.append(" WHERE ").append(String.join(" AND ", conditions));
            }

            // 排序/分页(3.7.8 兼容)
            if (param.getSorts() != null && !param.getSorts().isEmpty()) {
                sql.append(" ORDER BY ")
                   .append(param.getSorts().stream()
                                .map(s -> s.getName() + " " + s.getOrder())
                                .collect(Collectors.joining(", ")));
            }
            if (param.isPaging()) {
                int offset = (param.getPageIndex() - 1) * param.getPageSize();
                sql.append(" LIMIT ? OFFSET ?");
                params.add(param.getPageSize());
                params.add(offset);
            }

            return new QuerySql(sql.toString(), params);
        });
    }

    /**
     * 执行查询并转换为 TimeSeriesData 流(适配 TDengine 3.7.8 + 无编译/运行时异常)
     * @param querySql 封装的查询SQL+参数
     * @return TimeSeriesData 数据流
     */
    private Flux<TimeSeriesData> executeQuery(QuerySql querySql) {
        // 1. Flux.using 管理连接生命周期:创建-使用-释放
        return Flux.using(
            // 资源提供者:获取数据库连接(处理 SQLException 为运行时异常)
            () -> {
                try {
                    Connection conn = connFactory.getConnection();
                    log.debug("获取数据库连接成功,准备执行查询:{}", querySql.getSql());
                    return conn;
                } catch (SQLException e) {
                    // 核心修正:检查型异常包装为运行时异常,适配 Supplier 接口
                    throw new RuntimeException("获取查询连接失败", e);
                }
            },
            // 资源使用者:执行查询并转换结果
            (Connection conn) -> {
                try (PreparedStatement pstmt = conn.prepareStatement(querySql.getSql())) {
                    // 设置查询参数(处理参数绑定异常)
                    setParams(pstmt, querySql.getParams());

                    try (ResultSet rs = pstmt.executeQuery()) {
                        ResultSetMetaData meta = rs.getMetaData();
                        int colCount = meta.getColumnCount();

                        // 异步生成 TimeSeriesData 流
                        return Flux.<TimeSeriesData>create(sink -> {
                                       // 防止 sink 重复触发
                                       if (sink.isCancelled()) {
                                           log.debug("查询结果流已取消,停止读取数据");
                                           return;
                                       }

                                       try {
                                           while (rs.next() && !sink.isCancelled()) {
                                               long ts = 0;
                                               Map<String, Object> data = new HashMap<>(colCount); // 初始化容量提升性能

                                               for (int i = 1; i <= colCount; i++) {
                                                   String colName = meta.getColumnName(i);
                                                   Object colValue = rs.getObject(colName);

                                                   // 核心修正:处理 ts 列空值,避免 NPE
                                                   if ("ts".equalsIgnoreCase(colName)) { // 兼容大小写(TDengine 不敏感)
                                                       if (colValue != null) {
                                                           ts = rs.getTimestamp(colName).getTime();
                                                       } else {
                                                           log.warn("查询结果中 ts 列为空,默认赋值 0(列索引:{})", i);
                                                           ts = 0;
                                                       }
                                                   } else {
                                                       // 空值处理:避免 Map 中存入 null
                                                       data.put(colName, colValue == null ? "" : colValue);
                                                   }
                                               }

                                               // 发送数据到流
                                               sink.next(TimeSeriesData.of(ts, data));
                                           }

                                           // 流完成:无更多数据
                                           sink.complete();
                                           log.debug("查询结果读取完成,共处理 {} 列数据", colCount);
                                       } catch (SQLException e) {
                                           // 异步异常传递:通过 sink.error 抛出,上层可捕获
                                           log.error("读取查询结果集失败", e);
                                           sink.error(new RuntimeException("读取查询数据失败", e));
                                       }
                                   })
                                   // 流取消时的资源清理
                                   .doOnCancel(() -> {
                                       log.debug("查询结果流取消,清理结果集资源");
                                       try {
                                           rs.close();
                                       } catch (SQLException e) {
                                           log.warn("取消时关闭结果集失败", e);
                                       }
                                   });
                    } catch (SQLException e) {
                        log.error("执行查询SQL失败:{}", querySql.getSql(), e);
                        return Flux.<TimeSeriesData>error(new RuntimeException("执行查询失败", e));
                    }
                } catch (SQLException e) {
                    log.error("创建查询Statement失败:{}", querySql.getSql(), e);
                    return Flux.<TimeSeriesData>error(new RuntimeException("创建查询Statement失败", e));
                }
            },
            // 资源释放器:关闭连接(兜底,即使上面抛异常也会执行)
            (Connection conn) -> {
                try {
                    if (!conn.isClosed()) {
                        conn.close();
                        log.debug("查询连接已关闭");
                    }
                } catch (SQLException e) {
                    log.warn("关闭查询连接失败", e);
                }
            }
        ).subscribeOn(Schedulers.boundedElastic()); // 绑定到弹性线程池,避免阻塞事件循环
    }


    private Mono<Integer> executeCount(QuerySql querySql) {
        return Mono.fromCallable(() -> {
            try (Connection conn = connFactory.getConnection();
                 PreparedStatement pstmt = conn.prepareStatement(querySql.sql)) {
                setParams(pstmt, querySql.params);
                try (ResultSet rs = pstmt.executeQuery()) {
                    return rs.next() ? rs.getInt(1) : 0;
                }
            }
        }).subscribeOn(Schedulers.boundedElastic());
    }

    private ProcessedAggregation preProcessAggregation(AggregationQueryParam param) {
        ProcessedAggregation result = new ProcessedAggregation();
        for (AggregationColumn col : param.getAggColumns()) {
            String statType = extractStatType(col);
            if (statType != null) {
                String alias = col.getAlias();
                result.dbColumns.add(new AggregationColumn("COUNT(" + col.getProperty() + ")", "cnt_" + alias, Aggregation.COUNT));
                result.dbColumns.add(new AggregationColumn("AVG(" + col.getProperty() + ")", "avg_" + alias, Aggregation.AVG));
                result.dbColumns.add(new AggregationColumn(col.getProperty(), "sum2_" + alias, Aggregation.SUM));

                ProcessedAggregation.StatisticalColumn statCol = new ProcessedAggregation.StatisticalColumn();
                statCol.originalAlias = alias;
                statCol.property = col.getProperty();
                statCol.statType = statType;
                statCol.countAlias = "cnt_" + alias;
                statCol.avgAlias = "avg_" + alias;
                statCol.sum2Alias = "sum2_" + alias;
                result.statisticalColumns.put(alias, statCol);
            } else {
                result.dbColumns.add(col);
                result.normalColumns.add(col);
            }
        }
        return result;
    }

    private String extractStatType(AggregationColumn column) {
        try {
            String alias = column.getAlias().toUpperCase();
            if (alias.contains("STDDEV")) {
                return alias.contains("POP") ? "STDDEV_POP" : "STDDEV";
            }
            if (alias.contains("VARIANCE")) {
                return alias.contains("POP") ? "VARIANCE_POP" : "VARIANCE";
            }
        } catch (Exception e) {
            log.debug("提取统计类型失败", e);
        }
        return null;
    }

    private Mono<QuerySql> buildAggregationSql(AggregationQueryParam param, List<AggregationColumn> cols) {
        return Mono.fromCallable(() -> {
            TimeSeriesUtils.prepareAggregationQueryParam(param);
            StringBuilder sql = new StringBuilder("SELECT ");
            List<Object> params = new ArrayList<>();

            // 聚合列(3.7.8 支持的聚合函数)
            List<String> selectItems = Collections.singletonList(cols.stream()
                                                                     .map(col -> convertAggFunc(col.getAggregation()) + "(" + col.getProperty() + ") AS " + col.getAlias())
                                                                     .collect(Collectors.joining(", ")));
            sql.append(selectItems);

            // 超级表 + 时间条件
            sql.append(" FROM ").append(dbName).append(".").append(metric);
            sql.append(" WHERE ts >= ? AND ts <= ?");
            params.add(param.getStartWithTime());
            params.add(param.getEndWithTime());

            // 分组(3.7.8 时间窗口聚合)
            List<String> groupBy = new ArrayList<>();
            for (Group g : param.getGroups()) {
                if (g instanceof TimeGroup) {
                    String timeExpr = getTimeWindowExpr(((TimeGroup) g).getInterval().toMillis());
                    groupBy.add(timeExpr);
                    sql.append(", ").append(timeExpr).append(" AS ").append(g.getAlias());
                } else {
                    groupBy.add(g.getProperty());
                }
            }
            if (!groupBy.isEmpty()) {
                sql.append(" GROUP BY ").append(String.join(", ", groupBy));
            }

            return new QuerySql(sql.toString(), params);
        });
    }

    /**
     * 执行聚合查询并转换为 AggregationData 流(修复所有编译/运行时问题)
     * @param querySql 封装的聚合查询SQL+参数
     * @param param 聚合查询参数
     * @param processed 预处理后的聚合列配置
     * @return AggregationData 数据流
     */
    private Flux<AggregationData> executeAggregation(QuerySql querySql, AggregationQueryParam param, ProcessedAggregation processed) {
        // Flux.using 核心:管理数据库连接的生命周期(创建-使用-释放)
        return Flux.using(
            // 1. 资源提供者:获取数据库连接(处理检查型异常)
            () -> {
                try {
                    Connection conn = connFactory.getConnection();
                    log.debug("获取聚合查询连接成功,SQL:{}", querySql.getSql());
                    return conn;
                } catch (SQLException e) {
                    // 关键修正:检查型异常包装为运行时异常,适配 Supplier 接口
                    throw new RuntimeException("获取聚合查询连接失败", e);
                }
            },
            // 2. 资源使用者:执行聚合查询并转换结果
            (Connection conn) -> {
                try (PreparedStatement pstmt = conn.prepareStatement(querySql.getSql())) {
                    // 设置聚合查询参数(处理参数绑定异常)
                    setParams(pstmt, querySql.getParams());

                    try (ResultSet rs = pstmt.executeQuery()) {
                        ResultSetMetaData meta = rs.getMetaData();
                        int colCount = meta.getColumnCount();

                        // 异步生成 AggregationData 流
                        return Flux.<AggregationData>create(sink -> {
                                       // 流取消时立即停止处理,避免无效数据库操作
                                       if (sink.isCancelled()) {
                                           log.debug("聚合查询流已取消,停止读取结果");
                                           return;
                                       }

                                       try {
                                           while (rs.next() && !sink.isCancelled()) {
                                               Map<String, Object> rowData = new HashMap<>(colCount); // 初始化容量提升性能

                                               // 遍历结果集列,处理空值避免 NPE
                                               for (int i = 1; i <= colCount; i++) {
                                                   String colName = meta.getColumnName(i);
                                                   Object colValue = rs.getObject(colName);
                                                   // 空值替换:避免 Map 中存入 null 导致后续聚合计算异常
                                                   rowData.put(colName, colValue == null ? 0 : colValue);
                                               }

                                               // 处理聚合行数据,发送到流
                                               AggregationData aggData = processAggRow(rowData, param, processed);
                                               sink.next(aggData);
                                           }

                                           // 流完成:无更多聚合数据
                                           sink.complete();
                                           log.debug("聚合查询结果读取完成,共处理 {} 列数据", colCount);
                                       } catch (SQLException e) {
                                           // 异步异常传递:包装为运行时异常,上层可捕获
                                           log.error("读取聚合查询结果集失败", e);
                                           sink.error(new RuntimeException("读取聚合数据失败", e));
                                       }
                                   })
                                   // 流取消时主动清理结果集资源
                                   .doOnCancel(() -> {
                                       log.debug("聚合查询流取消,清理结果集资源");
                                       try {
                                           rs.close();
                                       } catch (SQLException e) {
                                           log.warn("取消时关闭聚合结果集失败", e);
                                       }
                                   });
                    } catch (SQLException e) {
                        log.error("执行聚合查询SQL失败:{}", querySql.getSql(), e);
                        return Flux.<AggregationData>error(new RuntimeException("执行聚合查询失败", e));
                    }
                } catch (SQLException e) {
                    log.error("创建聚合查询Statement失败:{}", querySql.getSql(), e);
                    return Flux.<AggregationData>error(new RuntimeException("创建聚合Statement失败", e));
                }
            },
            // 3. 资源释放器:兜底关闭连接(必加!原代码缺失导致语法错误)
            (Connection conn) -> {
                try {
                    if (!conn.isClosed()) {
                        conn.close();
                        log.debug("聚合查询连接已关闭");
                    }
                } catch (SQLException e) {
                    log.warn("关闭聚合查询连接失败", e);
                }
            }
        ).subscribeOn(Schedulers.boundedElastic()); // 绑定到弹性线程池,避免阻塞
    }
    private AggregationData processAggRow(Map<String, Object> row, AggregationQueryParam param, ProcessedAggregation processed) {
        Map<String, Object> result = new HashMap<>();
        // 普通聚合列
        processed.normalColumns.forEach(col -> {
            Object val = row.get(col.getAlias());
            if (val != null) {
                result.put(col.getAlias(), convertNumber(val));
            }
        });
        // 统计聚合列(标准差/方差)
        processed.statisticalColumns.values().forEach(statCol -> {
            Object val = calcStatValue(row, statCol);
            if (val != null) {
                result.put(statCol.originalAlias, val);
            }
        });
        // 分组列
        param.getGroups().forEach(g -> {
            Object val = row.get(g.getAlias());
            if (val != null) {
                result.put(g.getAlias(), val);
            }
        });
        return AggregationData.of(result);
    }

    private Object calcStatValue(Map<String, Object> row, ProcessedAggregation.StatisticalColumn statCol) {
        try {
            Number count = (Number) row.get(statCol.countAlias);
            Number avg = (Number) row.get(statCol.avgAlias);
            Number sum2 = (Number) row.get(statCol.sum2Alias);

            if (count == null || avg == null || sum2 == null || count.longValue() <= 1) {
                return 0.0;
            }

            long cnt = count.longValue();
            double avgVal = avg.doubleValue();
            double sum2Val = sum2.doubleValue();
            double sumSq = sum2Val * sum2Val;

            double varPop = (sumSq - cnt * avgVal * avgVal) / cnt;
            double varSample = (sumSq - cnt * avgVal * avgVal) / (cnt - 1);

            return switch (statCol.statType) {
                case "STDDEV" -> Math.sqrt(Math.max(varSample, 0));
                case "STDDEV_POP" -> Math.sqrt(Math.max(varPop, 0));
                case "VARIANCE" -> Math.max(varSample, 0);
                case "VARIANCE_POP" -> Math.max(varPop, 0);
                default -> null;
            };
        } catch (Exception e) {
            log.warn("计算统计值失败", e);
            return null;
        }
    }

    private String convertAggFunc(Aggregation agg) {
        return switch (agg) {
            case AVG -> "AVG";
            case SUM -> "SUM";
            case COUNT -> "COUNT";
            case MAX -> "MAX";
            case MIN -> "MIN";
            case FIRST -> "FIRST_VALUE";
            case LAST -> "LAST_VALUE";
            default -> "AVG";
        };
    }

    private String getTimeWindowExpr(long intervalMs) {
        if (intervalMs < 1000) return "INTERVAL(ts, '1s')";
        else if (intervalMs < 60000) return "INTERVAL(ts, '" + intervalMs / 1000 + "s')";
        else if (intervalMs < 3600000) return "INTERVAL(ts, '" + intervalMs / 60000 + "m')";
        else if (intervalMs < 86400000) return "INTERVAL(ts, '" + intervalMs / 3600000 + "h')";
        else return "INTERVAL(ts, '" + intervalMs / 86400000 + "d')";
    }

    private Object convertNumber(Object val) {
        if (val instanceof Number num) {
            double d = num.doubleValue();
            if (d == Math.floor(d) && !Double.isInfinite(d) && !Double.isNaN(d)) {
                return d >= Integer.MIN_VALUE && d <= Integer.MAX_VALUE ? num.intValue() : num.longValue();
            }
            return d;
        }
        return val;
    }

    private void setParams(PreparedStatement pstmt, List<Object> params) throws SQLException {
        int idx = 1;
        for (Object param : params) {
            if (param instanceof Integer) pstmt.setInt(idx++, (Integer) param);
            else if (param instanceof Long) pstmt.setLong(idx++, (Long) param);
            else if (param instanceof Double) pstmt.setDouble(idx++, (Double) param);
            else if (param instanceof Timestamp) pstmt.setTimestamp(idx++, (Timestamp) param);
            else pstmt.setString(idx++, param == null ? "" : param.toString());
        }
    }

    // ========== 内部类 ==========

    @Data
    public static class QuerySql {
        String sql;
        List<Object> params;

        public QuerySql(String sql, List<Object> params) {
            this.sql = sql;
            this.params = params;
        }
    }

    private static class ProcessedAggregation {
        List<AggregationColumn> dbColumns = new ArrayList<>();
        Map<String, StatisticalColumn> statisticalColumns = new HashMap<>();
        List<AggregationColumn> normalColumns = new ArrayList<>();

        public List<AggregationColumn> getDbColumns() {
            return dbColumns;
        }

        static class StatisticalColumn {
            String originalAlias;
            String property;
            String statType;
            String countAlias;
            String avgAlias;
            String sum2Alias;
        }
    }
}

工具类:

java 复制代码
package cn.iot.things.tdengine.timeseries;

import org.jetlinks.community.timeseries.TimeSeriesData;
import org.jetlinks.core.metadata.DataType;
import org.jetlinks.core.metadata.types.*;
import org.springframework.util.StringUtils;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

public class TDengineHelper {

    // 完整的TDengine SQL关键字集合(基于官方文档所有关键字)
    private static final Set<String> TAOS_KEYWORDS = new HashSet<>();
    private static final String DEFAULT_TABLE_PREFIX = "ts_";
    private static final Pattern LEGAL_NAME_PATTERN = Pattern.compile("^[a-zA-Z0-9_]+$");
    private static  final  Set<String> TAGS_NOCOLUMN=new HashSet<>();//不能是tag列
    static {
        TAGS_NOCOLUMN.add("threadid");

    }

    static {
        // A区关键字
        TAOS_KEYWORDS.add("ABORT");
        TAOS_KEYWORDS.add("ACCOUNT");
        TAOS_KEYWORDS.add("ACCOUNTS");
        TAOS_KEYWORDS.add("ADD");
        TAOS_KEYWORDS.add("AFTER");
        TAOS_KEYWORDS.add("AGGREGATE");
        TAOS_KEYWORDS.add("ALIAS");
        TAOS_KEYWORDS.add("ALIVE");
        TAOS_KEYWORDS.add("ALL");
        TAOS_KEYWORDS.add("ALTER");
        TAOS_KEYWORDS.add("ANALYZE");
        TAOS_KEYWORDS.add("ANODE");
        TAOS_KEYWORDS.add("ANODES");
        TAOS_KEYWORDS.add("ANOMALY_WINDOW");
        TAOS_KEYWORDS.add("ANTI");
        TAOS_KEYWORDS.add("APPS");
        TAOS_KEYWORDS.add("ARBGROUPS");
        TAOS_KEYWORDS.add("ARROW");
        TAOS_KEYWORDS.add("AS");
        TAOS_KEYWORDS.add("ASC");
        TAOS_KEYWORDS.add("ASOF");
        TAOS_KEYWORDS.add("ASYNC");
        TAOS_KEYWORDS.add("AT_ONCE");
        TAOS_KEYWORDS.add("ATTACH");
        TAOS_KEYWORDS.add("AUTO");
        TAOS_KEYWORDS.add("ASSIGN");

        // B区关键字
        TAOS_KEYWORDS.add("BALANCE");
        TAOS_KEYWORDS.add("BEFORE");
        TAOS_KEYWORDS.add("BEGIN");
        TAOS_KEYWORDS.add("BETWEEN");
        TAOS_KEYWORDS.add("BIGINT");
        TAOS_KEYWORDS.add("BIN");
        TAOS_KEYWORDS.add("BINARY");
        TAOS_KEYWORDS.add("BITAND");
        TAOS_KEYWORDS.add("BITOR");
        TAOS_KEYWORDS.add("BLOB");
        TAOS_KEYWORDS.add("BLOCKS");
        TAOS_KEYWORDS.add("BNODE");
        TAOS_KEYWORDS.add("BNODES");
        TAOS_KEYWORDS.add("BOOL");
        TAOS_KEYWORDS.add("BOTH");
        TAOS_KEYWORDS.add("BUFFER");
        TAOS_KEYWORDS.add("BUFSIZE");
        TAOS_KEYWORDS.add("BWLIMIT");
        TAOS_KEYWORDS.add("BY");

        // C区关键字
        TAOS_KEYWORDS.add("CACHE");
        TAOS_KEYWORDS.add("CACHEMODEL");
        TAOS_KEYWORDS.add("CACHESIZE");
        TAOS_KEYWORDS.add("CALC_NOTIFY_ONLY");
        TAOS_KEYWORDS.add("CASE");
        TAOS_KEYWORDS.add("CAST");
        TAOS_KEYWORDS.add("CHANGE");
        TAOS_KEYWORDS.add("CHILD");
        TAOS_KEYWORDS.add("CLIENT_VERSION");
        TAOS_KEYWORDS.add("CLUSTER");
        TAOS_KEYWORDS.add("COLON");
        TAOS_KEYWORDS.add("COLS");
        TAOS_KEYWORDS.add("COLUMN");
        TAOS_KEYWORDS.add("COMMA");
        TAOS_KEYWORDS.add("COMMENT");
        TAOS_KEYWORDS.add("COMP");
        TAOS_KEYWORDS.add("COMPACT");
        TAOS_KEYWORDS.add("COMPACTS");
        TAOS_KEYWORDS.add("COMPACT_INTERVAL");
        TAOS_KEYWORDS.add("COMPACT_TIME_OFFSET");
        TAOS_KEYWORDS.add("COMPACT_TIME_RANGE");
        TAOS_KEYWORDS.add("CONCAT");
        TAOS_KEYWORDS.add("CONFLICT");
        TAOS_KEYWORDS.add("CONNECTION");
        TAOS_KEYWORDS.add("CONNECTIONS");
        TAOS_KEYWORDS.add("CONNS");
        TAOS_KEYWORDS.add("CONSUMER");
        TAOS_KEYWORDS.add("CONSUMERS");
        TAOS_KEYWORDS.add("CONTAINS");
        TAOS_KEYWORDS.add("CONTINUOUS_WINDOW_CLOSE");
        TAOS_KEYWORDS.add("COPY");
        TAOS_KEYWORDS.add("COUNT");
        TAOS_KEYWORDS.add("COUNT_WINDOW");
        TAOS_KEYWORDS.add("CREATE");
        TAOS_KEYWORDS.add("CREATEDB");
        TAOS_KEYWORDS.add("CURRENT_USER");
        TAOS_KEYWORDS.add("SCAN");
        TAOS_KEYWORDS.add("SCANS");

        // D区关键字
        TAOS_KEYWORDS.add("DATABASE");
        TAOS_KEYWORDS.add("DATABASES");
        TAOS_KEYWORDS.add("DBS");
        TAOS_KEYWORDS.add("DECIMAL");
        TAOS_KEYWORDS.add("DEFERRED");
        TAOS_KEYWORDS.add("DELETE");
        TAOS_KEYWORDS.add("DELETE_MARK");
        TAOS_KEYWORDS.add("DELETE_OUTPUT_TABLE");
        TAOS_KEYWORDS.add("DELETE_RECALC");
        TAOS_KEYWORDS.add("DELIMITERS");
        TAOS_KEYWORDS.add("DESC");
        TAOS_KEYWORDS.add("DESCRIBE");
        TAOS_KEYWORDS.add("DETACH");
        TAOS_KEYWORDS.add("DISK_INFO");
        TAOS_KEYWORDS.add("DISTINCT");
        TAOS_KEYWORDS.add("DISTRIBUTED");
        TAOS_KEYWORDS.add("DIVIDE");
        TAOS_KEYWORDS.add("DNODE");
        TAOS_KEYWORDS.add("DNODES");
        TAOS_KEYWORDS.add("DOT");
        TAOS_KEYWORDS.add("DOUBLE");
        TAOS_KEYWORDS.add("DROP");
        TAOS_KEYWORDS.add("DURATION");

        // E区关键字
        TAOS_KEYWORDS.add("EACH");
        TAOS_KEYWORDS.add("ELSE");
        TAOS_KEYWORDS.add("ENABLE");
        TAOS_KEYWORDS.add("ENCRYPT_ALGORITHM");
        TAOS_KEYWORDS.add("ENCRYPT_KEY");
        TAOS_KEYWORDS.add("ENCRYPTIONS");
        TAOS_KEYWORDS.add("END");
        TAOS_KEYWORDS.add("EQ");
        TAOS_KEYWORDS.add("EVENT_TYPE");
        TAOS_KEYWORDS.add("EVENT_WINDOW");
        TAOS_KEYWORDS.add("EVERY");
        TAOS_KEYWORDS.add("EXCEPT");
        TAOS_KEYWORDS.add("EXISTS");
        TAOS_KEYWORDS.add("EXPIRED");
        TAOS_KEYWORDS.add("EXPIRED_TIME");
        TAOS_KEYWORDS.add("EXPLAIN");

        // F区关键字
        TAOS_KEYWORDS.add("FAIL");
        TAOS_KEYWORDS.add("FHIGH");
        TAOS_KEYWORDS.add("FILE");
        TAOS_KEYWORDS.add("FILL");
        TAOS_KEYWORDS.add("FILL_HISTORY");
        TAOS_KEYWORDS.add("FILL_HISTORY_FIRST");
        TAOS_KEYWORDS.add("FIRST");
        TAOS_KEYWORDS.add("FLOAT");
        TAOS_KEYWORDS.add("FLOW");
        TAOS_KEYWORDS.add("FLUSH");
        TAOS_KEYWORDS.add("FOR");
        TAOS_KEYWORDS.add("FORCE");
        TAOS_KEYWORDS.add("FORCE_OUTPUT");
        TAOS_KEYWORDS.add("FORCE_WINDOW_CLOSE");
        TAOS_KEYWORDS.add("FROM");
        TAOS_KEYWORDS.add("FROWTS");
        TAOS_KEYWORDS.add("FULL");
        TAOS_KEYWORDS.add("FUNCTION");
        TAOS_KEYWORDS.add("FUNCTIONS");

        // G区关键字
        TAOS_KEYWORDS.add("GE");
        TAOS_KEYWORDS.add("GEOMETRY");
        TAOS_KEYWORDS.add("GLOB");
        TAOS_KEYWORDS.add("GRANT");
        TAOS_KEYWORDS.add("GRANTS");
        TAOS_KEYWORDS.add("GROUP");
        TAOS_KEYWORDS.add("GT");

        // H区关键字
        TAOS_KEYWORDS.add("HAVING");
        TAOS_KEYWORDS.add("HEX");
        TAOS_KEYWORDS.add("HOST");

        // I区关键字
        TAOS_KEYWORDS.add("ID");
        TAOS_KEYWORDS.add("IF");
        TAOS_KEYWORDS.add("IGNORE");
        TAOS_KEYWORDS.add("IGNORE_DISORDER");
        TAOS_KEYWORDS.add("IGNORE_NODATA_TRIGGER");
        TAOS_KEYWORDS.add("ILLEGAL");
        TAOS_KEYWORDS.add("IMMEDIATE");
        TAOS_KEYWORDS.add("IMPORT");
        TAOS_KEYWORDS.add("IN");
        TAOS_KEYWORDS.add("INDEX");
        TAOS_KEYWORDS.add("INDEXES");
        TAOS_KEYWORDS.add("INITIALLY");
        TAOS_KEYWORDS.add("INNER");
        TAOS_KEYWORDS.add("INSERT");
        TAOS_KEYWORDS.add("INSTEAD");
        TAOS_KEYWORDS.add("INT");
        TAOS_KEYWORDS.add("INTEGER");
        TAOS_KEYWORDS.add("INTERSECT");
        TAOS_KEYWORDS.add("INTERVAL");
        TAOS_KEYWORDS.add("INTO");
        TAOS_KEYWORDS.add("IPTOKEN");
        TAOS_KEYWORDS.add("IROWTS");
        TAOS_KEYWORDS.add("IROWTS_ORIGIN");
        TAOS_KEYWORDS.add("IS");
        TAOS_KEYWORDS.add("IS_IMPORT");
        TAOS_KEYWORDS.add("ISFILLED");
        TAOS_KEYWORDS.add("ISNULL");

        // J区关键字
        TAOS_KEYWORDS.add("JLIMIT");
        TAOS_KEYWORDS.add("JOIN");
        TAOS_KEYWORDS.add("JSON");

        // K区关键字
        TAOS_KEYWORDS.add("KEEP");
        TAOS_KEYWORDS.add("KEEP_TIME_OFFSET");
        TAOS_KEYWORDS.add("KEY");
        TAOS_KEYWORDS.add("KILL");

        // L区关键字
        TAOS_KEYWORDS.add("LANGUAGE");
        TAOS_KEYWORDS.add("LAST");
        TAOS_KEYWORDS.add("LAST_ROW");
        TAOS_KEYWORDS.add("LE");
        TAOS_KEYWORDS.add("LEADER");
        TAOS_KEYWORDS.add("LEADING");
        TAOS_KEYWORDS.add("LEFT");
        TAOS_KEYWORDS.add("LEVEL");
        TAOS_KEYWORDS.add("LICENCES");
        TAOS_KEYWORDS.add("LIKE");
        TAOS_KEYWORDS.add("LIMIT");
        TAOS_KEYWORDS.add("LINEAR");
        TAOS_KEYWORDS.add("LOCAL");
        TAOS_KEYWORDS.add("LOGS");
        TAOS_KEYWORDS.add("LOW_LATENCY_CALC");
        TAOS_KEYWORDS.add("LP");
        TAOS_KEYWORDS.add("LSHIFT");
        TAOS_KEYWORDS.add("LT");

        // M区关键字
        TAOS_KEYWORDS.add("MACHINES");
        TAOS_KEYWORDS.add("MATCH");
        TAOS_KEYWORDS.add("MAX_DELAY");
        TAOS_KEYWORDS.add("MAXROWS");
        TAOS_KEYWORDS.add("MEDIUMBLOB");
        TAOS_KEYWORDS.add("MERGE");
        TAOS_KEYWORDS.add("META");
        TAOS_KEYWORDS.add("META_ONLY");
        TAOS_KEYWORDS.add("MINROWS");
        TAOS_KEYWORDS.add("MINUS");
        TAOS_KEYWORDS.add("MNODE");
        TAOS_KEYWORDS.add("MNODES");
        TAOS_KEYWORDS.add("MODIFY");
        TAOS_KEYWORDS.add("MODULES");

        // N区关键字
        TAOS_KEYWORDS.add("NCHAR");
        TAOS_KEYWORDS.add("NE");
        TAOS_KEYWORDS.add("NEXT");
        TAOS_KEYWORDS.add("NMATCH");
        TAOS_KEYWORDS.add("NONE");
        TAOS_KEYWORDS.add("NORMAL");
        TAOS_KEYWORDS.add("NOT");
        TAOS_KEYWORDS.add("NOTIFY");
        TAOS_KEYWORDS.add("NOTIFY_HISTORY");
        TAOS_KEYWORDS.add("NOTIFY_OPTIONS");
        TAOS_KEYWORDS.add("NOTNULL");
        TAOS_KEYWORDS.add("NOW");
        TAOS_KEYWORDS.add("NULL");
        TAOS_KEYWORDS.add("NULL_F");
        TAOS_KEYWORDS.add("NULLS");

        // O区关键字
        TAOS_KEYWORDS.add("OF");
        TAOS_KEYWORDS.add("OFFSET");
        TAOS_KEYWORDS.add("ON");
        TAOS_KEYWORDS.add("ONLY");
        TAOS_KEYWORDS.add("ON_FAILURE");
        TAOS_KEYWORDS.add("ON_FAILURE_PAUSE");
        TAOS_KEYWORDS.add("OPTIONS");
        TAOS_KEYWORDS.add("OR");
        TAOS_KEYWORDS.add("ORDER");
        TAOS_KEYWORDS.add("OUTER");
        TAOS_KEYWORDS.add("OUTPUT_SUBTABLE");
        TAOS_KEYWORDS.add("OUTPUTTYPE");

        // P区关键字
        TAOS_KEYWORDS.add("PAGES");
        TAOS_KEYWORDS.add("PAGESIZE");
        TAOS_KEYWORDS.add("PARTITION");
        TAOS_KEYWORDS.add("PASS");
        TAOS_KEYWORDS.add("PAUSE");
        TAOS_KEYWORDS.add("PERIOD");
        TAOS_KEYWORDS.add("PI");
        TAOS_KEYWORDS.add("PLUS");
        TAOS_KEYWORDS.add("PORT");
        TAOS_KEYWORDS.add("POSITION");
        TAOS_KEYWORDS.add("PPS");
        TAOS_KEYWORDS.add("PRE_FILTER");
        TAOS_KEYWORDS.add("PRECISION");
        TAOS_KEYWORDS.add("PREV");
        TAOS_KEYWORDS.add("PRIMARY");
        TAOS_KEYWORDS.add("PRIVILEGE");
        TAOS_KEYWORDS.add("PRIVILEGES");

        // Q区关键字
        TAOS_KEYWORDS.add("QDURATION");
        TAOS_KEYWORDS.add("QEND");
        TAOS_KEYWORDS.add("QNODE");
        TAOS_KEYWORDS.add("QNODES");
        TAOS_KEYWORDS.add("QSTART");
        TAOS_KEYWORDS.add("QTAGS");
        TAOS_KEYWORDS.add("QTIME");
        TAOS_KEYWORDS.add("QUERIES");
        TAOS_KEYWORDS.add("QUERY");
        TAOS_KEYWORDS.add("QUESTION");

        // R区关键字
        TAOS_KEYWORDS.add("RAISE");
        TAOS_KEYWORDS.add("RAND");
        TAOS_KEYWORDS.add("RANGE");
        TAOS_KEYWORDS.add("RATIO");
        TAOS_KEYWORDS.add("READ");
        TAOS_KEYWORDS.add("RECURSIVE");
        TAOS_KEYWORDS.add("REGEXP");
        TAOS_KEYWORDS.add("REDISTRIBUTE");
        TAOS_KEYWORDS.add("REM");
        TAOS_KEYWORDS.add("REPLACE");
        TAOS_KEYWORDS.add("REPLICA");
        TAOS_KEYWORDS.add("RESET");
        TAOS_KEYWORDS.add("RESTORE");
        TAOS_KEYWORDS.add("RESTRICT");
        TAOS_KEYWORDS.add("RESUME");
        TAOS_KEYWORDS.add("RETENTIONS");
        TAOS_KEYWORDS.add("REVOKE");
        TAOS_KEYWORDS.add("RIGHT");
        TAOS_KEYWORDS.add("ROLLUP");
        TAOS_KEYWORDS.add("ROW");
        TAOS_KEYWORDS.add("ROWTS");
        TAOS_KEYWORDS.add("RP");
        TAOS_KEYWORDS.add("RSHIFT");

        // S区关键字
        TAOS_KEYWORDS.add("SCHEMALESS");
        TAOS_KEYWORDS.add("SCORES");
        TAOS_KEYWORDS.add("SELECT");
        TAOS_KEYWORDS.add("SEMI");
        TAOS_KEYWORDS.add("SERVER_STATUS");
        TAOS_KEYWORDS.add("SERVER_VERSION");
        TAOS_KEYWORDS.add("SESSION");
        TAOS_KEYWORDS.add("SET");
        TAOS_KEYWORDS.add("SHOW");
        TAOS_KEYWORDS.add("SINGLE_STABLE");
        TAOS_KEYWORDS.add("SLASH");
        TAOS_KEYWORDS.add("SLIDING");
        TAOS_KEYWORDS.add("SLIMIT");
        TAOS_KEYWORDS.add("SMA");
        TAOS_KEYWORDS.add("SMALLINT");
        TAOS_KEYWORDS.add("SMIGRATE");
        TAOS_KEYWORDS.add("SNODE");
        TAOS_KEYWORDS.add("SNODES");
        TAOS_KEYWORDS.add("SOFFSET");
        TAOS_KEYWORDS.add("SPLIT");
        TAOS_KEYWORDS.add("SS_CHUNKPAGES");
        TAOS_KEYWORDS.add("SS_COMPACT");
        TAOS_KEYWORDS.add("SS_KEEPLOCAL");
        TAOS_KEYWORDS.add("SSMIGRATE");
        TAOS_KEYWORDS.add("STABLE");
        TAOS_KEYWORDS.add("STABLES");
        TAOS_KEYWORDS.add("STAR");
        TAOS_KEYWORDS.add("START");
        TAOS_KEYWORDS.add("STATE");
        TAOS_KEYWORDS.add("STATE_WINDOW");
        TAOS_KEYWORDS.add("STATEMENT");
        TAOS_KEYWORDS.add("STORAGE");
        TAOS_KEYWORDS.add("STREAM");
        TAOS_KEYWORDS.add("STREAMS");
        TAOS_KEYWORDS.add("STRICT");
        TAOS_KEYWORDS.add("STRING");
        TAOS_KEYWORDS.add("STT_TRIGGER");
        TAOS_KEYWORDS.add("SUBSCRIBE");
        TAOS_KEYWORDS.add("SUBSCRIPTIONS");
        TAOS_KEYWORDS.add("SUBSTR");
        TAOS_KEYWORDS.add("SUBSTRING");
        TAOS_KEYWORDS.add("SUBTABLE");
        TAOS_KEYWORDS.add("SYSINFO");
        TAOS_KEYWORDS.add("SYSTEM");

        // T区关键字
        TAOS_KEYWORDS.add("TABLE");
        TAOS_KEYWORDS.add("TABLE_PREFIX");
        TAOS_KEYWORDS.add("TABLE_SUFFIX");
        TAOS_KEYWORDS.add("TABLES");
        TAOS_KEYWORDS.add("TAG");
        TAOS_KEYWORDS.add("TAGS");
        TAOS_KEYWORDS.add("TBNAME");
        TAOS_KEYWORDS.add("THEN");
        TAOS_KEYWORDS.add("TIMES");
        TAOS_KEYWORDS.add("TIMESTAMP");
        TAOS_KEYWORDS.add("TIMEZONE");
        TAOS_KEYWORDS.add("TINYINT");
        TAOS_KEYWORDS.add("TO");
        TAOS_KEYWORDS.add("TODAY");
        TAOS_KEYWORDS.add("TOPIC");
        TAOS_KEYWORDS.add("TOPICS");
        TAOS_KEYWORDS.add("TRAILING");
        TAOS_KEYWORDS.add("TRANSACTION");
        TAOS_KEYWORDS.add("TRANSACTIONS");
        TAOS_KEYWORDS.add("TRIGGER");
        TAOS_KEYWORDS.add("TRIM");
        TAOS_KEYWORDS.add("TRUE_FOR");
        TAOS_KEYWORDS.add("TSDB_PAGESIZE");
        TAOS_KEYWORDS.add("TSERIES");
        TAOS_KEYWORDS.add("TSMA");
        TAOS_KEYWORDS.add("TSMAS");
        TAOS_KEYWORDS.add("TTL");

        // U区关键字
        TAOS_KEYWORDS.add("UNION");
        TAOS_KEYWORDS.add("UNSAFE");
        TAOS_KEYWORDS.add("UNSIGNED");
        TAOS_KEYWORDS.add("UNTREATED");
        TAOS_KEYWORDS.add("UPDATE");
        TAOS_KEYWORDS.add("USE");
        TAOS_KEYWORDS.add("USER");
        TAOS_KEYWORDS.add("USERS");
        TAOS_KEYWORDS.add("USING");

        // V区关键字
        TAOS_KEYWORDS.add("VALUE");
        TAOS_KEYWORDS.add("VALUE_F");
        TAOS_KEYWORDS.add("VALUES");
        TAOS_KEYWORDS.add("VARBINARY");
        TAOS_KEYWORDS.add("VARCHAR");
        TAOS_KEYWORDS.add("VARIABLE");
        TAOS_KEYWORDS.add("VARIABLES");
        TAOS_KEYWORDS.add("VERBOSE");
        TAOS_KEYWORDS.add("VGROUP");
        TAOS_KEYWORDS.add("VGROUPS");
        TAOS_KEYWORDS.add("VIEW");
        TAOS_KEYWORDS.add("VIEWS");
        TAOS_KEYWORDS.add("VNODE");
        TAOS_KEYWORDS.add("VNODES");

        // W区关键字
        TAOS_KEYWORDS.add("WAL");
        TAOS_KEYWORDS.add("WAL_FSYNC_PERIOD");
        TAOS_KEYWORDS.add("WAL_LEVEL");
        TAOS_KEYWORDS.add("WAL_RETENTION_PERIOD");
        TAOS_KEYWORDS.add("WAL_RETENTION_SIZE");
        TAOS_KEYWORDS.add("WAL_ROLL_PERIOD");
        TAOS_KEYWORDS.add("WAL_SEGMENT_SIZE");
        TAOS_KEYWORDS.add("WATERMARK");
        TAOS_KEYWORDS.add("WDURATION");
        TAOS_KEYWORDS.add("WEND");
        TAOS_KEYWORDS.add("WHEN");
        TAOS_KEYWORDS.add("WHERE");
        TAOS_KEYWORDS.add("WINDOW");
        TAOS_KEYWORDS.add("WINDOW_CLOSE");
        TAOS_KEYWORDS.add("WINDOW_OFFSET");
        TAOS_KEYWORDS.add("WINDOW_OPEN");
        TAOS_KEYWORDS.add("WITH");
        TAOS_KEYWORDS.add("WRITE");
        TAOS_KEYWORDS.add("WSTART");

        // 下划线开头关键字
        TAOS_KEYWORDS.add("_C0");
        TAOS_KEYWORDS.add("_IROWTS");
        TAOS_KEYWORDS.add("_QDURATION");
        TAOS_KEYWORDS.add("_QEND");
        TAOS_KEYWORDS.add("_QSTART");
        TAOS_KEYWORDS.add("_ROWTS");
        TAOS_KEYWORDS.add("_WDURATION");
        TAOS_KEYWORDS.add("_WEND");
        TAOS_KEYWORDS.add("_WSTART");
    }

    /**
     * 是否排除列
     * @param col
     * @return
     */
    public static  boolean tagNo(String col) {
        return TAOS_KEYWORDS.contains(col);
    }

    /**
     * 检查传入的字符串是否为TDengine SQL关键字。
     * 如果是关键字,则用反引号包裹返回;否则原样返回。
     *
     * @param identifier 要检查的列名、表名等标识符
     * @return 处理后的安全标识符字符串
     */
    public static String escapeIfKeyword(String identifier) {
        if (identifier == null || identifier.trim().isEmpty()) {
            return identifier;
        }
        // 转换为大写进行比较(TDengine关键字不区分大小写)
        String upperCaseIdentifier = identifier.toUpperCase().trim();
        if (TAOS_KEYWORDS.contains(upperCaseIdentifier)) {
            // 如果是关键字,用反引号包裹
            return "`" + identifier.trim() + "`";
        }
        // 不是关键字,原样返回
        return identifier.trim();
    }

    public static String mapToTDengineType(DataType dataType, String propertyId) {
        String lowerId = propertyId.toLowerCase();
        if (dataType instanceof IntType) return "INT";
        if (dataType instanceof LongType) return "BIGINT";
        if (dataType instanceof FloatType) return "FLOAT";
        if (dataType instanceof DoubleType) return "DOUBLE";
        if (dataType instanceof BooleanType) return "BOOL";
        if (dataType instanceof DateTimeType) return "TIMESTAMP";

        if (dataType instanceof StringType) {
//            if (lowerId.contains("id")) return "NCHAR(64)";
//            if (lowerId.contains("name") || lowerId.contains("type")) return "NCHAR(128)";
//            if (lowerId.contains("desc") || lowerId.contains("message")) return "NCHAR(256)";
//            if (lowerId.contains("info") || lowerId.contains("spec")) return "NCHAR(512)";
//            return "NCHAR(64)";
            return  "VARCHAR(255)";
        }

        if (dataType instanceof ObjectType || dataType instanceof ArrayType) return "JSON";

        return "NCHAR(64)";
    }

    /**
     * 修改表名称
     * @param rawName
     * @return
     */
    public static String getSafeTableName(String rawName) {
        if (!StringUtils.hasText(rawName)) {
            throw new IllegalArgumentException("表名不能为空");
        }
        String prefixed = DEFAULT_TABLE_PREFIX + rawName;
        if (LEGAL_NAME_PATTERN.matcher(prefixed).matches()) {
            return prefixed;
        }
        String safe = prefixed.replaceAll("[^a-zA-Z0-9_]", "_");

        return safe;
    }

    // 辅助方法:获取类的简单名称(如java.lang.String → String)
    public static String getSimpleClassName(Class<?> clazz) {
        if (clazz == null) return "String";
        return clazz.getSimpleName();
    }

    /**
     * 对象转类型
     * @param javaTypeName
     * @return
     */
    public  static   String mapClassToTDengineType(String javaTypeName){
        // 空值校验,避免NPE
        if (javaTypeName == null || javaTypeName.trim().isEmpty()) {
            return "VARCHAR(512)";
        }
        // 统一转为大写,避免大小写匹配问题(如传入 "string" 也能匹配)
        String type = javaTypeName.trim().toUpperCase();

        switch (type) {
            // 字符串类型
            case "STRING":
                return "VARCHAR(512)";
            // 整型
            case "INT":
            case "INTEGER":
                return "INT";
            // 长整型
            case "LONG":
                return "BIGINT";
            // 浮点型
            case "FLOAT":
                return "FLOAT";
            case "DOUBLE":
                return "DOUBLE";
            // 布尔型
            case "BOOLEAN":
                return "BOOL";
            // 时间类型(统一映射为TIMESTAMP,TDengine以毫秒为单位)
            case "DATE":
            case "LOCALDATE":
            case "LOCALDATETIME":
            case "TIMESTAMP":
                return "TIMESTAMP";
            // JSON类型(仅TDengine 3.0+支持)
            case "OBJECT":
                return "JSON";
            // 默认情况:兜底为VARCHAR(512)
            default:
                return "VARCHAR(512)";
        }
    }

}

优化批量:

java 复制代码
package cn.iot.things.tdengine.timeseries;

import com.alibaba.fastjson.JSON;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.timeseries.TimeSeriesData;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;

import java.sql.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import static cn.iot.things.tdengine.timeseries.TDengineHelper.escapeIfKeyword;

@Slf4j
public class BatchProcessor {
    private static final int MAX_VALUES_PER_SQL =20 ;
    private final String superTable;
    private final int batchSize;
    private final int flushSeconds;

    private final Sinks.Many<TimeSeriesData> dataSink;
    private final Flux<BatchResult> batchFlux;

    private final AtomicInteger bufferCount = new AtomicInteger(0);
    private final AtomicLong totalProcessed = new AtomicLong(0);
    private final AtomicLong totalBatches = new AtomicLong(0);

    /**
     * 列与数据的映射
     */
    private final Map<String, String> mapTagToData = new HashMap<>();

    /**
     * tag
     */
    private final Map<String, String> mapTags;

    private final HikariDataSource hikariDs;

    private  String td_db="jetlinks_db";
    public BatchProcessor(String superTable, int batchSize, int flushSeconds, Map<String, String> mapTage, HikariDataSource hikariDs) {
        this.superTable = superTable;
        this.batchSize = batchSize;
        this.flushSeconds = flushSeconds;
        this.mapTags = mapTage;
        this.hikariDs = hikariDs;

        // 创建响应式数据源
        this.dataSink = Sinks.many().unicast().onBackpressureBuffer(new ArrayDeque<>(1000));

        // 构建批处理流
        this.batchFlux = dataSink.asFlux()
                                 .bufferTimeout(batchSize, java.time.Duration.ofSeconds(flushSeconds))
                                 .filter(batch -> !batch.isEmpty())
                                 .doOnNext(batch -> {
                                     bufferCount.addAndGet(batch.size());
                                     log.debug("[{}] 批次准备: {}条", superTable, batch.size());
                                 })
                                 .flatMap(this::processBatch, 2) // 并行度2
                                 .doOnNext(result -> {
                                     totalBatches.incrementAndGet();
                                     totalProcessed.addAndGet(result.getSuccessCount());
                                     log.debug("[{}] 批次完成: 成功{}条/总共{}条",
                                               superTable, result.getSuccessCount(), result.getProcessedCount());
                                 })
                                 .onErrorResume(e -> {
                                     log.error("[{}] 批处理异常", superTable, e);
                                     return Flux.empty();
                                 });

        // 启动处理器
        start();
    }

    /**
     * 启动处理器
     */
    private void start() {
        batchFlux.subscribe(
            result -> {
            },
            error -> log.error("[{}] 处理器异常终止", superTable, error),
            () -> log.info("[{}] 处理器正常结束", superTable)
        );

        log.info("[{}] 批处理器启动 [size={}, interval={}s]",
                 superTable, batchSize, flushSeconds);
    }

    /**
     * 提交数据
     */
    public boolean submit(TimeSeriesData data) {
        try {
            Sinks.EmitResult result = dataSink.tryEmitNext(data);

            if (result.isSuccess()) {
                return true;
            } else {
                log.warn("[{}] 提交失败: {}", superTable, result);
                // 缓冲满时直接处理单条数据
                return processImmediately(data);
            }
        } catch (Exception e) {
            log.error("[{}] 提交异常", superTable, e);
            return false;
        }
    }

    /**
     * 立即处理单条数据
     */
    private boolean processImmediately(TimeSeriesData data) {
        List<TimeSeriesData> singleBatch = Collections.singletonList(data);

        processBatch(singleBatch)
            .subscribe(result -> {
                log.info("[{}] 缓冲满直接处理: {}条", superTable, result.getProcessedCount());
            });

        return true;
    }

    private String getChildTable(String tableName, TimeSeriesData data) {
        return getTdTableNameRegex(tableName, data);
    }

    /**
     * 处理子表同时查找映射关系
     *
     * @param tableName
     * @param data
     * @return
     */
    private String getTdTableNameRegex(String tableName, TimeSeriesData data) {

        Map<String, String> coloumns = mapTags;
        if (coloumns == null) {
            return "dev0000";
        }
        StringBuilder builder = new StringBuilder();
        builder.append("ts");
        for (String col : coloumns.keySet()) {
            //查找一下
            if (data.getData().containsKey(col)) {
                builder.append("_").append(data.getData().get(col));
                mapTagToData.put(col, col);
            } else {
                Map<String, Object> objectMap = data.getData();
                if (mapTagToData.containsKey(col)) {
                    String key = mapTagToData.get(col);
                    builder.append("_").append(objectMap.get(key));
                } else {
                    for (String key : objectMap.keySet()) {
                        if (key.toLowerCase().equals(col.toLowerCase())) {
                            builder.append("_").append(objectMap.get(key));
                            mapTagToData.put(col, key);
                            break;
                        }
                    }
                }
            }

        }
        // 仅保留字母、数字、下划线,其余替换为下划线
        String child = builder.toString();
        String cleanName = child.trim().replaceAll("[^a-zA-Z0-9_]", "_");
        // 确保以字母/下划线开头(避免数字开头)
        if (!cleanName.isEmpty() && Character.isDigit(cleanName.charAt(0))) {
            cleanName = "t_" + cleanName;
        }
        return cleanName;


    }


    /**
     * 处理批次数据
     */
    private Mono<BatchResult> processBatch(List<TimeSeriesData> batch) {
        if (batch.isEmpty()) {
            return Mono.just(new BatchResult(0, 0, 0));
        }

        return Mono.fromCallable(() -> {
            try {
                // 按子表分组(同一子表可以批量插入)
                Map<String, List<TimeSeriesData>> groupedByChildTable = new HashMap<>();

                for (TimeSeriesData data : batch) {
                    String childTable = getChildTable(superTable, data);
                    groupedByChildTable
                        .computeIfAbsent(childTable, k -> new ArrayList<>())
                        .add(data);
                }

                int totalSuccess = 0;
                int totalFailed = 0;

                // 处理每个子表的数据
                for (Map.Entry<String, List<TimeSeriesData>> entry : groupedByChildTable.entrySet()) {
                    String childTable = entry.getKey();
                    List<TimeSeriesData> childTableBatch = entry.getValue();

                    try {
                      //  BatchResult result = insertBatch(superTable, childTable, childTableBatch);

                        BatchResult result =insertBatchSql(superTable, childTable, childTableBatch);
                       totalSuccess += result.getSuccessCount();
                        totalFailed += result.getFailedCount();
                    } catch (Exception e) {
                        log.error("[{}] 子表[{}]批量插入失败", superTable, childTable, e);
                        // 降级为逐条插入
                        BatchResult fallbackResult = insertOneByOne(superTable, childTable, childTableBatch);
                        totalSuccess += fallbackResult.getSuccessCount();
                        totalFailed += fallbackResult.getFailedCount();
                    }
                }

                return new BatchResult(totalSuccess, totalFailed, batch.size());

            } catch (Exception e) {
                log.error("[{}] 处理批次异常", superTable, e);
                return new BatchResult(0, batch.size(), batch.size());
            }
        });
    }

    /**
     * 批量插入到同一子表
     */
    private BatchResult insertBatch(String superTable, String childTable, List<TimeSeriesData> batch) {
        if (batch.isEmpty()) {
            return new BatchResult(0, 0, 0);
        }

        // 使用第一条数据作为模板
        TimeSeriesData template = batch.get(0);
        Map<String, Object> tags = getTagsValues(template);
        Map<String, Object> columns = getValue(template);

        // 构建SQL
        String sql = buildInsertSql(superTable, childTable, tags, columns);

        log.info("批量构建sql:{}",sql);
        // 准备参数
        List<Object[]> batchParams = new ArrayList<>();

        for (TimeSeriesData data : batch) {
            Object[] params = prepareParams(data);
            log.info("构建参数:{}",params.length);
            batchParams.add(params);
        }

        try {
            // 执行批量插入
            int[] results = null;

//                jdbcTemplate.batchUpdate(sql, batchParams, batchParams.get(0).length,
//                                                     (ps, params) -> {
//                                                         for (int i = 0; i < params.length; i++) {
//                                                             setPreparedStatementValue(ps, i + 1, params[i]);
//                                                         }
//                                                     });
            try(Connection connection=hikariDs.getConnection()) {
                PreparedStatement preparedStatement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
                for (int i = 0; i < batchParams.size(); i++) {
                    setPreparedStatementValue(preparedStatement,i+1,batchParams.get(i));
                    preparedStatement.addBatch();
                }
                results= preparedStatement.executeBatch();
            }

            int success = 0;
            for (int result : results) {
                if (result > 0) {
                    success++;
                }
            }

            int failed = batch.size() - success;
            return new BatchResult(success, failed, batch.size());

        } catch (Exception e) {
            log.error("[{}] 批量插入异常", superTable, e);
          return new BatchResult(0, 0, 0);
        }
    }

    /**
     * 原生批量,参数化目前驱动不支持
     * @param superTable
     * @param childTable
     * @param batch
     * @return
     */
    private BatchResult insertBatchSql(String superTable, String childTable, List<TimeSeriesData> batch){
        List<String> lstBatch = buildNativeBatchSqls(superTable, childTable, batch);
        int successCount = 0;
        for (String sql : lstBatch) {
            try(Connection connection=hikariDs.getConnection()){
                Statement stmt = connection.createStatement();

                int affected = stmt.executeUpdate(sql);

                // TDengine返回受影响的行数
                if (affected >= 0) {
                    successCount += batch.size(); // 假设全部成功
                }

            } catch (SQLException e) {
                log.error("执行批量SQL失败: {}", e.getMessage());
                log.info("批量:{}",sql);
                throw new RuntimeException(e);
            }
        }
        int failedCount = batch.size() - successCount;
        return new BatchResult(successCount, failedCount, batch.size());
    }
    /**
     * 构建插入SQL
     */
    private String buildInsertSql(String superTable, String childTable,
                                  Map<String, Object> tags, Map<String, Object> columns) {

        StringBuilder sql = new StringBuilder();

        // INSERT INTO 子表名 USING 超级表名 TAGS (标签值) (字段列) VALUES (字段值)
        sql.append("INSERT INTO ? USING ").append(td_db).append(".").append(superTable).append(" TAGS (");

        // 标签占位符
        if (tags != null && !tags.isEmpty()) {
            List<String> tagPlaceholders = new ArrayList<>();
            for (int i = 0; i < tags.size(); i++) {
                tagPlaceholders.add("?");
            }
            sql.append(String.join(",", tagPlaceholders));
        } else {
            sql.append("?");
        }

        sql.append(") (");

        // 列名
        if (columns != null && !columns.isEmpty()) {
            List<String> lstcolumn = new ArrayList<>();
            for (Map.Entry<String, Object> entry : columns.entrySet()) {
                lstcolumn.add(escapeIfKeyword(entry.getKey()));
            }
            sql.append(String.join(",", lstcolumn));
        } else {
            sql.append("ts"); // 默认时间戳列
        }

        sql.append(") VALUES (");

        // 值占位符
        if (columns != null && !columns.isEmpty()) {
            List<String> valuePlaceholders = new ArrayList<>();
            for (int i = 0; i < columns.size(); i++) {
                valuePlaceholders.add("?");
            }
            sql.append(String.join(",", valuePlaceholders));
        } else {
            sql.append("?");
        }

        sql.append(")");

        return sql.toString();
    }


    private List<String> buildNativeBatchSqls(String superTable, String childTable,
                                              List<TimeSeriesData> batch) {

        List<String> sqlList = new ArrayList<>();
        TimeSeriesData first = batch.get(0);
       first.getData().put("ts", LocalDateTime.now());
        // 1. 构建SQL头部(固定部分)
        String sqlHeader = buildSqlHeader(superTable, childTable, first);

        // 2. 分批构建VALUES(防止SQL过长)
        List<List<TimeSeriesData>> batches = splitBatch(batch, MAX_VALUES_PER_SQL);

        for (List<TimeSeriesData> subBatch : batches) {
            StringBuilder sql = new StringBuilder(sqlHeader);

            // 构建VALUES部分
            List<String> valueStrings = new ArrayList<>();
            for (TimeSeriesData data : subBatch) {
                data.getData().put("ts", LocalDateTime.now());
                valueStrings.add(buildValueString(data));
            }

            sql.append(String.join(",", valueStrings));



                sqlList.add(sql.toString());

        }

        return sqlList;
    }

    /**
     * 构建SQL头部
     */
    private String buildSqlHeader(String superTable, String childTable, TimeSeriesData data) {
        StringBuilder header = new StringBuilder();

        // INSERT INTO 子表名 USING 超级表名 TAGS(标签值)
        header.append("INSERT INTO ").append(childTable)
              .append(" USING ").append(td_db).append(".").append(superTable)
              .append(" TAGS (");

         Map<String,Object> tags =getTagsValues(data);
        if (!tags.isEmpty()) {
            List<String> tagValues = new ArrayList<>();
            for (Object value : tags.values()) {
                tagValues.add(formatValue(value));
            }
            header.append(String.join(",", tagValues));
        } else {
            header.append("''");
        }

        header.append(") (");

        // 列名
        Map<String,Object> columns =getValue(data);
        if (!columns.isEmpty()) {
            List<String> columnNames = new ArrayList<>();
            for (String column : columns.keySet()) {
                columnNames.add(escapeIfKeyword(column));
            }
            header.append(String.join(",", columnNames));
        }

        header.append(") VALUES ");

        return header.toString();
    }
    /**
     * 拆分批次
     */
    private List<List<TimeSeriesData>> splitBatch(List<TimeSeriesData> batch, int maxPerBatch) {
        List<List<TimeSeriesData>> batches = new ArrayList<>();

        for (int i = 0; i < batch.size(); i += maxPerBatch) {
            int end = Math.min(i + maxPerBatch, batch.size());
            batches.add(batch.subList(i, end));
        }

        return batches;
    }
    /**
     * 构建单个VALUES字符串
     */
    private String buildValueString(TimeSeriesData data) {
        StringBuilder value = new StringBuilder("(");
       Map<String,Object> columns =getValue(data);
        if (!columns.isEmpty()) {
            List<String> columnValues = new ArrayList<>();
            for (Object valueObj :columns.values()) {
                columnValues.add(formatValue(valueObj));
            }
            value.append(String.join(",", columnValues));
        }

        value.append(")");
        return value.toString();
    }

    /**
     * 格式化值(用于SQL拼接)
     */
    private String formatValue(Object value) {
        if (value == null) {
            return "NULL";
        } else if (value instanceof Number) {
            return value.toString();
        } else if (value instanceof Boolean) {
            return ((Boolean) value) ? "true" : "false";
        } else if (value instanceof LocalDateTime) {
            // TDengine时间格式
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
            return "'" + ((LocalDateTime) value).format(formatter) + "'";
        } else if (value instanceof Date) {
            LocalDateTime ldt = ((Date) value).toInstant()
                                              .atZone(java.time.ZoneId.systemDefault())
                                              .toLocalDateTime();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
            return "'" + ldt.format(formatter) + "'";
        } else if (value instanceof String) {
            String str = (String) value;
            // 转义单引号
            str = str.replace("'", "''");
            // 限制长度
            if (str.length() > 16374) {
                str = str.substring(0, 16374 - 3) + "...";
            }
            return "'" + str + "'";
        } else if (value.getClass().getSimpleName().toLowerCase().equals("object")) {
            return JSON.toJSONString(value);
        } else if (value instanceof Optional) {
            Optional<?> optional = (Optional<?>) value;

            if (!optional.isPresent()) {
                return "''";
            }

            Object innerValue = optional.get();

            // 递归处理内部值
            return formatValue(innerValue);
        }

        // 处理 Map 类型
        else if (value instanceof Map) {

            return  "'"+JSON.toJSONString(value)+"'";

        }
        else {

            String className = value.getClass().getSimpleName();
            String str = value.toString();
            str = str.replace("'", "''");
            if (str.length() > 1000) {
                str = str.substring(0, 1000 - 3) + "...";
            }
            return "'" + str + "'";
        }
    }


/**
 * SQL 转义
 */
private static String escapeForSql(String str) {
    if (str == null) return "''";
    // 转义单引号
    str = str.replace("'", "''");
    // 限制长度
    if (str.length() > 100) {
        str = str.substring(0, 100 - 3) + "...";
    }
    return "'" + str + "'";
}
    /**
     * 标签列的值
     *
     * @param data
     * @return
     */
    private Map<String, Object> getTagsValues(TimeSeriesData data) {
        Map<String, String> tags = mapTags;
        Map<String, Object> objectMap = new HashMap<>();
        for (String key : tags.keySet()) {
            if (data.getData().containsKey(key)) {
                objectMap.put(key, data.get(key));
            } else {
                if (mapTagToData.containsKey(key)) {
                    String col = mapTagToData.get(key);
                    objectMap.put(key, data.get(col));
                }

            }
        }
        return objectMap;
    }

    /**
     * 准备参数数组
     */
    private Object[] prepareParams(TimeSeriesData data) {
        List<Object> params = new ArrayList<>();

        // 1. 子表名
        params.add(getChildTable(superTable, data));

        // 2. 标签值
        Map<String, Object> tagvalue = getTagsValues(data);
        if (!tagvalue.isEmpty()) {
            params.addAll(tagvalue.values());
        } else {
            params.add(""); // 空标签
        }

        // 3. 字段值
        Map<String, Object> columnvalue = getValue(data);
        if (!columnvalue.isEmpty()) {
            params.addAll(columnvalue.values());
        } else {
            params.add(LocalDateTime.now()); // 默认时间戳
        }

        return params.toArray();
    }

    private Map<String, Object> getValue(TimeSeriesData data) {
        Map<String, Object> vaue = new HashMap<>(data.getData());
        Map<String, String> tags = mapTags;
        mapTagToData.values().forEach(vaue::remove);
        return vaue;
    }


    /**
     * 逐条插入(降级方案)
     */
    private BatchResult insertOneByOne(String superTable, String childTable, List<TimeSeriesData> batch) {
        int success = 0;
        int failed = 0;

        for (TimeSeriesData data : batch) {
            try {
                boolean inserted = insertSingle(superTable, childTable, data);
                if (inserted) {
                    success++;
                } else {
                    failed++;
                }
            } catch (Exception e) {
                log.error("[{}] 单条插入失败", superTable, e);
                failed++;
            }
        }

        return new BatchResult(success, failed, batch.size());
    }

    /**
     * 插入单条数据
     */
    private boolean insertSingle(String superTable, String childTable, TimeSeriesData data) {
        Map<String, Object> tagvalue = getTagsValues(data);
        Map<String, Object> columnvalue = getValue(data);
        String sql = buildInsertSql(superTable, childTable, tagvalue,columnvalue);
        Object[] params = prepareParams(data);


        try {
            int affected = 0;
            try(Connection connection=hikariDs.getConnection()) {
                PreparedStatement stm = connection.prepareStatement(sql);
                for (int i = 0; i <params.length ; i++) {
                    setPreparedStatementValue(stm,i+1,params[i]);
                }

                affected=  stm.executeUpdate();
                stm.close();
                connection.close();
            }
            return affected > 0;
        } catch (Exception e) {
            log.error("[{}] 单条SQL执行失败", superTable, e);
            log.info("sql:{}",sql);
            return false;
        }
    }

    /**
     * 手动刷新
     */
    public Mono<BatchResult> forceFlush() {
        return Mono.fromCallable(() -> {
            log.info("[{}] 手动刷新", superTable);
            int count = bufferCount.getAndSet(0);
            return new BatchResult(0, 0, count);
        });
    }

    /**
     * 获取统计信息
     */
    public Map<String, Object> getStats() {
        return Map.of(
            "bufferCount", bufferCount.get(),
            "totalProcessed", totalProcessed.get(),
            "totalBatches", totalBatches.get(),
            "batchSize", batchSize,
            "flushInterval", flushSeconds
        );
    }

    /**
     * 关闭处理器
     */
    public void shutdown() {
        log.info("[{}] 关闭批处理器", superTable);
        dataSink.tryEmitComplete();
    }


    /**
     * 设置PreparedStatement的值
     */
    private void setPreparedStatementValue(PreparedStatement pstmt, int index, Object value)
        throws SQLException {
        if (value == null) {
            pstmt.setNull(index, Types.VARCHAR);
        } else if (value instanceof Integer) {
            pstmt.setInt(index, (Integer) value);
        } else if (value instanceof Long) {
            pstmt.setLong(index, (Long) value);
        } else if (value instanceof Double) {
            pstmt.setDouble(index, (Double) value);
        } else if (value instanceof Float) {
            pstmt.setFloat(index, (Float) value);
        } else if (value instanceof Boolean) {
            pstmt.setBoolean(index, (Boolean) value);
        } else if (value instanceof Timestamp) {
            pstmt.setTimestamp(index, (Timestamp) value);
        } else if (value instanceof Date) {
            java.sql.Date date = new java.sql.Date(((Date) value).getTime());
            pstmt.setDate(index, date);
        } else if (value instanceof LocalDateTime) {
            pstmt.setTimestamp(index, Timestamp.valueOf((LocalDateTime) value));
        } else if (value instanceof byte[]) {
            pstmt.setBytes(index, (byte[]) value);
        } else if (value instanceof String) {
            String str = (String) value;
            if (str.length() > 512) {
                str = str.substring(0, 512 - 3) + "...";
            }
            pstmt.setString(index, str);
        }
        else if (value instanceof Optional) {
            Optional<?> optional = (Optional<?>) value;

            if (!optional.isPresent()) {
                pstmt.setString(index, null);
            }

            Object innerValue = optional.get();
            pstmt.setString(index, JSON.toJSONString(innerValue));

        }

        // 处理 Map 类型
        else if (value instanceof Map) {

            pstmt.setString(index, JSON.toJSONString(value));

        }
        else {

            pstmt.setString(index, value.toString());
        }
    }
}

批量实体:

java 复制代码
package cn.iot.things.tdengine.timeseries;

import lombok.AllArgsConstructor;
import lombok.Data;


/**
 * 批次结果
 */
@Data
@AllArgsConstructor
public class BatchResult {
    private int successCount;
    private int failedCount;
    private int processedCount;

}
相关推荐
袋鼠云数栈2 小时前
媒体专访丨袋鼠云 CEO 宁海元:Agent元年之后,产业需回到“数据+智能”的长期结构
大数据·人工智能
涤生大数据2 小时前
放弃Canal后,我们用Flink CDC实现了99.99%的数据一致性
大数据·数据仓库·flink·大数据开发·flink cdc·数据开发·实时数据
云和数据.ChenGuang2 小时前
openEuler安装elasticSearch
大数据·elasticsearch·搜索引擎·全文检索·jenkins
Herlie2 小时前
AI 创业这三年:我的三次认知迭代与自我修正
大数据·人工智能
PNP Robotics3 小时前
聚焦具身智能,PNP机器人展出力反馈遥操作,VR动作捕捉等方案,获得中国科研贡献奖
大数据·人工智能·python·学习·机器人
木易 士心3 小时前
数字身份的通行证:深入解析单点登录(SSO)的架构与艺术
java·大数据·架构
2401_878820473 小时前
ES知识点二
大数据·elasticsearch·搜索引擎
Jackyzhe3 小时前
Flink源码阅读:Checkpoint机制(下)
大数据·flink
科创致远3 小时前
esop系统可量化 ROI 投资回报率客户案例故事-案例1:宁波某精密制造企业
大数据·人工智能·制造·精益工程