peng
2026-03-24 73d124ce71e60685b19cc74a44e21dc080f7bfb6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
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<OilRecord> wrapper = new LambdaQueryWrapper();
//        wrapper.ge(OilRecord::getStartTime, statisTime);
        wrapper.isNotNull(OilRecord::getStandard)
                .isNotNull(OilRecord::getBehavior)
                .isNotNull(OilRecord::getStartTime);
        wrapper.orderByDesc(OilRecord::getStartTime);
        List<OilRecord> recordList = oilRecordService.list(wrapper);
        //数据统计
        Map<String, OilStatisVo> statisMap = new HashMap<>();
    
        //客户类型
        Map<String, List<OilRecord>> 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<String, Integer> positionMap = statisVo.getPosition();
                String position = oilRecord.getOilPosition();
                positionMap.put(position, positionMap.getOrDefault(position, 0) + 1);
            }
            
            //高峰时段
            Map<Integer, Integer> phraseMap = statisVo.getPhrase();
            int timePhrase = getTimePhrase(oilRecord.getStartTime());
            phraseMap.put(timePhrase, phraseMap.getOrDefault(timePhrase, 0) + 1);
            
        }
        
        List<OilStatis> saveList = new ArrayList<>();
        
        //客户规则
        List<ClientVo> clientVos = clientConfigService.groupList();
        
        //计算汇总的数据
        for (Map.Entry<String, OilStatisVo> 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<OilRecord> 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<String, Integer> pos = Collections.max(statisVo.getPosition().entrySet(), Comparator.comparingInt(Map.Entry::getValue));
                statisVo.setOilPosition(pos.getKey());
            }
            //高峰时段
            Map.Entry<Integer, Integer> 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<OiloutRecord> wrapper = new LambdaQueryWrapper<OiloutRecord>();
        wrapper.isNull(OiloutRecord::getEndTime)
                .le(OiloutRecord::getStartTime, LocalDateTime.now().minusHours(2));
        List<OiloutRecord> 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<OiloutEvent> wrapper = new LambdaQueryWrapper<OiloutEvent>();
        wrapper.eq(OiloutEvent::getRecordId, record.getId())
                .orderByAsc(OiloutEvent::getAlgorithmCode);
        List<OiloutEvent> eventList = oiloutEventService.list();
        if (CollectionUtils.isEmpty(eventList)) {
            return;
        }
        log.info("---------开始处理未正常完结的卸油卸油事件----------");
        List<String> completed = eventList.stream().map(OiloutEvent::getAlgorithmCode).collect(Collectors.toList());
        //获取未入库事件的算法code
        List<String> unComplete = new ArrayList<>();
        unComplete.addAll(AlgorithmCache.oiloutAlgMap().keySet());
        unComplete.removeAll(completed);
        //待补充事件采用最后一个事件的基础信息
        OiloutEvent lastEvent = eventList.get(eventList.size() - 1);
        List<OiloutEvent> 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<OilRecord> 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<ClientConfig> configs = new ArrayList<>();
        configs.add(new ClientConfig().setTimeValue(3)
                .setTimeUnit("months")
                .setCountType((byte) 2)
                .setCountRef((byte) 1)
                .setCountNum(0));
        clientVo.setClientConfigs(configs);
        ClientTypeInfer<OilRecord> clientInfer = new ClientTypeInfer<>(
                clientVo,
                records,
                r -> LocalDate.parse(r.getRecordDay()));
        clientInfer.infer();
    }
    
    /**
     * 客户类型推断
     * @param <T> record
     */
    class ClientTypeInfer<T> {
        
        ClientVo rules;
    
        List<T> dataList;
    
        Function<T, LocalDate> timeVal;
        
        public ClientTypeInfer(ClientVo rules, List<T> dataList, Function<T, LocalDate> 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<T> 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<Integer, Integer> 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<Integer> 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<T> 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<LocalDate, Integer> recentMonthCount = new LinkedHashMap<>();
            for (int i = 0; i < recentMonths; i++) {
                LocalDate month = recentStart.plusMonths(i);
                recentMonthCount.put(month, 0);
            }
 
            Map<LocalDate, Integer> 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;
        }
        
    }
    
}