📚 在前端界面图形化展示中,目前主流以报表形式,或者以曲线图形式展示其所需数据的数字动态变化效果居多。在数据量不大或者不需要模糊对比的情况下,我们以报表展示为主;而我们需要从肉眼宏观可见的监控数据变化,一眼能看出其数据变动问题的时候,曲线图的展示则是首选。
📕 刚开始接手做项目大家应该都是从报表入手,因为这个较为简单,容易上手,逻辑上判断也并不复杂。但是曲线图上的开发考虑的因素就要更多一些:
⛳️(1)曲线数据值的取法怎样合适?
⛳️(2)曲线坐标轴当以多少分钟为时间间隔合适?
⛳️(3)数据库数据是否在所统计的时间点上有数值?
⛳️(4)没有数值应当以何种方式进行补值?
✂️这些并不是本人一开始就得知的,而是踩过坑并和前端同事对接后发现的问题。所谓不在其位不谋其政,一旦在岗工作有些坑该踩还得踩,该流的汗还得流,当然遇到很不合理的需求该battle还得battle~
✂️习惯上先说一段废话来引入主题:聊聊是如何获取上周均值的实时数据的(曲线图),主要根据曲线要考虑的因素展开思路剖析。
⚽️(一)数据取值
🔭之所以在Java代码层面去做数据处理是因为我们在数据库层面sql语句不好处理复杂业务数据,或者没有能直接可用的函数供给我们使用,我们才会想到用代码实现逻辑更便捷一些。(这里提一嘴:有些sql不好处理的业务,交给Java Stream流会给你意想不到的简单惊喜哦!当然这里我没用到Stream流,因为我也是刚在这个项目中接触一丢丢,用得确实不6,以后当然会666 🎯O(∩_∩)O哈哈~🎯)
🍻① 首先看一下我们的需求,拿上周七天的均值数据,那么取七天的数据是必然的。之前的文章有讲过易犯错点:“不要频繁滴访问数据库”,所以我们的做法是直接在数据库中取七天的原始数据在sql层面不做任何处理。这里也有一个“踩坑”环节,不是拿过去七天,而是拿上周七天。所以,我们还需有有一个判断环节,这个判断是要根据目前的日期往前推算:今天是周几,然后往前推算几天才能拿到上周每一天的数值。这个判断环节如果曲线图在项目中运用较多,可以将其封装成一个方法,不管是我们自己开发,还是后期同事进行维护代码会更轻松便捷。这里是借助Java DateUtil类自带方法,利用该方法判断今天是一周中的周几,然后进行逻辑判断,具体代码如下所示:
DateTime dateTime = DateUtil.date();
Week week = DateUtil.dayOfWeekEnum(dateTime);
Integer day = 0;
switch (week){
case MONDAY:
day = 1;
break;
case TUESDAY:
day = 2;
break;
case WEDNESDAY:
day = 3;
break;
case THURSDAY:
day = 4;
break;
case FRIDAY:
day = 5;
break;
case SATURDAY:
day = 6;
break;
case SUNDAY:
day = 7;
break;
default:
day = 0;
break;
}
🍻② 确认完今日是当前周的第几天后,还要限定在哪个时间范围内,并将时间格式化成正确的传入参数,参考代码如下所示:
//日期往前推day+6位开始时间,往前推day-1为截止时间
LocalDate beginLocalDate = LocalDate.now().minusDays(day+6);
LocalDate endLocalDate = LocalDate.now().minusDays(day-1);
//时间格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String beginFormat = dateTimeFormatter.format(beginLocalDate);
String endFormat = dateTimeFormatter.format(endLocalDate);
//在年月日的基础上加上时分秒
String beginTime = beginFormat + " 00:00:00";
String endTime = endFormat + " 00:00:00";
//String转Date格式
Date beginDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(beginTime);
Date endDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(endTime);
🍻③ 确认完开始时间和截止时间后,将beginDate 和 endDate 作为Mapper接口的入参查询数据即可完成数据取值的任务,参考代码如下:
List<?> lastWeekList= xxxMapper.selectXXX(beginDate, enDate);
//对应的Mapper接口省略,基本操作大家都会写。
🍗由于项目用的是ClickHouse数据库,诸多写法跟MySQL不同,mysql的Group by是比较友好的,但是要用CK的Group By,小伙伴试试就知道里面的坑是超级多的,你要查询多个字段,那么那些字段都需要Group By,带查询条件的基本都是要这样做,用得痛苦不堪… …
🍗提供用于ClickHouse中分组的友好函数(超好用),将时间按照五分钟间隔统计数据,如果大家也是用到了CK可以试试看,如果不是CK的话,mysql处理要好得多不用搞得这么复杂,ck时间函数参考以下:
formatDateTime(toStartOfInterval(loadtime, INTERVAL 5 minute ),'%T') as lastWeekLoadTime
⚽️(二)时间坐标轴的选定
🍔原始数据取好之后,下一步开始处理数据,处理数据要想好值如何摆放的问题,就像水浒英雄排座次一样的道理,本来在哪的就在哪,摆放不能搞乱了,否则影响的是前端展示效果,造成数据扭曲。
🍔坐标轴的选取,这里不能用数据库中查询的时间作为坐标轴。原因有二:
🍻(1)假设当前时间是没有数据的,那么在数据库中某一条或者某多条数据是缺失的,虽然后台程序不会报错,但是整体返回的值相对而言少了;
🍻(2)返回值一旦空缺,前端会以某个接口的时间点作为坐标轴画曲线,没有的值前端那边是无法简单进行判断的,他不会自动进行补值,而是会将值给顶出去,假设现在为16:00,但是数据库中的值为17:00,16:00~17:00中间是没有数值的,前端不会显示16:00,而是会直接那17:00之后的数据顶上。这样造成的后果就是,图形化展示出未来的时间,哦豁,有bug哦!
🍧解决办法当然有,我们用自定义方法作为坐标轴,好处就是这条坐标轴是固定不变的,不会因为某个时间点上没有数据而不存在的问题,获取固定时间间隔的方法参考如下代码:
/**
* 获取固定时间间隔
*/
public static List<String> getIntervalTimeList(String start, String end, int interval) {
Date startDate = convertString2Date("HH:mm:ss", start);
Date endDate = convertString2Date("HH:mm:ss", end);
List<String> list = new ArrayList<>();
while (startDate.getTime() <= endDate.getTime()) {
list.add(convertDate2String("HH:mm:ss", startDate));
Calendar calendar = Calendar.getInstance();
calendar.setTime(startDate);
calendar.add(Calendar.MINUTE, interval);
if (calendar.getTime().getTime() > endDate.getTime()) {
if (!startDate.equals(endDate)) {
list.add(convertDate2String("HH:mm:ss", endDate));
}
startDate = calendar.getTime();
} else {
startDate = calendar.getTime();
}
}
return list;
}
🍧以上方法返回一个以interval为时间间隔,类型为String的timeList,我们传入参数也是要注意的,根据时间间隔来确认我们的start 和 end 具体为什么,参考代码如下:
//timeList长度为289,间隔五分钟288个数据, 间隔十分钟144个数据
List<String> timeList = DateUtils.getIntervalTimeList("00:00:00", "23:55:00", 5);
⚽️(三)结果的返回
👑前两步的基础打好之后,后面的工作就好处理了,只需要将查询到的数据跟坐标轴进行匹配,匹配到之后整除7后进行返回,如果某个时间点没有值则可以返回null或者0,最终返回一个以interval为时间间隔完整的时间曲线。实例代码如下所示:
List<?> list = new ArrayList();
for (int i = 0; i < timeList.size(); i++) {
//业务逻辑匹配处理
Data data = new Data();
String lastWeekLoadTime = lastWeekList.get(i).getLastWeekLoadTime();
Integer num = 0;
if (lastWeekLoadTime.equals(timeList.get(i))){
num = lastWeekList.get(i).getNum();
}
num /= 7;
String passTime = "2022-11-28 " + timeList.get(i);
data.setLoadtime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(passTime));
data.setNum(num);
list.add(data);
}
return list;
⚽️(四)结语
🍰🍰🍰整个流程下来,主要是在代码层面对数据进行了一系列的处理。思路并不是一成不变的,需要时时刻刻撬动自己的脑筋才能把一个需求问题搞定。之前本人一直想在sql层面一次性把问题给解决了,左思右想最后发现自己根本不知道怎么动手合适。最终想到用传参到sql中和自定义方法解决问题,面临自己之前没接触过的数据库确实很多地方都是知识盲区,这个时候要多请教一下解决过相关业务的同事,多在网上寻求解决方案,希望自己的这点经历也能成为你们的“答案”🍭🍭🍭
🍆🍆🍆路过的小伙伴,如果本篇博文对你的学习或者工作有所帮助,可以点赞+收藏+关注一波呀~👊👊👊小编后续每过一段时间会整理出相关项目实例的博文,感谢您的支持哦!!!✈️✈️✈️