|
@@ -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());
|
|
|
}
|
|
|
|
|
|
/**
|