|
@@ -2,6 +2,8 @@ package cn.com.qmth.examcloud.core.oe.task.service.impl;
|
|
|
|
|
|
import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
|
import cn.com.qmth.examcloud.commons.helpers.JsonHttpResponseHolder;
|
|
|
+import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.task.service.bean.CalculateFaceCheckResultInfo;
|
|
|
import cn.com.qmth.examcloud.support.Constants;
|
|
|
import cn.com.qmth.examcloud.core.oe.task.base.ExamCaptureProcessStatisticController;
|
|
|
import cn.com.qmth.examcloud.core.oe.task.dao.ExamCaptureQueueRepo;
|
|
@@ -16,15 +18,19 @@ import cn.com.qmth.examcloud.support.cache.CacheHelper;
|
|
|
import cn.com.qmth.examcloud.support.cache.bean.ExamRecordPropertyCacheBean;
|
|
|
import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
|
|
|
import cn.com.qmth.examcloud.support.enums.ExamProperties;
|
|
|
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
|
|
|
import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
|
|
|
+import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
|
|
|
import cn.com.qmth.examcloud.web.baidu.BaiduClient;
|
|
|
import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
|
|
|
import cn.com.qmth.examcloud.web.facepp.FaceppClient;
|
|
|
import cn.com.qmth.examcloud.web.redis.RedisClient;
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
import org.apache.commons.logging.Log;
|
|
|
import org.apache.commons.logging.LogFactory;
|
|
|
+import org.json.JSONException;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
@@ -32,7 +38,9 @@ import org.springframework.data.domain.Example;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
+import java.math.BigDecimal;
|
|
|
import java.util.Date;
|
|
|
+import java.util.List;
|
|
|
|
|
|
/**
|
|
|
* @author chenken
|
|
@@ -56,6 +64,9 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
|
|
|
@Autowired
|
|
|
private ExamCaptureQueueService examCaptureQueueService;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordDataService examRecordDataService;
|
|
|
+
|
|
|
public static final String TEMP_FILE_EXP = "face_compare/";
|
|
|
|
|
|
@Autowired
|
|
@@ -347,29 +358,6 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
|
|
|
examCaptureQueueRepo.deleteById(examCaptureQueue.getId());
|
|
|
}
|
|
|
|
|
|
- private ExamCaptureEntity getExamCaptureFromQueue(ExamCaptureQueueInfo queue) {
|
|
|
- long currentTimeMillis = System.currentTimeMillis();
|
|
|
- long createTimeMillis = queue.getCreationTime().getTime();
|
|
|
- long faceCompareStartTimeMillis = queue.getFaceCompareStartTime();
|
|
|
-
|
|
|
- ExamCaptureEntity resultEntity = new ExamCaptureEntity();
|
|
|
- resultEntity.setCameraInfos(queue.getCameraInfos());
|
|
|
- resultEntity.setExamRecordDataId(queue.getExamRecordDataId());
|
|
|
- resultEntity.setExtMsg(queue.getExtMsg());
|
|
|
- resultEntity.setFaceCompareResult(queue.getFaceCompareResult());
|
|
|
- resultEntity.setFacelivenessResult(queue.getFacelivenessResult());
|
|
|
- resultEntity.setFileName(queue.getFileName());
|
|
|
- resultEntity.setFileUrl(queue.getFileUrl());
|
|
|
- resultEntity.setHasVirtualCamera(queue.getHasVirtualCamera());
|
|
|
- resultEntity.setIsStranger(queue.getStranger());
|
|
|
- resultEntity.setIsPass(queue.getPass());
|
|
|
- resultEntity.setProcessTime(currentTimeMillis - createTimeMillis);//从进队列到处理完毕的时间
|
|
|
- resultEntity.setUsedTime(currentTimeMillis - faceCompareStartTimeMillis);//从开始处理到处理完毕的时间
|
|
|
- return resultEntity;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
@Override
|
|
|
public CompareFaceSyncInfo compareFaceSyncByFileUrl(Long studentId, String baseFaceToken, String fileUrl) {
|
|
|
CompareFaceSyncInfo compareFaceSyncInfo = new CompareFaceSyncInfo();
|
|
@@ -442,12 +430,199 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
|
|
|
|
|
|
}
|
|
|
|
|
|
-
|
|
|
@Override
|
|
|
public ExamCaptureEntity getExamCaptureResult(Long examRecordDataId, String fileName) {
|
|
|
return examCaptureRepo.findByExamRecordDataIdAndFileName(examRecordDataId, fileName);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 计算人脸检测结果
|
|
|
+ * 相片数=0,系统判断为违纪,自动审核
|
|
|
+ * 考试记录为异常逻辑(进入待审):
|
|
|
+ * 1.陌生人次数>0
|
|
|
+ * 2.face++阈值 = 0 && 百度真实性阈值 > 0
|
|
|
+ * 真实性百分比<百度真实性阈值
|
|
|
+ * 3.face++阈值 > 0 && 百度真实性阈值 = 0
|
|
|
+ * face++成功率<face++阈值
|
|
|
+ * 4.face++阈值 > 0 && 百度真实性阈值 > 0
|
|
|
+ * face++成功率<face++阈值 ||
|
|
|
+ * 真实性百分比<百度真实性阈值
|
|
|
+ *
|
|
|
+ * @param examRecordData
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public CalculateFaceCheckResultInfo calculateFaceCheckResult(Long examRecordDataId) {
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
+
|
|
|
+ Long rootOrgId = examRecordData.getRootOrgId();
|
|
|
+ Long examId = examRecordData.getExamId();
|
|
|
+ Long orgId = examRecordData.getOrgId();
|
|
|
+ Long studentId = examRecordData.getStudentId();
|
|
|
+
|
|
|
+ //人脸最终检测结果
|
|
|
+ CalculateFaceCheckResultInfo resultInfo = new CalculateFaceCheckResultInfo();
|
|
|
+
|
|
|
+ //未开启人脸检测,直接认为异常数据
|
|
|
+ if (!FaceBiopsyHelper.isFaceEnable(rootOrgId, examId, studentId)) {
|
|
|
+ resultInfo.setWarn(false);
|
|
|
+ return resultInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<ExamCaptureEntity> examCaptureList = examCaptureRepo.findByExamRecordDataId(examRecordData.getId());
|
|
|
+
|
|
|
+ //无照片违纪
|
|
|
+ if (examCaptureList == null || examCaptureList.size() == 0) {
|
|
|
+ resultInfo.setWarn(true);//有异常
|
|
|
+ resultInfo.setIllegality(true);//违纪
|
|
|
+ resultInfo.setNoPhotoAndIllegality(true);//无照片违纪
|
|
|
+ return resultInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ //根据照片结果计算
|
|
|
+ return calculateByCaptureResult(examCaptureList, examRecordData.getExamId(), examRecordData.getStudentId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算人脸检测数据
|
|
|
+ * 陌生人记录数、成功次数、失败次数、成功率
|
|
|
+ *
|
|
|
+ * @param examCaptureEntityList
|
|
|
+ * @param examId
|
|
|
+ * @param studentId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private CalculateFaceCheckResultInfo calculateByCaptureResult(List<ExamCaptureEntity> examCaptureEntityList,
|
|
|
+ Long examId, Long studentId) {
|
|
|
+ int strangerCount = 0; // 人脸比较陌生人记录数
|
|
|
+ int succCount = 0; // 人脸比较成功次数
|
|
|
+ int falseCount = 0; // 人脸比较失败次数
|
|
|
+ double succPercent = 0d; // 人脸比较成功率
|
|
|
+ int livenessSuccessCount = 0;//百度活体检测成功次数
|
|
|
+ double livenessSuccessPercent = 0D;//百度活体检测成功率
|
|
|
+ for (ExamCaptureEntity examCaptureEntity : examCaptureEntityList) {
|
|
|
+ if (examCaptureEntity.getIsPass() != null && examCaptureEntity.getIsPass()) {
|
|
|
+ succCount++;
|
|
|
+ } else {
|
|
|
+ falseCount++;
|
|
|
+ }
|
|
|
+ if (examCaptureEntity.getIsStranger() != null && examCaptureEntity.getIsStranger()) {
|
|
|
+ strangerCount++;
|
|
|
+ }
|
|
|
+ livenessSuccessCount += calculateFacelivenessSuccessCount(examCaptureEntity.getFacelivenessResult());
|
|
|
+ }
|
|
|
+ int allNum = examCaptureEntityList.size();
|
|
|
+ BigDecimal bg = new BigDecimal(((double) succCount / allNum) * 100);
|
|
|
+ succPercent = bg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();// 人脸比较成功率
|
|
|
+
|
|
|
+ //活体检测最终结果
|
|
|
+ CalculateFaceCheckResultInfo resultInfo = new CalculateFaceCheckResultInfo();
|
|
|
+
|
|
|
+ resultInfo.setFaceTotalCount(allNum);//检测总次数
|
|
|
+ resultInfo.setFaceSuccessPercent(succPercent);//成功率
|
|
|
+ resultInfo.setFaceStrangerCount(strangerCount);//有陌生人的次数
|
|
|
+ resultInfo.setFaceSuccessCount(succCount);//成功次数
|
|
|
+ resultInfo.setFaceFailedCount(falseCount);//失败次数
|
|
|
+
|
|
|
+ //计算百度活体检测通过率
|
|
|
+ BigDecimal livenessSuccessBg = new BigDecimal(((double) livenessSuccessCount / allNum) * 100);
|
|
|
+ livenessSuccessPercent = livenessSuccessBg.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();//百度活体检测成功率
|
|
|
+ resultInfo.setBaiduFaceLivenessSuccessPercent(livenessSuccessPercent);
|
|
|
+
|
|
|
+ //陌生人个数>0
|
|
|
+ if (resultInfo.getFaceStrangerCount() > 0) {
|
|
|
+ resultInfo.setWarn(true);
|
|
|
+ return resultInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ //人脸识别阀值
|
|
|
+ String warnThresholdStr = ExamCacheTransferHelper.getCachedExamProperty(examId,
|
|
|
+ studentId, ExamProperties.WARN_THRESHOLD.name()).getValue();
|
|
|
+ if (StringUtils.isBlank(warnThresholdStr)) {
|
|
|
+ throw new StatusException("400101", "人脸检测预警阈值未设置");
|
|
|
+ }
|
|
|
+
|
|
|
+ //人脸真实性(百度活体检测)通过阀值
|
|
|
+ String liveWarnThresholdStr = ExamCacheTransferHelper.getCachedExamProperty(examId,
|
|
|
+ studentId, ExamProperties.LIVING_WARN_THRESHOLD.name()).getValue();
|
|
|
+
|
|
|
+ if (StringUtils.isBlank(liveWarnThresholdStr)) {
|
|
|
+ throw new StatusException("400102", "人脸真实性阈值未设置");
|
|
|
+ }
|
|
|
+
|
|
|
+ double warnThreshold = Double.parseDouble(warnThresholdStr);
|
|
|
+ double livenessThreshold = Double.parseDouble(liveWarnThresholdStr);
|
|
|
+
|
|
|
+ if (warnThreshold == 0d && livenessThreshold > 0d) {
|
|
|
+ resultInfo.setWarn(livenessSuccessPercent < livenessThreshold);
|
|
|
+ } else if (warnThreshold > 0d && livenessThreshold == 0d) {
|
|
|
+ resultInfo.setWarn(succPercent < warnThreshold);
|
|
|
+ } else if (warnThreshold > 0d && livenessThreshold > 0d) {
|
|
|
+ resultInfo.setWarn(succPercent < warnThreshold || livenessSuccessPercent < livenessThreshold);
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算百度活体检测成功数量
|
|
|
+ *
|
|
|
+ * @param facelivenessResult
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private int calculateFacelivenessSuccessCount(String facelivenessResult) {
|
|
|
+ if (StringUtils.isNotBlank(facelivenessResult)) {
|
|
|
+ org.json.JSONObject jsonObject;
|
|
|
+ try {
|
|
|
+ jsonObject = new org.json.JSONObject(facelivenessResult);
|
|
|
+ if (jsonObject.has("error_code") && jsonObject.getInt("error_code") == 0 && jsonObject.has("result")) {
|
|
|
+ org.json.JSONObject resultJson = jsonObject.getJSONObject("result");
|
|
|
+ if (resultJson.has("face_liveness")) {
|
|
|
+ double faceLivenessVal = resultJson.getDouble("face_liveness");
|
|
|
+
|
|
|
+ Double baiduFacelivenessThreshold;
|
|
|
+ SysPropertyCacheBean baiduFacelivenessThresholdProperty = CacheHelper.getSysProperty("$baidu.faceliveness.threshold");
|
|
|
+ if (!baiduFacelivenessThresholdProperty.getHasValue()) {
|
|
|
+ baiduFacelivenessThreshold = Constants.DEFAULT_BAIDU_FACELIVENESS_THRESHOLD;
|
|
|
+ } else {
|
|
|
+ baiduFacelivenessThreshold = Double.valueOf(baiduFacelivenessThresholdProperty.getValue().toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (faceLivenessVal > baiduFacelivenessThreshold) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ExamCaptureEntity getExamCaptureFromQueue(ExamCaptureQueueInfo queue) {
|
|
|
+ long currentTimeMillis = System.currentTimeMillis();
|
|
|
+ long createTimeMillis = queue.getCreationTime().getTime();
|
|
|
+ long faceCompareStartTimeMillis = queue.getFaceCompareStartTime();
|
|
|
+
|
|
|
+ ExamCaptureEntity resultEntity = new ExamCaptureEntity();
|
|
|
+ resultEntity.setCameraInfos(queue.getCameraInfos());
|
|
|
+ resultEntity.setExamRecordDataId(queue.getExamRecordDataId());
|
|
|
+ resultEntity.setExtMsg(queue.getExtMsg());
|
|
|
+ resultEntity.setFaceCompareResult(queue.getFaceCompareResult());
|
|
|
+ resultEntity.setFacelivenessResult(queue.getFacelivenessResult());
|
|
|
+ resultEntity.setFileName(queue.getFileName());
|
|
|
+ resultEntity.setFileUrl(queue.getFileUrl());
|
|
|
+ resultEntity.setHasVirtualCamera(queue.getHasVirtualCamera());
|
|
|
+ resultEntity.setIsStranger(queue.getStranger());
|
|
|
+ resultEntity.setIsPass(queue.getPass());
|
|
|
+ resultEntity.setProcessTime(currentTimeMillis - createTimeMillis);//从进队列到处理完毕的时间
|
|
|
+ resultEntity.setUsedTime(currentTimeMillis - faceCompareStartTimeMillis);//从开始处理到处理完毕的时间
|
|
|
+ return resultEntity;
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
* 校验是否有陌生人脸
|