1.问题
由于之前搞了一个停车场的项目,有一个区域的停车场收费被投诉了被举报没有按照政府要求来计费,所以需要将之前的那些停车计费的数据重新计算,将多缴费的钱拿去交罚款,这个就很坑,计费规则如下:

规则是公共停车场三类区的小型车,上图标准红色的那条计费规则,这个需求也是有点变态的,给我算了两天才把数据处理好了的,历史数据有1.7w多条,给的是一个execle表格的历史数据,所以需要搞一个表,将需要的列导入到表中,然后在一个表中设计了几个冗余的字段记录了:停车时长,夜间停车时长,夜间停车费用、白天停车时长、白天停车费用,多收费用、免费时长等字段,然后将数据写代码全部查出来,然后计算填充每一条这几个字段后使用mybatisPlus的Mapper的updateById或者使用updateBatchById更新这些数据都很慢很慢,然后我想了个办法,用两张表来搞,一张表存原始数据(导入的历史数据),一张表存计算后的结果数据
原始数据表截图:

数据处理的结果数据:

首先将原始数据数据量少的话,一次性读入内存,数据量大的话,分页多次读入内存计算处理,然后计算的数据结果使用mybatisPlus批量插入到数据处理的结果这张表中,采用mybatisPlus的批量插入sql注入器,这种方式是相当的快。
这个需求的难点就是去计算白天停车时长、夜间停车时长还有根据油车或者是电车计算费用;
油车7位车牌:停车时长小于0.25h不收费,超过0.25h才计费,大于等于0.25h小于0.5小时,按首小时每半小时1.5元计费,这种情况是1.5元,大于等于0.5小于等于1小时也是按首小时每半小时1.5元计费,这种情况下计算的是3元。
电车8位车牌:停时长小于2h内不收费,超过2小时才计费,入场时间加2小时后在计算两个时间段的费用。
白天时长是不足半小时按半小时算(半小时周期计费),夜间时长是不足一小时按一小时算(整点小时周期计费)。
处理白天或夜间停车时长:
private Double dayOrNightTimeCalculate(Boolean isType, Double d) {if (d <= 0) {return 0d;}log.info("dayOrNightTimeCalculate.isType:{},d:{}", isType, d);if (isType) {// 白天时长计算 (半小时周期计费),这段逻辑处理的时间,简单解释下:白天计算费用 = 1.5 * 2 + [(t -1) * 0.5 ]/0.5 这段逻辑就的返回直接加上3块就是总费用了 ,t是白天停车时长double v = d / 0.5;int discuss = (int) v;double remain = d % 0.5;if (remain > 0) {log.info("半小时计费停车时长计算前;{}", d);d = (discuss + 1) * 0.5;log.info("半小时计费停车时长计算后;{}", d);}} else {// 夜间时长计算 (整点小时时周期计费)double v = d / 1.0;int discuss = (int) v;double remain = d % 1.0;if (remain > 0) {log.info("整点计费停车时长计算前;{}", d);d = (discuss + 1) * 1.0;log.info("整点计费停车时长计算后;{}", d);}}return d;}
之前也做了一个需求就是计算夜间免费停车时长(晚20点到早上10点这个区间是免费停车的),知道入场时间和出场时间,所以我就搞了一个时间环TimeLoopService的类(类似于时钟或者是手表,时间走是一圈又一圈的转动,在这个类里面给一个入场时长和出场时间就可以算出夜间免费停车时长),有这个反向操作就可以算出夜间停车时长,只不过之前算的是夜间免费停车时长,现在算的是夜间停车时长,根据上面的规则,夜间20点到早8点这个区间是收费的,使用TimeLoopService找到的时间作为夜间停车时长,停车总时长减去夜间停车时长就是白天停车时长。
DateUtils类:
package com.xxxx;import cn.hutool.core.collection.CollectionUtil;import lombok.extern.slf4j.Slf4j;import java.time.Duration;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.Period;import java.time.format.DateTimeFormatter;import java.time.temporal.TemporalAdjusters;import java.util.ArrayList;import java.util.List;4jpublic class DateUtils {private static final DateTimeFormatter df1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");private static final DateTimeFormatter df2 = DateTimeFormatter.ofPattern("yyyy-MM-dd");private static final DateTimeFormatter df3 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");private static final DateTimeFormatter df4 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");private static final DateTimeFormatter df5 = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");public static final String DAY = "DAY";public static final String HOURS = "HOURS";public static final String MINUTES = "MINUTES";public static long timeSubstract(LocalDateTime startTime, LocalDateTime endTime, String returnType) {if (StringUtils.equals(returnType, HOURS)) {return Duration.between(startTime, endTime).toHours();}if (StringUtils.equals(returnType, DAY)) {return Duration.between(startTime, endTime).toDays();}if (StringUtils.equals(returnType, MINUTES)) {return Duration.between(startTime, endTime).toMinutes();}return 0L;}public static String localDateTimeToString(LocalDateTime localDateTime) {String format = df1.format(localDateTime);return format;}public static String localDateTimeToStringToYMD(LocalDateTime localDateTime) {String format = df2.format(localDateTime);return format;}public static LocalDateTime stringToLocalDateTime(String dateTimeStr) {LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, df1);return localDateTime;}public static LocalDateTime stringIncludeTStrToLocalDateTime(String dateTimeStr) {log.info("stringIncludeTStrToLocalDateTime入参:{}",dateTimeStr);dateTimeStr = dateTimeStr.replace("'T'", " ");dateTimeStr = dateTimeStr.substring(0, 19);log.info("stringIncludeTStrToLocalDateTime.dateTimeStr处理后的值:{}",dateTimeStr);LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, df1);return localDateTime;}public static LocalDateTime stringIncludeTStrToLocalDateTime2(String dateTimeStr) {log.info("stringIncludeTStrToLocalDateTime入参:{}",dateTimeStr);dateTimeStr = dateTimeStr.replace("T", " ");dateTimeStr = dateTimeStr.substring(0, 19);log.info("stringIncludeTStrToLocalDateTime.dateTimeStr处理后的值:{}",dateTimeStr);LocalDateTime localDateTime = LocalDateTime.parse(dateTimeStr, df1);return localDateTime;}public static String localDateTimeToStringT(LocalDateTime localDateTime) {String format = df3.format(localDateTime);return format;}public static String localDateTimeToStringT2(LocalDateTime localDateTime) {String format = df4.format(localDateTime).replace("T", " ");return format;}public static String localDateTimeToStringT3(LocalDateTime localDateTime) {String format = df5.format(localDateTime).replace("T", " ").replace("Z"," ");format = format.substring(0,format.lastIndexOf("."));return format;}public static LocalDateTime calculatTime(String time){LocalDateTime psTime = null;if (org.apache.commons.lang3.StringUtils.isNotEmpty(time) && !(time.indexOf("-") != -1)) {String s =time;s = s.substring(0, 4) + "-" + s.substring(4, 6) + "-" + s.substring(6, 8) + " " + s.substring(8, 10) + ":" + s.substring(10, 12) + ":" + s.substring(12, 14);psTime = DateUtils.stringToLocalDateTime(s);} else if (org.apache.commons.lang3.StringUtils.isNotEmpty(time) && (time.indexOf("-") != -1)) {String passTime =time;if (passTime.contains("T")) {psTime = DateUtils.stringIncludeTStrToLocalDateTime(passTime);} else {psTime = DateUtils.stringToLocalDateTime(passTime);}}return psTime;}public static ListcalculationDays(String startTime, String endTime) {ListallDays = new ArrayList<>(); DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");LocalDate startDt = LocalDate.parse(startTime, dateTimeFormatter1);int start_Y = startDt.getYear();int start_M = startDt.getMonth().getValue();int start_D = startDt.getDayOfMonth();log.info("Y=" + start_Y + ",M=" + start_M + ",D=" + start_D);LocalDate endDt = LocalDate.parse(endTime, dateTimeFormatter1);int start_Y1 = endDt.getYear();int start_M1 = endDt.getMonth().getValue();int start_D1 = endDt.getDayOfMonth();log.info("Y1=" + start_Y1 + ",M1=" + start_M1 + ",D1=" + start_D1);if (startDt.compareTo(endDt) > 1) {//开始时间大于结束时间返回空!return null;}String endTimeStr = dateTimeFormatter1.format(endDt);Period period = Period.between(LocalDate.parse(startTime), endDt);StringBuffer sb = new StringBuffer();sb.append(period.getYears()).append(",").append(period.getMonths()).append(",").append(period.getDays());log.info("=======时间分量:=======" + sb.toString());int py = start_Y1 - start_Y;int Y = 12;detailYear(allDays, start_Y, py, Y);log.info("=======allDays--size()=======" + allDays.size());if (CollectionUtil.isNotEmpty(allDays)) {for (int i = 0; i < allDays.size(); i++) {log.info(allDays.get(i) + "--------allDays------>第" + (i + 1) + "天");}ListokResult = getOkResult(allDays, startTime, endTimeStr); System.out.println("=======okResult--size()=======" + okResult.size());for (int i = 0; i < okResult.size(); i++) {log.info(okResult.get(i) + "--------okResult-------->第" + (i + 1));}return okResult;}return null;}/*** 处理整年** @param allDays* @param start_Y* @param py* @param y*/private static void detailYear(ListallDays, int start_Y, int py, int y) {//处理年的天for (int i = start_Y; i < start_Y + py + 1; i++) {for (int j = 1; j <= y; j++) {String fst = "";if (j <= 9) {fst = i + "-0" + j + "-01";} else {fst = i + "-" + j + "-01";}int diff_day = getDiff_day(fst);for (int k = 1; k <= diff_day + 1; k++) {String d = "";if (j <= 9) {d = i + "-0" + j;if (k <= 9) {d += "-0" + k;} else if (k > 9) {d += "-" + k;}} else if (j > 9) {d = i + "-" + j;if (k <= 9) {d += "-0" + k;} else if (k > 9) {d += "-" + k;}}allDays.add(d);}}}}/*** 获取正确的List** @param startTime* @param allDays* @return*/private static ListgetOkResult(List {allDays, String startTime, String endTime) Listresult = new ArrayList<>(); int indexStart = 0;int indexEnd = 0;for (int i = 0; i < allDays.size(); i++) {if (allDays.get(i).equals(startTime)) {indexStart = i;}}for (int i = 0; i < allDays.size(); i++) {if (allDays.get(i).equals(endTime)) {indexEnd = i;}}result = allDays.subList(indexStart, indexEnd + 1);return result;}/*** 根据当月第一天计算本月的开始天和结束天** @param fst* @return*/private static int getDiff_day(String fst) {LocalDate fstLd = LocalDate.parse(fst);//获取月的第一天LocalDate fstLd_fd = fstLd.with(TemporalAdjusters.firstDayOfMonth());//获取月的最后一天LocalDate fstLd_ld = fstLd.with(TemporalAdjusters.lastDayOfMonth());Period period2 = Period.between(fstLd_fd, fstLd_ld);int diff_day = period2.getDays();return diff_day;}}
TimeLoopService类:
package xxxx.impl;import com.xxxxxx.DateUtils;import lombok.Data;import lombok.extern.slf4j.Slf4j;import java.time.Duration;import java.time.LocalDate;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.time.temporal.TemporalAdjusters;import java.util.HashMap;import java.util.List;/*** @author zlf* @description:* @time: 2022/9/7 9:58*/@Slf4j@Datapublic class TimeLoopService {/*** 夜间开始时长点数*/private int start;/*** 夜间结束时长点数*/private int end;/*** 入场时间*/private LocalDateTime goInStart;/*** 入场时钟的开始计费点*/private LocalDateTime goInStartClockTime;/*** 入场时钟结束计费点*/private LocalDateTime goInEndClockTime;/*** 入场 minClock 00:00:00*/private LocalDateTime goInPassMinTime;/*** 入场 maxClock 23:59:59*/private LocalDateTime goInPassMaxTime;/*** 入场月的最后一天*/private Integer goInMothLastDay;/*** 出场时间*/private LocalDateTime goOutEnd;/*** 出场时钟的开始计费点*/private LocalDateTime goOutStartClockTime;/*** 出场时钟结束计费点*/private LocalDateTime goOutEndClockTime;/*** 出场 minClock 00:00:00*/private LocalDateTime goOutPassMinTime;/*** 出场clock 23:59:59*/private LocalDateTime goOutPassMaxTime;/*** 出场月的第一天*/private Integer goOutMothFirstDay;/*** 入场时间和出场时间相差几天*/private int lessDay;/*** 一个时钟周期的免费时长小时数*/private int oneClockFreeHours;/*** 00:00:00*/private String psMinStr = "00:00:00";/*** 23:59:59*/private String psMaxStr = "23:59:59";/*** 00:00*/private String oo = "00:00";private HashMapgoInStartMap = new HashMap<>(); private HashMapgoOutEndMap = new HashMap<>(); private Boolean flag1 = Boolean.FALSE;public TimeLoopService(Integer start, Integer end, LocalDateTime goInStart, LocalDateTime goOutEnd) {this.start = start;this.end = end;this.goInStart = goInStart;this.goOutEnd = goOutEnd;oneClockFreeHours = 24 - Math.abs((start - end));log.info("oneClockFreeHours:{}", oneClockFreeHours);DateTimeFormatter dateTimeFormatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");DateTimeFormatter dateTimeFormatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd");String gs = dateTimeFormatter1.format(goInStart);LocalDate startDt = LocalDate.parse(gs, dateTimeFormatter1);int start_Y = startDt.getYear();int start_M = startDt.getMonth().getValue();int start_D = startDt.getDayOfMonth();String[] s = gs.split(" ");String[] s2 = s[1].split(":");int start_H = Integer.valueOf(s2[0]).intValue();int start_MM = Integer.valueOf(s2[1]).intValue();int start_S = Integer.valueOf(s2[2]).intValue();goInStartMap.put("start_Y", start_Y);goInStartMap.put("start_M", start_M);goInStartMap.put("start_D", start_D);goInStartMap.put("start_H", start_H);goInStartMap.put("start_MM", start_MM);goInStartMap.put("start_S", start_S);log.info("开始时间各分量:Y=" + start_Y + ",M=" + start_M + ",D=" + start_D + ",H=" + start_H + ",MM:" + start_MM + ",start_S:" + start_S);String ge = dateTimeFormatter1.format(goOutEnd);LocalDate endDt = LocalDate.parse(ge, dateTimeFormatter1);int start_Y1 = endDt.getYear();int start_M1 = endDt.getMonth().getValue();int start_D1 = endDt.getDayOfMonth();String[] s3 = ge.split(" ");String[] s4 = s3[1].split(":");int start_H1 = Integer.valueOf(s4[0]).intValue();int start_MM1 = Integer.valueOf(s4[1]).intValue();int start_S1 = Integer.valueOf(s4[2]).intValue();goOutEndMap.put("start_Y1", start_Y1);goOutEndMap.put("start_M1", start_M1);goOutEndMap.put("start_D1", start_D1);goOutEndMap.put("start_H1", start_H1);goOutEndMap.put("start_MM1", start_MM1);goOutEndMap.put("start_S1", start_S1);log.info("结束时间各分量:Y1=" + start_Y1 + ",M1=" + start_M1 + ",D1=" + start_D1 + ",H1=" + start_H1 + ",MM1:" + start_MM1 + ",start_S1:" + start_S1);String startStr = "";if (0 < start && start <= 9) {startStr = "0" + start;} else {startStr = start.toString();}log.info("startStr:{}", startStr);String endStr = "";if (0 < end && end <= 9) {endStr = "0" + end;} else {endStr = end.toString();}log.info("endStr:{}", endStr);String start_M_str = "";if (start_M <= 9) {start_M_str = "0" + start_M;} else {start_M_str = String.valueOf(start_M);}log.info("start_M_str:{}", start_M_str);String start_M_str1 = "";if (start_M1 <= 9) {start_M_str1 = "0" + start_M1;} else {start_M_str1 = String.valueOf(start_M1);}log.info("start_M_str1:{}", start_M_str1);String start_D_str = "";if (start_D <= 9) {start_D_str = "0" + start_D;} else {start_D_str = String.valueOf(start_D);}log.info("start_D_str:{}", start_D_str);String start_D_str1 = "";if (start_D1 <= 9) {start_D_str1 = "0" + start_D1;} else {start_D_str1 = String.valueOf(start_D1);}log.info("start_D_str1:{}", start_D_str1);goInStartClockTime = DateUtils.stringToLocalDateTime(start_Y + "-" + start_M_str + "-" + start_D_str + " " + startStr + ":" + oo);log.info("goInStartClockTime:{}", DateUtils.localDateTimeToString(goInStartClockTime));goInEndClockTime = DateUtils.stringToLocalDateTime(start_Y + "-" + start_M_str + "-" + start_D_str + " " + endStr + ":" + oo);log.info("goInEndClockTime:{}", DateUtils.localDateTimeToString(goInEndClockTime));goInPassMinTime = DateUtils.stringToLocalDateTime(start_Y + "-" + start_M_str + "-" + start_D_str + " " + psMinStr);log.info("goInPassMinTime:{}", DateUtils.localDateTimeToString(goInPassMinTime));goInPassMaxTime = DateUtils.stringToLocalDateTime(start_Y + "-" + start_M_str + "-" + start_D_str + " " + psMaxStr);log.info("goInPassMaxTime:{}", DateUtils.localDateTimeToString(goInPassMaxTime));goOutStartClockTime = DateUtils.stringToLocalDateTime(start_Y1 + "-" + start_M_str1 + "-" + start_D_str1 + " " + startStr + ":" + oo);log.info("goOutStartClockTime:{}", DateUtils.localDateTimeToString(goOutStartClockTime));goOutEndClockTime = DateUtils.stringToLocalDateTime(start_Y1 + "-" + start_M_str1 + "-" + start_D_str1 + " " + endStr + ":" + oo);log.info("goOutEndClockTime:{}", DateUtils.localDateTimeToString(goOutEndClockTime));goOutPassMinTime = DateUtils.stringToLocalDateTime(start_Y1 + "-" + start_M_str1 + "-" + start_D_str1 + " " + psMinStr);log.info("goOutPassMinTime:{}", DateUtils.localDateTimeToString(goOutPassMinTime));goOutPassMaxTime = DateUtils.stringToLocalDateTime(start_Y1 + "-" + start_M_str1 + "-" + start_D_str1 + " " + psMaxStr);log.info("goOutPassMaxTime:{}", DateUtils.localDateTimeToString(goOutPassMaxTime));log.info("start_D1:{},start_D:{}", start_D1, start_D);lessDay = start_D1 - start_D - 1;log.info("计算lessDay = start_D1 - start_D - 1得:{}", lessDay);if (lessDay < 0) {lessDay = 0;}log.info("开始时间和结束时间相差几天:lessDay:{}", lessDay);//跨月计算if (start_M != start_M1 && start_Y == start_Y1) {log.info("=========跨越计算开始=========");//入场本月的最后一天LocalDateTime goInLastDay = goInStart.with(TemporalAdjusters.lastDayOfMonth());log.info("goInLastDay:{}", DateUtils.localDateTimeToString(goInLastDay));String goInLastDayStr = dateTimeFormatter1.format(goInLastDay);LocalDate goInLastDayLd = LocalDate.parse(goInLastDayStr, dateTimeFormatter1);goInMothLastDay = goInLastDayLd.getDayOfMonth();log.info("goInMothLastDay:{}", goInMothLastDay);lessDay = (goInMothLastDay - start_D) + start_D1 - 1;log.info("跨月计算开始时间和结束时间相差几天:lessDay:{}", lessDay);//出场本月的第一天LocalDateTime goOutFirstDay = goOutEnd.with(TemporalAdjusters.firstDayOfMonth());log.info("goOutFirstDay:{}", DateUtils.localDateTimeToString(goOutFirstDay));String goOutFirstDayStr = dateTimeFormatter1.format(goOutFirstDay);LocalDate goOutFirstDayLd = LocalDate.parse(goOutFirstDayStr, dateTimeFormatter1);goOutMothFirstDay = goOutFirstDayLd.getDayOfMonth();log.info("goOutMothFirstDay:{}", goOutMothFirstDay);//入场所在天是否是本月的最后一天 且出场天是否是本月第一天if (start_D == goInMothLastDay && 1 == goOutMothFirstDay) {flag1 = Boolean.TRUE;}log.info("=========跨越计算结束=========");}//跨年if (start_Y1 - start_Y >= 1) {String startStr1 = startDt.format(dateTimeFormatter2);String enStr1 = endDt.format(dateTimeFormatter2);log.info("startStr1:{}", startStr1);log.info("enStr1:{}", enStr1);Listres = DateUtils.calculationDays(startStr1, enStr1); log.info("跨年两个时间相隔几天:{}", res.size());lessDay = res.size() - 2;log.info("跨年两个时间相隔几天减2后得:{}", lessDay);if (lessDay < 0) {lessDay = 0;}}}public Double TotalFreeTime() {//开始大于结束 开始等于结束if (goInStart.compareTo(getGoOutEnd()) >= 0) {return 0d;}int start_D = goInStartMap.get("start_D");int start_D1 = goOutEndMap.get("start_D1");log.info("start_D:{},start_D1:{}", start_D1, start_D);// 进的时间顺时针找,出的时间逆时针找if (lessDay == 0 && start_D == start_D1) {if (goInStart.compareTo(goInEndClockTime) >= 1 && goOutEnd.compareTo(goOutStartClockTime) <= 0) {log.info("================ 进出都在没落在计费区间直接返回0 ===========");return 0d;}// 一个时钟周期if (goInStart.compareTo(goInPassMinTime) == 0 && goOutEnd.compareTo(goOutPassMaxTime) == 0) {int ocf = oneClockFreeHours * 60;log.info("ocf:{}", ocf);return Double.valueOf(ocf);}log.info("================ 进出都在 同侧 (2种情况)开始 ===========");// 大于等于 00:00:00 小于等于 本个时钟的结束计费点 goOutEndClockTimeif (goInStart.compareTo(goInPassMinTime) >= 0 && goOutEnd.compareTo(goOutEndClockTime) <= 0) {Duration between = Duration.between(goInStart, goOutEnd);long t1 = between.toMinutes();log.info("t1:{}", t1);return Double.valueOf(String.valueOf(t1));}// 大于等于 本个时钟的开始计费点 goInStartClockTime 小于等于 23:59:59if (goInStart.compareTo(goInStartClockTime) >= 0 && goOutEnd.compareTo(goInPassMaxTime) <= 0) {Duration between = Duration.between(goInStart, goOutEnd);long t2 = between.toMinutes();log.info("t2:{}", t2);return Double.valueOf(String.valueOf(t2));}log.info("================ 进出都在 同侧 (2种情况)结束 ===========");log.info("================ 进出都有交叉 分割的情况 (3种情况)开始 ===========");if (goInStart.compareTo(goInPassMinTime) >= 0&& goInStart.compareTo(goInEndClockTime) == -1&& goOutEnd.compareTo(goInStartClockTime) <= 0&& goOutEnd.compareTo(goInEndClockTime) >= 0) {Duration between = Duration.between(goInStart, goInEndClockTime);long t3 = between.toMinutes();log.info("t3:{}", t3);return Double.valueOf(String.valueOf(t3));}if (goOutEnd.compareTo(goInPassMaxTime) <= 0&& goOutEnd.compareTo(goInStartClockTime) == 1&& goInStart.compareTo(goInEndClockTime) >= 0&& goInStart.compareTo(goInStartClockTime) <= 0) {Duration between = Duration.between(goInStartClockTime, goOutEnd);long t4 = between.toMinutes();log.info("t4:{}", t4);return Double.valueOf(String.valueOf(t4));}if (goInStart.compareTo(goInPassMinTime) == 1&& goInStart.compareTo(goInEndClockTime) == -1&& goOutEnd.compareTo(goInPassMaxTime) == -1&& goOutEnd.compareTo(goInStartClockTime) == 1) {Duration between = Duration.between(goInStart, goInEndClockTime);long t5 = between.toMinutes();log.info("t5:{}", t5);Duration between2 = Duration.between(goInStartClockTime, goOutEnd);long t6 = between2.toMinutes();log.info("t6:{}", t6);long t7 = t5 + t6;log.info("t7:{}", t7);return Double.valueOf(String.valueOf(t7));}log.info("================ 进出都有交叉 分割的情况 (3种情况)结束 ===========");} else if (lessDay >= 1 || (start_D1 - start_D) == 1 || flag1) {Double heard = this.calculateHeardClock();Double between = this.calculateBetweenClock();Double tail = this.calculateTailClock();log.info("heard:{},between:{},tail:{}", heard, between, tail);Double threeSum = heard + between + tail;log.info("threeSum:{}", threeSum);return threeSum;}return 0d;}private Double calculateHeardClock() {if (goInStart.compareTo(goInPassMaxTime) == 0) {log.info("heard return 0");return 0d;}if (goInStart.compareTo(goInPassMinTime) == 0) {int t8 = oneClockFreeHours * 60;log.info("t8:{}", t8);return Double.valueOf(t8);}if (goInStart.compareTo(goInPassMinTime) == 1&& goInStart.compareTo(goInEndClockTime) == -1) {Duration between2 = Duration.between(goInStart, goInEndClockTime);long t9 = between2.toMinutes();log.info("t9:{}", t9);Duration between3 = Duration.between(goInStartClockTime, goInPassMaxTime);long t10 = between3.toMinutes();log.info("t10:{}", t10);long t11 = t9 + t10;return Double.valueOf(t11);}if (goInStart.compareTo(goInEndClockTime) <= 0&& goInStart.compareTo(goInEndClockTime) >= 0) {Duration between4 = Duration.between(goInStartClockTime, goInPassMaxTime);long t12 = between4.toMinutes();log.info("t12:{}", t12);return Double.valueOf(t12);}if (goInStart.compareTo(goInStartClockTime) == 1&& goInStart.compareTo(goInPassMaxTime) <= 0) {Duration between4 = Duration.between(goInStart, goInPassMaxTime);long t19 = between4.toMinutes();log.info("t19:{}", t19);return Double.valueOf(t19);}if (goInStart.compareTo(goInEndClockTime) >= 0&& goInStart.compareTo(goInStartClockTime) <= 0) {Duration between4 = Duration.between(goInStartClockTime, goInPassMaxTime);long t19 = between4.toMinutes();log.info("t19:{}", t19);return Double.valueOf(t19);}return 0d;}private Double calculateBetweenClock() {Double bt = Double.valueOf(lessDay * oneClockFreeHours * 60);log.info("bt:{}", bt);return bt;}private Double calculateTailClock() {if (goOutEnd.compareTo(goOutPassMinTime) == 0) {log.info("tail return 0");return 0d;}if (goOutEnd.compareTo(getGoOutPassMaxTime()) == 0) {int t13 = oneClockFreeHours * 60;log.info("t13:{}", t13);return Double.valueOf(t13);}if (goOutEnd.compareTo(goOutPassMinTime) == 1&& goOutEnd.compareTo(goOutEndClockTime) == -1) {Duration between6 = Duration.between(goOutPassMinTime, goOutEnd);long t14 = between6.toMinutes();log.info("t14:{}", t14);return Double.valueOf(t14);}if (goOutEnd.compareTo(goOutEndClockTime) >= 0&& goOutEnd.compareTo(goOutStartClockTime) <= 0) {Duration between7 = Duration.between(goOutPassMinTime, goOutEndClockTime);long t15 = between7.toMinutes();log.info("t15:{}", t15);return Double.valueOf(t15);}if (goOutEnd.compareTo(goOutPassMaxTime) == -1&& goOutEnd.compareTo(goOutStartClockTime) == 1) {Duration between8 = Duration.between(goOutStartClockTime, goOutEnd);long t16 = between8.toMinutes();log.info("t16:{}", t16);Duration between9 = Duration.between(goOutPassMinTime, goOutEndClockTime);long t17 = between9.toMinutes();log.info("t17:{}", t17);long t18 = t16 + t17;log.info("t18:{}", t18);return Double.valueOf(t18);}return 0d;}}
图形化理解如下:

这个实现基本上是没有啥问题的,如果哪位小伙伴发现上面存在的bug的话请联系我,或者有其它的实现可以联系我,把你的实现发给我参考和学习下。
2.新姿势
一般的业务数据处理都是借助定时任务加MybatisPlus持久层框架来批量处理数据到一张表中,定时任务有选型参看,然后在这种表由于业务需要新增了一些冗余字段,接着需要用定时任务跑批处理填充这些冗余的字段,会使用到mybatisPlus的IService层的saveBatch、saveOrUpdateBatch、updateBatchById,或者是使用Mapper层的updateById在一个for循环里面根据主键更新一批数据(或者可以在for中使用mybatisPlus的sql注入器alwaysUpdateSomeColumnById,这种方式比上面的Iservice层或者是Mapper层原生的api要快),这种操作拿1-2万条数据来测试,性能真的是会很慢很慢,如果数据量太大查询的数据过的的话会导致OOM,如果是在一段代码中查先把表的数据查出来,然后在使用多线程更新了一些数据就会导致多线程并发的问题,因为MyBatisPlus的sqlSession也是不是线程安全的,所以多线程批量更新是不靠谱的,就不要用这种姿势了,如果你想用线程批量更新除非你要处理好并发问题、事务问题,避免会出现一些稀奇古怪的问题,一般更新都是在一个主线程里面一撸到底。
mybatisPlus批量插入优化可以参考我之前写的一篇文章:mybatisPlus批量插入优化,性能快的飞起。
数据处理可以使用JDK的Stream流提高数据计算处理的速度;
使用mybatisPlus批量插入数据最坑的两点就是主键不唯一和重复跑批数据重复性校验的问题;
查询数据量大:mybatisPlus流式查询,也叫游标查询,客户端应用和mysql数据库建立连接后,通过这种查询方式是利用mysql的游标,少量多批次的方式避免了一次性将大量的数据查询加载到JVM的堆内存中;
主键不唯一:业务数据的主键id必须是全局唯一的,可以使用ID生成器生成,保证其全球唯一,主键不唯一,跑批多次每一次的保存的数据结果会变少了,之前我就遇到这种情况了;
数据重复性校验:首先要保证处理入库的数据的id是唯一的,比如财务对账单的数据处理,可以使用业务测的交易流水号作为财务对账数据的主键,如果某一天或者某一批数据又问题,可以将有问题的数据删除,然后重新跑批入库,避免数据重复(多次跑批导致数据重复的问题)。
可以采用ES来保存处理好的数据:
这种方式的好处是:
1.降低mysql(行式-支持事务)数据数据库的压力
如果有很多定时任务的跑批的数据处理都是使用mysql来存储批处理的结果数据,无论是在哪个时段都会拉胯mysql的数据库,如果是在业务高峰期的话一个处理不恰当导致mysql数据宕机或者是在业务高峰期造成系统会变慢。
2.避免数据重复
只要保证ID是唯一的,更新ES中的数据是先将之前的数据删除后在插入,充分利用ES的大宽表的思想和ES的海量数据存储和搜索的能力,采用Mysql + ES来处理分析查询业务数据。
本文中遇到的是使用两个表来分开处理业务数据,一个源数据表(源数据也可以使业务查询出来需要计算处理的数据),一个结果数据表(使用mysql就会遇到在一个结果数据表里面批量更新的之前的数据结果,所以使用mybatisPlus的api就会很慢,可以使用在for循环中mybatisPlus的sql注入器alwaysUpdateSomeColumnById,或者是将有问题的数据先删除,然后使用mybatisPlus的sql注入器InsertBatchSomeColumn批量插入数据这种就会很快,或者可以将处理的结果数据存到ES里面),这几个点就可以让我们处理业务数据快到起飞,好的设计和实现才能给业务带来价值,差的设计和实现只会给业务代码拖累和负增长,这种简单的业务数据且数据量不大可以通过过写业务代码实现,如果数据量确实是太大了要使用大数据领域的相关解决方案了或者一些其它的解决方案了。
3.总结
通过记录我在项目中想遇到的问题和一些奇葩的需求是如何解决和实现的跟大家做一个分享,场景化学习我觉得是一个比较好的学习方式,也是比较深刻的方式,学以致用,解决实际的问题,在学习中实践,在实践中学习,同时也是一次总结,希望我的分享对你有所帮助,请一键三连,点赞收藏加关注,么么哒!