peng
2026-03-18 e59a0201057ba67cad425fed804c82ff4ba0c6f1
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
419
420
421
422
423
424
425
426
package com.tievd.jyz.handler.alg.common;
 
import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tievd.cube.modules.system.entity.SysDepart;
import com.tievd.jyz.cache.CarPlaceCache;
import com.tievd.jyz.cache.DepartCache;
import com.tievd.jyz.cache.DeviceCache;
import com.tievd.jyz.cache.EventCodeRelOilCache;
import com.tievd.jyz.constants.SystemConstant;
import com.tievd.jyz.dto.CarScoreCacheDTO;
import com.tievd.jyz.entity.*;
import com.tievd.jyz.handler.alg.AlgHandleInterface;
import com.tievd.jyz.mapper.*;
import com.tievd.jyz.mqtt.command.MqttCommandReceiver;
import com.tievd.jyz.mqtt.dto.EventInfoDTO;
import com.tievd.jyz.service.ICarInfoService;
import com.tievd.jyz.util.StringCampareUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * 车辆识别处理类
 * @author timi
 */
@Slf4j
@Component
public class CarIdentificationAlgHandler implements AlgHandleInterface {
 
    /** 车辆识别标识头 */
    private final String[] ALG_TAG = new String[]{"C10001"};
 
    @Autowired
    private OilEventMapper oilEventMapper;
    @Autowired
    private CameraMapper cameraMapper;
    @Autowired
    private OilRecordMapper oilRecordMapper;
    @Autowired
    private ICarInfoService carInfoService;
    @Autowired
    private SysCarModelMapper sysCarModelMapper;
    @Autowired
    private OilPositionMapper oilPositionMapper;
    @Autowired
    private MqttCommandReceiver mqttCommandReceiver;
    @Value("${init.sendCommandTopic:/ty/aibox/command/}")
    private String sendCommandTopic;
 
    @Value("${init.carInOutTimeout:1800000}")
    private Long carInOutTimeout;
    @Value("${init.carOilTimeThreshold:60000}")
    private Long carOilTimeThreshold;
 
    /** 轻量级解决并发请求导致数据重复问题 */
    private final Set<String> platNumberSet = new ConcurrentHashSet<>();
    /** 车辆在当前事务中,第一次出现在加油区的时间,platNumber_orgCode:time */
    private final Map<String,Long> firstOilTimeMap = new ConcurrentHashMap<>();
    
    private final List<CarScoreCacheDTO> carCache = new ArrayList<>();
    
    double handleScore = 0.9;
    
    double discardScore = 0.6;
 
    @Override
    public String[] getAlgTag(){
        return ALG_TAG;
    }
 
    /**
     * 处理入口
     * @param eventInfoDTO
     * @param sn
     * @param time
     */
    @Override
    public void handle(EventInfoDTO<JSONObject> eventInfoDTO, String sn,String time) {
        //当前事件唯一标识
        String eventCode = eventInfoDTO.getEventCode();
        //检测算法编码
        String algCode = eventInfoDTO.getAlgCode();
        //终端编码
        String cameraCode = eventInfoDTO.getCameraCode();
        //扩展属性
        JSONObject extend = new JSONObject();
        Object extendObj = eventInfoDTO.getExtend();
        if(extendObj instanceof String){
            extend = JSONObject.parseObject(extendObj.toString());
        }else if(extendObj instanceof JSONObject){
            extend = (JSONObject)extendObj;
        }
        log.info("车辆识别事件 eventCode:{},extend:{}",eventCode,extendObj);
        Device device = DeviceCache.getDeviceBySn(sn);
        if(ObjectUtil.isNull(device)){
            log.error("网关设备不存在,sn:{}",sn);
            return;
        }
        JSONArray carArr = extend.getJSONArray("cars");
        if(carArr.size() == 0){
            return;
        }
        long timeLong = System.currentTimeMillis();
        if (StringUtils.isBlank(time)) {
            timeLong = Long.valueOf(timeLong);
        }
        vehicleIdentificationHandle(eventCode,cameraCode,carArr,device, timeLong);
    }
 
