Эх сурвалжийг харах

update handlerExamCaptureQueues

deason 10 сар өмнө
parent
commit
afb48514df

+ 99 - 135
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamCaptureQueueServiceImpl.java

@@ -5,9 +5,7 @@ import cn.com.qmth.examcloud.core.oe.student.dao.ExamCaptureQueueRepo;
 import cn.com.qmth.examcloud.core.oe.student.dao.ExamCaptureRepo;
 import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamCaptureEntity;
 import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamCaptureQueueEntity;
-import cn.com.qmth.examcloud.core.oe.student.dao.enums.ExamCaptureQueueStatus;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureQueueService;
-import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
 import cn.com.qmth.examcloud.core.oe.task.service.bean.FaceApiParam;
 import cn.com.qmth.examcloud.starters.face.verify.common.CommonUtils;
 import cn.com.qmth.examcloud.starters.face.verify.common.FaceVerifyException;
@@ -19,14 +17,10 @@ import cn.com.qmth.examcloud.starters.face.verify.model.param.ImageParm;
 import cn.com.qmth.examcloud.starters.face.verify.service.FaceVerifyService;
 import cn.com.qmth.examcloud.support.Constants;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
-import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.OrgPropertyCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
-import cn.com.qmth.examcloud.support.enums.ExamProperties;
-import cn.com.qmth.examcloud.support.examing.ExamRecordData;
 import cn.com.qmth.examcloud.support.fss.FssFactory;
 import cn.com.qmth.examcloud.support.fss.FssHelper;
-import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -37,8 +31,8 @@ import org.springframework.data.domain.Example;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
+import java.util.Date;
 import java.util.List;
