|
@@ -1,9 +1,37 @@
|
|
package cn.com.qmth.examcloud.core.oe.task.service.job;
|
|
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.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
+import org.springframework.data.domain.Example;
|
|
import org.springframework.stereotype.Component;
|
|
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);
|
|
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 {
|
|
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());
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
}
|