package com.tievd.jyz.Timer; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.RandomUtil; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.tievd.jyz.cache.AlgorithmCache; import com.tievd.jyz.constants.SystemConstant; import com.tievd.jyz.entity.*; import com.tievd.jyz.entity.vo.ClientVo; import com.tievd.jyz.entity.vo.OilStatisVo; import com.tievd.jyz.service.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @Slf4j @Component @EnableScheduling public class SystemSchedule { @Autowired ICameraService cameraService; @Autowired IOilStaticService oilStaticService; @Autowired IOilRecordService oilRecordService; @Autowired IOiloutRecordService oiloutRecordService; @Autowired IOiloutEventService oiloutEventService; @Autowired IClientConfigService clientConfigService; /** * 统计车辆加油停靠等数据 */ @Scheduled(cron = "${jyz.timer.oil-statis:0 0 * * * ?}") public void statisOil() { log.info("----------------开始统计车辆信息数据-----------------"); LambdaQueryWrapper wrapper = new LambdaQueryWrapper(); // wrapper.ge(OilRecord::getStartTime, statisTime); wrapper.isNotNull(OilRecord::getStandard) .isNotNull(OilRecord::getBehavior) .isNotNull(OilRecord::getStartTime); wrapper.orderByDesc(OilRecord::getStartTime); List recordList = oilRecordService.list(wrapper); //数据统计 Map statisMap = new HashMap<>(); //客户类型 Map> recordMap = new HashMap<>(); for (OilRecord oilRecord : recordList) { String statisKey = oilRecord.getLicenseNum() + "_" + oilRecord.getOrgCode(); statisMap.putIfAbsent(statisKey, new OilStatisVo(oilRecord)); OilStatisVo statisVo = statisMap.get(statisKey); //出现次数 statisVo.setAppearCount(statisVo.getAppearCount() + 1); //加油次数 if (SystemConstant.BEHAVIOR_TYPE_OIL == oilRecord.getBehavior() + 0 ) { statisVo.setOilCount(statisVo.getOilCount() + 1); recordMap.putIfAbsent(statisKey, new ArrayList<>()); recordMap.get(statisKey).add(oilRecord); } //加油量 int oilVolume = oilRecord.getOilVolume() == null ? 0 : oilRecord.getOilVolume(); statisVo.setOilSum(statisVo.getOilSum() + oilVolume); //异常次数 if (SystemConstant.STANDARD_ERROR == oilRecord.getStandard() + 0 ) { statisVo.setEventCount(statisVo.getEventCount() + 1); } //停留时间 int spandTime = oilRecord.getSpandTime() == null ? 0 : oilRecord.getSpandTime(); statisVo.setStayTime(statisVo.getStayTime() + spandTime); //加油位 if (StringUtils.isNotBlank(oilRecord.getOilPosition())) { Map positionMap = statisVo.getPosition(); String position = oilRecord.getOilPosition(); positionMap.put(position, positionMap.getOrDefault(position, 0) + 1); } //高峰时段 Map phraseMap = statisVo.getPhrase(); int timePhrase = getTimePhrase(oilRecord.getStartTime()); phraseMap.put(timePhrase, phraseMap.getOrDefault(timePhrase, 0) + 1); } List saveList = new ArrayList<>(); //客户规则 List clientVos = clientConfigService.groupList(); //计算汇总的数据 for (Map.Entry statis : statisMap.entrySet()) { OilStatisVo statisVo = statis.getValue(); String key = statis.getKey(); //客户类型 for (ClientVo clientVo : clientVos) { if (!recordMap.containsKey(key) || CollectionUtils.isEmpty(clientVo.getClientConfigs())) { continue; } ClientTypeInfer clientInfer = new ClientTypeInfer<>( clientVo, recordMap.get(key), r -> LocalDate.parse(r.getRecordDay())); if (clientInfer.infer() != null) { statisVo.setClientName(clientVo.getClientName()); statisVo.setClientId(clientVo.getId()); } } //加油位置 if (statisVo.getPosition().size() != 0) { Map.Entry pos = Collections.max(statisVo.getPosition().entrySet(), Comparator.comparingInt(Map.Entry::getValue)); statisVo.setOilPosition(pos.getKey()); } //高峰时段 Map.Entry phrase = Collections.max(statisVo.getPhrase().entrySet(), Comparator.comparingInt(Map.Entry::getValue)); statisVo.setHigherPhrase(phrase.getKey().byteValue()); OilStatis oilStatis = new OilStatis(); BeanUtils.copyProperties(statisVo, oilStatis); saveList.add(oilStatis); } oilStaticService.saveOrUpdateBatch(saveList); log.info("----------------车辆信息数据统计成功-----------------"); } private int getTimePhrase(Date date) { int hour = date.getHours(); if (hour >= 7 && hour < 10) { return SystemConstant.MORNING_PHRASE; } else if (hour >= 17 && hour < 19) { return SystemConstant.EVENING_PHRASE; } else if (hour >= 0 && hour < 7) { return SystemConstant.MIDNIGHT_PHRASE; } else { return SystemConstant.COMMON_PHRASE; } } /** * 每小时定时完结卸油记录及事件 * 默认补齐卸油时长: 2h * 默认图片: 该次记录事件的最后一张 */ @Scheduled(cron = "${jyz.timer.oilout-complete:0 30 * * * ?}") public void oilOutComplet() { log.info("---------开始处理未正常完结的卸油流程----------"); LambdaQueryWrapper wrapper = new LambdaQueryWrapper(); wrapper.isNull(OiloutRecord::getEndTime) .le(OiloutRecord::getStartTime, LocalDateTime.now().minusHours(2)); List records = oiloutRecordService.list(wrapper); if (CollectionUtils.isEmpty(records)) { return; } log.info("---------检测到存在未正常完结的卸油流程----------", JSON.toJSONString(records)); int hour = 2; for (OiloutRecord record : records) { Timestamp startTime = record.getStartTime(); LocalDateTime endTime = startTime.toLocalDateTime().plusHours(hour); record.setEndTime(Timestamp.valueOf(endTime)); record.setSpandTime(hour * 60); eventComplet(record); } oiloutRecordService.updateBatchById(records); log.info("---------已完善未正常完结的卸油流程----------"); } /** * 补充未入库的事件 * @param record */ private void eventComplet(OiloutRecord record) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper(); wrapper.eq(OiloutEvent::getRecordId, record.getId()) .orderByAsc(OiloutEvent::getAlgorithmCode); List eventList = oiloutEventService.list(); if (CollectionUtils.isEmpty(eventList)) { return; } log.info("---------开始处理未正常完结的卸油卸油事件----------"); List completed = eventList.stream().map(OiloutEvent::getAlgorithmCode).collect(Collectors.toList()); //获取未入库事件的算法code List unComplete = new ArrayList<>(); unComplete.addAll(AlgorithmCache.oiloutAlgMap().keySet()); unComplete.removeAll(completed); //待补充事件采用最后一个事件的基础信息 OiloutEvent lastEvent = eventList.get(eventList.size() - 1); List unCompleteEvent = new ArrayList<>(); for (String algCode : unComplete) { OiloutEvent event = new OiloutEvent(); SysAlgorithmItem algorithm = AlgorithmCache.getAlgorithmMap(algCode); BeanUtil.copyProperties(lastEvent, event); event.setId(null) .setAlgorithmCode(algCode) .setAlgorithmName(algorithm.getAlgorithmName()) .setEventPhrase(algorithm.getPhrase()) .setImgUid(RandomUtil.randomNumbers(10)) .setEventType(SystemConstant.OILOUT_EVETN_TYPE_ERROR.byteValue()) .setCreateTime(new Date()); unCompleteEvent.add(event); } oiloutEventService.saveBatch(unCompleteEvent); log.info("---------已完善未正常完结的卸油卸油事件----------"); } public static void main(String[] args) { new SystemSchedule().test(); } void test(){ List records = new ArrayList<>(); records.add(new OilRecord().setRecordDay("2023-08-21")); records.add(new OilRecord().setRecordDay("2023-05-15")); records.add(new OilRecord().setRecordDay("2023-07-21")); records.add(new OilRecord().setRecordDay("2023-06-21")); ClientVo clientVo = new ClientVo(); clientVo.setClientName("哈哈哈哈"); List configs = new ArrayList<>(); configs.add(new ClientConfig().setTimeValue(3) .setTimeUnit("months") .setCountType((byte) 2) .setCountRef((byte) 1) .setCountNum(0)); clientVo.setClientConfigs(configs); ClientTypeInfer clientInfer = new ClientTypeInfer<>( clientVo, records, r -> LocalDate.parse(r.getRecordDay())); clientInfer.infer(); } /** * 客户类型推断 * @param record */ class ClientTypeInfer { ClientVo rules; List dataList; Function timeVal; public ClientTypeInfer(ClientVo rules, List dataList, Function timeVal){ this.rules = rules; this.dataList = dataList; this.timeVal = timeVal; } /** * 获得客户类型 * @return */ public String infer() { try { if (CollectionUtils.isEmpty(rules.getClientConfigs())) { return null; } for (ClientConfig rule : rules.getClientConfigs()) { if (rule == null || rule.getTimeValue() == null || StringUtils.isBlank(rule.getTimeUnit())) { return null; } ChronoUnit unit = ChronoUnit.valueOf(rule.getTimeUnit().toUpperCase()); LocalDate timeLimit = LocalDate.now().minus(rule.getTimeValue(), unit); switch(unit) { case MONTHS: timeLimit = timeLimit.with(ChronoField.DAY_OF_MONTH, 1); break; case YEARS: timeLimit = timeLimit.with(ChronoField.DAY_OF_MONTH, 1); break; } LocalDate finalTimeLimit = timeLimit; List filterData = dataList.stream().filter(t -> !timeVal.apply(t).isBefore(finalTimeLimit)).collect(Collectors.toList()); // 根据规则类型选择判断逻辑 Byte ruleType = rule.getRuleType(); if (ruleType == null) { return null; } if (ruleType == 1) { if (rule.getCountType() == null || rule.getCountRef() == null || rule.getCountNum() == null) { return null; } // 加油频次(原有逻辑) int limit = rule.getCountNum(); int ref = rule.getCountRef(); if (rule.getCountType() == 1) { int sum = filterData.size(); if (Integer.compare(sum, limit) != ref) { return null; } } else if((rule.getCountType() == 2)) { Map monthCount = new HashMap<>(); int nowMonth = LocalDate.now().getMonthValue(); for (int month = timeLimit.getMonthValue(); month != nowMonth ; month++) { monthCount.put(month, 0); } for (T tmp : filterData) { int month = timeVal.apply(tmp).getMonthValue(); if (monthCount.containsKey(month)) { monthCount.put(month, monthCount.get(month) + 1); } } Collection countNums = monthCount.values(); if (countNums.stream().anyMatch(n -> Integer.compare(n, limit) != ref)) { return null; } } else { return null; } } else if (ruleType == 2) { // 加油趋势(新逻辑) if (rule.getCountTrend() == null || rule.getCountNum() == null || rule.getHistoryMonths() == null || rule.getRecentMonths() == null) { return null; } if (!checkTrend(rule, filterData)) { return null; } } else { return null; } } } catch (Exception e) { e.printStackTrace(); return null; } return rules.getClientName(); } /** * 趋势判断(使用配置的月数) * @param rule 规则 * @param dataList 数据列表 * @return */ private boolean checkTrend(ClientConfig rule, List dataList) { if (dataList == null || dataList.isEmpty()) { return false; } Byte countTrend = rule.getCountTrend(); Integer countNum = rule.getCountNum(); Integer historyMonths = rule.getHistoryMonths(); Integer recentMonths = rule.getRecentMonths(); if (countTrend == null || countNum == null || historyMonths == null || recentMonths == null) { return false; } if (historyMonths <= 0 || recentMonths <= 0 || countNum <= 0) { return false; } LocalDate now = LocalDate.now(); LocalDate currentMonth = now.withDayOfMonth(1); LocalDate recentStart = currentMonth.minusMonths(recentMonths - 1L); LocalDate historyStart = recentStart.minusMonths(historyMonths); LocalDate historyEnd = recentStart; Map recentMonthCount = new LinkedHashMap<>(); for (int i = 0; i < recentMonths; i++) { LocalDate month = recentStart.plusMonths(i); recentMonthCount.put(month, 0); } Map historyMonthCount = new LinkedHashMap<>(); for (int i = 0; i < historyMonths; i++) { LocalDate month = historyStart.plusMonths(i); historyMonthCount.put(month, 0); } for (T tmp : dataList) { LocalDate month = timeVal.apply(tmp).withDayOfMonth(1); if (recentMonthCount.containsKey(month)) { recentMonthCount.put(month, recentMonthCount.get(month) + 1); } if (historyMonthCount.containsKey(month) && !month.isBefore(historyStart) && month.isBefore(historyEnd)) { historyMonthCount.put(month, historyMonthCount.get(month) + 1); } } if (countTrend == 1) { boolean historyStable = historyMonthCount.values().stream().allMatch(n -> n >= countNum); boolean recentStable = recentMonthCount.values().stream().allMatch(n -> n >= countNum); return historyStable && recentStable; } else if (countTrend == 2) { boolean historyStable = historyMonthCount.values().stream().allMatch(n -> n >= countNum); boolean recentDecrease = recentMonthCount.values().stream().allMatch(n -> n < countNum); return historyStable && recentDecrease; } return false; } } }