package com.ycl.task; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.google.common.util.concurrent.AtomicDouble; import com.ycl.platform.domain.entity.DemeritRecord; import com.ycl.platform.domain.entity.Report; import com.ycl.platform.domain.result.UY.RecordMetaDSumResult; import com.ycl.platform.mapper.DemeritRecordMapper; import com.ycl.platform.mapper.ReportMapper; import com.ycl.platform.service.IDemeritRecordService; import com.ycl.utils.DateUtils; import enumeration.ConstructionTypeEnum; import enumeration.general.AreaDeptEnum; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Component; import utils.StringUtils; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.*; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; /** * zgyw * * @author : zxl * @date : 2025-09-15 17:32 **/ @Slf4j @RequiredArgsConstructor @Component("demeritRecordTask") public class DemeritRecordTask { private final MongoTemplate mongoTemplate; private final ReportMapper reportMapper; private static final ExecutorService executorService = new ThreadPoolExecutor(16, 128, 5000, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), new ThreadPoolExecutor.CallerRunsPolicy() ); private final IDemeritRecordService demeritRecordService; // 提取公共过滤方法 private Predicate deviceNameStartsWith(String prefix) { return result -> result.getDeviceName() != null && result.getDeviceName().startsWith(prefix); } private Predicate deviceNameStartsWithAny(String... prefixes) { return result -> result.getDeviceName() != null && Arrays.stream(prefixes).anyMatch(prefix -> result.getDeviceName().startsWith(prefix)); } private DemeritRecord buildDemeritRecord(String constructionType,BigDecimal demerit,Integer deptId) { DemeritRecord record = new DemeritRecord(); record.setConstructionType(constructionType); record.setDemerit(demerit); record.setDeptId(deptId); return record; } private BigDecimal calculateTotalDeduction(List records) { BigDecimal total = BigDecimal.ZERO; // 单线程循环,无需AtomicReference for (RecordMetaDSumResult record : records) { // 安全处理空值:若missDuration为null,默认按0分钟处理 Double missDurationHours = record.getMissDuration(); double missDurationMinutes = Optional.ofNullable(missDurationHours) .map(hours -> hours * 60.0) // 小时转分钟 .orElse(0.0); // 空值默认0分钟 // 计算单条记录的扣分 BigDecimal deduction; if (missDurationMinutes > 720) { deduction = new BigDecimal("2.0"); } else if (missDurationMinutes > 240) { deduction = new BigDecimal("1.5"); } else if (missDurationMinutes > 60) { deduction = new BigDecimal("1.0"); } else if (missDurationMinutes > 30) { deduction = new BigDecimal("0.5"); } else { deduction = BigDecimal.ZERO; } // 累加总扣分(最后统一处理精度,避免中间四舍五入误差) total = total.add(deduction); } // 最终统一保留1位小数,四舍五入 return total.setScale(1, RoundingMode.HALF_UP); } public void run(){ log.info("开始执行计算每日扣分记录情况"); //获得mongodb中所有全景的设备 //mongodb查询条件 Date today =new Date(); // Calendar calendar = Calendar.getInstance(); // calendar.setTime(today); // calendar.add(Calendar.DAY_OF_YEAR, -2); // 减去2天 测试用 // Date twoDaysAgo = calendar.getTime(); //计算录像可用率和重点录像可用率 Query query = new Query(); query.addCriteria(Criteria .where("mongoCreateTime").gte(DateUtils.getDayStart(today)).lt(DateUtils.getDayEnd(today))); List results = mongoTemplate.find(query, RecordMetaDSumResult.class); log.info("日期:{},查询出的设备录像记录数{}",today,results.size()); //过滤掉非全景的设备 且 1则为细节,如果是0则为全景 results.stream().filter(obj -> { String deviceId = obj.getDeviceId(); if (deviceId == null || deviceId.length() < 7) { return false; // 去除元素 } // 获取倒数第七位的字符 char seventhFromEnd = deviceId.charAt(deviceId.length() - 7); return seventhFromEnd != '1'; //为1 则会 false 去除掉 }); log.info("过滤后剩余全景设备数{}",results.size()); //只考核LT_、(三期) // DX_ 、(一二期) // DX_R、(四区人脸) // DX_RS、(四区人脸) // (需要排除DX_R2、DX_RD、J_、T1、T3以及没有前缀的设备) List prefixes = Arrays.asList("LT_", "DX_", "DX_R", "DX_RS"); results.stream() .filter(result -> { String deviceName = result.getDeviceName(); if (deviceName == null) { return false; } return prefixes.stream().anyMatch(deviceName::startsWith); }) .collect(Collectors.toList()); log.info("剩余考核设备过滤后设备数{}",results.size()); //过滤掉报备的设备 //查询在当前时间有报备的所有设备, //因为录像数据的时间 List deviceIds = new LambdaQueryChainWrapper<>(reportMapper) .eq(Report::getStatus, 1) .ge(Report::getBeginCreateTime, today) .le(Report::getEndCreateTime, today) .list().stream() .collect(Collectors.toMap( Report::getSerialNumber, // key: serialNumber Function.identity(), // value: Report对象本身 (existing, replacement) -> // 当key冲突时,保留createTime更大的(更新的)记录 existing.getCreateTime().after(replacement.getCreateTime()) ? existing : replacement )) .values().stream() .map(Report::getSerialNumber).collect(Collectors.toList()); Set deviceIdSet = new HashSet<>(deviceIds); log.info("报备设备数{}",deviceIdSet.size()); results.stream() .filter(result -> { // 获取当前对象的deviceId String resultDeviceId = result.getDeviceId(); // 过滤条件:deviceId不在集合中(注意处理null值,避免NPE) return resultDeviceId != null && !deviceIdSet.contains(resultDeviceId); }) .collect(Collectors.toList()); log.info("剩余过滤报备后设备数{}",results.size()); // 按区域划分 组装成map results.forEach(item -> { String areaCode = item.getArealayername().substring(0, 6); AreaDeptEnum areaDeptEnum = AreaDeptEnum.fromCode(areaCode); if (areaDeptEnum != null) { item.setArealayerno(areaDeptEnum.getCode()); } }); //需要添加数据库的数据集合 List demeritRecords = new ArrayList<>(); Map> groupByArealayerno = results.stream() .collect(Collectors.groupingBy(RecordMetaDSumResult::getArealayerno)); //循环分组后的map for (Map.Entry> entry : groupByArealayerno.entrySet()) { String arealayerno = entry.getKey(); log.info("循环区域{}",arealayerno); //获得区 AreaDeptEnum areaDeptEnum = AreaDeptEnum.fromCode(arealayerno); if (areaDeptEnum == null) { log.info("区域数据异常,异常区域键值:{}",arealayerno); return; } List resultList = entry.getValue(); log.info("resultList:{}",resultList.size()); if (CollectionUtils.isNotEmpty(resultList)) { // 对每个List进行处理 分建类型处理集合 List phase_one_two = resultList.stream() .filter(deviceNameStartsWith("DX_")) .collect(Collectors.toList()); log.info("一二期考核记录数{}",phase_one_two.size()); List phase_three = resultList.stream() .filter(deviceNameStartsWith("LT_")) .collect(Collectors.toList()); log.info("三期考核记录数{}",phase_three.size()); List phase_fourth = resultList.stream() .filter(deviceNameStartsWithAny("DX_R", "DX_RS")) .collect(Collectors.toList()); log.info("四期考核记录数{}",phase_fourth.size()); if (CollectionUtils.isNotEmpty(phase_one_two)){ BigDecimal phaseOneTwoDeduction = calculateTotalDeduction(phase_one_two); DemeritRecord demeritRecordPhaseOneTwo = buildDemeritRecord( ConstructionTypeEnum.PHASE_ONE_TWO.name(), phaseOneTwoDeduction, areaDeptEnum.getDeptId()); demeritRecords.add(demeritRecordPhaseOneTwo); }else{ DemeritRecord phaseOneTwoDeduction = buildDemeritRecord( ConstructionTypeEnum.PHASE_ONE_TWO.name(), BigDecimal.ZERO, areaDeptEnum.getDeptId()); demeritRecords.add(phaseOneTwoDeduction); } if (CollectionUtils.isNotEmpty(phase_three)){ BigDecimal phaseThreeDeduction = calculateTotalDeduction(phase_three); DemeritRecord demeritRecordPhaseThree = buildDemeritRecord( ConstructionTypeEnum.PHASE_THREE.name(), phaseThreeDeduction, areaDeptEnum.getDeptId()); demeritRecords.add(demeritRecordPhaseThree); }else { DemeritRecord phaseThreeDeduction = buildDemeritRecord( ConstructionTypeEnum.PHASE_THREE.name(), BigDecimal.ZERO, areaDeptEnum.getDeptId()); demeritRecords.add(phaseThreeDeduction); } if (CollectionUtils.isNotEmpty(phase_fourth)){ BigDecimal phaseFourthDeduction = calculateTotalDeduction(phase_fourth); DemeritRecord demeritRecordPhaseFourth = buildDemeritRecord( ConstructionTypeEnum.PHASE_FOURTH.name(), phaseFourthDeduction, areaDeptEnum.getDeptId()); demeritRecords.add(demeritRecordPhaseFourth); }else{ DemeritRecord phaseFourthDeduction = buildDemeritRecord( ConstructionTypeEnum.PHASE_FOURTH.name(), BigDecimal.ZERO, areaDeptEnum.getDeptId()); demeritRecords.add(phaseFourthDeduction); } } } //处理完数据插入数据库中 //先删除需要插入时间是否存在数据 LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.ge(DemeritRecord::getCreateTime,DateUtils.getDayStart(today)) .le(DemeritRecord::getCreateTime,DateUtils.getDayEnd(today)); demeritRecordService.remove(queryWrapper); demeritRecordService.saveBatch(demeritRecords); log.info("结束计算每日扣分记录情况:插入数据量{},数据信息:{}",demeritRecords.size(),demeritRecords); } }