java 计算连续日期时间,并填充统计接口缺失日期数据默认值

业务场景:在一些统计中,我们需要在一段时间范围对于系统现有数据进行统计查询,但是我们的业务系统中存在的数据可能是跳跃日期的。但是我们返回给前端的数据是要对于其中缺失的日期进行填充零。

这种情况其实可以被抽象出来为一个行为,本文利用localDate,反射来编写了一个工具类,便捷处理这种情况。

1.效果展示

此处展示对于是两个完全不相同维度的统计vo类的填充(一个根据月份统计,一个根据日期统计),并且两者的vo类的字段也完全不一样,但是工具类可以很好的对于他们传参兼容
1.1 根据日期统计
可以看到,之前只有一条数据。经过填充后,变成了连续日期的数据
在这里插入图片描述

1.2 根据月份统计
可以看到,之前只有一条数据。经过填充后,变成了连续月份的数据
在这里插入图片描述

2. 设计思路

开发设计思路:
1.先根据《开始日期》《结束日期》计算出中间的连续日期dateList
2.将数据库统计的数据 mysqlDataList 转为Map<String, T>格式,key是日期,val是其本身。这里叫它mysqlDataListToMap
2.遍历这个dateList,遍历的每个元素叫 timeStr
2.1 如果mysqlMap 中存在计算出来这个 timeStr 就跳过
2.2 如果mysqlMap 中不存在这个 timeStr ,说明我们要去填充缺失的对应的日期数据
3. 根据反射动态对于字段构造数据

下面直接进行代码展示,代码处都有注释,有疑问的话再评论区交流

3. 代码展示

3.1 工具类

package com.lzq.learn.test.构造假数据;

import cn.hutool.core.util.StrUtil;
import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 润色数据,补充数据库中的没有统计到的月份填充值
 *
 * @author LiuZhiQiang
 */
public class BuildDateDataUtil {
    
    


    /**
     * <p>@Description: 对于时间范围内的数据进行连续时间填充</p >
     * <p>@param [mysqlDataList 从MySQL查询到的数据List, startTime 开始时间, endTime 结束时间, type 类型:(年, 月, 日) ]</p >
     * <p>@return java.util.List<T></p >
     * <p>@throws </p >
     * <p>@author LiuZhiQiang</p >
     * <p>@date 11:35 11:35</p >
     */
    public static <T> List<T> addMissDateData(List<T> mysqlDataList, LocalDate startDate, LocalDate endDate, String type, Map<String, Object> fakeFieldAndFieldValue, Class<T> dataClass, String timeFieldName) throws Exception {
    
    
        // 进行日期list的计算
        List<String> dateList = BuildDateDataUtil.computeDateList(type, startDate, endDate);
        LinkedHashMap<String, T> mysqlDataListToMap = new LinkedHashMap<>();
        for (T t : mysqlDataList) {
    
    
            Class<?> objClass = t.getClass();
            Field declaredField = objClass.getDeclaredField(timeFieldName);
            declaredField.setAccessible(true);
            String timeStr = (String) declaredField.get(t);
            mysqlDataListToMap.put(timeStr, t);
        }
        List<T> finalResultList = new ArrayList<>();
        // 返回给前端数据润色
        for (String timeStr : dateList) {
    
    
            if (mysqlDataListToMap.containsKey(timeStr)) {
    
    
                finalResultList.add(mysqlDataListToMap.get(timeStr));
                continue;
            }
            fakeFieldAndFieldValue.put(timeFieldName, timeStr);
            T resultItem = BuildDateDataUtil.makeFakeData(fakeFieldAndFieldValue, dataClass);
            finalResultList.add(resultItem);
        }
        return finalResultList;
    }

