Elasticsearch - Java API【一】日期直方图

Elasticsearch 按时间进行聚合统计

需求:

1、统计某一个小时,每分钟的数据条数

2、统计某一天,每个小数的数据条数

3、统计一周,每天的数据条数

pom依赖

bash 复制代码
     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

代码实现

bash 复制代码
package com.woodare.tsp.portal.facade;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.woodare.tsp.common.core.context.Context;
import com.woodare.tsp.portal.enums.DateType;
import com.woodare.tsp.portal.helper.DateHelper;
import com.woodare.tsp.portal.pojo.vo.DashboardVO;
import com.woodare.tsp.portal.pojo.vo.StatisticsChartVO;
import com.woodare.tsp.service.consts.ElasticsearchIndexNameConst;
import com.woodare.tsp.service.domain.Product;
import com.woodare.tsp.service.entity.IotDataLog;
import com.woodare.tsp.service.service.DeviceOnlineStatusService;
import com.woodare.tsp.service.service.DeviceService;
import com.woodare.tsp.service.service.ProductService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
import org.elasticsearch.search.aggregations.bucket.histogram.ParsedDateHistogram;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.elasticsearch.core.ElasticsearchAggregations;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;

/**
 * @author WANG
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class DashboardFacade {


    final DeviceService deviceService;
    final ProductService productService;
    final DeviceOnlineStatusService deviceOnlineStatusService;
    final ElasticsearchRestTemplate elasticsearchRestTemplate;
    @Value("${device.online.time:10}")
    private Integer deviceOnlineTime;

    /**
     * 统计
     *
     * @return 统计详情
     */
    public DashboardVO statistics() {
        DashboardVO dashboardVO = new DashboardVO();
        List<Product> productList = productService.getListByCondition(new Product());
        Long totalCount = deviceService.getTotalDeviceCount();
        // 设备最后在线时间在最近{10}分钟内,算在线
        long time = DateUtil.offsetMinute(new Date(), -deviceOnlineTime).getTime();
        long onlineCount = 0;
        for (Product product : productList) {
            onlineCount += deviceOnlineStatusService.count(product.getUid(), time);
        }
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        String tenantId = Context.getTenantId();
        if (StrUtil.isNotBlank(tenantId)) {
            queryBuilder.filter(QueryBuilders.termQuery("tenantId", tenantId));
        }
        StatisticsChartVO.DateRange dateRange = this.getDateRange(DateType.DAY);
        queryBuilder.filter(QueryBuilders.rangeQuery("createdTime").gte(dateRange.getStart()).lte(dateRange.getEnd()));
        IndexCoordinates indexCoordinates = IndexCoordinates.of(ElasticsearchIndexNameConst.TEMP_IOT_DATA_LOG);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withQuery(queryBuilder)
                .build();
        SearchHits<IotDataLog> searchHits = elasticsearchRestTemplate.search(query, IotDataLog.class, indexCoordinates);
        long totalHits = searchHits.getTotalHits();
        return dashboardVO
                .setTotalAlertCount(0L)
                .setTotalMessageCount(totalHits)
                .setTotalDeviceCount(totalCount)
                .setTotalOnlineDeviceCount(onlineCount);
    }

    public StatisticsChartVO statisticsChart(DateType dateType) {
        StatisticsChartVO statisticsChartVO = new StatisticsChartVO();
        StatisticsChartVO.DateRange dateRange = this.getDateRange(dateType);

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        String tenantId = Context.getTenantId();
        if (StrUtil.isNotBlank(tenantId)) {
            queryBuilder.filter(QueryBuilders.termQuery("tenantId", tenantId));
        }
        queryBuilder.filter(QueryBuilders.rangeQuery("createdTime").gte(dateRange.getStart()).lte(dateRange.getEnd()));
        NativeSearchQuery query = this.buildQueryByDateType(queryBuilder, dateType);
        SearchHits<Object> searchHits = elasticsearchRestTemplate.search(query, Object.class, IndexCoordinates.of(ElasticsearchIndexNameConst.TEMP_IOT_DATA_LOG));
        ElasticsearchAggregations elasticsearchAggregations = (ElasticsearchAggregations) searchHits.getAggregations();
        assert elasticsearchAggregations != null;
        Aggregations aggregations = elasticsearchAggregations.aggregations();
        Map<String, Long> map = new LinkedHashMap<>();
        for (Aggregation aggregation : aggregations) {
            ParsedDateHistogram dateHistogram = (ParsedDateHistogram) aggregation;
            for (Histogram.Bucket bucket : dateHistogram.getBuckets()) {
                long docCount = bucket.getDocCount();
                String date = bucket.getKeyAsString();
                map.put(date, docCount);
                log.info("data: {} --- {}", date, docCount);
            }
        }
        List<StatisticsChartVO.DataItem> dataList = this.buildDataByDateType(map, dateType, dateRange);
        statisticsChartVO.setDataList(dataList);
        return statisticsChartVO;
    }

    private List<StatisticsChartVO.DataItem> buildDataByDateType(Map<String, Long> map, DateType dateType, StatisticsChartVO.DateRange dateRange) {
        List<String> dateList;
        String timeZone = Context.getTimezone();
        List<StatisticsChartVO.DataItem> list = new ArrayList<>();
        if (DateType.HOUR.equals(dateType)) {
            dateList = DateHelper.getMinuteList(dateRange.getStart(), dateRange.getEnd());
        } else if (DateType.DAY.equals(dateType)) {
            dateList = DateHelper.getHourList();
        } else {
            dateList = DateHelper.getDayList(dateRange.getStart(), dateRange.getEnd());
        }
        if (DateType.HOUR.equals(dateType)) {
            buildHourDataList(map, dateList, list);
        } else if (DateType.DAY.equals(dateType)) {
            for (String date : dateList) {
                // es查询出来的是日期是0时区的日期,需转成当前时区的时间,例如:时间为14:00,这是0时区的14:00,转成+8区则为22:00
                String hour = date.split(StrUtil.COLON)[0];
                String newHour = getHour(Integer.parseInt(hour), Integer.parseInt(timeZone));
                StatisticsChartVO.DataItem item = new StatisticsChartVO.DataItem();
                String newDate = newHour + StrUtil.COLON + date.split(StrUtil.COLON)[1];
                item.setDate(newDate);
                item.setValue(map.containsKey(date) ? map.remove(date) : 0L);
                list.add(item);
            }
        } else {
            for (String date : dateList) {
                StatisticsChartVO.DataItem item = new StatisticsChartVO.DataItem();
                item.setDate(date);
                item.setValue(map.getOrDefault(date, 0L));
                list.add(item);
            }
        }
        list.sort(Comparator.comparing(StatisticsChartVO.DataItem::getDate));
        return list;
    }

    private void buildHourDataList(Map<String, Long> map, List<String> dateList, List<StatisticsChartVO.DataItem> list) {
        if (MapUtil.isEmpty(map)) {
            setDefaultData(dateList, list);
        } else {
            String offsetHour = dateList.get(0).split(StrUtil.COLON)[0];
            String currUtcHour = getHourInZeroTimeZone(System.currentTimeMillis());
            // 先去除其他数据
            Map<String, Long> stashMap = new LinkedHashMap<>();
            for (Map.Entry<String, Long> entry : map.entrySet()) {
                String date = entry.getKey();
                String currHour = date.split(StrUtil.COLON)[0];
                if (currHour.equals(currUtcHour)) {
                    String minute = date.split(StrUtil.COLON)[1];
                    stashMap.put(offsetHour + StrUtil.COLON + minute, entry.getValue());
                }
            }
            if (MapUtil.isEmpty(stashMap)) {
                setDefaultData(dateList, list);
            } else {
                for (String date : dateList) {
                    StatisticsChartVO.DataItem item = new StatisticsChartVO.DataItem();
                    item.setDate(date);
                    item.setValue(stashMap.getOrDefault(date, 0L));
                    list.add(item);
                }
            }
        }
    }

    public String getHourInZeroTimeZone(long timestamp) {
        LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC);
        int hour = dateTime.getHour();
        return (hour < 10 ? "0" : "") + hour;
    }


    private void setDefaultData(List<String> dateList, List<StatisticsChartVO.DataItem> list) {
        for (String date : dateList) {
            StatisticsChartVO.DataItem item = new StatisticsChartVO.DataItem();
            item.setDate(date);
            item.setValue(0L);
            list.add(item);
        }
    }

    private String getHour(int num1, int num2) {
        int num = num1 + num2;
        if (num >= 24) {
            num -= 24;
        }
        if (num < 0) {
            num += 24;
        }
        if (num < 10) {
            return "0" + num;
        } else {
            return StrUtil.EMPTY + num;
        }
    }


    private NativeSearchQuery buildQueryByDateType(BoolQueryBuilder queryBuilder, DateType dateType) {
        if (DateType.HOUR.equals(dateType)) {
            return new NativeSearchQueryBuilder()
                    .withQuery(queryBuilder)
                    .withAggregations(
                            AggregationBuilders
                                    .dateHistogram("minute_stats")
                                    .field("createdTime")
                                    .calendarInterval(DateHistogramInterval.MINUTE)
                                    .format("HH:mm")
                    )
                    .build();
        } else if (DateType.DAY.equals(dateType)) {
            return new NativeSearchQueryBuilder()
                    .withQuery(queryBuilder)
                    .withAggregations(
                            AggregationBuilders
                                    .dateHistogram("minute_stats")
                                    .field("createdTime")
                                    .calendarInterval(DateHistogramInterval.HOUR)
                                    .format("HH:mm")
                    )
                    .build();
        } else {
            return new NativeSearchQueryBuilder()
                    .withQuery(queryBuilder)
                    .withAggregations(
                            AggregationBuilders.dateHistogram("logs_per_day")
                                    .field("createdTime")
                                    .calendarInterval(DateHistogramInterval.DAY)
                                    .format("yyyy-MM-dd")
                    )
                    .build();
        }
    }

    private StatisticsChartVO.DateRange getDateRange(DateType dateType) {
        StatisticsChartVO.DateRange dateRange = new StatisticsChartVO.DateRange();
        if (DateType.HOUR.equals(dateType)) {
            dateRange.setStart(DateUtil.beginOfHour(new Date()).getTime());
            dateRange.setEnd(DateUtil.endOfHour(new Date()).getTime());
        } else if (DateType.DAY.equals(dateType)) {
            dateRange.setStart(DateUtil.beginOfDay(new Date()).getTime());
            dateRange.setEnd(DateUtil.endOfDay(new Date()).getTime());
        } else if (DateType.WEEK.equals(dateType)) {
            DateTime start = DateUtil.offsetDay(new Date(), -7);
            DateTime end = DateUtil.offsetDay(new Date(), -1);
            dateRange.setStart(start.getTime());
            dateRange.setEnd(end.getTime());
        }
        return dateRange;
    }
}
bash 复制代码
package com.woodare.tsp.portal.helper;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.woodare.tsp.common.core.context.Context;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author WANG
 */
