一、InfluxDB简介
InfluxDB 是一款高性能的时序数据库(Time Series Database, TSDB),专为处理时间戳数据的高效写入、存储和查询而设计。其核心优势在于针对时间序列数据的特性(如时间戳对齐、数据压缩、高频采样等)进行了深度优化,广泛应用于物联网(IoT)、监控系统、实时分析、金融交易等场景。
核心特性
1、时序数据优化
按时间戳自动分区存储,支持高效范围查询(如“过去1小时的数据”)。
内置数据压缩算法,显著降低存储成本(压缩比可达10:1以上)。
支持降采样(Downsampling)和连续查询(Continuous Query),减少冗余数据。
2、高性能写入与查询
写入吞吐量高(每秒数百万数据点),适合高频采集场景。
查询语言(Flux)支持复杂的时间序列聚合、过滤和转换。
3、灵活的数据模型
基于“测量(Measurement)”“标签(Tag)”“字段(Field)”和“时间戳”构建数据模型,支持多维数据组织。
标签(Tag)用于索引和分组,字段(Field)存储实际值,提升查询效率。
4、集群与高可用
支持分布式部署,通过分片(Shard)和副本(Replica)实现水平扩展和容灾。
提供数据一致性保证(强一致性或最终一致性)。
5、生态集成
兼容Telegraf(数据采集代理)、Chronograf(可视化工具)、Kapacitor(流处理引擎)等组件,形成完整时序数据栈。
支持HTTP API、Golang/Java/Python等客户端库。
典型应用场景
物联网(IoT):传感器数据存储与分析(如温度、湿度、设备状态)。
监控系统:服务器性能指标(CPU、内存、网络)、应用日志聚合。
金融领域:股票价格、交易量、风险指标的时间序列分析。
工业互联网:生产线设备状态监控、预测性维护。
与传统数据库的对比
|
特性 |
InfluxDB |
关系型数据库 |
|---|---|---|
|
数据模型 |
时序数据(时间戳+标签+字段 |
表格(行+列) |
|
写入性能 |
极高(针对时间序列优化) |
较低(事务开销大) |
|
查询语言 |
Flux(专为时序设计) |
SQL(通用但非时序优化) |
|
存储效率 |
高压缩比 |
较低(无时序优化) |
二、SpringBoot集成
1、添加依赖
在SpringBoot项目的pom.xml文件中添加以下依赖:
<dependency>
<groupId>com.influxdb</groupId>
<artifactId>influxdb-client-java</artifactId>
<version>6.9.0</version>
</dependency>
2、配置InfluxDB连接
在application.xml中添加InfluxDB的连接配置信息:
spring:
data:
influxdb:
url: http://127.0.0.1:8086
token: testtoken
org: testorg
bucket: testbucket
3、初始化InfluxDB连接
创建一个配置类来初始化InfluxDB连接:
import com.influxdb.client.BucketsApi;
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.InfluxDBClientFactory;
import com.influxdb.client.domain.Bucket;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* 初始化InfluxDB配置类
* @author 大凌青年
* @version 1.0
*/
@Data
@Component
@ConfigurationProperties("spring.data.influxdb")
public class InfluxDBConfig {
/**
* 连接地址
*/
private String url;
/**
* 认证token
*/
private String token;
/**
* 组织
*/
private String org;
/**
* 数据库
*/
private String bucket;
@Bean
public InfluxDBClient influxDBClient() {
return InfluxDBClientFactory.create(url, token.toCharArray(), org, bucket);
}
@PostConstruct
public void initializeBucket() {
InfluxDBClient client = influxDBClient();
BucketsApi bucketsApi = client.getBucketsApi();
// 获取组织信息以获得正确的orgID
String orgId = client.getOrganizationsApi()
.findOrganizations().stream()
.filter(o -> o.getName().equals(org))
.findFirst()
.orElseThrow(() -> new RuntimeException("Organization not found: " + org))
.getId();
// 检查bucket是否存在
Optional<Bucket> existingBucket = bucketsApi.findBuckets().stream()
.filter(b -> b.getName().equals(bucket) && b.getOrgID().equals(orgId))
.findFirst();
// 如果bucket不存在,则创建
if (!existingBucket.isPresent()) {
// 创建Bucket对象并设置名称和orgID
Bucket newBucket = new Bucket();
newBucket.setName(bucket);
newBucket.setOrgID(orgId);
bucketsApi.createBucket(newBucket);
}
}
}
三、常用方法封装
1、接口类定义:
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.write.Point;
import com.influxdb.query.FluxRecord;
import com.influxdb.query.FluxTable;
import com.kind.ies.common.core.pojo.PaginationResult;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* InfluxDB 2.x 服务接口
*
* 提供对InfluxDB数据库的完整操作支持,包括数据写入、查询、聚合统计等功能
* 主要面向时序数据处理场景,支持Flux查询语法
*
* @author 大凌青年
* @version 1.0
*/
public interface InfluxDBService {
/**
* 获取InfluxDB客户端对象
*
* @return InfluxDBClient InfluxDB客户端对象
*/
InfluxDBClient getClient();
/**
* 创建数据点对象,用于后续写入操作
*
* @param measurement 表名/测量名称,不能为空
* @param time 时间戳,可为null(使用服务器当前时间)
* @param tags 标签数据(key-value对),用于索引和查询过滤
* @param fields 字段数据(key-value对),存储实际的时序数值
* @return Point 数据点对象,可用于写入操作
* @throws IllegalArgumentException 当measurement为空时抛出
*/
Point createPoint(String measurement, Instant time, Map<String, String> tags, Map<String, Object> fields);
/**
* 写入单条数据点到默认bucket
*
* @param measurement 表名/测量名称
* @param time 时间戳
* @param tags 标签数据,用于高效查询和分组
* @param fields 字段数据,存储实际业务数值
*/
void writePoint(String measurement, Instant time, Map<String, String> tags, Map<String, Object> fields);
/**
* 写入数据点对象到默认bucket
*
* @param point 已构建的数据点对象,不能为null
* @see #createPoint(String, Instant, Map, Map)
*/
void writePoint(Point point);
/**
* 批量写入数据点到默认bucket
*
* @param points 数据点列表,不能为null
*/
void writePoints(List<Point> points);
/**
* 执行自定义Flux查询语句
*
* @param query 完整的Flux查询语句,必须包含对bucket的引用
* @return List<FluxTable> 查询结果表列表,每个表代表一个数据流
* @throws com.influxdb.exceptions.InfluxException 查询执行失败时抛出
*/
List<FluxTable> query(String query);
/**
* 处理查询结果,提取所有记录
*
* @param tables 查询结果表
* @return List<FluxRecord> 记录列表
*/
List<FluxRecord> getRecordsFromTables(List<FluxTable> tables);
/**
* 按时间范围查询指定measurement的数据
*
* @param measurement 表名/测量名称
* @param start 查询开始时间(包含)
* @param end 查询结束时间(包含)
* @return List<FluxTable> 符合时间范围的数据结果
*/
List<FluxTable> queryByTimeRange(String measurement, LocalDateTime start, LocalDateTime end);
/**
* 查询最近N分钟的数据记录
*
* @param measurement 表名/测量名称
* @param minutes 查询时间范围(分钟),必须大于0
* @return List<FluxTable> 最近N分钟内的数据记录
* @throws IllegalArgumentException 当minutes小于等于0时抛出
*/
List<FluxTable> queryLastMinutes(String measurement, int minutes);
/**
* 按标签过滤查询
*
* @param measurement 表名/测量名称
* @param tags 标签过滤条件
* @return List<FluxTable> 查询结果
*/
List<FluxTable> queryByTags(String measurement, Map<String, String> tags);
/**
* 按时间窗口进行数据聚合统计
*
* 支持常见的聚合函数如mean(平均值)、max(最大值)、min(最小值)、sum(求和)等
*
* @param measurement 表名/测量名称
* @param window 时间窗口大小,如"1h"(1小时)、"30m"(30分钟)、"1d"(1天)
* @param aggFunction 聚合函数名称,如"mean"、"max"、"min"、"sum"
* @param tags 标签过滤条件,可为null表示不过滤
* @param fields 参与聚合的字段名列表,可为null表示所有字段
* @return List<FluxTable> 聚合统计结果
*/
List<FluxTable> queryWithAggregation(String measurement, String window, String aggFunction, Map<String, String> tags, List<String> fields);
/**
* 按时间范围和聚合函数查询数据
*
* @param measurement 表名/测量名称
* @param tags 标签过滤条件
* @param fields 字段名列表
* @param aggFunction 聚合函数 (max, min, mean等)
* @param startTime 开始时间
* @param endTime 结束时间
* @return List<FluxTable> 查询结果
*/
List<FluxTable> queryAggregationWithTimeRange(String measurement, Map<String, String> tags,
List<String> fields, String aggFunction,
Instant startTime, Instant endTime);
/**
* 查询最新值记录
*
* @param measurement 表名/测量名称
* @return List<FluxTable> 查询结果
*/
List<FluxTable> queryLast(String measurement);
/**
* 查询指定字段的最新值
*
* @param measurement 表名/测量名称
* @param field 字段名
* @param tags 标签过滤条件
* @return List<FluxTable> 查询结果
*/
List<FluxTable> queryLastFieldsWithTags(String measurement, String field, Map<String, String> tags);
/**
* 查询指定字段的最新值
*
* @param measurement 表名/测量名称
* @param fields 字段名列表
* @param tags 标签过滤条件
* @return List<FluxTable> 查询结果
*/
List<FluxTable> queryLastFieldsWithTags(String measurement, List<String> fields, Map<String, String> tags);
/**
* 分页查询数据
*
* @param measurement 表名/测量名称
* @param tags tags过滤条件
* @param fields 字段列表
* @param page 页码(从1开始)
* @param size 每页数量
* @return PaginationResult<FluxTable> 查询结果
*/
PaginationResult<FluxTable> queryWithPagination(String measurement, Map<String, String> tags, List<String> fields, int page, int size);
/**
* 将查询结果转换为易于处理的Map列表格式
*
* 自动合并具有相同时间戳和标签的记录,将多个字段整合到单个Map中
*
* @param tables 查询结果表列表
* @return List<Map<String, Object>> 转换后的数据列表,每个Map代表一条记录
* 包含_time(时间戳)、_measurement(表名)等系统字段及所有用户字段
*/
List<Map<String, Object>> convertToMapList(List<FluxTable> tables);
/**
* 从查询结果中提取单一数值
*
* 适用于只关注特定字段最新值或聚合值的场景
*
* @param tables 查询结果表列表
* @return Object 提取的单一值,当无结果时返回null
*/
Object getSingleValue(List<FluxTable> tables);
/**
* 查询指定measurement的总记录数
*
* @param measurement 表名/测量名称
* @return long 记录数
*/
long count(String measurement);
/**
* 按时间窗口统计记录数
*
* @param measurement 表名/测量名称
* @param field 字段名
* @param range 时间范围
* @param window 时间窗口 (如 "10m"表示10分钟)
* @return List<FluxTable> 查询结果
*/
List<FluxTable> countWithFieldByWindow(String measurement, String field, String range, String window);
/**
* 获取窗口统计结果的时间和值信息
*
* @param tables 查询结果表
* @return List<Map<String, Object>> 包含时间窗口和统计值的列表
*/
List<Map<String, Object>> getWindowCountResult(List<FluxTable> tables);
/**
* 按指定时间单位统计指定时间范围内某字段的数量
*
* 用于生成按时间维度的统计数据,如每日/每月/每年的数据量统计
*
* @param measurement 表名/测量名称
* @param tags 标签过滤条件,用于筛选特定数据
* @param field 统计的目标字段名
* @param duration 时间跨度数量
* @param unit 时间单位类型: "d"(天)、"M"(月)、"y"(年)
* @return List<Map<String, Long>> 按时间单位分组的统计结果
*/
List<Map<String, Long>> countByTimeUnit(String measurement, Map<String, String> tags, String field, Integer duration, String unit);
}
2、接口类实现:
import com.influxdb.client.InfluxDBClient;
import com.influxdb.client.QueryApi;
import com.influxdb.client.WriteApi;
import com.influxdb.client.domain.WritePrecision;
import com.influxdb.client.write.Point;
import com.influxdb.query.FluxRecord;
import com.influxdb.query.FluxTable;
import com.kind.framework.core.db.bean.Pagination;
import com.kind.ies.common.core.pojo.PaginationResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* InfluxDB 2.x 服务实现类
* @author 大凌青年
* @version 1.0
*/
@Service
public class InfluxDBServiceImpl implements InfluxDBService{
@Autowired
private InfluxDBClient influxDBClient;
@Value("${spring.data.influxdb.bucket}")
private String bucket;
/**
* 获取InfluxDB客户端对象
*
* @return InfluxDBClient InfluxDB客户端对象
*/
@Override
public InfluxDBClient getClient() {
return influxDBClient;
}
@Override
public Point createPoint(String measurement, Instant time, Map<String, String> tags, Map<String, Object> fields) {
Point point = Point.measurement(measurement)
.time(time, WritePrecision.MS);
// 添加标签
if (tags != null) {
for (Map.Entry<String, String> entry : tags.entrySet()) {
point.addTag(entry.getKey(), entry.getValue());
}
}
// 添加字段
if (fields != null) {
for (Map.Entry<String, Object> entry : fields.entrySet()) {
Object value = entry.getValue();
String key = entry.getKey();
// 根据值的类型调用相应的addField方法
if (value instanceof String) {
point.addField(key, (String) value);
} else if (value instanceof Long) {
point.addField(key, (Long) value);
} else if (value instanceof Integer) {
point.addField(key, (Integer) value);
} else if (value instanceof Double) {
point.addField(key, (Double) value);
} else if (value instanceof Boolean) {
point.addField(key, (Boolean) value);
} else if (value != null) {
// 对于其他类型,转换为字符串
point.addField(key, value.toString());
}
}
}
return point;
}
@Override
public void writePoint(String measurement, Instant time, Map<String, String> tags, Map<String, Object> fields) {
Point point = createPoint(measurement, time, tags, fields);
writePoint(point);
}
@Override
public void writePoint(Point point) {
try (WriteApi writeApi = influxDBClient.makeWriteApi()) {
writeApi.writePoint(point);
}
}
@Override
public void writePoints(List<Point> points) {
try (WriteApi writeApi = influxDBClient.makeWriteApi()) {
writeApi.writePoints(points);
}
}
@Override
public List<FluxTable> query(String query) {
QueryApi queryApi = influxDBClient.getQueryApi();
return queryApi.query(query);
}
@Override
public List<FluxRecord> getRecordsFromTables(List<FluxTable> tables) {
return tables.stream()
.flatMap(table -> table.getRecords().stream())
.collect(java.util.stream.Collectors.toList());
}
@Override
public List<FluxTable> queryByTimeRange(String measurement, LocalDateTime start, LocalDateTime end) {
String startTime = start.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
String endTime = end.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
String query = String.format("from(bucket: \"%s\") " +
"|> range(start: %s, stop: %s) " +
"|> filter(fn: (r) => r._measurement == \"%s\")",
bucket, startTime, endTime, measurement);
return query(query);
}
@Override
public List<FluxTable> queryLastMinutes(String measurement, int minutes) {
String query = String.format("from(bucket: \"%s\") " +
"|> range(start: -%dm) " +
"|> filter(fn: (r) => r._measurement == \"%s\")",
bucket, minutes, measurement);
return query(query);
}
@Override
public List<FluxTable> queryByTags(String measurement, Map<String, String> tags) {
StringBuilder filterBuilder = new StringBuilder();
filterBuilder.append(String.format("from(bucket: \"%s\") " +
"|> range(start: 0) " +
"|> filter(fn: (r) => r._measurement == \"%s\")", bucket, measurement));
for (Map.Entry<String, String> entry : tags.entrySet()) {
filterBuilder.append(String.format(" " +
"|> filter(fn: (r) => r.%s == \"%s\")",
entry.getKey(), entry.getValue()));
}
return query(filterBuilder.toString());
}
@Override
public List<FluxTable> queryWithAggregation(String measurement, String window, String aggFunction,
Map<String, String> tags, List<String> fields) {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append(String.format("from(bucket: \"%s\") " +
"|> range(start: 0) " +
"|> filter(fn: (r) => r._measurement == \"%s\")",
bucket, measurement));
// 添加标签过滤条件
if (tags != null && !tags.isEmpty()) {
for (Map.Entry<String, String> entry : tags.entrySet()) {
queryBuilder.append(String.format(" " +
"|> filter(fn: (r) => r.%s == \"%s\")",
entry.getKey(), entry.getValue()));
}
}
// 添加字段过滤条件
if (fields != null && !fields.isEmpty()) {
StringBuilder fieldCondition = new StringBuilder();
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
fieldCondition.append(" or ");
}
fieldCondition.append(String.format("r._field == \"%s\"", fields.get(i)));
}
queryBuilder.append(String.format(" |> filter(fn: (r) => %s)", fieldCondition));
}
// 添加数值类型过滤和转换
queryBuilder.append(" |> filter(fn: (r) => exists r._value)");
// 将值转换为浮点数,这样可以处理整数和浮点数,同时过滤掉无法转换的字符串
queryBuilder.append(" |> toFloat()");
// 过滤掉转换后为null的记录
queryBuilder.append(" |> filter(fn: (r) => exists r._value)");
return query(queryBuilder.toString());
}
@Override
public List<FluxTable> queryAggregationWithTimeRange(String measurement, Map<String, String> tags,
List<String> fields, String aggFunction,
Instant startTime, Instant endTime) {
// 将Instant时间转换为RFC3339格式字符串
String startStr = startTime.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
String endStr = endTime.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
// 构建基础查询
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append(String.format("from(bucket: \"%s\") " +
"|> range(start: %s, stop: %s) " +
"|> filter(fn: (r) => r._measurement == \"%s\")",
bucket, startStr, endStr, measurement));
// 添加标签过滤条件
if (tags != null && !tags.isEmpty()) {
for (Map.Entry<String, String> entry : tags.entrySet()) {
queryBuilder.append(String.format(" " +
"|> filter(fn: (r) => r.%s == \"%s\")",
entry.getKey(), entry.getValue()));
}
}
// 添加字段过滤条件
if (fields != null && !fields.isEmpty()) {
StringBuilder fieldCondition = new StringBuilder();
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
fieldCondition.append(" or ");
}
fieldCondition.append(String.format("r._field == \"%s\"", fields.get(i)));
}
queryBuilder.append(String.format(" |> filter(fn: (r) => %s)", fieldCondition));
}
// 添加数值类型过滤和转换
queryBuilder.append(" |> filter(fn: (r) => exists r._value)");
// 将值转换为浮点数,这样可以处理整数和浮点数,同时过滤掉无法转换的字符串
queryBuilder.append(" |> toFloat()");
// 添加聚合函数
queryBuilder.append(String.format(" |> %s()", aggFunction));
// 执行查询并返回结果
return query(queryBuilder.toString());
}
@Override
public List<FluxTable> queryLast(String measurement) {
String query = String.format("from(bucket: \"%s\") " +
"|> range(start: 0) " +
"|> filter(fn: (r) => r._measurement == \"%s\") " +
"|> last()",
bucket, measurement);
return query(query);
}
@Override
public List<FluxTable> queryLastFieldsWithTags(String measurement, String field, Map<String, String> tags) {
return queryLastFieldsWithTags(measurement, Collections.singletonList(field), tags);
}
@Override
public List<FluxTable> queryLastFieldsWithTags(String measurement, List<String> fields, Map<String, String> tags) {
StringBuilder filterBuilder = new StringBuilder();
filterBuilder.append(String.format("from(bucket: \"%s\") " +
"|> range(start: 0) " +
"|> filter(fn: (r) => r._measurement == \"%s\")", bucket, measurement));
// 添加标签过滤条件
for (Map.Entry<String, String> entry : tags.entrySet()) {
filterBuilder.append(String.format(" " +
"|> filter(fn: (r) => r.%s == \"%s\")",
entry.getKey(), entry.getValue()));
}
// 构造字段的或关系过滤条件
if (fields != null && !fields.isEmpty()) {
StringBuilder fieldCondition = new StringBuilder();
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
fieldCondition.append(" or ");
}
fieldCondition.append(String.format("r._field == \"%s\"", fields.get(i)));
}
filterBuilder.append(String.format(" |> filter(fn: (r) => %s)", fieldCondition));
}
filterBuilder.append("|> last()");
return query(filterBuilder.toString());
}
@Override
public PaginationResult<FluxTable> queryWithPagination(String measurement, Map<String, String> tags,
List<String> fields, int page, int size) {
// 构建基础查询
StringBuilder baseQuery = new StringBuilder();
baseQuery.append(String.format("from(bucket: \"%s\") " +
"|> range(start: 0) " +
"|> filter(fn: (r) => r._measurement == \"%s\") ",
bucket, measurement));
// 添加标签过滤条件
if (tags != null && !tags.isEmpty()) {
for (Map.Entry<String, String> entry : tags.entrySet()) {
baseQuery.append(String.format(" " +
"|> filter(fn: (r) => r.%s == \"%s\")",
entry.getKey(), entry.getValue()));
}
}
// 添加字段过滤条件
if (fields != null && !fields.isEmpty()) {
StringBuilder fieldCondition = new StringBuilder();
for (int i = 0; i < fields.size(); i++) {
if (i > 0) {
fieldCondition.append(" or ");
}
fieldCondition.append(String.format("r._field == \"%s\"", fields.get(i)));
}
baseQuery.append(String.format(" |> filter(fn: (r) => %s)", fieldCondition));
}
// 查询总数 - 使用手动计数方式
long totalCount = 0;
try {
String uniqueQuery = baseQuery.toString() +
" |> keep(columns: [\"_time\"]) " +
" |> unique(column: \"_time\")";
List<FluxTable> uniqueResult = query(uniqueQuery);
// 手动计算唯一时间点数量
int count = 0;
for (FluxTable table : uniqueResult) {
count += table.getRecords().size();
}
totalCount = count;
} catch (Exception e) {
System.err.println("Error calculating total count: " + e.getMessage());
e.printStackTrace();
totalCount = 0;
}
// 如果总数为0,直接返回空结果
if (totalCount == 0) {
return PaginationResult.<FluxTable>builder()
.data(new ArrayList<>())
.totalCount(0)
.totalPages(0)
.currentPage(page)
.pageSize(size)
.build();
}
// 第一步:获取当前页的时间点
StringBuilder timePointsQuery = new StringBuilder(baseQuery);
timePointsQuery.append(" |> keep(columns: [\"_time\"])")
.append(" |> unique(column: \"_time\")")
.append(" |> sort(columns: [\"_time\"], desc: true)");
// 分页获取时间点
int offset = (page - 1) * size;
timePointsQuery.append(String.format(" |> limit(n: %d, offset: %d)", size, offset));
List<FluxTable> timePointTables = query(timePointsQuery.toString());
// 提取时间点
List<Instant> timePoints = timePointTables.stream()
.flatMap(table -> table.getRecords().stream())
.map(FluxRecord::getTime)
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// 如果没有时间点,返回空结果
if (timePoints.isEmpty()) {
return PaginationResult.<FluxTable>builder()
.data(new ArrayList<>())
.totalCount(totalCount)
.totalPages((int)((totalCount + size - 1) / size))
.currentPage(page)
.pageSize(size)
.build();
}
// 第二步:根据时间点查询详细数据
StringBuilder dataQuery = new StringBuilder(baseQuery);
dataQuery.append(" |> sort(columns: [\"_time\"], desc: true)");
// 构建时间点过滤条件
StringBuilder timeFilter = new StringBuilder();
timeFilter.append(" |> filter(fn: (r) => ");
for (int i = 0; i < timePoints.size(); i++) {
if (i > 0) {
timeFilter.append(" or ");
}
String timeStr = timePoints.get(i).atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
timeFilter.append(String.format("r._time == %s", timeStr));
}
timeFilter.append(")");
dataQuery.append(timeFilter.toString());
List<FluxTable> data = query(dataQuery.toString());
return PaginationResult.<FluxTable>builder()
.data(data)
.totalCount(totalCount)
.totalPages((int)((totalCount + size - 1) / size))
.currentPage(page)
.pageSize(size)
.build();
}
@Override
public List<Map<String, Object>> convertToMapList(List<FluxTable> tables) {
// 按时间戳和标签分组记录
Map<String, Map<String, Object>> resultMap = new LinkedHashMap<>();
// 先收集所有记录并按时间排序
List<FluxRecord> allRecords = tables.stream()
.flatMap(table -> table.getRecords().stream())
.sorted((r1, r2) -> r2.getTime().compareTo(r1.getTime())) // 按时间倒序排序
.collect(Collectors.toList());
allRecords.forEach(record -> {
// 创建唯一键,基于measurement、时间戳和标签
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(record.getMeasurement()).append("|");
keyBuilder.append(record.getTime().toString()).append("|");
// 添加标签到键中
record.getValues().entrySet().stream()
.filter(entry -> entry.getValue() instanceof String && !entry.getKey().startsWith("_"))
.sorted(Map.Entry.comparingByKey())
.forEach(entry -> keyBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append(","));
String key = keyBuilder.toString();
// 如果已存在相同键的Map,则添加字段;否则创建新的Map
Map<String, Object> map = resultMap.computeIfAbsent(key, k -> {
Map<String, Object> newMap = new LinkedHashMap<>();
newMap.put("_measurement", record.getMeasurement());
newMap.put("_time", record.getTime());
// 添加标签
record.getValues().entrySet().stream()
.filter(entry -> entry.getValue() instanceof String && !entry.getKey().startsWith("_"))
.forEach(entry -> newMap.put(entry.getKey(), entry.getValue()));
return newMap;
});
// 添加字段值
map.put(record.getField(), record.getValue());
});
return new ArrayList<>(resultMap.values());
}
@Override
public Object getSingleValue(List<FluxTable> tables) {
return tables.stream()
.flatMap(table -> table.getRecords().stream())
.findFirst()
.map(FluxRecord::getValue)
.orElse(null);
}
@Override
public long count(String measurement) {
String query = String.format("from(bucket: \"%s\") " +
"|> range(start: 0) " +
"|> filter(fn: (r) => r._measurement == \"%s\") " +
"|> count()",
bucket, measurement);
List<FluxTable> result = query(query);
return result.stream()
.flatMap(table -> table.getRecords().stream())
.findFirst()
.map(record -> ((Number) record.getValue()).longValue())
.orElse(0L);
}
@Override
public List<FluxTable> countWithFieldByWindow(String measurement, String field, String range, String window) {
String query = String.format("from(bucket: \"%s\") " +
"|> range(start: %s) " +
"|> filter(fn: (r) => r._measurement == \"%s\") " +
"|> filter(fn: (r) => r._field == \"%s\") " +
"|> window(every: %s) " +
"|> count()" +
"|> group(columns: [\"_measurement\", \"_field\"], mode: \"except\")",
bucket, range, measurement, field, window);
return query(query);
}
@Override
public List<Map<String, Object>> getWindowCountResult(List<FluxTable> tables) {
List<Map<String, Object>> result = new ArrayList<>();
for (FluxTable table : tables) {
List<FluxRecord> records = table.getRecords();
// 合并同一窗口的记录
Instant windowStart = null;
Instant windowStop = null;
Object value = null;
for (FluxRecord record : records) {
// 获取窗口时间信息
Object startObj = record.getValueByKey("_start");
Object stopObj = record.getValueByKey("_stop");
if (startObj instanceof Instant && windowStart == null) {
windowStart = (Instant) startObj;
}
if (stopObj instanceof Instant && windowStop == null) {
windowStop = (Instant) stopObj;
}
// 获取统计值
if (record.getValue() != null && value == null) {
value = record.getValue();
}
}
// 只有当有完整数据时才添加
if (windowStart != null && value != null) {
Map<String, Object> map = new HashMap<>();
// 将Instant时间转换为时间戳(毫秒)
map.put("start", windowStart.toEpochMilli());
map.put("stop", windowStop != null ? windowStop.toEpochMilli() : null);
map.put("value", value);
// 使用窗口结束时间作为代表时间,并转换为时间戳
map.put("time", windowStop != null ? windowStop.toEpochMilli() : null);
result.add(map);
}
}
return result;
}
@Override
public List<Map<String, Long>> countByTimeUnit(String measurement, Map<String, String> tags, String field, Integer duration, String unit) {
if (duration == null || duration <= 0) {
return new ArrayList<>();
}
ZoneId systemZone = ZoneId.systemDefault();
Map<String, Long> timeUnitCountMap = new LinkedHashMap<>();
LocalDateTime now = LocalDateTime.now(systemZone);
DateTimeFormatter formatter;
LocalDateTime startTime;
// 根据时间单位类型初始化时间和格式化器
switch (unit) {
case "1":
formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
LocalDateTime today = now.withHour(0).withMinute(0).withSecond(0).withNano(0);
startTime = today.minusDays(duration - 1);
// 初始化日期映射
for (int i = duration - 1; i >= 0; i--) {
LocalDateTime targetDay = today.minusDays(i);
String dateStr = targetDay.format(formatter);
timeUnitCountMap.put(dateStr, 0L);
}
break;
case "2":
formatter = DateTimeFormatter.ofPattern("yyyy-MM");
LocalDateTime currentMonth = now.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
startTime = currentMonth.minusMonths(duration - 1);
// 初始化月份映射
for (int i = duration - 1; i >= 0; i--) {
LocalDateTime targetMonth = currentMonth.minusMonths(i);
String monthStr = targetMonth.format(formatter);
timeUnitCountMap.put(monthStr, 0L);
}
break;
case "3":
formatter = DateTimeFormatter.ofPattern("yyyy");
LocalDateTime currentYear = now.withDayOfYear(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
startTime = currentYear.minusYears(duration - 1);
// 初始化年份映射
for (int i = duration - 1; i >= 0; i--) {
LocalDateTime targetYear = currentYear.minusYears(i);
String yearStr = targetYear.format(formatter);
timeUnitCountMap.put(yearStr, 0L);
}
break;
default:
throw new IllegalArgumentException("Unsupported time unit: " + unit);
}
// 构建查询时间范围
LocalDateTime endTime = now;
String startStr = startTime.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
String endStr = endTime.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT);
// 构建Flux查询语句
StringBuilder query = new StringBuilder();
query.append(String.format("from(bucket: \"%s\") " +
"|> range(start: %s, stop: %s) " +
"|> filter(fn: (r) => r._measurement == \"%s\") " +
"|> filter(fn: (r) => r._field == \"%s\")",
bucket, startStr, endStr, measurement, field));
// 添加标签过滤条件
if (tags != null && !tags.isEmpty()) {
for (Map.Entry<String, String> entry : tags.entrySet()) {
query.append(String.format(" " +
"|> filter(fn: (r) => r.%s == \"%s\")",
entry.getKey(), entry.getValue()));
}
}
try {
List<FluxTable> queryResult = query(query.toString());
// 手动按时间单位分组计数
Map<String, Long> tempCountMap = new HashMap<>();
if (queryResult != null && !queryResult.isEmpty()) {
for (FluxTable table : queryResult) {
if (table != null && table.getRecords() != null) {
for (FluxRecord record : table.getRecords()) {
Instant time = record.getTime();
if (time != null) {
// 使用系统默认时区将时间转换为相应的时间单位字符串
LocalDateTime localDateTime = LocalDateTime.ofInstant(time, systemZone);
String timeUnitStr = localDateTime.format(formatter);
// 计数
tempCountMap.put(timeUnitStr, tempCountMap.getOrDefault(timeUnitStr, 0L) + 1);
}
}
}
}
}
// 更新主映射
for (Map.Entry<String, Long> entry : tempCountMap.entrySet()) {
if (timeUnitCountMap.containsKey(entry.getKey())) {
timeUnitCountMap.put(entry.getKey(), entry.getValue());
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 转换为List<Map<String, Long>>格式
List<Map<String, Long>> result = new ArrayList<>();
for (Map.Entry<String, Long> entry : timeUnitCountMap.entrySet()) {
Map<String, Long> countMap = new HashMap<>();
countMap.put(entry.getKey(), entry.getValue());
result.add(countMap);
}
return result;
}
}
311

被折叠的 条评论
为什么被折叠?



