|
@@ -0,0 +1,851 @@
|
|
|
+package cn.com.qmth.examcloud.core.oe.student.service.impl;
|
|
|
+
|
|
|
+import cn.com.qmth.examcloud.commons.exception.StatusException;
|
|
|
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.base.utils.CommonUtil;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.FaceBiopsyInfo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.FaceBiopsyStepInfo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.SaveFaceBiopsyResultReq;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.bean.SaveFaceBiopsyResultResp;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.dao.FaceBiopsyItemRepo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.dao.FaceBiopsyItemStepRepo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.dao.FaceBiopsyRepo;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.FaceBiopsyEntity;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.FaceBiopsyItemEntity;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.FaceBiopsyItemStepEntity;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceBiopsyAction;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceBiopsyType;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.service.ExamControlService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordDataService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.service.ExamingSessionService;
|
|
|
+import cn.com.qmth.examcloud.core.oe.student.service.FaceBiopsyService;
|
|
|
+import cn.com.qmth.examcloud.support.Constants;
|
|
|
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
|
|
|
+import cn.com.qmth.examcloud.support.enums.IsSuccess;
|
|
|
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
|
|
|
+import cn.com.qmth.examcloud.support.examing.ExamingSession;
|
|
|
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
|
|
|
+import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
|
|
|
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
|
|
|
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
|
|
|
+import com.google.common.collect.Lists;
|
|
|
+import com.mysql.cj.util.StringUtils;
|
|
|
+import org.apache.commons.lang3.RandomUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @Description 人脸活体检测接口实现类
|
|
|
+ * @Author lideyin
|
|
|
+ * @Date 2019/10/14 10:56
|
|
|
+ * @Version 1.0
|
|
|
+ */
|
|
|
+@Service("faceBiopsyService")
|
|
|
+public class FaceBiopsyServiceImpl implements FaceBiopsyService {
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private FaceBiopsyRepo faceBiopsyRepo;
|
|
|
+ @Autowired
|
|
|
+ private FaceBiopsyItemRepo faceBiopsyItemRepo;
|
|
|
+ @Autowired
|
|
|
+ private FaceBiopsyItemStepRepo faceBiopsyItemStepRepo;
|
|
|
+ @Autowired
|
|
|
+ private ExamControlService examControlService;
|
|
|
+ @Autowired
|
|
|
+ private ExamRecordDataService examRecordDataService;
|
|
|
+ @Autowired
|
|
|
+ ExamingSessionService examingSessionService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public FaceBiopsyInfo getFaceBiopsyInfo(Long rootOrgId, Long examRecordDataId, FaceBiopsyType faceBiopsyType) {
|
|
|
+ //如果是第一次进行人脸活体检测,则初始化相关信息保存并返回
|
|
|
+ FaceBiopsyEntity faceBiopsyEntity = faceBiopsyRepo.findByExamRecordDataId(examRecordDataId);
|
|
|
+ if (faceBiopsyEntity == null) {
|
|
|
+ return addFirstFaceBiopsy(rootOrgId, examRecordDataId, faceBiopsyType);
|
|
|
+ }
|
|
|
+
|
|
|
+ //如果不是第一次人脸活体检测,判断是否有未检测完的记录,
|
|
|
+ FaceBiopsyInfo result = new FaceBiopsyInfo();
|
|
|
+ // 未完成的活体检测列表
|
|
|
+ List<FaceBiopsyItemEntity> unCompletedFaceBiopsyItemList =
|
|
|
+ faceBiopsyItemRepo.findByExamRecordDataIdAndCompleted(examRecordDataId, false);
|
|
|
+
|
|
|
+ //如果存在未完成的活体检测信息,则直接返回上次未完成的活检记录
|
|
|
+ if (unCompletedFaceBiopsyItemList != null && !unCompletedFaceBiopsyItemList.isEmpty()) {
|
|
|
+ FaceBiopsyItemEntity faceBiopsyItemEntity = unCompletedFaceBiopsyItemList.get(0);
|
|
|
+ result.setFaceBiopsyItemId(faceBiopsyItemEntity.getId());
|
|
|
+ result.setFaceVerifyMinute(calculateFaceBiopsyStartMinute(examRecordDataId));
|
|
|
+
|
|
|
+ List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList =
|
|
|
+ faceBiopsyItemStepRepo.findByFaceBiopsyItemId(faceBiopsyItemEntity.getId());
|
|
|
+ result.setVerifySteps(copyFaceBiopsyStepDomainListFrom(faceBiopsyItemStepEntityList));
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ //不存在未完成的活检的校验逻辑
|
|
|
+ // 已活检次数
|
|
|
+ int verifiedTimes = faceBiopsyEntity.getVerifiedTimes();
|
|
|
+
|
|
|
+ if (verifiedTimes == 1) {
|
|
|
+ FaceBiopsyItemEntity firstFaceBiopsyItem =
|
|
|
+ faceBiopsyItemRepo.findFirstByFaceBiopsyIdOrderByIdAsc(faceBiopsyEntity.getId());
|
|
|
+ if (null != firstFaceBiopsyItem.getResult() && firstFaceBiopsyItem.getResult()) {
|
|
|
+ //第一次活检成功,且未开启追加新活检,再次调用,则抛出异常
|
|
|
+ if (!isAddFaceVerifyOutFreezeTime(examRecordDataId)) {
|
|
|
+ throw new StatusException("201006", "非法请求,无活体检测机会");
|
|
|
+ }
|
|
|
+ //第一次活检成功,且开启追加新活检,再次调用,按冻结时间外生成新的活检步骤
|
|
|
+ return appendFaceBiopsy(faceBiopsyEntity.getId(), verifiedTimes, examRecordDataId, faceBiopsyType, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ //如果第一次活检失败,再次调用,按冻结时间内生成新的活检步骤
|
|
|
+ return appendFaceBiopsy(faceBiopsyEntity.getId(), verifiedTimes, examRecordDataId, faceBiopsyType, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (verifiedTimes == 2) {
|
|
|
+ FaceBiopsyItemEntity secondFaceBiopsyItem =
|
|
|
+ faceBiopsyItemRepo.findFirstByFaceBiopsyIdOrderByIdDesc(faceBiopsyEntity.getId());
|
|
|
+
|
|
|
+ //如果是第二次活检为冻结时间外的活检记录,再次调用,则直接抛出异常
|
|
|
+ if (!secondFaceBiopsyItem.getInFreezeTime()) {
|
|
|
+ throw new StatusException("201007", "非法请求,无活体检测机会");
|
|
|
+ }
|
|
|
+
|
|
|
+ //如果第二次活检也失败,再次调用,则抛出异常
|
|
|
+ if (!secondFaceBiopsyItem.getResult()) {
|
|
|
+ throw new StatusException("201008", "非法请求,无活体检测机会");
|
|
|
+ }
|
|
|
+
|
|
|
+ //否则追加冻结时间外的活检
|
|
|
+ return appendFaceBiopsy(faceBiopsyEntity.getId(), verifiedTimes, examRecordDataId, faceBiopsyType, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ //三交活检已完成,则抛出异常
|
|
|
+ throw new StatusException("201009", "非法请求,无活体检测机会");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional
|
|
|
+ public SaveFaceBiopsyResultResp saveFaceBiopsyResult(SaveFaceBiopsyResultReq req, Long studentId) {
|
|
|
+ // 检测结果至少有一条检测结果不允许为空
|
|
|
+ if (!req.getVerifySteps().stream().anyMatch(p -> null != p.getResult())) {
|
|
|
+ throw new StatusException("201005", "检测结果不允许为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ //构建业务实体
|
|
|
+ SaveFaceBiopsyResultResp resp = buildSaveFaceBiopsyResultResp(req);
|
|
|
+
|
|
|
+ //更新人脸活体检测结果至数据库
|
|
|
+ updateFaceBiopsyResult(req.getExamRecordDataId(), req.getFaceBiopsyItemId(), req.getVerifySteps(),
|
|
|
+ resp.getVerifyResult(), resp.getErrorMessage());
|
|
|
+
|
|
|
+ //同步更新考试记录表中的活体检测结果
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(req.getExamRecordDataId());
|
|
|
+ if (examRecordData == null) {
|
|
|
+ throw new StatusException("201010", "找不到相关考试记录数据");
|
|
|
+ }
|
|
|
+ examRecordData.setFaceVerifyResult(resp.getVerifyResult() ? IsSuccess.SUCCESS : IsSuccess.FAILED);
|
|
|
+ examRecordDataService.saveExamRecordDataCache(req.getExamRecordDataId(), examRecordData);
|
|
|
+
|
|
|
+ return resp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算人脸活体检测开始分钟数
|
|
|
+ *
|
|
|
+ * @param examRecordDataId 考试记录id
|
|
|
+ * @return Integer
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public Integer calculateFaceBiopsyStartMinute(Long examRecordDataId) {
|
|
|
+ //如果未开启人脸活体检测,则返回null
|
|
|
+ if (!getIsFaceVerify(examRecordDataId)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * case0.首次调用或超过3次调用的特殊处理
|
|
|
+ * case0.1.第一次调用,无冻结时间内还是冻结时间外,均按照冻结时间内的算法计算下次活检时间(相当于补偿措施)
|
|
|
+ * case0.2.超过3次调用,
|
|
|
+ * case0.2.1.如果第三次活检未完成,再次调用,则按活检外计算方式,返回下次活检时间
|
|
|
+ * case0.2.2.如果第三次活检已完成,则直接返回null
|
|
|
+ */
|
|
|
+ FaceBiopsyEntity faceBiopsy = faceBiopsyRepo.findByExamRecordDataId(examRecordDataId);
|
|
|
+ if (faceBiopsy == null) {
|
|
|
+ return generateInFreezeTimeFaceBiopsyStartMinute(examRecordDataId);
|
|
|
+ }
|
|
|
+
|
|
|
+ //超过三次的特殊处理
|
|
|
+ if (faceBiopsy.getVerifiedTimes() >= 3) {
|
|
|
+ FaceBiopsyItemEntity lastFaceBiopsyItem =
|
|
|
+ faceBiopsyItemRepo.findFirstByFaceBiopsyIdOrderByIdDesc(faceBiopsy.getId());
|
|
|
+
|
|
|
+ //如果第三次活检已完成,则直接返回null
|
|
|
+ if (lastFaceBiopsyItem.getCompleted()) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ //如果第三次活检未完成,再次调用,则按活检外计算方式,返回下次活检时间
|
|
|
+ return generateOutFreezeTimeFaceBiopsyStartMinute(examRecordDataId);
|
|
|
+ }
|
|
|
+
|
|
|
+ //按id升序排列的活检集合
|
|
|
+ List<FaceBiopsyItemEntity> sortedFaceBiopsyItems =
|
|
|
+ faceBiopsyItemRepo.findByFaceBiopsyIdOrderByIdAsc(faceBiopsy.getId());
|
|
|
+
|
|
|
+ //第一次活检明细
|
|
|
+ FaceBiopsyItemEntity firstFaceBiopsyItem = sortedFaceBiopsyItems.get(0);
|
|
|
+
|
|
|
+ //第一次活检未完成,再次调用,继续按冻结时间内方式计算活检开始时间
|
|
|
+ if (!firstFaceBiopsyItem.getCompleted()) {
|
|
|
+ return generateInFreezeTimeFaceBiopsyStartMinute(examRecordDataId);
|
|
|
+ }
|
|
|
+
|
|
|
+ //第一次活体检测完成且成功,再次调用时根据是否开启追加活检判断相关逻辑
|
|
|
+ if (firstFaceBiopsyItem.getResult()) {
|
|
|
+ //如果未开启冻结时间外人脸活体检测,则无需求下次活检,故直接返回null
|
|
|
+ if (!isAddFaceVerifyOutFreezeTime(examRecordDataId)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ //如果开启冻结时间外人脸活体检测,则直接返回冻结时间外的活检开始时间
|
|
|
+ return generateOutFreezeTimeFaceBiopsyStartMinute(examRecordDataId);
|
|
|
+ }
|
|
|
+
|
|
|
+ //第一次活检完成且失败,那么第二次调用或者第N(N>=2)次调用且第二次活检未完成,采用活检内计算方式
|
|
|
+ if (faceBiopsy.getVerifiedTimes() == 1 ||
|
|
|
+ (faceBiopsy.getVerifiedTimes() == 2 && !sortedFaceBiopsyItems.get(1).getCompleted())) {
|
|
|
+ return generateInFreezeTimeFaceBiopsyStartMinute(examRecordDataId);
|
|
|
+ }
|
|
|
+
|
|
|
+ //其它情况,采用冻结时间外活检计算方式
|
|
|
+ return generateOutFreezeTimeFaceBiopsyStartMinute(examRecordDataId);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建保存人脸检测结果的业务实体
|
|
|
+ *
|
|
|
+ * @param req
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private SaveFaceBiopsyResultResp buildSaveFaceBiopsyResultResp(SaveFaceBiopsyResultReq req) {
|
|
|
+ //构建业务实体
|
|
|
+ SaveFaceBiopsyResultResp resp = new SaveFaceBiopsyResultResp();
|
|
|
+
|
|
|
+ Boolean finalIsSuccess = calculateIsSuccess(req.getVerifySteps());
|
|
|
+ resp.setVerifyResult(finalIsSuccess);
|
|
|
+
|
|
|
+ Boolean isEndExam = calculateIsEndExam(req.getVerifySteps(), req.getExamRecordDataId(), finalIsSuccess);
|
|
|
+ resp.setEndExam(isEndExam);
|
|
|
+
|
|
|
+ resp.setNeedNextVerify(calculateNeedNextVerify(isEndExam, finalIsSuccess, req.getExamRecordDataId()));
|
|
|
+ resp.setErrorMessage(getErrorMsg(req.getVerifySteps()));
|
|
|
+ return resp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新人脸活体检测结果至数据库
|
|
|
+ *
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @param faceBiopsyItemId
|
|
|
+ * @param verifySteps
|
|
|
+ * @param finalIsSuccess
|
|
|
+ * @param errorMsg
|
|
|
+ */
|
|
|
+ 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());
|
|
|
+
|
|
|
+ if (null != stepInfo.getTimeout()) {
|
|
|
+ faceBiopsyItemStepEntity.setExt2(stepInfo.getTimeout().toString());//指令是否超时
|
|
|
+ }
|
|
|
+
|
|
|
+ if (null != stepInfo.getStranger()) {
|
|
|
+ faceBiopsyItemStepEntity.setExt3(stepInfo.getStranger().toString());//是否有陌生人脸
|
|
|
+ }
|
|
|
+
|
|
|
+ if (null != stepInfo.getWaggleOutCamera()) {
|
|
|
+ faceBiopsyItemStepEntity.setExt3(stepInfo.getWaggleOutCamera().toString());//是否晃同摄像头
|
|
|
+ }
|
|
|
+
|
|
|
+ if (null != stepInfo.getHasFace()) {
|
|
|
+ faceBiopsyItemStepEntity.setExt4(stepInfo.getHasFace().toString());//是否有人脸
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ //批量更新活检步骤执行结果
|
|
|
+ faceBiopsyItemStepRepo.saveAll(faceBiopsyItemStepEntityList);
|
|
|
+ //更新人脸活体检测最终结果
|
|
|
+ faceBiopsyItemRepo.updateFaceBiopsyItemResult(faceBiopsyItemId, finalIsSuccess, errorMsg, true);
|
|
|
+ faceBiopsyRepo.updateFaceBiopsyResult(examRecordDataId, finalIsSuccess, errorMsg);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算活检是否成功
|
|
|
+ *
|
|
|
+ * @param verifySteps
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean calculateIsSuccess(List<FaceBiopsyStepInfo> verifySteps) {
|
|
|
+ for (FaceBiopsyStepInfo stepInfo : verifySteps) {
|
|
|
+ //如果当前步骤为超时,则直接返回失败或者整个检测步骤中只要有一次失败,则认为整个活检失败
|
|
|
+ if ((null != stepInfo.getTimeout() && stepInfo.getTimeout()) ||
|
|
|
+ (null != stepInfo.getResult() && !stepInfo.getResult())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取错误信息
|
|
|
+ *
|
|
|
+ * @param verifySteps
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String getErrorMsg(List<FaceBiopsyStepInfo> verifySteps) {
|
|
|
+ for (FaceBiopsyStepInfo stepInfo : verifySteps) {
|
|
|
+ //如果当前步骤为超时,则直接跳出循环
|
|
|
+ if ((stepInfo.getTimeout() != null && stepInfo.getTimeout())) {
|
|
|
+ return "超时未完成";
|
|
|
+ }
|
|
|
+
|
|
|
+ //整个检测步骤中只要有一次失败,则认为整个活检失败
|
|
|
+ if (null != stepInfo.getResult() && !stepInfo.getResult()) {
|
|
|
+ return stepInfo.getErrorMsg();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算是否结束考试
|
|
|
+ *
|
|
|
+ * @param verifySteps
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @param finalIsSuccess
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean calculateIsEndExam(List<FaceBiopsyStepInfo> verifySteps, Long examRecordDataId, Boolean finalIsSuccess) {
|
|
|
+ /**
|
|
|
+ * 指定指令失败,(多人脸,人脸晃出摄像头,无人脸)直接结束考试
|
|
|
+ */
|
|
|
+ for (FaceBiopsyStepInfo stepInfo : verifySteps) {
|
|
|
+ switch (stepInfo.getAction()) {
|
|
|
+ case FACE_COMPARE:
|
|
|
+ //如果有陌生人脸或晃出摄像头或无人脸,直接结束考试
|
|
|
+ if ((null != stepInfo.getStranger() && stepInfo.getStranger()) ||
|
|
|
+ (null != stepInfo.getWaggleOutCamera() && stepInfo.getWaggleOutCamera()) ||
|
|
|
+ (null != stepInfo.getHasFace() && !stepInfo.getHasFace())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ //如果第一步检测部分失败(照片非本人或检测中多人脸),需要结束考试
|
|
|
+ Map faceCompareResult = JsonUtil.fromJson(stepInfo.getResultJson(), Map.class);
|
|
|
+ if (null != faceCompareResult) {
|
|
|
+ if (null == faceCompareResult.get("isStranger") || null == faceCompareResult.get("isPass") ||
|
|
|
+ null == faceCompareResult.get("existsSystemError")) {
|
|
|
+ throw new StatusException("201007", "活体检测第一步检测结果的json串格式不正确");
|
|
|
+ }
|
|
|
+
|
|
|
+ Boolean isStranger;
|
|
|
+ if (null == faceCompareResult.get("isStranger")) {
|
|
|
+ isStranger = false;
|
|
|
+ } else {
|
|
|
+ isStranger = Boolean.valueOf(faceCompareResult.get("isStranger").toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ Boolean isPass = Boolean.valueOf(faceCompareResult.get("isPass").toString());
|
|
|
+ Boolean existsSystemError = Boolean.valueOf(faceCompareResult.get("existsSystemError").toString());
|
|
|
+
|
|
|
+ //如果有系统错误,也需要抛出异常
|
|
|
+ if (existsSystemError) {
|
|
|
+ throw new StatusException("201008", "活体检测失败,请稍候重试");
|
|
|
+ }
|
|
|
+ //case1.1.有陌生人(即多人脸),不管是否比对成功,直接结束考试
|
|
|
+ if (isStranger) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ //case1.2.无陌生人且检测失败且不是系统错误(即照片非本人或无人脸),也直接结束考试
|
|
|
+ else {
|
|
|
+ if (!isPass && !existsSystemError) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case HAPPY:
|
|
|
+ case SERIOUS:
|
|
|
+ //如果有陌生人脸或晃出摄像头或无人脸,直接结束考试
|
|
|
+ if ((null != stepInfo.getStranger() && stepInfo.getStranger()) ||
|
|
|
+ (null != stepInfo.getWaggleOutCamera() && stepInfo.getWaggleOutCamera()) ||
|
|
|
+ (null != stepInfo.getHasFace() && !stepInfo.getHasFace())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据活检情况结束考试
|
|
|
+ * 当前为第二次活检,且失败,且第一次活检也失败,则直接结束考试
|
|
|
+ * 当前为第二次活检,且失败,且第一次活检成功,则直接结束考试
|
|
|
+ * 当前为第三次活检,且失败,且第一次活检失败,第二次活检成功,则直接结束考试
|
|
|
+ */
|
|
|
+ //如果是第二次活检,且失败,则结束考试(无论第一次是否成功)
|
|
|
+ int verifyTimes = getVerifyTimes(examRecordDataId);
|
|
|
+ if (verifyTimes == 2 && !finalIsSuccess) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (verifyTimes == 3 && !finalIsSuccess) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ //其它情况不结束考试
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算是否需要下次活检
|
|
|
+ *
|
|
|
+ * @param isEndExam
|
|
|
+ * @param finalIsSuccess
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean calculateNeedNextVerify(boolean isEndExam, boolean finalIsSuccess, Long examRecordDataId) {
|
|
|
+ //考试已结束,不需要继续下次活检
|
|
|
+ if (isEndExam) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ int verifyTimes = getVerifyTimes(examRecordDataId);
|
|
|
+ //如果是第一次活检,且成功,且开启追加活检,则允许继续活检
|
|
|
+ //或者是第一次活检,且失败,则允许继续活检
|
|
|
+ if (verifyTimes == 1) {
|
|
|
+ if ((finalIsSuccess && isAddFaceVerifyOutFreezeTime(examRecordDataId)) ||
|
|
|
+ !finalIsSuccess) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //如果是第二次活检,且成功,且第一次活检失败,且开启追加活检,则允许继续活检
|
|
|
+ if (verifyTimes == 2 && finalIsSuccess) {
|
|
|
+ FaceBiopsyItemEntity firstFaceBiopsyItem =
|
|
|
+ faceBiopsyItemRepo.findFirstByExamRecordDataIdOrderByIdAsc(examRecordDataId);
|
|
|
+ if (!firstFaceBiopsyItem.getResult() && isAddFaceVerifyOutFreezeTime(examRecordDataId)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //其它情况均不允许继续活检
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取活体检测次数
|
|
|
+ *
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private int getVerifyTimes(Long examRecordDataId) {
|
|
|
+ FaceBiopsyEntity faceBiopsy = faceBiopsyRepo.findByExamRecordDataId(examRecordDataId);
|
|
|
+ return faceBiopsy.getVerifiedTimes();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据完整路径获取相对路径
|
|
|
+ *
|
|
|
+ * @param resourceUrl
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private String getRelativePath(String resourceUrl) {
|
|
|
+ if (!StringUtils.isNullOrEmpty(resourceUrl)) {
|
|
|
+ String domain = PropertyHolder.getString("$upyun.site.1.domain");
|
|
|
+ String backupDomain = PropertyHolder.getString("$upyun.site.1.domain.backup");
|
|
|
+ if (!StringUtils.isNullOrEmpty(domain)) {
|
|
|
+ resourceUrl = resourceUrl.replace(domain, "");
|
|
|
+ }
|
|
|
+ if (!StringUtils.isNullOrEmpty(backupDomain)) {
|
|
|
+ resourceUrl = resourceUrl.replace(backupDomain, "");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return resourceUrl;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 第一次添加人脸活体检测结果
|
|
|
+ *
|
|
|
+ * @param rootOrgId
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @param faceBiopsyType
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public FaceBiopsyInfo addFirstFaceBiopsy(Long rootOrgId, Long examRecordDataId, FaceBiopsyType faceBiopsyType) {
|
|
|
+ //保存人脸活体检测相关信息
|
|
|
+ Long faceBiopsyId = addFaceBiopsyEntity(rootOrgId, examRecordDataId);
|
|
|
+ Long faceBiopsyItemId = addFaceBiopsyItemEntity(examRecordDataId, faceBiopsyType, faceBiopsyId, true);
|
|
|
+ List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList =
|
|
|
+ addFaceBiopsyItemStepList(examRecordDataId, faceBiopsyItemId);
|
|
|
+
|
|
|
+ //构建业务实体
|
|
|
+ return buildFaceBiopsyInfo(examRecordDataId, faceBiopsyItemId, faceBiopsyItemStepEntityList);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 追加人脸活体检测结果
|
|
|
+ *
|
|
|
+ * @param faceBiopsyId
|
|
|
+ * @param verifiedTimes
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @param faceBiopsyType
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ @Transactional
|
|
|
+ public FaceBiopsyInfo appendFaceBiopsy(Long faceBiopsyId, Integer verifiedTimes, Long examRecordDataId,
|
|
|
+ FaceBiopsyType faceBiopsyType, boolean isInFreezeTime) {
|
|
|
+ verifiedTimes++;
|
|
|
+ //更新人脸活体检测次数
|
|
|
+ FaceBiopsyEntity faceBiopsyEntity = GlobalHelper.getEntity(faceBiopsyRepo, faceBiopsyId, FaceBiopsyEntity.class);
|
|
|
+ faceBiopsyEntity.setVerifiedTimes(verifiedTimes);
|
|
|
+ faceBiopsyRepo.save(faceBiopsyEntity);
|
|
|
+ //添加人脸活体检测明细
|
|
|
+ Long faceBiopsyItemId = addFaceBiopsyItemEntity(examRecordDataId, faceBiopsyType, faceBiopsyId, isInFreezeTime);
|
|
|
+ //添加人脸活体检测步骤
|
|
|
+ List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList =
|
|
|
+ addFaceBiopsyItemStepList(examRecordDataId, faceBiopsyItemId);
|
|
|
+
|
|
|
+ return buildFaceBiopsyInfo(examRecordDataId, faceBiopsyItemId, faceBiopsyItemStepEntityList);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建人脸检测基本信息的业务实体对象
|
|
|
+ *
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @param faceBiopsyItemId
|
|
|
+ * @param faceBiopsyItemStepEntityList
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private FaceBiopsyInfo buildFaceBiopsyInfo(Long examRecordDataId, Long faceBiopsyItemId,
|
|
|
+ List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList) {
|
|
|
+ //构建业务实体
|
|
|
+ FaceBiopsyInfo faceBiopsyInfo = new FaceBiopsyInfo();
|
|
|
+ faceBiopsyInfo.setFaceBiopsyItemId(faceBiopsyItemId);
|
|
|
+ faceBiopsyInfo.setFaceVerifyMinute(calculateFaceBiopsyStartMinute(examRecordDataId));
|
|
|
+ faceBiopsyInfo.setVerifySteps(copyFaceBiopsyStepDomainListFrom(faceBiopsyItemStepEntityList));
|
|
|
+ return faceBiopsyInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<FaceBiopsyStepInfo> copyFaceBiopsyStepDomainListFrom
|
|
|
+ (List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList) {
|
|
|
+ List<FaceBiopsyStepInfo> resultList = new ArrayList<>();
|
|
|
+ for (FaceBiopsyItemStepEntity entity : faceBiopsyItemStepEntityList) {
|
|
|
+ FaceBiopsyStepInfo domain = new FaceBiopsyStepInfo();
|
|
|
+ domain.setAction(entity.getAction());
|
|
|
+ domain.setStay(entity.getActionStay());
|
|
|
+ domain.setStepId(entity.getId());
|
|
|
+ resultList.add(domain);
|
|
|
+ }
|
|
|
+ return resultList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加活体检测的具体步骤
|
|
|
+ *
|
|
|
+ * @param examRecordDataId 考试记录id
|
|
|
+ * @param faceBiopsyItemId 人脸活体检测明细id
|
|
|
+ */
|
|
|
+ private List<FaceBiopsyItemStepEntity> addFaceBiopsyItemStepList(Long examRecordDataId, Long faceBiopsyItemId) {
|
|
|
+ List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList = new ArrayList<FaceBiopsyItemStepEntity>();
|
|
|
+
|
|
|
+ //step1.人脸比对
|
|
|
+ FaceBiopsyItemStepEntity firstStep = new FaceBiopsyItemStepEntity();
|
|
|
+ firstStep.setExamRecordDataId(examRecordDataId);
|
|
|
+ firstStep.setFaceBiopsyItemId(faceBiopsyItemId);
|
|
|
+ firstStep.setAction(FaceBiopsyAction.FACE_COMPARE);
|
|
|
+ faceBiopsyItemStepEntityList.add(firstStep);
|
|
|
+
|
|
|
+ //生成乱序表情集合
|
|
|
+ List<FaceBiopsyAction> shuffledExpressionList = generateShuffleExpressions();
|
|
|
+
|
|
|
+ //step2随机表情
|
|
|
+ faceBiopsyItemStepEntityList.add(
|
|
|
+ randomGenerateExpressionStep(examRecordDataId, faceBiopsyItemId, shuffledExpressionList));
|
|
|
+
|
|
|
+ //step3随机表情
|
|
|
+ faceBiopsyItemStepEntityList.add(
|
|
|
+ randomGenerateExpressionStep(examRecordDataId, faceBiopsyItemId, shuffledExpressionList));
|
|
|
+
|
|
|
+ faceBiopsyItemStepRepo.saveAll(faceBiopsyItemStepEntityList);
|
|
|
+ return faceBiopsyItemStepEntityList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 乱序生成人脸表情
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<FaceBiopsyAction> generateShuffleExpressions() {
|
|
|
+ List<FaceBiopsyAction> expressionList = Lists.newArrayList();
|
|
|
+ expressionList.add(FaceBiopsyAction.HAPPY);
|
|
|
+ expressionList.add(FaceBiopsyAction.SERIOUS);
|
|
|
+ Collections.shuffle(expressionList);
|
|
|
+ return expressionList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 随机生成表情相关步骤
|
|
|
+ *
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @param faceBiopsyItemId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private FaceBiopsyItemStepEntity randomGenerateExpressionStep(Long examRecordDataId,
|
|
|
+ Long faceBiopsyItemId,
|
|
|
+ List<FaceBiopsyAction> shuffledExpressionList) {
|
|
|
+ FaceBiopsyItemStepEntity secondStep = new FaceBiopsyItemStepEntity();
|
|
|
+ secondStep.setExamRecordDataId(examRecordDataId);
|
|
|
+ secondStep.setFaceBiopsyItemId(faceBiopsyItemId);
|
|
|
+ secondStep.setAction(takeShuffledExpression(shuffledExpressionList));//获取一条随机生成表情指令
|
|
|
+ secondStep.setActionStay(randomGenerateActionStay());//随机生成指令时长(3至6秒)
|
|
|
+ return secondStep;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取一条随机生成的指令
|
|
|
+ *
|
|
|
+ * @param shuffledExpressionList
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private FaceBiopsyAction takeShuffledExpression(List<FaceBiopsyAction> shuffledExpressionList) {
|
|
|
+ //取完第一条并删除
|
|
|
+ FaceBiopsyAction expression = shuffledExpressionList.get(0);
|
|
|
+ shuffledExpressionList.remove(0);
|
|
|
+ return expression;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 随机生成指令持续的时长
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Integer randomGenerateActionStay() {
|
|
|
+ int minStay = PropertyHolder.getInt("oe.faceBiopsy.minActionStay", 3);
|
|
|
+ int maxStay = PropertyHolder.getInt("oe.faceBiopsy.maxActionStay", 6);
|
|
|
+ return RandomUtils.nextInt(minStay, maxStay);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加人脸活体检测明细
|
|
|
+ *
|
|
|
+ * @param examRecordDataId 考试记录id
|
|
|
+ * @param faceBiopsyType 人脸活体检测类型
|
|
|
+ * @param faceBiopsyId 人脸活体检测id
|
|
|
+ * @return Long 人脸活体检测明细id
|
|
|
+ */
|
|
|
+ private Long addFaceBiopsyItemEntity(Long examRecordDataId, FaceBiopsyType faceBiopsyType,
|
|
|
+ Long faceBiopsyId, boolean isInFreezeTime) {
|
|
|
+ FaceBiopsyItemEntity faceBiopsyItemEntity = new FaceBiopsyItemEntity();
|
|
|
+ faceBiopsyItemEntity.setExamRecordDataId(examRecordDataId);
|
|
|
+ faceBiopsyItemEntity.setFaceBiopsyId(faceBiopsyId);
|
|
|
+ faceBiopsyItemEntity.setFaceBiopsyType(faceBiopsyType);
|
|
|
+ faceBiopsyItemEntity.setCompleted(false);
|
|
|
+ faceBiopsyItemEntity.setInFreezeTime(isInFreezeTime);
|
|
|
+ faceBiopsyItemRepo.save(faceBiopsyItemEntity);
|
|
|
+ return faceBiopsyItemEntity.getId();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加人脸活体检测结果
|
|
|
+ *
|
|
|
+ * @param rootOrgId 学校id
|
|
|
+ * @param examRecordDataId 考试记录id
|
|
|
+ * @return Long 人脸活体检测结果id
|
|
|
+ */
|
|
|
+ private Long addFaceBiopsyEntity(Long rootOrgId, Long examRecordDataId) {
|
|
|
+ FaceBiopsyEntity faceBiopsyEntity = new FaceBiopsyEntity();
|
|
|
+ faceBiopsyEntity.setRootOrgId(rootOrgId);
|
|
|
+ faceBiopsyEntity.setExamRecordDataId(examRecordDataId);
|
|
|
+ faceBiopsyEntity.setVerifiedTimes(1);
|
|
|
+ faceBiopsyRepo.save(faceBiopsyEntity);
|
|
|
+ return faceBiopsyEntity.getId();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取冻结时间
|
|
|
+ *
|
|
|
+ * @param studentId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Integer getFreezeTime(Long studentId) {
|
|
|
+ ExamingSession examSessionInfo = examingSessionService.getExamingSession(studentId);
|
|
|
+ if (examSessionInfo == null) {
|
|
|
+ throw new StatusException("201002", "考试会话已过期");
|
|
|
+ }
|
|
|
+
|
|
|
+ Integer freezeTime = examSessionInfo.getFreezeTime();
|
|
|
+ return freezeTime;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 冻结时间内生成人脸活体检测的开始时间
|
|
|
+ *
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Integer generateInFreezeTimeFaceBiopsyStartMinute(Long examRecordDataId) {
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
+ Long examId = examRecordData.getExamId();
|
|
|
+ Long studentId = examRecordData.getStudentId();
|
|
|
+
|
|
|
+ //如果一次接口都没调用过,则默认活检次数为1,否则从库中取出已活检次数
|
|
|
+ Integer verifyTimes = 1;
|
|
|
+ FaceBiopsyEntity faceBiopsyEntity = faceBiopsyRepo.findByExamRecordDataId(examRecordDataId);
|
|
|
+ if (faceBiopsyEntity != null) {
|
|
|
+ verifyTimes = faceBiopsyEntity.getVerifiedTimes();
|
|
|
+ }
|
|
|
+
|
|
|
+ //默认二次活检
|
|
|
+ int minSecondFaceCheckMinute = PropertyHolder.getInt("oe.faceBiopsy.minSecondFaceCheckMinute", 1);
|
|
|
+ int maxSecondFaceCheckMinute = PropertyHolder.getInt("oe.faceBiopsy.maxSecondFaceCheckMinute", 3);
|
|
|
+
|
|
|
+ //如果是第一次活体检测
|
|
|
+ if (verifyTimes == 1) {
|
|
|
+ String faceVerifyStartMinuteStr = ExamCacheTransferHelper.getCachedExamProperty(examId,
|
|
|
+ studentId, ExamProperties.FACE_VERIFY_START_MINUTE.name()).getValue();
|
|
|
+ Integer faceVerifyStartMinute = Integer.valueOf(faceVerifyStartMinuteStr);
|
|
|
+
|
|
|
+ String faceVerifyEndMinuteStr = ExamCacheTransferHelper.getCachedExamProperty(examId,
|
|
|
+ studentId, ExamProperties.FACE_VERIFY_END_MINUTE.name()).getValue();
|
|
|
+ Integer faceVerifyEndMinute = Integer.valueOf(faceVerifyEndMinuteStr);
|
|
|
+
|
|
|
+ Integer examUsedMinutes = getExamUsedMinutes(studentId);
|
|
|
+
|
|
|
+ // case1.如果考生已使用的考试时间(即心跳时间)还未达到系统设置的活体检测开始时间,
|
|
|
+ // 则实际活体检测时间=random(配置结束时间,配置开始时间)-考试已用时间
|
|
|
+ if (examUsedMinutes < faceVerifyStartMinute) {
|
|
|
+ return CommonUtil.calculationRandomNumber(faceVerifyStartMinute, faceVerifyEndMinute) - examUsedMinutes;
|
|
|
+ }
|
|
|
+ // case2如果配置开始时间<考生已使用的考试时间<配置结束时间,
|
|
|
+ // 则实际活体检测时间=random(配置结束时间,考试已用时间)-考试已用时间,如果结果小于1分钟则默认1分钟
|
|
|
+ else if (examUsedMinutes >= faceVerifyStartMinute && examUsedMinutes < faceVerifyEndMinute) {
|
|
|
+ int startMinute = CommonUtil.calculationRandomNumber(examUsedMinutes, faceVerifyEndMinute) - examUsedMinutes;
|
|
|
+ return startMinute < 1 ? 1 : startMinute;
|
|
|
+ }
|
|
|
+ //case3如果考试已用时间>配置结束时间,则默认random(1,4)分钟后开始人脸检测
|
|
|
+ else {
|
|
|
+ return CommonUtil.calculationRandomNumber(minSecondFaceCheckMinute, maxSecondFaceCheckMinute);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ //如果是第二次人脸
|
|
|
+ else {
|
|
|
+ return CommonUtil.calculationRandomNumber(minSecondFaceCheckMinute, maxSecondFaceCheckMinute);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 冻结时间外生成人脸活体检测的开始时间
|
|
|
+ *
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Integer generateOutFreezeTimeFaceBiopsyStartMinute(Long examRecordDataId) {
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
+ Long examId = examRecordData.getExamId();
|
|
|
+ Long orgId = examRecordData.getOrgId();
|
|
|
+ Long studentId = examRecordData.getStudentId();
|
|
|
+
|
|
|
+ //如果冻结时间外不添加活体检测,则直接返回null
|
|
|
+ if (!isAddFaceVerifyOutFreezeTime(examId, orgId, studentId)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String strOutFreezeTimeFaceVerifyStartMinute = ExamCacheTransferHelper.getCachedExamProperty(examId,
|
|
|
+ studentId, ExamProperties.OUT_FREEZE_TIME_FACE_VERIFY_START_MINUTE.name()).getValue();
|
|
|
+ Integer faceVerifyStartMinute = Integer.valueOf(strOutFreezeTimeFaceVerifyStartMinute);
|
|
|
+
|
|
|
+ String strOutFreezeTimeFaceVerifyEndMinute = ExamCacheTransferHelper.getCachedExamProperty(examId,
|
|
|
+ studentId, ExamProperties.OUT_FREEZE_TIME_FACE_VERIFY_END_MINUTE.name()).getValue();
|
|
|
+ Integer faceVerifyEndMinute = Integer.valueOf(strOutFreezeTimeFaceVerifyEndMinute);
|
|
|
+
|
|
|
+ //活检开始时间 = (冻结时间 + random(配置开始时间,配置结束时间)) - 考试已用时间
|
|
|
+ int result = getFreezeTime(studentId) +
|
|
|
+ CommonUtil.calculationRandomNumber(faceVerifyStartMinute, faceVerifyEndMinute) - getExamUsedMinutes(studentId);
|
|
|
+ return result < 1 ? 1 : result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否允许冻结时间外添加人脸活体检测
|
|
|
+ *
|
|
|
+ * @param examId
|
|
|
+ * @param orgId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean isAddFaceVerifyOutFreezeTime(Long examId, Long orgId, Long studentId) {
|
|
|
+ String addFaceVerifyOutFreezeTime = ExamCacheTransferHelper.getCachedExamProperty(examId,
|
|
|
+ studentId, ExamProperties.ADD_FACE_VERIFY_OUT_FREEZE_TIME.name()).getValue();
|
|
|
+ return Constants.isTrue.equals(addFaceVerifyOutFreezeTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否允许冻结时间外添加人脸活体检测
|
|
|
+ *
|
|
|
+ * @param examRecordDataId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean isAddFaceVerifyOutFreezeTime(Long examRecordDataId) {
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
+ return isAddFaceVerifyOutFreezeTime(examRecordData.getExamId(),
|
|
|
+ examRecordData.getOrgId(), examRecordData.getStudentId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取是否开启人脸活体检测
|
|
|
+ *
|
|
|
+ * @param examRecordDataId 考试记录id
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Boolean getIsFaceVerify(Long examRecordDataId) {
|
|
|
+ ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
|
|
|
+
|
|
|
+ //如果未开启人脸活体检测,则返回null
|
|
|
+ return FaceBiopsyHelper.isFaceVerify(examRecordData.getRootOrgId(),
|
|
|
+ examRecordData.getExamId(), examRecordData.getStudentId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取心跳时间
|
|
|
+ *
|
|
|
+ * @param studentId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Integer getExamUsedMinutes(Long studentId) {
|
|
|
+ ExamingSession examingSession = examingSessionService.getExamingSession(studentId);
|
|
|
+ if (examingSession == null) {
|
|
|
+ throw new StatusException("201002", "考试会话已过期");
|
|
|
+ }
|
|
|
+
|
|
|
+ return Math.toIntExact(examingSession.getCost() / 60);
|
|
|
+ }
|
|
|
+}
|