    /**
     * 车辆识别处理
     */
    private void vehicleIdentificationHandle(String eventCode, String cameraCode, JSONArray carArr, Device device, long time){
        for(Object obj : carArr){
            JSONObject infoObj = (JSONObject) obj;
            //车辆识别
            //车牌号
            String plateNumber = infoObj.getString("number");
            //过滤置信度低的数据--------------------
            double score = infoObj.getDoubleValue("score");
            if (score < discardScore) {
                log.info("检测到存在车牌号置信度较低,丢弃 {}====>{}", plateNumber, score);
                continue;
            }
            if(StringUtils.isEmpty(plateNumber) || plateNumber.length() < 7){
                continue;
            }
            //加油位
            String oilPosition = infoObj.getString("oilPosition");
            if (StringUtils.isNotBlank(oilPosition)) {
                List<OilPosition> positions = oilPositionMapper.selectList(new QueryWrapper<>(new OilPosition().setPositionCode(oilPosition)));
                oilPosition = positions.size() > 0 ? positions.get(0).getOilPosition() : oilPosition;
            }
            String carType = infoObj.getString("type");
            String carReqStr = new StringBuilder().append(plateNumber).append("_").append(device.getOrgCode()).toString();
            if(platNumberSet.contains(carReqStr)){
                //存在并发请求,同加油站、同车辆请求还未处理完,跳过
                log.info("存在并发请求,同加油站、同车辆请求还未处理完,跳过,carReqStr:{}",carReqStr);
                return;
            }
            synchronized (this){
                //轻量级解决并发请求导致数据重复问题
                if(platNumberSet.contains(carReqStr)){
                    //存在并发请求,同加油站、同车辆请求还未处理完,跳过
                    log.info("存在并发请求,同加油站、同车辆请求还未处理完,跳过,carReqStr:{}",carReqStr);
                    return;
                }
                platNumberSet.add(carReqStr);
            }
            try {
                SysCarModel sysCarModel = sysCarModelMapper.selectOne(new QueryWrapper<SysCarModel>().eq("model_code", carType));
                LambdaQueryWrapper<OilRecord> wrapper = new LambdaQueryWrapper();
                wrapper.eq(OilRecord::getLicenseNum, plateNumber)
                        .eq(OilRecord::getOrgCode, device.getOrgCode())
                        .isNull(OilRecord::getEndTime);
                OilRecord oilRecord = oilRecordMapper.selectOne(wrapper);
                //查看是否有相似车牌号数据,有则使其进入更新流程---------------------
                if (ObjectUtil.isNull(oilRecord)) {
                    wrapper.clear();
                    wrapper.eq(OilRecord::getOrgCode, device.getOrgCode())
                            .eq(OilRecord::getDeviceId, device.getId())
                            .isNull(OilRecord::getEndTime);
                    List<OilRecord> recordList = oilRecordMapper.selectList(wrapper);
                    if (recordList.size() > 0) {
                        for (OilRecord record : recordList) {
                            if (StringCampareUtil.scoreLCS(plateNumber, record.getLicenseNum()) > discardScore) {
                                log.info("检测到存在相似车牌号记录{}====>{}", plateNumber, record.getLicenseNum());
                                oilRecord = record;
                                plateNumber = record.getLicenseNum();
                                break;
                            }
                        }
                    }
                }
                //-------------------------------------------------------------------
                Camera camera = cameraMapper.selectOne(new QueryWrapper<Camera>().eq("code", cameraCode).eq("device_id", device.getId()));
                if (ObjectUtil.isNull(oilRecord)) {
                    //如果新记录为出口摄像头拍到,则不新增记录
                    if (SystemConstant.INSTALL_ADDRESS_OUT.equals(camera.getInstallAddress().intValue())) {
                        platNumberSet.remove(carReqStr);
                        continue;
                    }
                    //新增记录,默认为停靠
                    if (score < handleScore) {
                        //若置信度小于标准,则加入池中等待比较
                        log.info("检测到存在车牌号置信度中等 {}====>{}", plateNumber, score);
                        platNumberSet.remove(carReqStr);
                        joinWaitPool(eventCode, cameraCode, infoObj, device, time, plateNumber, score);
                        continue;
                    }
                    oilRecord = oilRecordGen(cameraCode, plateNumber, oilPosition, device, time);
                    oilRecord.setOilPosition(oilPosition);
                    oilRecord.setModelCode(sysCarModel.getModelCode());
                    oilRecordMapper.insert(oilRecord);
                    //请求图片
                    EventCodeRelOilCache.put(eventCode, oilRecord.getId());
                } else {
                    //更新进出记录
                    if (ObjectUtil.isNull(camera)) {
                        log.error("抓拍摄像头获取失败,丢弃本次数据");
                        return;
                    }
                    if (SystemConstant.INSTALL_ADDRESS_OUT.equals(camera.getInstallAddress().intValue())) {
                        //为出口抓拍到时,更新离开时间、停靠时长和停车是否规范。
                        oilRecordEndTimeUpdate(oilRecord);
                        EventCodeRelOilCache.put(eventCode, oilRecord.getId());
                    } else if (SystemConstant.INSTALL_ADDRESS_FUEL_OIL.equals(camera.getInstallAddress().intValue())) {
                        // 为加油位抓拍到时,则更新加油位和加油量
                        OilRecord tmpOilRecord = new OilRecord();
                        tmpOilRecord.setId(oilRecord.getId());
                        if (StringUtils.isNotBlank(oilPosition)) {
                            tmpOilRecord.setOilPosition(oilPosition);
                        }
                        if(SystemConstant.BEHAVIOR_TYPE_TMP.equals(oilRecord.getBehavior().intValue())){
                            Long firstOilTime = firstOilTimeMap.get(carReqStr);
                            if(ObjectUtil.isNull(firstOilTime)){
                                firstOilTimeMap.put(carReqStr,System.currentTimeMillis());
                            }else{
                                if(System.currentTimeMillis() - firstOilTime > carOilTimeThreshold){
                                    //需求为加油位连续抓拍1分钟,则为加油行为
                                    tmpOilRecord.setBehavior(SystemConstant.BEHAVIOR_TYPE_OIL.byteValue());
                                    //FIXME 先随便取一个默认容量
                                    tmpOilRecord.setOilVolume(50);
                                    if (ObjectUtil.isNotNull(sysCarModel)) {
                                        tmpOilRecord.setOilVolume(sysCarModel.getOilCapacity());
                                    }
                                }
                            }
                        }
                        oilRecordMapper.updateById(tmpOilRecord);
                    }
                }
                //建立车辆档案信息
                CarInfo carInfo = new CarInfo();
                carInfo.setLicenseNum(plateNumber);
                carInfo.setLicensePlace(CarPlaceCache.getPlace(plateNumber.substring(0, 2)));
                if (oilRecord != null) {
                    carInfo.setImgPath(oilRecord.getImgPath());
                    carInfo.setCreateTime(oilRecord.getStartTime());
                }
                if (sysCarModel != null) {
                    carInfo.setModelId(sysCarModel.getModelCode()).setModelName(sysCarModel.getModelName());
                }
                carInfoService.saveOrUpdate(carInfo);
            }finally {
                platNumberSet.remove(carReqStr);
            }
        }
        //默认自动上报图片视频
//        if(EventCodeRelOilCache.count(eventCode) > 0){
//            //需要请求图片
//            log.info("请求图片,eventCode:{}",eventCode);
//            EventResourceDTO eventResourceDTO = new EventResourceDTO(SystemConstant.IMG_SOURCE_TYPE, eventCode, cameraCode);
//            EventResourceCommand eventResourceCommand = new EventResourceCommand(mqttCommandReceiver, eventResourceDTO, sendCommandTopic, device.getSn());
//            eventResourceCommand.init();
//            if (eventResourceCommand.execute() == SystemConstant.DEAL_FAIL) {
//                log.error("事件图片指令发送失败");
//            }
//        }
    }
    
    
    /**
     * 置信度中等的信息加入比较等待池,1分钟后重新提取进行处理
     */
    private void joinWaitPool(String eventCode, String cameraCode, JSONObject car, Device device, long time, String plateNumber, double score) {
        CarScoreCacheDTO scoreCacheDTO = new CarScoreCacheDTO();
        scoreCacheDTO.setEventCode(eventCode)
                .setCameraCode(cameraCode)
                .setCarInfo(car)
                .setLienseNum(plateNumber)
                .setTime(time)
                .setDeviceSn(device.getSn())
                .setScore(score);
        carCache.add(scoreCacheDTO);
    }
    