public class DateHelper {

    private static final int HOURS_OF_DAY = 24;

    private static List<String> getMinuteIntervals(long startTimestamp, long endTimestamp) {
        List<String> intervals = new ArrayList<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
        int timezone = Integer.parseInt(Context.getTimezone());
        LocalDateTime current = LocalDateTime.ofEpochSecond(startTimestamp / 1000, 0, ZoneOffset.ofHours(timezone));
        LocalDateTime endTime = LocalDateTime.ofEpochSecond(endTimestamp / 1000, 0, ZoneOffset.ofHours(timezone));
        while (!current.isAfter(endTime)) {
            intervals.add(current.format(formatter));
            current = current.plusMinutes(1);
        }
        return intervals;
    }

    public static List<String> getHourList() {
        List<String> hourList = new ArrayList<>();
        for (int i = 0; i < HOURS_OF_DAY; i++) {
            String hour;
            if (i < 10) {
                hour = "0" + i;
            } else {
                hour = "" + i;
            }
            hourList.add(hour + ":00");
        }
        return hourList;
    }

    public static List<String> getMinuteList(Long start, Long end) {
        return getMinuteIntervals(start, end);
    }


    public static List<String> getDayList(Long start, Long end) {
        List<String> dates = new ArrayList<>();
        LocalDateTime startDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(start), ZoneId.systemDefault());
        LocalDateTime endDate = LocalDateTime.ofInstant(Instant.ofEpochMilli(end), ZoneId.systemDefault());

