Sfoglia il codice sorgente

完善新人脸活体检测接口代码

lideyin 5 anni fa
parent
commit
66edb0bd41

+ 12 - 11
examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/controller/FaceBiopsyController.java

@@ -94,25 +94,25 @@ public class FaceBiopsyController extends ControllerSupport {
         ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, req.getExamRecordDataId(),
                 ExamRecordDataEntity.class);
         if (examRecordData == null) {
-            throw new StatusException("200101", "无效的考试记录");
+            throw new StatusException("200105", "无效的考试记录");
         }
         if (req.getFaceBiopsyItemId() == null) {
-            throw new StatusException("200105", "人脸活体检测明细id不允许为空");
+            throw new StatusException("200106", "人脸活体检测明细id不允许为空");
         }
         if (req.getVerifySteps() == null || req.getVerifySteps().isEmpty()) {
-            throw new StatusException("200106", "活体检测步骤不允许为空");
+            throw new StatusException("200107", "活体检测步骤不允许为空");
         }
 
         if (req.getVerifySteps().stream().anyMatch(p -> p.getStepId() == null)) {
-            throw new StatusException("200107", "活体检测步骤id不允许为空");
+            throw new StatusException("200108", "活体检测步骤id不允许为空");
         }
 
         if (req.getVerifySteps().stream().anyMatch(p -> p.getAction() == null)) {
-            throw new StatusException("200107", "活体检测执行动作不允许为空");
+            throw new StatusException("200109", "活体检测执行动作不允许为空");
         }
 