    /**
     * 对比等待池中的车牌相似情况,并分组,取出每组评分最高的车牌,并将置信度置为0.9,重新加入处理流程
     */
    @Scheduled(fixedDelay = 60000L)
    public void waitHandle(){
        if (carCache.size() == 0) return;
        log.info("开始处理缓存车牌数据==============》");
        List<CarScoreCacheDTO> carCache0 = new ArrayList<>();
        synchronized (carCache) {
            carCache0.addAll(carCache);
            carCache.clear();
        }
        carCache0.sort(Comparator.comparingDouble(CarScoreCacheDTO::getScore));
        //分组数据
        List<List<CarScoreCacheDTO>> handleData = new ArrayList<>();
        Set idxSet = new HashSet();
        for (int i = 0; i < carCache0.size(); i++) {
            if (idxSet.contains(i)) {
                continue;
            }
            List<CarScoreCacheDTO> carGroup = new ArrayList<>();
            CarScoreCacheDTO carInfo = carCache0.get(i);
            carGroup.add(carInfo);
            String lienseNum0 = carInfo.getLienseNum();
            String cameraCode0 = carInfo.getCameraCode();
            for (int j = i + 1; j < carCache0.size(); j++) {
                if (idxSet.contains(j)) {
                    continue;
                }
                String lienseNum = carCache0.get(j).getLienseNum();
                String cameraCode = carCache0.get(j).getCameraCode();
                if (!cameraCode.equals(cameraCode0)) continue;
                if (lienseNum0.equals(lienseNum)) {
                    carInfo.setCount(carInfo.getCount() + 1);
                    idxSet.add(j);
                    continue;
                }
                //获取相似度,相似则加入同一组
                double similarScore = StringCampareUtil.scoreLCS(lienseNum0, lienseNum);
                if (similarScore > discardScore) {
                    carGroup.add(carCache0.get(j));
                    idxSet.add(j);
                }
            }
            handleData.add(carGroup);
        }
        carCache0.clear();
        for (List<CarScoreCacheDTO> groupData : handleData) {
            groupData.sort((o1, o2) -> o1.getScore() * o1.getCount() >= o2.getScore() * o2.getCount() ? 1 : -1);
            carCache0.add(groupData.get(0));
        }
        joinHandle(carCache0);
        log.info("处理完成,已筛选出车牌数据==============》{}", JSON.toJSONString(carCache0));
    }
    