        while (!startDate.isAfter(endDate)) {
            dates.add(startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            startDate = startDate.plusDays(1);
        }

        return dates;
    }



    public static void main(String[] args) {
        Context.setTimezone("8");
        long start = DateUtil.beginOfHour(new Date()).getTime();
        long end = DateUtil.endOfHour(new Date()).getTime();
        List<String> intervals = getMinuteList(start, end);
        for (String interval : intervals) {
            System.out.println(interval);
        }
        System.out.println("==========");
        List<String> hourList = getHourList();
        for (String interval : hourList) {
            System.out.println(interval);
        }

        System.out.println("==========");
        DateTime startDate = DateUtil.offsetDay(new Date(), -7);
        DateTime endDate = DateUtil.offsetDay(new Date(), -1);

        List<String> dayList = getDayList(startDate.getTime(), endDate.getTime());
        for (String date : dayList) {
            System.out.println(date);
        }

    }


}
相关推荐
confiself10 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq041515 分钟前
J2EE平台
java·java-ee
XiaoLeisj21 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
豪宇刘36 分钟前
SpringBoot+Shiro权限管理
java·spring boot·spring
Elaine20239140 分钟前
02多线程基础知识
java·多线程
gorgor在码农42 分钟前
Redis 热key总结
java·redis·热key
百事老饼干1 小时前
Java[面试题]-真实面试
java·开发语言·面试
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
2402_857589361 小时前
SpringBoot框架:作业管理技术新解
java·spring boot·后端