业务场景:在一些统计中,我们需要在一段时间范围对于系统现有数据进行统计查询,但是我们的业务系统中存在的数据可能是跳跃日期的。但是我们返回给前端的数据是要对于其中缺失的日期进行填充零。
这种情况其实可以被抽象出来为一个行为,本文利用localDate,反射来编写了一个工具类,便捷处理这种情况。
目录
- 1.效果展示
- 2. 设计思路
- 3. 代码展示
- 3.1 工具类
- 3.2 统计VO类
- 3.2.1 商品统计VO类
- 3.2.2 订单统计VO类
- 3.3 测试类, 使用用例
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.追加《小时》《分钟》的类型支持
哪里有问题欢迎各位同学在评论区留言,看到了我会及时回复。