    public void joinHandle(List<CarScoreCacheDTO> carCacheList) {
        for (CarScoreCacheDTO car : carCacheList) {
            JSONArray carArr = new JSONArray(){{
                add(car.getCarInfo());
            }};
            Device device = DeviceCache.getDeviceBySn(car.getDeviceSn());
            log.info("=====> 低置信度数据重新加入流程", car.getCarInfo().toJSONString());
            car.getCarInfo().put("score", handleScore);
            vehicleIdentificationHandle(car.getEventCode(), car.getCameraCode(), carArr, device, car.getTime());
        }
    }
    
    
    
    /**
     * 车辆记录对象生成
     * @param cameraCode
     * @param plateNumber
     * @param oilPosition
     * @param device
     * @param time
     * @return
     */
    private OilRecord oilRecordGen(String cameraCode, String plateNumber, String oilPosition, Device device, long time){
        OilRecord oilRecord = new OilRecord();
        oilRecord.setCameraCode(cameraCode);
        oilRecord.setCreateTime(new Date());
        oilRecord.setRecordDay(LocalDateTime.now().format(SystemConstant.DATE_DAY_FORMATTER));
        oilRecord.setDeviceId(device.getId());
        //默认停靠
        oilRecord.setBehavior(SystemConstant.BEHAVIOR_TYPE_TMP.byteValue());
        oilRecord.setLicenseNum(plateNumber);
        oilRecord.setOilPosition(oilPosition);
        oilRecord.setOrgCode(device.getOrgCode());
        SysDepart sysDepart = DepartCache.getDepartByOrgCode(device.getOrgCode());
        if(ObjectUtil.isNotNull(sysDepart)){
            oilRecord.setOrgName(sysDepart.getDepartName());
        }
        oilRecord.setStartTime(new Timestamp(time));
        return oilRecord;
    }
 
 
    /**
     * 定时更新车辆进出状态
     * 超时未检测到车辆离开时,自动设置为离开状态
     */
    @Scheduled(fixedDelay = 60000L)
    public void scheduled(){
        List<OilRecord> oilRecords = oilRecordMapper.selectList(new QueryWrapper<OilRecord>().isNull("end_time"));
        for(OilRecord oilRecord:oilRecords){
            if((System.currentTimeMillis() - oilRecord.getStartTime().getTime()) > carInOutTimeout){
                //超时,自动设置为离开状态
                log.info("车辆超时未离场,自动设置为离开状态,机构:{},车牌号:{},进场时间:{}",oilRecord.getOrgName(),oilRecord.getLicenseNum(),oilRecord.getStartTime());
                oilRecordEndTimeUpdate(oilRecord);
            }
        }
    }
 
    /**
     * 更新车辆出场信息
     * @param oilRecord
     */
    private void oilRecordEndTimeUpdate(OilRecord oilRecord){
        OilRecord tmpOilRecord = new OilRecord();
        tmpOilRecord.setId(oilRecord.getId());
        tmpOilRecord.setStandard(SystemConstant.STANDARD_OK.byteValue());
        Long eventCount = oilEventMapper.selectCount(new QueryWrapper<OilEvent>().eq("license_num",oilRecord.getLicenseNum()).eq("org_code",oilRecord.getOrgCode()).between("create_time",oilRecord.getCreateTime(),new Date()));
        if(ObjectUtil.isNotNull(eventCount) && eventCount > 0){
            //存在相关告警则认为为存在违规
            tmpOilRecord.setStandard(SystemConstant.STANDARD_ERROR.byteValue());
        }
        tmpOilRecord.setEndTime(new Timestamp(System.currentTimeMillis()));
        int spandTime = (int) (tmpOilRecord.getEndTime().getTime() - oilRecord.getStartTime().getTime())/1000/60;
        tmpOilRecord.setSpandTime(spandTime);
        oilRecordMapper.updateById(tmpOilRecord);
        String carReqStr = new StringBuilder().append(oilRecord.getLicenseNum()).append("_").append(oilRecord.getOrgCode()).toString();
        firstOilTimeMap.remove(carReqStr);
    }
}