    /**
     * <p>@Description: 根据type和startDate和endDate来进行计算 日期List</p >
     * <p>@param [type 日期间隔类型:年,月,日, startDate 开始日期, endDate 结束日期]</p >
     * <p>@return 连续间隔的日期列表</p >
     * <p>@throws </p >
     */
    public static List<String> computeDateList(String type, LocalDate startDate, LocalDate endDate) {
    
    
        int index = 9999;
        List<String> dateList = new ArrayList<>();
        int i = 0;
        if (StrUtil.equals(type, "month")) {
    
    
            startDate = LocalDate.of(startDate.getYear(), startDate.getMonthValue(), 1);
            endDate = LocalDate.of(endDate.getYear(), endDate.getMonthValue(), 1);
            dateList.add(startDate.format(DateTimeFormatter.ofPattern("yyyy-MM")));
            while (startDate.isBefore(endDate)) {
    
    
                if (i >= index) {
    
    
                    throw new RuntimeException("程序错误:陷入了死循环");
                }
                startDate = startDate.plusMonths(1);
                dateList.add(startDate.format(DateTimeFormatter.ofPattern("yyyy-MM")));
                i++;
            }
        }
        if (StrUtil.equals(type, "year")) {
    
    
            startDate = LocalDate.of(startDate.getYear(), 1, 1);
            endDate = LocalDate.of(endDate.getYear(), 1, 1);
            dateList.add(startDate.format(DateTimeFormatter.ofPattern("yyyy")));
            while (startDate.isBefore(endDate)) {
    
    
                if (i >= index) {
    
    
                    throw new RuntimeException("程序错误:陷入了死循环");
                }
                startDate = startDate.plusYears(1);
                dateList.add(startDate.format(DateTimeFormatter.ofPattern("yyyy")));
                i++;
            }
        }
        if (StrUtil.equals(type, "day")) {
    
    
            // 深拷贝
            startDate = LocalDate.of(startDate.getYear(), startDate.getMonthValue(), startDate.getDayOfMonth());
            endDate = LocalDate.of(endDate.getYear(), endDate.getMonthValue(), endDate.getDayOfMonth());
            dateList.add(startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            while (startDate.isBefore(endDate)) {
    
    
                if (i >= index) {
    
    
                    throw new RuntimeException("程序错误:陷入了死循环");
                }
                startDate = startDate.plusDays(1);
                dateList.add(startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
                i++;
            }
        }
        return dateList;
    }


    // :
    /**
     * <p>@Description: 根据 fieldAndFieldValue 进行实例化假数据</p >
     * <p>@param [fieldAndFieldValue 需要进行填充的字段以及字段值, dataClass 实例化的class模板]</p >
     * <p>@return 填充数据后的实例化对象</p >
     */
    public static <T> T makeFakeData(Map<String, Object> fieldAndFieldValue, Class<T> dataClass) throws Exception {
    
    
        // 利用反射进行 字段值的填充
        T fakeData = dataClass.newInstance();
        // 2.通过迭代器遍历map
        Iterator<Map.Entry<String, Object>> iterator = fieldAndFieldValue.entrySet().iterator();
        while (iterator.hasNext()) {
    
    
            Map.Entry<String, Object> entry = iterator.next();
            Field declaredField = fakeData.getClass().getDeclaredField(entry.getKey());
            declaredField.setAccessible(true);
            declaredField.set(fakeData, entry.getValue());
        }
        return fakeData;
    }
}

3.2 统计VO类

3.2.1 商品统计VO类


package com.lzq.learn.test.构造假数据;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;

/**
 * <p>@Description: 商品统计vo类</p >
 * <p>@author lzq</p >
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class GoodStatistic {
    
    

    // 日期,例如:2023-01-01
    private String dateString;

    // 购买次数
    private BigDecimal count;
    
}

3.2.2 订单统计VO类

package com.lzq.learn.test.构造假数据;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;

/**
 * <p>@Description: 订单统计vo类</p >
 * <p>@author lzq</p >
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderStatistic {
    
    

    // 月份,例如:2023-02
    private String monthString;

    // 交易订单累计金额
    private BigDecimal money;

}

3.3 测试类, 使用用例

package com.lzq.learn.test.构造假数据;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
/**
 * <p>@Description: 构造假数据测试类</p >
 * @author lzq
 */
public class TestMain {
    
    

    public static void main(String[] args) throws Exception {
    
    
        // 1.统计商品信息
        GoodsTest();
        // 2.统计订单信息
        OrderTest();

    }

    // 测试 GoodStatistic 填充假数据
    public static void GoodsTest() throws Exception {
    
    
        ArrayList<GoodStatistic> mysqlList = new ArrayList<>();
        // 模拟从mysql查询数据
        mysqlList.add(new GoodStatistic("2023-02-25", BigDecimal.TEN));
        LocalDate startDate = LocalDate.of(2023,2,24);
        LocalDate endDate = LocalDate.of(2023,3,5);
        LinkedHashMap<String, Object> fillFakeDataMap = new LinkedHashMap<>();
        fillFakeDataMap.put("dateString", null);
        fillFakeDataMap.put("count", BigDecimal.ZERO);
        List<GoodStatistic> finalList = BuildDateDataUtil.addMissDateData(mysqlList, startDate, endDate, "day", fillFakeDataMap,
                GoodStatistic.class, "dateString");
        finalList.forEach(System.out::println);
    }

    // 测试 OrderStatistic 填充假数据
    public static void OrderTest() throws Exception {
    
    
        ArrayList<OrderStatistic> mysqlList = new ArrayList<>();
        // 模拟从mysql查询数据
        mysqlList.add(new OrderStatistic("2023-02", BigDecimal.TEN));
        LocalDate startDate = LocalDate.of(2023,2,1);
        LocalDate endDate = LocalDate.of(2023,6,1);
        LinkedHashMap<String, Object> fillFakeDataMap = new LinkedHashMap<>();
        fillFakeDataMap.put("monthString", null);
        fillFakeDataMap.put("money", BigDecimal.ZERO);
        List<OrderStatistic> finalList = BuildDateDataUtil.addMissDateData(mysqlList, startDate, endDate, "month", fillFakeDataMap,
                OrderStatistic.class, "monthString");
        finalList.forEach(System.out::println);
    }
}

总结: 本文的核心代码其实是根据《开始日期》和《结束日期》计算连续时间,本文只做了简单demo展示,后续可以扩充为 :
1.计算指定《步长》的连续时间计算
2.追加《小时》《分钟》的类型支持

哪里有问题欢迎各位同学在评论区留言,看到了我会及时回复。

猜你喜欢

转载自blog.csdn.net/lzq2357639195/article/details/131749896