-        if (verifyStepsAllMatch(req.getFaceBiopsyItemId(), req.getVerifySteps())) {
-            throw new StatusException("200108", "活体检测步骤与原始定义不匹配");
+        if (verifyStepsAllMatch(req.getFaceBiopsyItemId(), req.getExamRecordDataId(), req.getVerifySteps())) {
+            throw new StatusException("200110", "活体检测步骤与原始定义不匹配");
         }
 
         return faceBiopsyService.saveFaceBiopsyResult(req);
@@ -125,7 +125,7 @@ public class FaceBiopsyController extends ControllerSupport {
      * @param verifySteps
      * @return
      */
-    private boolean verifyStepsAllMatch(Long faceBiopsyItemId, List<FaceBiopsyStepInfo> verifySteps) {
+    private boolean verifyStepsAllMatch(Long faceBiopsyItemId, Long examRecordDataId, List<FaceBiopsyStepInfo> verifySteps) {
         List<FaceBiopsyItemStepEntity> originalVerifySteps = faceBiopsyItemStepRepo.findByFaceBiopsyItemId(faceBiopsyItemId);
 
         if (originalVerifySteps == null || originalVerifySteps.isEmpty() ||
@@ -133,12 +133,13 @@ public class FaceBiopsyController extends ControllerSupport {
             return false;
         }
 
-        for (int i=0;i<originalVerifySteps.size();i++){
+        for (int i = 0; i < originalVerifySteps.size(); i++) {
             FaceBiopsyItemStepEntity originalStep = originalVerifySteps.get(i);
             FaceBiopsyStepInfo newStep = verifySteps.get(i);
-            //如果步骤id和动作不同时匹配,则认为不匹配
+            //如果步骤id和动作以及考试记录id不同时匹配,则认为不匹配
             if (!(originalStep.getId().equals(newStep.getStepId()) &&
-                    originalStep.getAction().equals(newStep.getAction()))){
+                    originalStep.getAction().equals(newStep.getAction()) &&
+                    originalStep.getExamRecordDataId().equals(examRecordDataId))) {
                 return false;
             }
         }

+ 125 - 97
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamControlService.java

@@ -13,16 +13,16 @@ import javax.validation.Valid;
 import java.util.List;
 
 /**
- * 
- * @author  	chenken
- * @date    	2018年8月13日 下午2:09:38
- * @company 	QMTH
+ * @author chenken
+ * @date 2018年8月13日 下午2:09:38
+ * @company QMTH
  * @description 在线考试控制服务接口
  */
 public interface ExamControlService {
 
     /**
      * 开始考试
+     *
      * @param examStudentId
      * @param user
      */
@@ -30,112 +30,140 @@ public interface ExamControlService {
 
     /**
      * 考试心跳
+     *
      * @param
      */
     public long examHeartbeat(User user);
 
     /**
      * 断点续考:检查正在进行中的考试
+     *
      * @param studentId
      */
     public CheckExamInProgressInfo checkExamInProgress(Long studentId);
-	/**
+
+    /**
      * 获取考试记录信息
+     *
+     * @param examRecordDataId
+     */
+    public EndExamInfo getEndExamInfo(Long examRecordDataId);
+
+    /**
+     * 照片失败扫描处理
+     */
+    public void handleByExamCaptureQueueFailedDispose(Long examRecordDataId);
+
+    /**
+     * 获取上传考试音频所需的又拍云签名
+     *
+     * @param req
+     * @return
+     */
+    UpyunSignatureInfo getUpyunSignature(GetUpyunSignatureReq req);
+
+    /**
+     * 发送消息到websocket
+     *
+     * @param examRecordDataId 考试记录id
+     * @param order            题序号
+     * @param fileUrl          文件路径
+     * @param transferFileType 传输文件类型
+     * @throws Exception
+     */
+    public void sendFileAnswerToWebSocket(Long examRecordDataId, Integer order,
+                                          String fileUrl, String transferFileType, Long userId) throws Exception;
+
+    public String getQrCode(GetQrCodeReq req, String key);
+
+    /**
+     * 校验二维码
+     *
+     * @param param
+     * @return
+     */
+    public CheckQrCodeInfo checkQrCode(String param);
+
+    /**
+     * 成功上传音频结果推送(微信小程序调用)
+     *
+     * @param req
+     */
+    public ExamFileAnswerTempEntity saveUploadedFile(SaveUploadedFileReq req, User user);
+
+    /**
+     * 查询上传音频结果推送状态(微信小程序调用)
+     *
+     * @param req
+     */
+    public String getUploadedFileAcknowledgeStatus(GetUploadedFileAcknowledgeStatusReq req);
+
+    public void saveUploadedFileAcknowledgeStatus(SaveUploadedFileAcknowledgeStatusReq req);
+
+    /**
+     * 通过websocket发送二维码扫描信息
+     *
      * @param examRecordDataId
+     * @param clientId
+     * @throws Exception
+     */
+    public void sendScanQrCodeToWebSocket(String clientId, Long examRecordDataId, Integer order) throws Exception;
+
+    /**
+     * 获取已上传的文件答案列表(微信小程序调用)
+     *
+     * @param req
+     * @return
+     */
+    public List<UploadedFileAnswerInfo> getUploadedFileAnswerList(@Valid GetUploadedFileAnswerListReq req);
+
+    String getQrCode(GetQrCodeReq req, User user);
+
+    void deleteExamFileAnswerTemp(SaveUploadedFileReq req);
+
+    /**
+     * 计算考试已用时间(毫秒)
+     *
+     * @param examSessionInfo 考试会话对象
+     * @return
+     */
+    Long calculateExamUsedMilliseconds(ExamSessionInfo examSessionInfo);
+
+    /**
+     * 交卷
+     *
+     * @param examRecordDataId 考试记录id
+     * @param handInExamType   交卷类型
+     */
+    void handInExam(Long examRecordDataId, HandInExamType handInExamType);
+
+    /**
+     * 交卷的后续处理
+     *
+     * @param examRecordDataId 考试记录id
+     * @param handInExamType   交卷类型
+     * @param studentId        学生id
+     * @return boolean
+     */
+    boolean processAfterHandInExam(Long examRecordDataId, Long studentId, HandInExamType handInExamType);
+
+    /**
+     * 清理考试已结束的文件作答记录
+     */
+    void cleanTempFileAnswers();
+
+    /**
+     * 清理考试中的考试记录
+     *
+     * @param examingRecordEntity
      */
-	public EndExamInfo getEndExamInfo(Long examRecordDataId);
-	
-	/**
-	 * 照片失败扫描处理
-	 */
-	public void handleByExamCaptureQueueFailedDispose(Long examRecordDataId);
-
-	/**获取上传考试音频所需的又拍云签名
-	 * @param req
-	 * @return
-	 */
-	UpyunSignatureInfo getUpyunSignature(GetUpyunSignatureReq req);
-
-	/**
-	 *  发送消息到websocket
-	 * @param examRecordDataId 考试记录id
-	 * @param order 题序号
-	 * @param fileUrl 文件路径
-	 * @param transferFileType 传输文件类型
-	 * @throws Exception
-	 */
-	public void sendFileAnswerToWebSocket(Long examRecordDataId, Integer order,
-										  String fileUrl, String transferFileType,Long userId) throws Exception;
-	public String getQrCode(GetQrCodeReq req,String key);
-
-	/**校验二维码
-	 * @param param
-	 * @return
-	 */
-	public CheckQrCodeInfo checkQrCode(String param);
-
-	/**成功上传音频结果推送(微信小程序调用)
-	 * @param req
-	 */
-	public ExamFileAnswerTempEntity saveUploadedFile(SaveUploadedFileReq req, User user);
-
-	/**查询上传音频结果推送状态(微信小程序调用)
-	 * @param req
-	 */
-	public String getUploadedFileAcknowledgeStatus(GetUploadedFileAcknowledgeStatusReq req);
-
-	public void saveUploadedFileAcknowledgeStatus(SaveUploadedFileAcknowledgeStatusReq req);
-
-	/**
-	 * 通过websocket发送二维码扫描信息
-	 * @param examRecordDataId
-	 * @param clientId
-	 * @throws Exception
-	 */
-	public void sendScanQrCodeToWebSocket(String clientId,Long examRecordDataId, Integer order) throws Exception;
-
-	/**
-	 * 获取已上传的文件答案列表(微信小程序调用)
-	 * @param req
-	 * @return
-	 */
-	public List<UploadedFileAnswerInfo> getUploadedFileAnswerList(@Valid GetUploadedFileAnswerListReq req);
-
-	String getQrCode(GetQrCodeReq req, User user);
-
-	void deleteExamFileAnswerTemp(SaveUploadedFileReq req);
-
-	/**
-	 * 交卷
-	 * @param examRecordDataId 考试记录id
-	 * @param handInExamType 交卷类型
-	 */
-	void handInExam(Long examRecordDataId, HandInExamType handInExamType);
-
-	/**
-	 * 交卷的后续处理
-	 * @param examRecordDataId 考试记录id
-	 * @param handInExamType 交卷类型
-	 * @param studentId 学生id
-	 * @return boolean
-	 */
-    boolean processAfterHandInExam(Long examRecordDataId,Long studentId, HandInExamType handInExamType);
-
-	/**
-	 * 清理考试已结束的文件作答记录
-	 */
-	void cleanTempFileAnswers();
-
-	/**
-	 * 清理考试中的考试记录
-	 * @param examingRecordEntity
-	 */
     void cleanExamingRecord(ExamingRecordEntity examingRecordEntity);
 
-	/**
-	 * 清理已交卷的考试记录
-	 * @param handInExamRecordEntity
-	 */
-	void cleanHandInExamRecord(HandInExamRecordEntity handInExamRecordEntity);
+    /**
+     * 清理已交卷的考试记录
+     *
+     * @param handInExamRecordEntity
+     */
+    void cleanHandInExamRecord(HandInExamRecordEntity handInExamRecordEntity);
 
 }

+ 36 - 21
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/impl/ExamControlServiceImpl.java

@@ -266,7 +266,7 @@ public class ExamControlServiceImpl implements ExamControlService {
         String isFaceEnable = CacheHelper.getExamOrgProperty(examRecordData.getExamId(), examRecordData.getOrgId(),
                 ExamProperties.IS_FACE_ENABLE.name()).getValue();
         if (isFaceEnable != null && Constants.isTrue.equals(isFaceEnable)) {
-            SaveExamCaptureSyncCompareResultReq req= new SaveExamCaptureSyncCompareResultReq();
+            SaveExamCaptureSyncCompareResultReq req = new SaveExamCaptureSyncCompareResultReq();
             req.setExamRecordDataId(examRecordData.getId());
             req.setStudentId(user.getUserId());
             examCaptureCloudService.saveExamCaptureSyncCompareResult(req);
@@ -593,24 +593,17 @@ public class ExamControlServiceImpl implements ExamControlService {
     /**
      * 计算考试时长 校验是否达到冻结时间
      *
-     * @param examSessionInfo
+     * @param studentId 学生id
      * @return
-     * @Param studentId
      */
-    private Long checkAndComputeExamDuration(ExamSessionInfo examSessionInfo) {
+    private Long checkAndComputeExamDuration(Long studentId) {
 
-        long now = System.currentTimeMillis();
-        Long lastHeartbeat = examSessionInfo.getLastHeartbeat();
-        long usedTime = examSessionInfo.getHeartbeat() * 60 * 1000;// 考试时长=心跳次数*60*1000
-        if (lastHeartbeat == null) {
-            lastHeartbeat = now;
-            usedTime = 1 * 60 * 1000;// 算作1分钟
-        }
-
-        if (now - lastHeartbeat < 60 * 1000) {
-            // 如果最后一次心跳和当前时间小于1分钟,则把零碎的时间也加进去
-            usedTime += now - lastHeartbeat;
+        // 获取考试会话,判断考生是否已结束考试(二次校验)
+        ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(studentId);
+        if (examSessionInfo == null) {
+            throw new StatusException("oestudent-100100", "考试会话已过期");
         }
+        Long usedTime = calculateExamUsedMilliseconds(examSessionInfo);
         // 如果没有超过冻结时间,抛出异常
         if (examSessionInfo.getExamType().equals(ExamType.ONLINE.name())) {
             ExamingRecordEntity rec = GlobalHelper.getEntity(examingRecordRepo, examSessionInfo.getExamRecordDataId(), ExamingRecordEntity.class);
@@ -628,6 +621,32 @@ public class ExamControlServiceImpl implements ExamControlService {
         return usedTime;
     }
 
+    /**
+     * 计算考试已用时间(毫秒)
+     *
+     * @param examSessionInfo 考试会话对象
+     * @return
+     */
+    @Override
+    public Long calculateExamUsedMilliseconds(ExamSessionInfo examSessionInfo) {
+        if (examSessionInfo == null) {
+            throw new StatusException("oestudent-100100", "考试会话已过期");
+        }
+        long now = System.currentTimeMillis();
+        Long lastHeartbeat = examSessionInfo.getLastHeartbeat();
+        long usedTime = examSessionInfo.getHeartbeat() * 60 * 1000;// 考试时长=心跳次数*60*1000
+        if (lastHeartbeat == null) {
+            lastHeartbeat = now;
+            usedTime = 1 * 60 * 1000;// 算作1分钟
+        }
+
+        if (now - lastHeartbeat < 60 * 1000) {
+            // 如果最后一次心跳和当前时间小于1分钟,则把零碎的时间也加进去
+            usedTime += now - lastHeartbeat;
+        }
+        return usedTime;
+    }
+
     /**
      * @param examRecordDataId 考试记录id
      * @param handInExamType   交卷类型
@@ -654,13 +673,9 @@ public class ExamControlServiceImpl implements ExamControlService {
 
         if (handInExamType == HandInExamType.MANUAL) {
             startTime = System.currentTimeMillis();
-            // 获取考试会话,判断考生是否已结束考试(二次校验)
-            ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(examRecordData.getStudentId());
-            if (examSessionInfo == null) {
-                throw new StatusException("oestudent-100100", "考试会话已过期");
-            }
+
             // 得到考试时长,校验是否达到冻结时间
-            long usedExamTime = checkAndComputeExamDuration(examSessionInfo);
+            long usedExamTime = checkAndComputeExamDuration(examRecordData.getStudentId());
             examRecordData.setUsedExamTime(usedExamTime);
             examRecordData.setExamRecordStatus(ExamRecordStatus.EXAM_HAND_IN);
             examRecordData.setEndTime(new Date());

+ 198 - 8
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/impl/FaceBiopsyServiceImpl.java

@@ -15,6 +15,7 @@ import cn.com.qmth.examcloud.core.oe.common.repository.FaceBiopsyItemRepo;
 import cn.com.qmth.examcloud.core.oe.common.repository.FaceBiopsyItemStepRepo;
 import cn.com.qmth.examcloud.core.oe.common.repository.FaceBiopsyRepo;
 import cn.com.qmth.examcloud.core.oe.student.bean.*;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamControlService;
 import cn.com.qmth.examcloud.core.oe.student.service.ExamSessionInfoService;
 import cn.com.qmth.examcloud.core.oe.student.service.FaceBiopsyService;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
@@ -47,6 +48,8 @@ public class FaceBiopsyServiceImpl implements FaceBiopsyService {
     private ExamRecordDataRepo examRecordDataRepo;
     @Autowired
     private ExamSessionInfoService examSessionInfoService;
+    @Autowired
+    private ExamControlService examControlService;
 
     @Override
     public FaceBiopsyInfo getFaceBiopsyInfo(Long rootOrgId, Long examRecordDataId, FaceBiopsyType faceBiopsyType) {
@@ -91,11 +94,176 @@ public class FaceBiopsyServiceImpl implements FaceBiopsyService {
 
     @Override
     public SaveFaceBiopsyResultResp saveFaceBiopsyResult(SaveFaceBiopsyResultReq req) {
-        List<FaceBiopsyStepInfo> verifySteps = req.getVerifySteps();
-        for (int i=0;i<verifySteps.size();i++){
+        //构建业务实体
+        SaveFaceBiopsyResultResp resp = buildSaveFaceBiopsyResultResp(req.getExamRecordDataId(), req.getVerifySteps());
+
+        //更新人脸活体检测结果至数据库
+        updateFaceBiopsyResult(req.getExamRecordDataId(), req.getFaceBiopsyItemId(), req.getVerifySteps(),
+                resp.getVerifyResult(), resp.getErrorMessage());
+        return resp;
+    }
+
+    /**
+     * 构建保存人脸检测结果的业务实体
+     *
+     * @param examRecordDataId
+     * @param verifySteps
+     * @return
+     */
+    private SaveFaceBiopsyResultResp buildSaveFaceBiopsyResultResp(Long examRecordDataId,
+                                                                   List<FaceBiopsyStepInfo> verifySteps) {
+        //本次检测的最终结果是否成功(检测步骤中只要有一项检测失败,则认为检测失败)
+        boolean finalIsSuccess = true;
+        String errorMsg = null;
+        /**
+         * 是否结束考试
+         * case1:人脸比对失败(即活检第1步,action==FACE_COMPARE)时,结束考试
+         * case2:冻结时间内,第2次人脸活检整体失败,结束考试
+         * case3:冻结时间外,人脸活检失败,结束考试
+         */
+        boolean isEndExam = false;
+        //检测次数
+        int verifyTimes = getVerifyTimes(examRecordDataId);
+        //是否在冻结时间内
+        boolean isInFreezeTime = calculateIsInFreezeTime(examRecordDataId);
+
+        for (FaceBiopsyStepInfo stepInfo : verifySteps) {
+            if (!stepInfo.getResult()) {
+                errorMsg = stepInfo.getErrorMsg();
+            }
+
+            //整个检测步骤中只要有一次失败,则认为整个活检失败
+            if (!stepInfo.getResult()) {
+                finalIsSuccess = false;
+            }
+
+            switch (stepInfo.getAction()) {
+                case FACE_COMPARE:
+                    //如果第一步检测(即人脸比对)失败,则结束考试
+                    if (!stepInfo.getResult()) {
+                        isEndExam = true;
+                    }
+                    break;
+                case HAPPY:
+                    //如果此前步骤已判断为需要结束考试则不作任何处理
+                    if (!isEndExam) {
+                        if (isInFreezeTime) {
+                            //如果冻结时间内第2次检测失败,则需要结束考试
+                            if (verifyTimes > 1 && !stepInfo.getResult()) {
+                                isEndExam = true;
+                            }
+                        } else {
+                            //冻结时间外检测失败,直接结束考试
+                            if (!stepInfo.getResult()) {
+                                isEndExam = true;
+                            }
+                        }
+                    }
+                    break;
+            }
+
+        }
+        //构建业务实体
+        SaveFaceBiopsyResultResp resp = new SaveFaceBiopsyResultResp();
+        resp.setEndExam(isEndExam);
+        resp.setNeedNextVerify(calculateNeedNextVerify(
+                isEndExam, isInFreezeTime, finalIsSuccess, verifyTimes, examRecordDataId));
+        resp.setVerifyResult(finalIsSuccess);
+        resp.setErrorMessage(errorMsg);
+        return resp;
+    }
+
+    /**
+     * 更新人脸活体检测结果至数据库
+     *
+     * @param examRecordDataId
+     * @param faceBiopsyItemId
+     * @param verifySteps
+     * @param finalIsSuccess
+     * @param errorMsg
+     */
+    @Transactional
+    public void updateFaceBiopsyResult(Long examRecordDataId, Long faceBiopsyItemId, List<FaceBiopsyStepInfo> verifySteps,
+                                       boolean finalIsSuccess, String errorMsg) {
+        List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList =
+                faceBiopsyItemStepRepo.findByFaceBiopsyItemId(faceBiopsyItemId);
+        for (FaceBiopsyStepInfo stepInfo : verifySteps) {
+            //循环更新新活体检测每步的执行结果
+            FaceBiopsyItemStepEntity faceBiopsyItemStepEntity = faceBiopsyItemStepEntityList.stream().
+                    filter(p -> p.getId().equals(stepInfo.getStepId())).findFirst().get();
+            faceBiopsyItemStepEntity.setErrorMsg(stepInfo.getErrorMsg());
+            faceBiopsyItemStepEntity.setResourceType(stepInfo.getResourceType());
+            faceBiopsyItemStepEntity.setResourceRelativePath(getRelativePath(stepInfo.getResourceUrl()));
+            faceBiopsyItemStepEntity.setExt1(stepInfo.getResultJson());//存储动作执行结果
+            faceBiopsyItemStepEntity.setResult(stepInfo.getResult());
+        }
+        //批量更新活检步骤执行结果
+        faceBiopsyItemStepRepo.saveAll(faceBiopsyItemStepEntityList);
+        //更新人脸活体检测最终结果
+        faceBiopsyItemRepo.updateFaceBiopsyItemResult(faceBiopsyItemId, finalIsSuccess, errorMsg, true);
+        faceBiopsyRepo.updateFaceBiopsyResult(examRecordDataId, finalIsSuccess, errorMsg);
+    }
+
+    /**
+     * 计算是否需要下次活检
+     *
+     * @param isEndExam
+     * @param isInFreezeTime
+     * @param finalIsSuccess
+     * @param verifyTimes
+     * @return
+     */
+    private boolean calculateNeedNextVerify(boolean isEndExam, boolean isInFreezeTime, boolean finalIsSuccess,
+                                            int verifyTimes, Long examRecordDataId) {
+        //考试已结束,不需要继续下次活检
+        if (isEndExam) {
+            return false;
+        }
+
+        //如果考试未结束的情况
+        if (isInFreezeTime) {
+            if (verifyTimes == 1) {
+                //冻结时间内第一次活检失败,则需要下次活检
+                if (!finalIsSuccess) {
+                    return true;
+                }
+            } else {
+                //冻结时间内非第一次活检失败,则不需要下次活检
+                if (!finalIsSuccess) {
+                    return false;
+                }
+            }
 
+            //冻结时间内第一次或第二次活检成功,且开启冻结时间外增加一次活检,则需要下次活检,否则不需要
+            ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId,
+                    ExamRecordDataEntity.class);
+            return addFaceVerifyOutFreezeTime(examRecordData.getExamId(), examRecordData.getOrgId());
+        } else {
+            //冻结时间外,无论活检成功或失败,均不可进行下次活检
+            return false;
         }
-        return null;
+    }
+
+    /**
+     * 获取活体检测次数
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    private int getVerifyTimes(Long examRecordDataId) {
+        FaceBiopsyEntity faceBiopsy = faceBiopsyRepo.findByExamRecordDataId(examRecordDataId);
+        return faceBiopsy.getVerifiedTimes();
+    }
+
+    /**
+     * 根据完整路径获取相对路径
+     *
+     * @param resourceUrl
+     * @return
+     */
+    private String getRelativePath(String resourceUrl) {
+        //TODO 待完善代码
+        return resourceUrl;
     }
 
     /**
@@ -273,7 +441,7 @@ public class FaceBiopsyServiceImpl implements FaceBiopsyService {
 
         Integer heartbeat = examSessionInfo.getHeartbeat();
         //如果冻结时间内,使用冻结时间内的计算方式
-        if (heartbeat < examSessionInfo.getFreezeTime()) {
+        if (calculateIsInFreezeTime(examRecordDataId)) {
             return generateInFreezeTimeFaceBiopsyStartMinute(examId, orgId, heartbeat, verifyTimes);
         }
         //如果超过冻结时间则使用冻结时间外的计算方式
@@ -332,11 +500,8 @@ public class FaceBiopsyServiceImpl implements FaceBiopsyService {
      * @return
      */
     private Integer generateOutFreezeTimeFaceBiopsyStartMinute(Long examId, Long orgId, Integer heartbeat) {
-        //冻结时间外是否添加活体检测
-        String addFaceVerifyOutFreezeTime = CacheHelper.getExamOrgProperty(examId, orgId,
-                ExamProperties.ADD_FACE_VERIFY_OUT_FREEZE_TIME.name()).getValue();
         //如果冻结时间外不添加活体检测,则直接返回null
-        if (!Constants.isTrue.equals(addFaceVerifyOutFreezeTime)) {
+        if (!addFaceVerifyOutFreezeTime(examId, orgId)) {
             return null;
         }
 
@@ -363,4 +528,29 @@ public class FaceBiopsyServiceImpl implements FaceBiopsyService {
             return 1;
         }
     }
+
+    private boolean addFaceVerifyOutFreezeTime(Long examId, Long orgId) {
+        String addFaceVerifyOutFreezeTime = CacheHelper.getExamOrgProperty(examId, orgId,
+                ExamProperties.ADD_FACE_VERIFY_OUT_FREEZE_TIME.name()).getValue();
+        return Constants.isTrue.equals(addFaceVerifyOutFreezeTime);
+    }
+
+    /**
+     * 当前考试是否在冻结时间内
+     *
+     * @param examRecordDataId 考试记录id
+     * @return
+     */
+    private boolean calculateIsInFreezeTime(Long examRecordDataId) {
+        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId,
+                ExamRecordDataEntity.class);
+
+        ExamSessionInfo examSessionInfo = examSessionInfoService.getExamSessionInfo(examRecordData.getStudentId());
+        if (examSessionInfo == null) {
+            throw new StatusException("201002", "考试会话已过期");
+        }
+        Long usedMilliseconds = examControlService.calculateExamUsedMilliseconds(examSessionInfo);
+
+        return usedMilliseconds < examSessionInfo.getFreezeTime() * 60 * 1000;
+    }
 }