|
@@ -0,0 +1,478 @@
|
|
|
+package cn.com.qmth.examcloud.core.oe.student.face.service.impl;
|
|
|
+
|
|
|
+import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.base.Constants;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.entity.ExamCaptureEntity;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.entity.ExamCaptureQueueEntity;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.enums.ExamCaptureQueueStatus;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.enums.ExamProperties;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.repository.ExamCaptureQueueRepo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.repository.ExamCaptureRepo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.common.service.GainBaseDataService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.face.service.*;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.face.service.bean.CompareFaceSyncInfo;
|
|
|
+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.web.bootstrap.PropertyHolder;
|
|
|
+import org.apache.commons.io.IOUtils;
|
|
|
+import org.apache.commons.logging.Log;
|
|
|
+import org.apache.commons.logging.LogFactory;
|
|
|
+import org.json.JSONArray;
|
|
|
+import org.json.JSONException;
|
|
|
+import org.json.JSONObject;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.data.domain.Example;
|
|
|
+import org.springframework.data.redis.core.RedisTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
|
|
|
+
|
|
|
+import java.io.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author chenken
|
|
|
+ * @date 2018年9月5日 下午3:31:37
|
|
|
+ * @company QMTH
|
|
|
+ * @description 考试抓拍服务实现
|
|
|
+ */
|
|
|
+@SuppressWarnings("ALL")
|
|
|
+@Service("examCaptureService")
|
|
|
+public class ExamCaptureServiceImpl implements ExamCaptureService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(ExamCaptureServiceImpl.class);
|
|
|
+ private final Log captureLog = LogFactory.getLog("PROCESS_EXAM_CAPTURE_TASK_LOGGER");
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamCaptureRepo examCaptureRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamCaptureQueueRepo examCaptureQueueRepo;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamCaptureQueueService examCaptureQueueService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FaceLivenessService faceLivenessService;
|
|
|
+ @Autowired
|
|
|
+ private FaceCompareService faceCompareService;
|
|
|
+
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamCaptureCommonService examCaptureCommonService;
|
|
|
+ @Autowired
|
|
|
+ private GainBaseDataService gainBaseDataService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private RedisTemplate<String, Object> redisTemplate;
|
|
|
+
|
|
|
+ public static final String TEMP_FILE_EXP = "face_compare/";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对图片进行人脸对比
|
|
|
+ *
|
|
|
+ * @param examCaptureQueue
|
|
|
+ * @param callType 同步或异步
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void disposeFaceCompare(ExamCaptureQueueEntity examCaptureQueue) throws JSONException {
|
|
|
+ //将队列记录修改为处理中
|
|
|
+ examCaptureQueueRepo.updateExamCaptureQueueStatusWithProcessing(examCaptureQueue.getId());
|
|
|
+ examCaptureQueue.setFaceCompareStartTime(System.currentTimeMillis());
|
|
|
+
|
|
|
+ //facepp超时最大重试次数
|
|
|
+ int maxRetryTimes = PropertyHolder.getInt("facepp.compare.timeOut.maxRetryTimes", 3);
|
|
|
+ boolean retry = false;
|
|
|
+ JSONObject faceCompareResult;
|
|
|
+ //人脸比对超时次数
|
|
|
+ int faceCompareTimeOutTimes = 0;
|
|
|
+ do {
|
|
|
+ retry = false;
|
|
|
+
|
|
|
+ //仅用于日志时间计算
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ captureLog.debug("[DISPOSE_FACE_COMPARE] face++人脸比对开始...");
|
|
|
+
|
|
|
+ //调用face++API执行人脸比对,得到返回结果
|
|
|
+ faceCompareResult = faceCompareService.getFaceppCompareResultByUrl(examCaptureQueue.getBaseFaceToken(),
|
|
|
+ examCaptureQueue.getFileUrl());
|
|
|
+ examCaptureQueue.setFaceCompareResult(faceCompareResult.toString());
|
|
|
+
|
|
|
+ if (faceCompareResult.has(Constants.ERROR_MSG)) {
|
|
|
+ String errMsg = faceCompareResult.getString(Constants.ERROR_MSG);
|
|
|
+
|
|
|
+ //如果API并发次数超过上限,则保存错误信息到队列,并抛出异常,用于协调满载队列线程
|
|
|
+ if (errMsg.contains(Constants.FACE_COMPARE_CONCURRENCY_LIMIT_EXCEEDED)) {
|
|
|
+ examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue,
|
|
|
+ faceCompareResult.toString(), ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_FAILED);
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_FACE_COMPARE] face++人脸比对接口超过最大并发次数");
|
|
|
+
|
|
|
+ throw new StatusException(Constants.FACE_COMPARE_CONCURRENCY_LIMIT_EXCEEDED, "face++ API接口超过最大并发次数");
|
|
|
+ }
|
|
|
+
|
|
|
+ //face++无需重试的错误信息
|
|
|
+ SysPropertyCacheBean objNotRetryErrMsg = CacheHelper.getSysProperty("facePlusPlus.faceCompare.notRetry.errMsg");
|
|
|
+ if (!objNotRetryErrMsg.getHasValue()){
|
|
|
+ throw new StatusException("100001","未找到face++人脸比对错误消息的相关配置");
|
|
|
+ }
|
|
|
+ String objNotRetryErrMsgs = objNotRetryErrMsg.getValue().toString();
|
|
|
+ String[] notRetryErrMsgsArr = objNotRetryErrMsgs.split(",");
|
|
|
+ for (int i=0;i<notRetryErrMsgsArr.length;i++){
|
|
|
+ //如果是配置中的无法处理的图片,则保存人脸检测最终结果并删除队列
|
|
|
+ if (errMsg.contains(notRetryErrMsgsArr[i])){
|
|
|
+ saveExamCaptureAndDeleteQueue(examCaptureQueue);
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_FACE_COMPARE] face++人脸比对无法处理的图片,保存人脸检测最终结果并删除队列,errMsg=" + errMsg);
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //超时错误特殊处理,重试3次后
|
|
|
+ if (errMsg.contains(Constants.FACE_COMPARE_IMAGE_DOWNLOAD_TIMEOUT)) {
|
|
|
+ faceCompareTimeOutTimes++;
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_FACE_COMPARE] face++人脸比对超时,将进行第" + faceCompareTimeOutTimes + "次重试");
|
|
|
+
|
|
|
+ //如果没有达到最大重试次数,则继续重试
|
|
|
+ if (faceCompareTimeOutTimes < maxRetryTimes) {
|
|
|
+ retry = true;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ //超过最大重试次数,则直接保存最终结果
|
|
|
+ saveExamCaptureAndDeleteQueue(examCaptureQueue);
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_FACE_COMPARE] face++人脸比对超过最大检测次数:" + maxRetryTimes + ",停止重试,直接保存最终结果");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_FACE_COMPARE] face++人脸比对出现错误,即将重试,errMsg:" + errMsg);
|
|
|
+ // 其它错误类型,保存错误信息到队列中,待自动重新服务处理
|
|
|
+ examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue,
|
|
|
+ faceCompareResult.toString(), ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_FAILED);
|
|
|
+ } else {
|
|
|
+ //face++的结果检测到人脸,才执行百度活体检测
|
|
|
+ if (faceCompareResult.has("confidence")) {
|
|
|
+ examCaptureQueue.setIsPass(calculateFaceCompareIsPass(faceCompareResult));
|
|
|
+ examCaptureQueue.setIsStranger(calculateFaceCompareIsStranger(examCaptureQueue.getExamRecordDataId(), faceCompareResult));
|
|
|
+ //更新队列状态为face++比对完成
|
|
|
+ examCaptureQueue.setStatus(ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_COMPLETE);
|
|
|
+ examCaptureQueueRepo.save(examCaptureQueue);
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_FACE_COMPARE] face++人脸比对完成,即将进行百度活体检测,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+ //face++ 没有检测到人脸,直接保存人脸检测最终结果并删除队列
|
|
|
+ else {
|
|
|
+ examCaptureQueue.setIsPass(false);
|
|
|
+ examCaptureQueue.setIsStranger(false);
|
|
|
+ saveExamCaptureAndDeleteQueue(examCaptureQueue);
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_FACE_COMPARE] face++人脸比对完成,且未检测到人脸,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } while (retry);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ //face++人脸比对失败时,需要将状态置为失败,并清空批次号
|
|
|
+ public void disposeFaceCompareFaild(ExamCaptureQueueEntity examCaptureQueue){
|
|
|
+ examCaptureQueue.setStatus(ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_FAILED);
|
|
|
+ examCaptureQueue.setProcessBatchNum("000000");
|
|
|
+ examCaptureQueueRepo.save(examCaptureQueue);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ //百度活体检测失败时,需要将状态置为失败,并清空批次号
|
|
|
+ public void disposeBaiDuFaceLivenessFaild(ExamCaptureQueueEntity examCaptureQueue){
|
|
|
+ examCaptureQueue.setStatus(ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED);
|
|
|
+ examCaptureQueue.setProcessBatchNum("000000");
|
|
|
+ examCaptureQueueRepo.save(examCaptureQueue);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对照片进行百度活体检测
|
|
|
+ *
|
|
|
+ * @param examCapture 抓拍照片最终检测最终结果实体
|
|
|
+ * @param examCaptureQueue 抓拍照片队列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void disposeBaiDuFaceLiveness(ExamCaptureQueueEntity examCaptureQueue) throws JSONException {
|
|
|
+ //活体检测超时次数
|
|
|
+ int facelivenessTimeOutTimes = 0;
|
|
|
+ //百度活检超时最大重试次数
|
|
|
+ int maxRetryTimes = PropertyHolder.getInt("baidu.faceliveness.timeOut.maxRetryTimes", 3);
|
|
|
+ boolean retry = false;
|
|
|
+ JSONObject faceLivenessResultJson;
|
|
|
+ do {
|
|
|
+ retry=false;
|
|
|
+
|
|
|
+ //仅用于日志时间计算
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+ captureLog.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测开始...");
|
|
|
+
|
|
|
+ //获取百度活检结果
|
|
|
+ faceLivenessResultJson = faceLivenessService.getBaiduFaceLivenessResultJson(examCaptureQueue.getFileUrl());
|
|
|
+
|
|
|
+ //如果百度活体检测执行失败,调用队列失败处理,程序退出,失败的数据,后续会有自动服务重新处理
|
|
|
+ if (faceLivenessResultJson.has(Constants.BAIDU_ERROR_CODE) &&
|
|
|
+ !faceLivenessResultJson.getString(Constants.BAIDU_ERROR_CODE).equals(Constants.BAIDU_SUCCESS_ERROR_CODE_VALUE)) {
|
|
|
+ String errCode = faceLivenessResultJson.getString(Constants.BAIDU_ERROR_CODE);
|
|
|
+
|
|
|
+ //如果API并发次数超过上限,则保存错误信息到队列,并抛出异常,用于协调满载队列线程
|
|
|
+ if (errCode.equals(Constants.BAIDU_FACELIVENESS_QPS_LIMIT_EXCEEDED_CODE)) {
|
|
|
+ examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue,
|
|
|
+ faceLivenessResultJson.toString(), ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED);
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_BAIDUFACELIVENESS] 百度在线活体API接口超过最大并发次数");
|
|
|
+
|
|
|
+ throw new StatusException(Constants.BAIDU_FACELIVENESS_QPS_LIMIT_EXCEEDED_CODE, "百度在线活体API接口超过最大并发次数");
|
|
|
+ }
|
|
|
+
|
|
|
+ //百度无需重试的错误信息
|
|
|
+ SysPropertyCacheBean objNotRetryErrMsg = CacheHelper.getSysProperty("baidu.faceLiveness.notRetry.errCode");
|
|
|
+ if (!objNotRetryErrMsg.getHasValue()){
|
|
|
+ throw new StatusException("100002","未找到百度活体检测错误代码的相关配置");
|
|
|
+ }
|
|
|
+ String objNotRetryErrMsgs = objNotRetryErrMsg.getValue().toString();
|
|
|
+ String[] notRetryErrMsgsArr = objNotRetryErrMsgs.split(",");
|
|
|
+ for (int i=0;i<notRetryErrMsgsArr.length;i++){
|
|
|
+ //如果是配置中的无法处理的图片,则保存人脸检测最终结果并删除队列
|
|
|
+ if (errCode.equals(notRetryErrMsgsArr[i])){
|
|
|
+ captureLog.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测,无法处理的图片,将保存人脸检测最终结果并删除队列,errCode=" + errCode);
|
|
|
+
|
|
|
+ saveExamCaptureAndDeleteQueue(examCaptureQueue);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //超时错误特殊处理,重试3次后
|
|
|
+ if (errCode.equals(Constants.BAIDU_FACELIVENESS_CONNECTION_OR_READ_DATA_TIME_OUT_CODE)) {
|
|
|
+ facelivenessTimeOutTimes++;
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测超时,将进行第" + facelivenessTimeOutTimes + "次重试");
|
|
|
+
|
|
|
+ //如果没有达到最大重试次数,则继续重试
|
|
|
+ if (facelivenessTimeOutTimes < maxRetryTimes) {
|
|
|
+ retry = true;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ //超过最大重试次数,则直接保存最终结果
|
|
|
+ saveExamCaptureAndDeleteQueue(examCaptureQueue);
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测超过最大检测次数:" + maxRetryTimes + ",停止重试,直接保存最终结果");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测出现错误,即将重试,错误码:" + errCode);
|
|
|
+ // 其它错误类型,保存错误信息到队列中,待自动重新服务处理
|
|
|
+ examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue,
|
|
|
+ faceLivenessResultJson.toString(), ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED);
|
|
|
+
|
|
|
+ }
|
|
|
+ //百度活体检测成功,则保存最终检测结果,并删除临时的图片处理队列
|
|
|
+ else {
|
|
|
+ examCaptureQueue.setFacelivenessResult(faceLivenessResultJson.toString());
|
|
|
+ saveExamCaptureAndDeleteQueue(examCaptureQueue);
|
|
|
+
|
|
|
+ captureLog.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测完成,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
|
|
|
+ }
|
|
|
+ } while (retry);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存人脸检测最终结果并删除队列
|
|
|
+ *
|
|
|
+ * @param examCapture 抓拍照片最终检测最终结果实体
|
|
|
+ * * @param examCaptureQueue 抓拍照片队列表
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public void saveExamCaptureAndDeleteQueue(ExamCaptureQueueEntity examCaptureQueue) {
|
|
|
+ ExamCaptureEntity examCapture = getExamCaptureFromQueue(examCaptureQueue);
|
|
|
+
|
|
|
+ //同一考试记录下如果有重复的照片,则直接跳过
|
|
|
+ ExamCaptureEntity query = new ExamCaptureEntity();
|
|
|
+ query.setExamRecordDataId(examCapture.getExamRecordDataId());
|
|
|
+ query.setFileName(examCapture.getFileName());
|
|
|
+ Example<ExamCaptureEntity> example = Example.of(query);
|
|
|
+ //照片处理结果中如果已存在,则以已有的数据为准
|
|
|
+ if (!examCaptureRepo.exists(example)) {
|
|
|
+ examCaptureRepo.save(examCapture);
|
|
|
+ }
|
|
|
+ //删除队列中的记录
|
|
|
+ examCaptureQueueRepo.delete(examCaptureQueue);
|
|
|
+ }
|
|
|
+
|
|
|
+ private ExamCaptureEntity getExamCaptureFromQueue(ExamCaptureQueueEntity 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.getIsStranger());
|
|
|
+ resultEntity.setIsPass(queue.getIsPass());
|
|
|
+ resultEntity.setProcessTime(currentTimeMillis - createTimeMillis);//从进队列到处理完毕的时间
|
|
|
+ resultEntity.setUsedTime(currentTimeMillis - faceCompareStartTimeMillis);//从开始处理到处理完毕的时间
|
|
|
+ return resultEntity;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步比较人脸:用于进入考试
|
|
|
+ * 只需要判断是否通过
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public CompareFaceSyncInfo compareFaceSyncByFile(Long studentId, String baseFaceToken, CommonsMultipartFile file) {
|
|
|
+ CompareFaceSyncInfo compareFaceSyncInfo = new CompareFaceSyncInfo();
|
|
|
+ compareFaceSyncInfo.setStudentId(studentId);
|
|
|
+ File tempFile = getUploadFile(file);
|
|
|
+
|
|
|
+ try {
|
|
|
+ JSONObject facePPResult = faceCompareService.getFaceppCompareResultByFile(baseFaceToken, tempFile);
|
|
|
+ if (facePPResult.has(Constants.ERROR_MSG)) {
|
|
|
+ compareFaceSyncInfo.setIsPass(false);
|
|
|
+ compareFaceSyncInfo.setErrorMsg(facePPResult.toString());
|
|
|
+ } else {
|
|
|
+ if (facePPResult.has("confidence")) {
|
|
|
+ double confidence = facePPResult.getDouble("confidence");
|
|
|
+ JSONObject thresholdsJsonObject = facePPResult.getJSONObject("thresholds");
|
|
|
+ double le4 = thresholdsJsonObject.getDouble("1e-4");
|
|
|
+ compareFaceSyncInfo.setIsPass(confidence >= le4);//是否通过
|
|
|
+ compareFaceSyncInfo.setErrorMsg(confidence >= le4 ? null : "confidence<le4");
|
|
|
+ } else {
|
|
|
+ compareFaceSyncInfo.setIsPass(false);
|
|
|
+ compareFaceSyncInfo.setErrorMsg(facePPResult.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ throw new StatusException("JsonFormat001", "同步比较人脸失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ tempFile.delete();
|
|
|
+ return compareFaceSyncInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public CompareFaceSyncInfo compareFaceSyncByFileUrl(Long studentId, String baseFaceToken, String fileUrl) {
|
|
|
+ CompareFaceSyncInfo compareFaceSyncInfo = new CompareFaceSyncInfo();
|
|
|
+ compareFaceSyncInfo.setStudentId(studentId);
|
|
|
+ try {
|
|
|
+ JSONObject facePPResult = faceCompareService.getFaceppCompareResultByUrl(baseFaceToken, fileUrl);
|
|
|
+ if (facePPResult.has(Constants.ERROR_MSG)) {
|
|
|
+ compareFaceSyncInfo.setIsPass(false);
|
|
|
+ compareFaceSyncInfo.setErrorMsg(facePPResult.toString());
|
|
|
+ } else {
|
|
|
+ if (facePPResult.has("confidence")) {
|
|
|
+ double confidence = facePPResult.getDouble("confidence");
|
|
|
+ JSONObject thresholdsJsonObject = facePPResult.getJSONObject("thresholds");
|
|
|
+ double le4 = thresholdsJsonObject.getDouble("1e-4");
|
|
|
+ JSONArray face2Array = facePPResult.getJSONArray("faces2");
|
|
|
+ compareFaceSyncInfo.setIsStranger(face2Array.length() > 1);//是否有陌生人
|
|
|
+ compareFaceSyncInfo.setIsPass(confidence >= le4);//是否通过
|
|
|
+ compareFaceSyncInfo.setErrorMsg(confidence >= le4 ? null : "confidence<le4");
|
|
|
+ } else {
|
|
|
+ compareFaceSyncInfo.setIsPass(false);
|
|
|
+ compareFaceSyncInfo.setErrorMsg(facePPResult.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (JSONException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ throw new StatusException("JsonFormat002", "同步比较人脸失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ return compareFaceSyncInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ private File getUploadFile(MultipartFile file) {
|
|
|
+ //建临时文件夹
|
|
|
+ File dirFile = new File(TEMP_FILE_EXP);
|
|
|
+ if (!dirFile.exists()) {
|
|
|
+ dirFile.mkdirs();
|
|
|
+ }
|
|
|
+ String fileName = file.getOriginalFilename();
|
|
|
+ File tempFile = new File(TEMP_FILE_EXP + fileName);
|
|
|
+ OutputStream os = null;
|
|
|
+ try {
|
|
|
+ os = new FileOutputStream(tempFile);
|
|
|
+ IOUtils.copyLarge(file.getInputStream(), os);
|
|
|
+ } catch (FileNotFoundException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } catch (IOException e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ } finally {
|
|
|
+ IOUtils.closeQuietly(os);
|
|
|
+ }
|
|
|
+ return tempFile;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ExamCaptureEntity getExamCaptureResult(Long examRecordDataId, String fileName) {
|
|
|
+ return examCaptureRepo.findByExamRecordDataIdAndFileName(examRecordDataId, fileName);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验是否有陌生人脸
|
|
|
+ *
|
|
|
+ * @param examCaptureQueue
|
|
|
+ * @param jsonObject
|
|
|
+ * @return
|
|
|
+ * @throws JSONException
|
|
|
+ */
|
|
|
+ private boolean calculateFaceCompareIsStranger(Long examRecordDataId, JSONObject jsonObject) throws JSONException {
|
|
|
+ JSONArray face2Array = jsonObject.getJSONArray("faces2");
|
|
|
+ //添加是否有陌生人开关功能
|
|
|
+ ExamRecordPropertyCacheBean examRecordPropertyCache = CacheHelper.getExamRecordProperty(examRecordDataId);
|
|
|
+ //默认开启了陌生人检测
|
|
|
+ String isStrangerEnableStr = "true";
|
|
|
+ if (examRecordPropertyCache != null) {
|
|
|
+ isStrangerEnableStr = CacheHelper.getExamOrgProperty(examRecordPropertyCache.getExamId(),
|
|
|
+ examRecordPropertyCache.getOrgId(),ExamProperties.IS_STRANGER_ENABLE.name()).getValue();
|
|
|
+ }
|
|
|
+ boolean isStranger;
|
|
|
+ // 如果开启了陌生人检测才记录陌生人数据,否则认为没有陌生人
|
|
|
+ if (Constants.isTrue.equals(isStrangerEnableStr)) {
|
|
|
+ isStranger = face2Array.length() > 1;//是否有陌生人
|
|
|
+ } else {
|
|
|
+ isStranger = false;
|
|
|
+ }
|
|
|
+ return isStranger;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算人脸比对是否通过
|
|
|
+ *
|
|
|
+ * @param jsonObject
|
|
|
+ * @return
|
|
|
+ * @throws JSONException
|
|
|
+ */
|
|
|
+ private boolean calculateFaceCompareIsPass(JSONObject jsonObject) throws JSONException {
|
|
|
+ //比对结果置信度,范围 [0,100],小数点后3位有效数字,数字越大表示两个人脸越可能是同一个人。
|
|
|
+ double confidence = jsonObject.getDouble("confidence");
|
|
|
+ //一组用于参考的置信度阈值,包含以下三个字段。每个字段的值为一个 [0,100] 的浮点数,小数点后 3 位有效数字。
|
|
|
+ //1e-3:误识率为千分之一的置信度阈值;
|
|
|
+ //1e-4:误识率为万分之一的置信度阈值;
|
|
|
+ //1e-5:误识率为十万分之一的置信度阈值;
|
|
|
+ JSONObject thresholdsJsonObject = jsonObject.getJSONObject("thresholds");
|
|
|
+ double le4 = thresholdsJsonObject.getDouble("1e-4");
|
|
|
+ //如果置信值低于“千分之一”阈值则不建议认为是同一个人;如果置信值超过“十万分之一”阈值,则是同一个人的几率非常高。
|
|
|
+ return confidence >= le4;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|