浏览代码

update face api

deason 2 年之前
父节点
当前提交
f220a70513

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

@@ -1,9 +1,37 @@
 package cn.com.qmth.examcloud.core.oe.task.service.job;
 
+import cn.com.qmth.examcloud.api.commons.enums.FaceApiProvider;
+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.ExamRecordDataService;
+import cn.com.qmth.examcloud.starters.face.verify.common.CommonUtils;
+import cn.com.qmth.examcloud.starters.face.verify.common.FaceVerifyException;
+import cn.com.qmth.examcloud.starters.face.verify.model.FaceResult;
+import cn.com.qmth.examcloud.starters.face.verify.model.param.ImageBase64Parm;
+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.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
 import org.springframework.stereotype.Component;
 
+import java.util.List;
+
 /**
  * 处理考试过程中抓拍照片比对任务
  */
@@ -12,8 +40,210 @@ public class FaceVerifyJobHandler {
 
     private static final Logger log = LoggerFactory.getLogger(FaceVerifyJobHandler.class);
 
+    @Autowired
+    private ExamCaptureQueueRepo examCaptureQueueRepo;
+
+    @Autowired
+    private ExamCaptureRepo examCaptureRepo;
+
+    @Autowired
+    private FaceVerifyService faceVerifyService;
+
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+
     public void run(int shardTotal, int shardIndex, String jobParam) throws Exception {
-        log.info("todo..");
+        int batchSize = 1000;
+        List<Long> todoExamRecordDataIds = examCaptureQueueRepo.findQueuesGroupByExamRecordDataId(shardTotal, shardIndex, batchSize);
+
+        log.warn("shardTotal:{}, shardIndex:{}, todoExamRecordDataIdsSize:{}", shardTotal, shardIndex, todoExamRecordDataIds.size());
+        if (CollectionUtils.isEmpty(todoExamRecordDataIds)) {
+            return;
+        }
+
+        for (Long examRecordDataId : todoExamRecordDataIds) {
+            try {
+                // 处理业务
+                this.handler(examRecordDataId);
+            } catch (Exception e) {
+                if (e instanceof InterruptedException) {
+                    // 若线程终止,则抛出交由任务调度中心处理
+                    log.warn("当前任务线程被终止!examRecordDataId:{}, error:{}", examRecordDataId, e.getMessage());
+                    throw e;
+                } else {
+                    // 若异常,下次会继续执行(需要排查原因)
+                    log.error("当前考试记录数据处理失败!examRecordDataId:{}, error:{}", examRecordDataId, e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    private void handler(Long examRecordDataId) throws Exception {
+        // 每个考试记录ID下通常只有几条待处理抓拍记录,直接取所有一次性处理。
+        List<ExamCaptureQueueEntity> queues = examCaptureQueueRepo.findByExamRecordDataId(examRecordDataId);
+        if (CollectionUtils.isEmpty(queues)) {
+            return;
+        }
+
+        // 获取当前学生的底照信息
+        Long studentId = queues.get(0).getStudentId();
+        StudentCacheBean studentCache = CacheHelper.getStudent(studentId);
+        if (StringUtils.isBlank(studentCache.getFaceToken()) || StringUtils.isBlank(studentCache.getPhotoPath())) {
+            throw new IllegalArgumentException("学生底照不存在!studentId is " + studentId);
+        }
+
+        // 获取学校人脸API方案配置
+        boolean useBaiduApi = false;
+        Double baiduExpectFaceCompareScore = null;
+        OrgPropertyCacheBean property1 = CacheHelper.getOrgProperty(studentCache.getRootOrgId(), "FACE_API_PROVIDER");
+        if (FaceApiProvider.BAIDU.name().equals(property1.getValue())) {
+            useBaiduApi = true;
+        }
+        OrgPropertyCacheBean property2 = CacheHelper.getOrgProperty(studentCache.getRootOrgId(), "BAIDU_EXPECT_FACE_COMPARE_SCORE");
+        if (StringUtils.isNotEmpty(property2.getValue())) {
+            baiduExpectFaceCompareScore = Double.parseDouble(property2.getValue());
+        }
+        log.warn("studentId:{} examRecordDataId:{} todoCaptureQueueSize:{} api:{} {}", studentId, examRecordDataId,
+                queues.size(), useBaiduApi ? "baidu" : "face++", baiduExpectFaceCompareScore);
+
+        // 将学生底照图片转成Base64格式
+        ImageParm basePhoto = new ImageBase64Parm(CommonUtils.toBase64(studentCache.getPhotoPath()));
+
+        for (ExamCaptureQueueEntity queue : queues) {
+            queue.setFaceCompareStartTime(System.currentTimeMillis());
+
+            // 将抓拍照图片转成Base64格式
+            ImageParm capturePhoto = new ImageBase64Parm(CommonUtils.toBase64(FileStorageUtil.realPath(queue.getFileUrl())));
+
+            boolean errFaceNum = 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);
+                    } else {
+                        faceCompareResult = faceVerifyService.faceCompareByFacePlus(basePhoto, capturePhoto);
+                    }
+
+                    if (!faceCompareResult.isApiSuccess()) {
+                        throw new FaceVerifyException(faceCompareResult.getError());
+                    }
+
+                    queue.setIsPass(faceCompareResult.isPass());
+                    queue.setFaceCompareResult(faceCompareResult.getJsonResult());
+                    queue.setStatus(ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_COMPLETE);
+                    queue.setIsStranger(false);
+
+                    if (faceCompareResult.getFaceNum() == 0) {
+                        errFaceNum = 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);
+                            }
+                        }
+                    }
+
+                    if (!faceCompareResult.isPass()) {
+                        queue.setExtMsg(faceCompareResult.getError());
+                    }
+                }
+            } 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());
+                examCaptureQueueRepo.save(queue);
+                return;
+            }
+
+            if (errFaceNum) {
+                // 没有检测到正常人脸,直接保存人脸检测最终结果并删除该队列记录
+                this.saveExamCaptureAndDeleteQueue(queue);
+                return;
+            }
+
+            try {
+                // 人脸活体检测开始
+                FaceResult faceLivenessResult = faceVerifyService.faceVerifyByBaidu(capturePhoto);
+                queue.setFacelivenessResult(faceLivenessResult.getJsonResult());
+
+                if (!faceLivenessResult.isApiSuccess()) {
+                    throw new FaceVerifyException(faceLivenessResult.getError());
+                }
+
+                if (!faceLivenessResult.isPass()) {
+                    queue.setExtMsg(faceLivenessResult.getError());
+                }
+            } 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);
+                return;
+            }
+
+            // 保存人脸检测最终结果并删除该队列记录
+            this.saveExamCaptureAndDeleteQueue(queue);
+        }
+    }
+
+    public 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)) {
+            // 照片处理结果中若已存在则以已有的数据为准,删除该队列记录
+            examCaptureQueueRepo.deleteById(queue.getId());
+            return;
+        }
+
+        ExamCaptureEntity result = new ExamCaptureEntity();
+        result.setExamRecordDataId(queue.getExamRecordDataId());
+        result.setFileName(queue.getFileName());
+        result.setFileUrl(queue.getFileUrl());
+        result.setIsPass(queue.getIsPass());
+        result.setIsStranger(queue.getIsStranger());
+        result.setFaceCompareResult(queue.getFaceCompareResult());
+        result.setFacelivenessResult(queue.getFacelivenessResult());
+        result.setExtMsg(queue.getExtMsg());
+
+        long currentTimeMillis = System.currentTimeMillis();
+        // 从创建队列到处理完毕的时间
+        result.setProcessTime(currentTimeMillis - queue.getCreationTime().getTime());
+        // 从开始处理到处理完毕的时间
+        result.setUsedTime(currentTimeMillis - queue.getFaceCompareStartTime());
+
+        result.setHasVirtualCamera(queue.getHasVirtualCamera());
+        if (StringUtils.length(queue.getCameraInfos()) >= Constants.VM_CAMERA_SIZE_LIMIT) {
+            log.warn("虚拟摄像头信息超长!examRecordDataId:{} queueId:{}", queue.getExamRecordDataId(), queue.getId());
+            result.setCameraInfos(Constants.VM_CAMERA_WARN);
+        } else {
+            result.setCameraInfos(queue.getCameraInfos());
+        }
+
+        examCaptureRepo.save(result);
+
+        // 删除该队列记录
+        examCaptureQueueRepo.deleteById(queue.getId());
     }
 
 }