-import java.util.Optional;
 
 /**
  * 处理考试抓拍队列
@@ -60,9 +54,6 @@ public class ExamCaptureQueueServiceImpl implements ExamCaptureQueueService {
     @Autowired
     private FaceVerifyService faceVerifyService;
 
-    @Autowired
-    private ExamRecordDataService examRecordDataService;
-
     @Override
     public void handlerExamCaptureQueuesByExamRecordDataId(Long examRecordDataId, List<ExamCaptureQueueEntity> queues, FaceApiParam param) throws Exception {
         if (CollectionUtils.isEmpty(queues)) {
@@ -73,27 +64,33 @@ public class ExamCaptureQueueServiceImpl implements ExamCaptureQueueService {
         Long studentId = queues.get(0).getStudentId();
         StudentCacheBean studentCache = CacheHelper.getStudent(studentId);
         if (StringUtils.isBlank(studentCache.getPhotoPath())) {
-            throw new IllegalArgumentException("学生底照不存在!studentId is " + studentId);
+            for (ExamCaptureQueueEntity queue : queues) {
+                queue.setErrorNum(param.getMaxErrorNum());
+                queue.setErrorMsg("学生底照不存在");
+                queue.setUpdateTime(new Date());
+            }
+            examCaptureQueueRepo.saveAll(queues);
+            log.warn("跳过人脸比对,学生底照不存在!studentId:{} examRecordDataId:{}", studentId, examRecordDataId);
+            return;
         }
 
-        // 获取学校人脸API方案配置
-        boolean useBaiduApi = false;
-        Double baiduExpectFaceCompareScore = null;
-        OrgPropertyCacheBean property1 = CacheHelper.getOrgProperty(studentCache.getRootOrgId(),
-                Constants.FACE_COMPARE_API_PROVIDER);
-        if (property1.getHasValue() && FaceApiProvider.BAIDU.name().equals(property1.getValue())) {
-            useBaiduApi = true;
+        // 获取学校“抓拍时人脸比对API”配置
+        boolean useBaiduApi = param.isUseBaiduApi();
+        OrgPropertyCacheBean faceApiConfig = CacheHelper.getOrgProperty(studentCache.getRootOrgId(), Constants.FACE_COMPARE_API_PROVIDER);
+        if (faceApiConfig.getHasValue() && FaceApiProvider.FACEPP.name().equals(faceApiConfig.getValue())) {
+            useBaiduApi = false;
         }
-        OrgPropertyCacheBean property2 = CacheHelper.getOrgProperty(studentCache.getRootOrgId(),
-                Constants.BAIDU_EXPECT_FACE_COMPARE_SCORE);
-        if (property2.getHasValue() && StringUtils.isNotEmpty(property2.getValue())) {
-            baiduExpectFaceCompareScore = Double.parseDouble(property2.getValue());
+
+        // 获取学校“百度人脸比对通过阈值”配置
+        Double baiduExpectFaceCompareScore = null;
+        OrgPropertyCacheBean faceScoreConfig = CacheHelper.getOrgProperty(studentCache.getRootOrgId(), Constants.BAIDU_EXPECT_FACE_COMPARE_SCORE);
+        if (faceScoreConfig.getHasValue() && StringUtils.isNotEmpty(faceScoreConfig.getValue())) {
+            baiduExpectFaceCompareScore = Double.parseDouble(faceScoreConfig.getValue());
         }
 
-        log.warn(
-                "studentId:{} examRecordDataId:{} todoSize:{} useBaiduApi:{} useLocalBaiduApiForFaceLiveness:{} useLocalBaiduApiForFaceCompare:{} expectScore:{}",
-                studentId, examRecordDataId, queues.size(), useBaiduApi, param.isUseLocalBaiduApiForFaceLiveness(),
-                param.isUseLocalBaiduApiForFaceCompare(), baiduExpectFaceCompareScore);
+        log.warn("studentId:{} examRecordDataId:{} queueSize:{} useBaiduApi:{} useLocalBaiduApiForFaceCompare:{} useLocalBaiduApiForFaceLiveness:{} scoreConfig:{}",
+                studentId, examRecordDataId, queues.size(), useBaiduApi, param.isUseLocalBaiduApiForFaceCompare(),
+                param.isUseLocalBaiduApiForFaceLiveness(), baiduExpectFaceCompareScore);
 
         // 将学生底照图片转成Base64格式
         ImageParm basePhoto;
@@ -108,8 +105,6 @@ public class ExamCaptureQueueServiceImpl implements ExamCaptureQueueService {
         }
 
         for (ExamCaptureQueueEntity queue : queues) {
-            queue.setFaceCompareStartTime(System.currentTimeMillis());
-
             // 将抓拍照图片转成Base64格式
             String capturePhotoPath = FssHelper.fixFilePath(queue.getFileUrl());
             if (StringUtils.isBlank(capturePhotoPath)) {
@@ -130,127 +125,99 @@ public class ExamCaptureQueueServiceImpl implements ExamCaptureQueueService {
                 continue;
             }
 
-            String extMsg = "";
-            boolean errFace = false;
-
-            try {
-                if (ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_COMPLETE != queue.getStatus()
-                        && ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED != queue.getStatus()) {
-                    // 人脸比对开始处理
-                    queue.setStatus(ExamCaptureQueueStatus.PROCESSING);
-
-                    FaceResult faceCompareResult;
-                    if (useBaiduApi) {
-                        // faceCompareResult = faceVerifyService.faceCompareByBaidu(basePhoto, capturePhoto,
-                        //         baiduExpectFaceCompareScore, param.isUseLocalBaiduApiForFaceCompare());
-                        faceCompareResult = faceVerifyService.faceCompare(FaceParam.builder()
-                                .images(basePhoto, capturePhoto)
-                                .expectFaceCompareScore(baiduExpectFaceCompareScore)
-                                .apiType(param.isUseLocalBaiduApiForFaceCompare() ? FaceApiType.PRIVATE_BAIDU_API : FaceApiType.BAIDU_API));
-                    } else {
-                        // faceCompareResult = faceVerifyService.faceCompareByFacePlus(basePhoto, capturePhoto);
-                        faceCompareResult = faceVerifyService.faceCompare(FaceParam.builder()
-                                .images(basePhoto, capturePhoto)
-                                .apiType(FaceApiType.FACE_PLUS));
-                    }
-
-                    if (faceCompareResult.isApiNeedRetry()) {
-                        throw new FaceVerifyException(faceCompareResult.getError());
-                    }
-
-                    queue.setIsPass(faceCompareResult.isFacePass());
-                    queue.setFaceCompareResult(faceCompareResult.getJsonResult());
-                    queue.setStatus(ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_COMPLETE);
-                    queue.setIsStranger(false);
-
-                    if (!faceCompareResult.isFacePass()) {
-                        extMsg = "比对:" + Optional.ofNullable(faceCompareResult.getError()).orElse("不通过");
-                        queue.setExtMsg(extMsg);
-                    }
-
-                    if (faceCompareResult.getFaceNum() == 0) {
-                        // 百度人脸比对接口(不支持人脸数量检测)
-                        if (!useBaiduApi) {
-                            errFace = true;
-                        }
-                    } else if (faceCompareResult.getFaceNum() > 1) {
-                        // 是否启用陌生人检测
-                        ExamRecordData examRecordDataCache = examRecordDataService.getExamRecordDataCache(
-                                examRecordDataId);
-                        if (examRecordDataCache != null) {
-                            ExamPropertyCacheBean examProperty = ExamCacheTransferHelper.getCachedExamProperty(
-                                    examRecordDataCache.getExamId(), studentId,
-                                    ExamProperties.IS_STRANGER_ENABLE.name());
-                            if ("true".equalsIgnoreCase(examProperty.getValue())) {
-                                queue.setIsStranger(true);
-                            }
-                        }
-                    }
-                }
-            } catch (Exception e) {
-                int errorNum = queue.getErrorNum() != null ? queue.getErrorNum() : 0;
-                queue.setErrorNum(errorNum + 1);
-                queue.setErrorMsg(e.getMessage());
-                queue.setStatus(ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_FAILED);
-
-                log.error("人脸比对错误!examRecordDataId:{} queueId:{} errorNum:{} error:{}",
-                        queue.getExamRecordDataId(), queue.getId(), queue.getErrorNum(), e.getMessage());
+            queue.setFaceCompareStartTime(System.currentTimeMillis());
+            int curErrorNum = queue.getErrorNum() != null ? queue.getErrorNum() : 0;
+
+            // 执行人脸比对
+            boolean compareFinished = this.doFaceCompare(queue, basePhoto, capturePhoto, param, useBaiduApi, baiduExpectFaceCompareScore, curErrorNum);
+
+            // 执行人脸真实性检测
+            boolean verifyFinished = this.doFaceVerify(queue, capturePhoto, param, curErrorNum);
+
+            if (compareFinished && verifyFinished) {
+                // 保存最终结果并删除该队列记录
+                this.saveExamCaptureAndDeleteQueue(queue);
+            } else {
                 examCaptureQueueRepo.save(queue);
-                continue;
             }
+        }
+    }
 
-            if (errFace) {
-                // 没有检测到正常人脸,直接保存人脸检测最终结果并删除该队列记录
-                this.saveExamCaptureAndDeleteQueue(queue);
-                continue;
+    private boolean doFaceCompare(ExamCaptureQueueEntity queue, ImageParm basePhoto, ImageParm capturePhoto,
+                                  FaceApiParam param, boolean useBaiduApi, Double baiduExpectFaceCompareScore,
+                                  int curErrorNum) {
+        if (StringUtils.isNotBlank(queue.getFaceCompareResult())) {
+            // 已执行过“人脸比对”,则跳过
+            return true;
+        }
+
+        try {
+            // 人脸比对开始处理
+            FaceResult faceCompareResult;
+            if (useBaiduApi) {
+                faceCompareResult = faceVerifyService.faceCompare(FaceParam.builder()
+                        .images(basePhoto, capturePhoto)
+                        .expectFaceCompareScore(baiduExpectFaceCompareScore)
+                        .apiType(param.isUseLocalBaiduApiForFaceCompare() ? FaceApiType.PRIVATE_BAIDU_API : FaceApiType.BAIDU_API));
+            } else {
+                faceCompareResult = faceVerifyService.faceCompare(FaceParam.builder()
+                        .images(basePhoto, capturePhoto)
+                        .apiType(FaceApiType.FACE_PLUS));
             }
 
-            try {
-                // 人脸真实性检测开始
-                FaceResult faceLivenessResult = faceVerifyService.faceVerify(FaceParam.builder().images(capturePhoto)
-                        .apiType(param.isUseLocalBaiduApiForFaceLiveness() ? FaceApiType.PRIVATE_BAIDU_API : FaceApiType.BAIDU_API));
-                queue.setFacelivenessResult(faceLivenessResult.getJsonResult());
-
-                if (faceLivenessResult.isApiNeedRetry()) {
-                    throw new FaceVerifyException(faceLivenessResult.getError());
-                }
-
-                if (!faceLivenessResult.isFacePass()) {
-                    extMsg = extMsg + " 真实性:" + Optional.ofNullable(faceLivenessResult.getError()).orElse("不通过");
-                    queue.setExtMsg(extMsg);
-                }
-            } catch (Exception e) {
-                int errorNum = queue.getErrorNum() != null ? queue.getErrorNum() : 0;
-                queue.setErrorNum(errorNum + 1);
-                queue.setErrorMsg(e.getMessage());
-                queue.setStatus(ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED);
-
-                log.error("人脸真实性检测错误!examRecordDataId:{} queueId:{} errorNum:{} error:{}",
-                        queue.getExamRecordDataId(), queue.getId(), queue.getErrorNum(), e.getMessage());
-                examCaptureQueueRepo.save(queue);
-                continue;
+            if (faceCompareResult.isApiNeedRetry()) {
+                throw new FaceVerifyException(faceCompareResult.getError());
             }
 
-            // 保存人脸检测最终结果并删除该队列记录
-            this.saveExamCaptureAndDeleteQueue(queue);
+            queue.setIsPass(faceCompareResult.isFacePass());
+            queue.setFaceCompareResult(faceCompareResult.getJsonResult());
+            queue.setIsStranger(faceCompareResult.getFaceNum() > 1);
+            return true;
+        } catch (Exception e) {
+            queue.setErrorNum(curErrorNum + 1);
+            queue.setErrorMsg("人脸比对异常!" + e.getMessage());
+
+            log.error("人脸比对异常!examRecordDataId:{} queueId:{} errorNum:{} error:{}",
+                    queue.getExamRecordDataId(), queue.getId(), queue.getErrorNum(), e.getMessage());
+            return false;
+        }
+    }
 
-            capturePhoto = null;
+    private boolean doFaceVerify(ExamCaptureQueueEntity queue, ImageParm capturePhoto, FaceApiParam param, int curErrorNum) {
+        if (StringUtils.isNotBlank(queue.getFacelivenessResult())) {
+            // 已执行过“真实性检测”,则跳过
+            return true;
         }
 
-        basePhoto = null;
+        try {
+            // 人脸真实性检测开始
+            FaceResult faceLivenessResult = faceVerifyService.faceVerify(FaceParam.builder().images(capturePhoto)
+                    .apiType(param.isUseLocalBaiduApiForFaceLiveness() ? FaceApiType.PRIVATE_BAIDU_API : FaceApiType.BAIDU_API));
+
+            if (faceLivenessResult.isApiNeedRetry()) {
+                throw new FaceVerifyException(faceLivenessResult.getError());
+            }
+
+            queue.setFacelivenessResult(faceLivenessResult.getJsonResult());
+            return true;
+        } catch (Exception e) {
+            queue.setErrorNum(curErrorNum + 1);
+            queue.setErrorMsg("人脸真实性检测异常!" + e.getMessage());
+
+            log.error("人脸真实性检测异常!examRecordDataId:{} queueId:{} errorNum:{} error:{}",
+                    queue.getExamRecordDataId(), queue.getId(), queue.getErrorNum(), e.getMessage());
+            return false;
+        }
     }
 
     private void saveExamCaptureAndDeleteQueue(ExamCaptureQueueEntity queue) {
         ExamCaptureEntity query = new ExamCaptureEntity();
         query.setExamRecordDataId(queue.getExamRecordDataId());
         query.setFileName(queue.getFileName());
-        Example<ExamCaptureEntity> example = Example.of(query);
-
-        if (examCaptureRepo.exists(example)) {
+        if (examCaptureRepo.exists(Example.of(query))) {
             // 照片处理结果中若已存在则以已有的数据为准,删除该队列记录
             examCaptureQueueRepo.deleteById(queue.getId());
-            log.info("==>ExamCaptureQueue clear... examRecordDataId:{} fileName:{}", queue.getExamRecordDataId(),
-                    queue.getFileName());
+            log.info("RemoveQueue examRecordDataId:{} fileName:{}", queue.getExamRecordDataId(), queue.getFileName());
             return;
         }
 
@@ -267,12 +234,10 @@ public class ExamCaptureQueueServiceImpl implements ExamCaptureQueueService {
         long currentTimeMillis = System.currentTimeMillis();
         // 从创建队列到处理完毕的时间
         result.setProcessTime(currentTimeMillis - queue.getCreationTime().getTime());
-
         // 从开始处理到处理完毕的时间
         result.setUsedTime(currentTimeMillis - queue.getFaceCompareStartTime());
 
         result.setHasVirtualCamera(queue.getHasVirtualCamera() != null ? queue.getHasVirtualCamera() : false);
-
         if (StringUtils.length(queue.getCameraInfos()) >= Constants.VM_CAMERA_SIZE_LIMIT) {
             log.warn("虚拟摄像头信息超长!examRecordDataId:{} queueId:{}", queue.getExamRecordDataId(), queue.getId());
             result.setCameraInfos(Constants.VM_CAMERA_WARN);
@@ -284,8 +249,7 @@ public class ExamCaptureQueueServiceImpl implements ExamCaptureQueueService {
 
         // 删除该队列记录
         examCaptureQueueRepo.deleteById(queue.getId());
-        log.info("-->ExamCaptureQueue clear... examRecordDataId:{} fileName:{}", queue.getExamRecordDataId(),
-                queue.getFileName());
+        log.info("RemoveQueue examRecordDataId:{} fileName:{}", queue.getExamRecordDataId(), queue.getFileName());
     }
 
     /**

+ 6 - 4
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/job/FaceVerifyJobHandler.java

@@ -39,7 +39,7 @@ public class FaceVerifyJobHandler {
     public void run(int shardTotal, int shardIndex, String jobParam) throws Exception {
         FaceApiParam param = this.parseJobParam(jobParam);
 
-        // 数值太大不利于任务调度策略,推荐50
+        // 根据任务调度策略,推荐50
         final int batchSize = 50;
 
         // 优先取“已交卷”的考试记录ID集合
@@ -47,9 +47,11 @@ public class FaceVerifyJobHandler {
                 shardIndex, batchSize, param.getMaxErrorNum(), true);
 
         if (examRecordDataIds.size() < batchSize) {
-            // 未取到或取到的数量不足时,再按常规取一次考试记录ID集合来补充
+            // 未取到或取到的数量不足时,再按“不区分交卷状态”方式取一次考试记录ID集合来补充(避免调度任务不饱和空转)
             List<Long> moreExamRecordDataIds = examCaptureQueueService.findQueuesGroupByExamRecordDataId(shardTotal,
-                    shardIndex, batchSize - examRecordDataIds.size(), param.getMaxErrorNum(), false);
+                    shardIndex, batchSize, param.getMaxErrorNum(), false);
+
+            log.warn("分片任务_FACE_{}_{} 已交卷考试记录数:{} 补充考试记录数:{}", shardTotal, shardIndex, examRecordDataIds.size(), moreExamRecordDataIds.size());
             examRecordDataIds.addAll(moreExamRecordDataIds);
 
             if (CollectionUtils.isEmpty(examRecordDataIds)) {
@@ -64,7 +66,7 @@ public class FaceVerifyJobHandler {
             return;
         }
 
-        log.warn("分片任务_FACE_{}_{} examRecordDataIdSize:{} queueSize:{}", shardTotal, shardIndex, todoIds.size(), todoQueues.size());
+        log.warn("分片任务_FACE_{}_{} 本次处理考试记录数:{} 待比对照片数:{}", shardTotal, shardIndex, todoIds.size(), todoQueues.size());
         Map<Long, List<ExamCaptureQueueEntity>> maps = todoQueues.stream().collect(Collectors.groupingBy(ExamCaptureQueueEntity::getExamRecordDataId));
 
         for (Map.Entry<Long, List<ExamCaptureQueueEntity>> entry : maps.entrySet()) {