Переглянути джерело

同步活体检测controller相关方法

lideyin 5 роки тому
батько
коміт
45171587b2

+ 295 - 0
examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/controller/ExamFaceLivenessVerifyController.java

@@ -0,0 +1,295 @@
+package cn.com.qmth.examcloud.core.oe.student.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+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.Check;
+import cn.com.qmth.examcloud.core.oe.student.bean.ExamProcessResultInfo;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetFaceVerifyTokenInfo;
+import cn.com.qmth.examcloud.core.oe.student.dao.ExamFaceLivenessVerifyRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamFaceLivenessVerifyEntity;
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceVerifyResult;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamFaceLivenessVerifyService;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordDataService;
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import cn.com.qmth.examcloud.ws.api.WsCloudService;
+import cn.com.qmth.examcloud.ws.api.WsPath;
+import cn.com.qmth.examcloud.ws.api.request.SendMessageReq;
+import cn.com.qmth.examcloud.ws.api.request.SendTextReq;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * @Description 活体检测Controller
+ * @Author lideyin
+ * @Date 2019/12/16 17:38
+ * @Version 1.0
+ */
+@Api(tags = "活体检测")
+@RestController
+@RequestMapping("${app.api.oe.student}/examFaceLivenessVerify")
+public class ExamFaceLivenessVerifyController extends ControllerSupport {
+
+    @Autowired
+    private ExamFaceLivenessVerifyService examFaceLivenessVerifyService;
+
+    @Autowired
+    private WsCloudService wsCloudService;
+
+    @Autowired
+    private ExamFaceLivenessVerifyRepo examFaceLivenessVerifyRepo;
+
+    @Autowired
+    ExamRecordDataService examRecordDataService;
+
+
+    @ApiOperation(value = "检测学生底照是否能获取到faceId验证的token")
+    @GetMapping("/checkFaceLiveness")
+    public GetFaceVerifyTokenInfo checkFaceLiveness() {
+        User user = getAccessUser();
+        String bizNo = user.getUserId().toString();
+        return examFaceLivenessVerifyService.getFaceVerifyToken(user.getUserId(), bizNo);
+    }
+
+    /**
+     * 获得一个faceid用于网页端活体检测的token
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    @ApiOperation(value = "获得一个faceid用于网页端活体检测的token")
+    @GetMapping("/getFaceLivenessVerifyToken/{examRecordDataId}")
+    @Deprecated
+    public GetFaceVerifyTokenInfo getFaceLivenessVerifyToken(@PathVariable Long examRecordDataId) {
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+        User user = getAccessUser();
+        ExamFaceLivenessVerifyEntity faceVerify = examFaceLivenessVerifyService.saveFaceVerifyByExamRecordDataId(examRecordDataId);
+        GetFaceVerifyTokenInfo getFaceVerifyTokenInfo = examFaceLivenessVerifyService.getFaceVerifyToken(user.getUserId(), faceVerify.getId().toString());
+        if (!getFaceVerifyTokenInfo.getSuccess()) {
+            faceVerify.setIsError(true);
+            faceVerify.setErrorMsg(getFaceVerifyTokenInfo.getErrorMsg());
+            examFaceLivenessVerifyRepo.save(faceVerify);
+        }
+        return getFaceVerifyTokenInfo;
+    }
+
+    /**
+     * 获得一个faceid用于网页端活体检测的token
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    @ApiOperation(value = "获得一个faceid用于网页端活体检测的token")
+    @GetMapping("/startFaceVerify/{examRecordDataId}")
+    public GetFaceVerifyTokenInfo startFaceVerify(@PathVariable Long examRecordDataId) {
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+
+        //活检如果已经进行了两次不允许进行第三次
+        List<ExamFaceLivenessVerifyEntity> faceVerifyList =
+                examFaceLivenessVerifyService.listFaceVerifyByExamRecordId(examRecordDataId);
+        if (faceVerifyList.size() >= 2) {
+            throw new StatusException("100002", "活检次数不能超过2次");
+        }
+
+        ExamFaceLivenessVerifyEntity faceVerify = examFaceLivenessVerifyService.saveFaceVerifyByExamRecordDataId(examRecordDataId);
+
+        String bizNo = faceVerify.getId() + "_" + System.currentTimeMillis();
+        GetFaceVerifyTokenInfo getFaceVerifyTokenInfo = examFaceLivenessVerifyService.
+                getFaceVerifyToken(getAccessUser().getUserId(), bizNo);
+        if (!getFaceVerifyTokenInfo.getSuccess()) {
+            faceVerify.setIsError(true);
+            faceVerify.setErrorMsg(getFaceVerifyTokenInfo.getErrorMsg());
+            examFaceLivenessVerifyRepo.save(faceVerify);
+        }
+        getFaceVerifyTokenInfo.setFaceVerifyId(faceVerify.getId());
+        return getFaceVerifyTokenInfo;
+    }
+
+    /**
+     * 获得一个faceid用于网页端活体检测的token
+     *
+     * @param faceVerifyId
+     * @return
+     */
+    @ApiOperation(value = "获得一个faceid用于网页端活体检测的token")
+    @GetMapping("/getFaceVerifyToken/{faceVerifyId}")
+    public GetFaceVerifyTokenInfo getFaceVerifyToken(@PathVariable Long faceVerifyId) {
+        Check.isNull(faceVerifyId, "faceVerifyId不能为空");
+        User user = getAccessUser();
+
+        String bizNo = faceVerifyId + "_" + System.currentTimeMillis();
+//        ExamFaceLivenessVerifyEntity faceVerify = examFaceLivenessVerifyService.saveFaceVerifyByExamRecordDataId(examRecordDataId);
+        GetFaceVerifyTokenInfo getFaceVerifyTokenInfo = examFaceLivenessVerifyService.
+                getFaceVerifyToken(user.getUserId(), bizNo);
+        getFaceVerifyTokenInfo.setFaceVerifyId(faceVerifyId);
+        return getFaceVerifyTokenInfo;
+    }
+
+    /**
+     * 获取活体检测的回调结果
+     *
+     * @param faceVerifyId
+     * @return
+     */
+    @ApiOperation(value = "获取活体检测的回调结果")
+    @GetMapping("/getFaceVerifyResult/{faceVerifyId}")
+    public Map<String, Object> getFaceVerifyResult(@PathVariable Long faceVerifyId) {
+        Check.isNull(faceVerifyId, "faceVerifyId不能为空");
+
+        ExamFaceLivenessVerifyEntity faceVerify = examFaceLivenessVerifyService.findFaceVerifyById(faceVerifyId);
+        if (faceVerify == null) {
+            throw new StatusException("100001", "活检结果不存在");
+        }
+
+        Map<String, Object> jsonObject = new HashMap<>();
+        List<ExamFaceLivenessVerifyEntity> faceVerifies = examFaceLivenessVerifyService.listFaceVerifyByExamRecordId(faceVerify.getExamRecordDataId());
+        //验证次数
+        jsonObject.put("verifyCount", faceVerifies.size());
+
+        if (faceVerify.getVerifyResult() == null) {
+            //取最后一次验证结果
+            jsonObject.put("verifyResult", FaceVerifyResult.UNKNOWN.name());
+        } else {
+            //取最后一次验证结果
+            jsonObject.put("verifyResult", faceVerify.getVerifyResult().name());
+        }
+        jsonObject.put("examRecordDataId", faceVerify.getExamRecordDataId());
+
+        return jsonObject;
+    }
+
+
+    @ApiOperation(value = "更新活体检测结果")
+    @GetMapping("/updateFaceLivenessVerify/{examRecordDataId}")
+    public void updateFaceVerify(@PathVariable Long examRecordDataId, @RequestParam String errorMsg) {
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+        List<ExamFaceLivenessVerifyEntity> examFaceLivenessVerifyEntities = examFaceLivenessVerifyService.listFaceVerifyByExamRecordId(examRecordDataId);
+        if (examFaceLivenessVerifyEntities != null && examFaceLivenessVerifyEntities.size() > 0) {
+            ExamFaceLivenessVerifyEntity examFaceLivenessVerifyEntity = examFaceLivenessVerifyEntities.get(0);
+            examFaceLivenessVerifyEntity.setIsError(true);
+            examFaceLivenessVerifyEntity.setErrorMsg(errorMsg);
+            examFaceLivenessVerifyRepo.save(examFaceLivenessVerifyEntity);
+        }
+    }
+
+
+    /**
+     * 人脸验证完成后的回调,由faceId调用
+     *
+     * @param data
+     * @throws Exception
+     */
+    @Naked
+    @ApiOperation(value = "人脸验证完成后的回调,由faceId调用")
+    @PostMapping("/faceLivenessVerifyCallback")
+    public void faceLivenessVerifyCallback(@RequestParam String data) throws Exception {
+        log.info("faceId回调,data=" + data);
+
+        JSONObject returnJsonObject = new JSONObject(data);
+        String bizNo = returnJsonObject.get("biz_no").toString();
+        Long faceVerifyId;
+        if (bizNo.indexOf("_") == -1) {
+            faceVerifyId = Long.parseLong(bizNo + "");
+        } else {
+            faceVerifyId = Long.parseLong(bizNo.substring(0, bizNo.indexOf("_")));
+        }
+
+        ExamFaceLivenessVerifyEntity currentFaceVerify = examFaceLivenessVerifyService.findFaceVerifyById(faceVerifyId);
+        /*
+         * 如果该检测记录结果已经非空了,直接返回,
+         * 有可能超时程序已经将结果填成TIME_OUT了
+         */
+        if (currentFaceVerify.getVerifyResult() != null) {
+            return;
+        }
+
+        ExamFaceLivenessVerifyEntity faceVerify = examFaceLivenessVerifyService.faceIdNotify(data);
+        List<ExamFaceLivenessVerifyEntity> faceVerifies = examFaceLivenessVerifyService.listFaceVerifyByExamRecordId(faceVerify.getExamRecordDataId());
+        JSONObject jsonObject = new JSONObject();
+        //验证次数
+        jsonObject.put("verifyCount", faceVerifies.size());
+        //取最后一次验证结果
+        jsonObject.put("verifyResult", faceVerifies.get(faceVerifies.size() - 1).getVerifyResult().name());
+        jsonObject.put("examRecordDataId", faceVerify.getExamRecordDataId());
+
+        SendMessageReq sendMessageReq = new SendMessageReq();
+        sendMessageReq.setExamRecordDataId(faceVerify.getExamRecordDataId());
+        sendMessageReq.setReturnMsgJson(jsonObject.toString());
+
+        SendTextReq sendTextReq = new SendTextReq();
+        sendTextReq.setUserType(UserType.STUDENT);
+
+        ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(faceVerify.getExamRecordDataId());
+        sendTextReq.setUserId(examRecordData.getStudentId());
+        sendTextReq.setRootOrgId(examRecordData.getRootOrgId());
+        sendTextReq.setPath(WsPath.FACE_BIOPSY);
+        sendTextReq.setContent(JsonUtil.toJson(sendMessageReq));
+        wsCloudService.sendText(sendTextReq);
+    }
+
+    /**
+     * 人脸检测超时处理
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    @ApiOperation(value = "人脸检测超时处理")
+    @GetMapping("/faceLivenessVerifyTimeOut/{examRecordDataId}")
+    public String faceTestTimeOut(@PathVariable Long examRecordDataId) {
+        JSONObject jsonObject = new JSONObject();
+        examFaceLivenessVerifyService.faceTestTimeOut(examRecordDataId);
+        List<ExamFaceLivenessVerifyEntity> faceVerifies = examFaceLivenessVerifyService.listFaceVerifyByExamRecordId(examRecordDataId);
+        //验证次数
+        try {
+            jsonObject.put("verifyCount", faceVerifies.size());
+            //取最后一次验证结果
+            jsonObject.put("verifyResult", faceVerifies.get(faceVerifies.size() - 1).getVerifyResult().name());
+            jsonObject.put("examRecordDataId", examRecordDataId);
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+
+        return jsonObject.toString();
+    }
+
+    /**
+     * 人脸活体检测结束处理
+     *
+     * @param examRecordDataId 考试记录id
+     * @param result           活体检测结果
+     * @throws Exception
+     */
+    @ApiOperation(value = "人脸检测结束处理")
+    @GetMapping(value = "faceLivenessVerifyEnd/{examRecordDataId}")
+    public ExamProcessResultInfo faceTestEndHandle(@PathVariable Long examRecordDataId, @RequestParam String result) throws Exception {
+        ExamProcessResultInfo res = new ExamProcessResultInfo();
+        try {
+            User user = getAccessUser();
+            examFaceLivenessVerifyService.faceTestEndHandle(examRecordDataId, user.getUserId(), result);
+            res.setCode(Constants.COMMON_SUCCESS_CODE);
+            return res;
+        } catch (StatusException e) {
+            if (e.getCode().equals(Constants.EXAM_RECORD_NOT_END_STATUS_CODE)) {
+                res.setCode(Constants.PROCESSING_EXAM_RECORD_CODE);
+                return res;
+            }
+            throw e;
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+
+}

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

@@ -0,0 +1,232 @@
+package cn.com.qmth.examcloud.core.oe.student.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.student.bean.*;
+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.entity.FaceBiopsyItemEntity;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.FaceBiopsyItemStepEntity;
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceBiopsyType;
+import cn.com.qmth.examcloud.core.oe.student.service.*;
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.support.enums.FaceBiopsyScheme;
+import cn.com.qmth.examcloud.support.enums.HandInExamType;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import cn.com.qmth.examcloud.support.examing.ExamingSession;
+import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @Description 人脸活体检测接口
+ * @Author lideyin
+ * @Date 2019/10/14 10:59
+ * @Version 1.0
+ */
+@Api(tags = "新人脸活体检测接口")
+@RestController
+@RequestMapping("${app.api.oe.student}/faceBiopsy")
+public class FaceBiopsyController extends ControllerSupport {
+    @Autowired
+    private ExamingSessionService examingSessionService;
+    @Autowired
+    private FaceBiopsyService faceBiopsyService;
+    @Autowired
+    private FaceBiopsyItemRepo faceBiopsyItemRepo;
+    @Autowired
+    private FaceBiopsyItemStepRepo faceBiopsyItemStepRepo;
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+
+    @Autowired
+    private ExamFaceLivenessVerifyService examFaceLivenessVerifyService;
+
+    @Autowired
+    private ExamControlService examControlService;
+
+    @ApiOperation(value = "获取活体检测基本信息")
+    @GetMapping("/getFaceBiopsyBaseInfo")
+    public FaceBiopsyBaseInfo getFaceBiopsyBaseInfo(@RequestParam Long examRecordDataId) {
+        User user = getAccessUser();
+        Long studentId = user.getUserId();
+        String sequenceLockKey = Constants.GET_FACE_BIOPSY_INFO_PREFIX + studentId;
+        //系统在请求结束后会,自动释放锁,无需手动解锁
+        SequenceLockHelper.getLock(sequenceLockKey);
+
+        //判断考试记录id是否有效
+        ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+        if (examRecordData == null) {
+            throw new StatusException("200101", "无效的考试记录");
+        }
+
+        if (ExamRecordStatus.EXAM_ING != examRecordData.getExamRecordStatus()) {
+            throw new StatusException("200103", "考试已结束");
+        }
+
+        // 获取考试会话,判断考生是否已结束考试
+        ExamingSession examSessionInfo = examingSessionService.getExamingSession(studentId);
+        if (examSessionInfo == null) {
+            throw new StatusException("200104", "考试会话已过期");
+        }
+
+        //考试未开启人脸活体检测,不允许获取活检信息
+        Long rootOrgId = user.getRootOrgId();
+        Long examId = examRecordData.getExamId();
+        Long orgId = examRecordData.getOrgId();
+
+        if (!FaceBiopsyHelper.isFaceVerify(rootOrgId, examId, studentId)) {
+            throw new StatusException("200105", "本场考试未开启人脸活体检测");
+        }
+
+        FaceBiopsyScheme faceBiopsyScheme = FaceBiopsyHelper.getFaceBiopsyScheme(user.getRootOrgId());
+
+        Integer faceVerifyMinute = null;
+        // 如果是新活体检测方案,则使用新的计算方案计算活检开始时间
+        if (faceBiopsyScheme == FaceBiopsyScheme.NEW) {
+            faceVerifyMinute = faceBiopsyService.calculateFaceBiopsyStartMinute(examRecordDataId);
+        }
+        // 非新活检,默认使用旧的活检计算方式
+        else {
+            faceVerifyMinute = examFaceLivenessVerifyService.getFaceLivenessVerifyMinute(user.getRootOrgId(),
+                    orgId, examId, studentId, examRecordData.getId(), examSessionInfo.getCost().intValue() / 60);
+        }
+
+        FaceBiopsyBaseInfo faceBiopsyBaseInfo = new FaceBiopsyBaseInfo();
+        faceBiopsyBaseInfo.setIdentificationOfLivingBodyScheme(faceBiopsyScheme.getCode());
+        faceBiopsyBaseInfo.setFaceVerifyMinute(faceVerifyMinute == null ? null : (faceVerifyMinute + 1));
+        return faceBiopsyBaseInfo;
+    }
+
+    @ApiOperation(value = "获取人脸活体检测详细步骤")
+    @GetMapping("/getFaceBiopsyInfo")
+    public FaceBiopsyInfo getFaceBiopsyInfo(@RequestParam Long examRecordDataId) {
+        User user = getAccessUser();
+        Long studentId = user.getUserId();
+        String sequenceLockKey = Constants.GET_FACE_BIOPSY_INFO_PREFIX + studentId;
+        //系统在请求结束后会,自动释放锁,无需手动解锁
+        SequenceLockHelper.getLock(sequenceLockKey);
+
+        //判断考试记录id是否有效
+        ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+        if (examRecordData == null) {
+            throw new StatusException("200101", "无效的考试记录");
+        }
+
+        if (ExamRecordStatus.EXAM_ING != examRecordData.getExamRecordStatus()) {
+            throw new StatusException("200102", "考试已结束");
+        }
+
+        // 获取考试会话,判断考生是否已结束考试
+        ExamingSession examSessionInfo = examingSessionService.getExamingSession(studentId);
+        if (examSessionInfo == null) {
+            throw new StatusException("200103", "考试会话已过期");
+        }
+
+        //考试未开启人脸活体检测,不允许获取活检信息
+        Long rootOrgId = user.getRootOrgId();
+        Long examId = examRecordData.getExamId();
+        Long orgId = examRecordData.getOrgId();
+        if (!FaceBiopsyHelper.isFaceVerify(rootOrgId, examId, studentId)) {
+            throw new StatusException("200104", "本场考试未开启人脸活体检测");
+        }
+
+        return faceBiopsyService.getFaceBiopsyInfo(user.getRootOrgId(), examRecordDataId, FaceBiopsyType.FACE_MOTION);
+    }
+
+
+    @ApiOperation(value = "保存活体检测结果")
+    @PostMapping("/saveFaceBiopsyResult")
+    public SaveFaceBiopsyResultResp saveFaceBiopsyResult(@RequestBody SaveFaceBiopsyResultReq req) {
+        User user = getAccessUser();
+        Long studentId = user.getUserId();
+        String sequenceLockKey = Constants.GET_FACE_BIOPSY_INFO_PREFIX + studentId;
+        //系统在请求结束后会,自动释放锁,无需手动解锁
+        SequenceLockHelper.getLock(sequenceLockKey);
+
+        if (req.getExamRecordDataId() == null) {
+            throw new StatusException("200104", "考试记录id不允许为空");
+        }
+        //判断考试记录id是否有效
+        ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(req.getExamRecordDataId());
+        if (examRecordData == null) {
+            throw new StatusException("200105", "无效的考试记录");
+        }
+
+        if (ExamRecordStatus.EXAM_ING != examRecordData.getExamRecordStatus()) {
+            throw new StatusException("200105", "考试已结束");
+        }
+
+        if (req.getFaceBiopsyItemId() == null) {
+            throw new StatusException("200106", "人脸活体检测明细id不允许为空");
+        }
+        FaceBiopsyItemEntity faceBiopsyItemEntity = GlobalHelper.getEntity(faceBiopsyItemRepo, req.getFaceBiopsyItemId(),
+                FaceBiopsyItemEntity.class);
+        if (faceBiopsyItemEntity == null) {
+            throw new StatusException("200111", "人脸活体检测明细id不存在");
+        }
+
+        if (faceBiopsyItemEntity.getCompleted() == true) {
+            throw new StatusException("200112", "不允许操作已结束的人脸活体检测数据");
+        }
+
+        if (req.getVerifySteps() == null || req.getVerifySteps().isEmpty()) {
+            throw new StatusException("200107", "活体检测步骤不允许为空");
+        }
+
+        if (req.getVerifySteps().stream().anyMatch(p -> p.getStepId() == null)) {
+            throw new StatusException("200108", "活体检测步骤id不允许为空");
+        }
+
+        if (req.getVerifySteps().stream().anyMatch(p -> p.getAction() == null)) {
+            throw new StatusException("200109", "活体检测执行动作不允许为空");
+        }
+
+        if (!verifyStepsAllMatch(req.getFaceBiopsyItemId(), req.getExamRecordDataId(), req.getVerifySteps())) {
+            throw new StatusException("200110", "活体检测步骤与原始定义不匹配");
+        }
+        SaveFaceBiopsyResultResp resp = faceBiopsyService.saveFaceBiopsyResult(req, studentId);
+
+        //如果活检满足交卷条件,则系统自动交卷,自动交卷逻辑不应该影响活检保存结果,所以不能放一个事务中
+        if (resp.getEndExam()) {
+            examControlService.handInExam(req.getExamRecordDataId(), HandInExamType.AUTO);
+        }
+        return resp;
+    }
+
+    /**
+     * 校验活检步骤和原始步骤是否匹配
+     *
+     * @param faceBiopsyItemId
+     * @param verifySteps
+     * @return
+     */
+    private boolean verifyStepsAllMatch(Long faceBiopsyItemId, Long examRecordDataId, List<FaceBiopsyStepInfo> verifySteps) {
+        List<FaceBiopsyItemStepEntity> originalVerifySteps = faceBiopsyItemStepRepo.findByFaceBiopsyItemId(faceBiopsyItemId);
+
+        if (originalVerifySteps == null || originalVerifySteps.isEmpty() ||
+                originalVerifySteps.size() != verifySteps.size()) {
+            return false;
+        }
+
+        for (int i = 0; i < originalVerifySteps.size(); i++) {
+            FaceBiopsyItemStepEntity originalStep = originalVerifySteps.get(i);
+            FaceBiopsyStepInfo newStep = verifySteps.get(i);
+            //如果步骤id和动作以及考试记录id不同时匹配,则认为不匹配
+            if (!(originalStep.getId().equals(newStep.getStepId()) &&
+                    originalStep.getAction().equals(newStep.getAction()) &&
+                    originalStep.getExamRecordDataId().equals(examRecordDataId))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}

+ 44 - 41
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/FaceVerifyResult.java

@@ -1,49 +1,52 @@
 package cn.com.qmth.examcloud.core.oe.student.dao.enums;
 
 /**
- * @author  	chenken
- * @date    	2018年2月5日 上午8:38:04
- * @company 	QMTH
+ * @author chenken
+ * @date 2018年2月5日 上午8:38:04
+ * @company QMTH
  * @description 人脸活体检测验证结果
  */
 public enum FaceVerifyResult {
-	
-	VERIFY_SUCCESS("验证成功"),
-	
-	VERIFY_FAILED("动作有误,验证失败"),
-	
-	NOT_ONESELF("不是本人"),
-	
-	TIME_OUT("超时未完成");
-	
-	public static String getDescByName(Object name){
-		if(name == null){
-			return "检测界面打开后未开启检测";
-		}
-		String nameString = name.toString();
-		for(FaceVerifyResult faceVerifyResult:FaceVerifyResult.values()){
-			if(nameString.equals(faceVerifyResult.name())){
-				return faceVerifyResult.getDesc();
-			}
-		}
-		return "";
-	}
-	
-	private String desc;
-	
-	private FaceVerifyResult(String desc){
-		this.desc = desc;
-	}
-
-	public String getDesc() {
-		return desc;
-	}
-
-	public void setDesc(String desc) {
-		this.desc = desc;
-	}
-	public static void main(String[] args) {
-		
-	}
+
+    VERIFY_SUCCESS("验证成功"),
+
+    VERIFY_FAILED("动作有误,验证失败"),
+
+    NOT_ONESELF("不是本人"),
+
+    TIME_OUT("超时未完成"),
+
+    UNKNOWN("未知");;
+
+    public static String getDescByName(Object name) {
+        if (name == null) {
+            return "检测界面打开后未开启检测";
+        }
+        String nameString = name.toString();
+        for (FaceVerifyResult faceVerifyResult : FaceVerifyResult.values()) {
+            if (nameString.equals(faceVerifyResult.name())) {
+                return faceVerifyResult.getDesc();
+            }
+        }
+        return "";
+    }
+
+    private String desc;
+
+    private FaceVerifyResult(String desc) {
+        this.desc = desc;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+    }
+
+    public static void main(String[] args) {
+
+    }
 }	
 

+ 36 - 0
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/FaceBiopsyBaseInfo.java

@@ -0,0 +1,36 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 人脸活体检测基础信息信息
+ * @Author lideyin
+ * @Date 2019/10/14 11:21
+ * @Version 1.0
+ */
+public class FaceBiopsyBaseInfo implements JsonSerializable {
+    private static final long serialVersionUID = 3639013689409712841L;
+
+    @ApiModelProperty(value = "活体检测方案", notes = "S1:旧方案;S2:新方案", required = true)
+    private String identificationOfLivingBodyScheme;
+
+    @ApiModelProperty(value = "活体检测开始分钟数", required = true)
+    private Integer faceVerifyMinute;
+
+    public String getIdentificationOfLivingBodyScheme() {
+        return identificationOfLivingBodyScheme;
+    }
+
+    public void setIdentificationOfLivingBodyScheme(String identificationOfLivingBodyScheme) {
+        this.identificationOfLivingBodyScheme = identificationOfLivingBodyScheme;
+    }
+
+    public Integer getFaceVerifyMinute() {
+        return faceVerifyMinute;
+    }
+
+    public void setFaceVerifyMinute(Integer faceVerifyMinute) {
+        this.faceVerifyMinute = faceVerifyMinute;
+    }
+}

+ 2 - 2
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamFaceLivenessVerifyService.java

@@ -75,11 +75,11 @@ public interface ExamFaceLivenessVerifyService {
      *
      * @param examId
      * @param examRecordDataId
-     * @param heartbeat
+     * @param usedMinute 考试已用的分钟数
      * @return
      */
     Integer getFaceLivenessVerifyMinute(Long rootOrgId, Long orgId, Long examId,
-                                        Long studentId, Long examRecordDataId, Integer heartbeat);
+                                        Long studentId, Long examRecordDataId, Integer usedMinute);
 
 }
 

+ 6 - 6
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/impl/ExamFaceLivenessVerifyServiceImpl.java

@@ -276,7 +276,7 @@ public class ExamFaceLivenessVerifyServiceImpl implements ExamFaceLivenessVerify
 
     @Override
     public Integer getFaceLivenessVerifyMinute(Long rootOrgId, Long orgId, Long examId, Long studentId,
-                                               Long examRecordDataId, Integer heartbeat) {
+                                               Long examRecordDataId, Integer usedMinute) {
         //开启了人脸检测
         if (FaceBiopsyHelper.isFaceVerify(rootOrgId, examId, studentId)) {
 
@@ -292,16 +292,16 @@ public class ExamFaceLivenessVerifyServiceImpl implements ExamFaceLivenessVerify
                         studentId, ExamProperties.FACE_VERIFY_END_MINUTE.name()).getValue();
                 Integer faceVerifyEndMinute = Integer.valueOf(faceVerifyEndMinuteStr);
                 //	case1.如果考生已使用的考试时间(即心跳时间)还未达到系统设置的活体检测开始时间,则实际活体检测时间=random(配置结束时间-配置结束时间)-考试已用时间
-                if (heartbeat < faceVerifyStartMinute) {
-                    return CommonUtil.calculationRandomNumber(faceVerifyStartMinute, faceVerifyEndMinute) - heartbeat;
+                if (usedMinute < faceVerifyStartMinute) {
+                    return CommonUtil.calculationRandomNumber(faceVerifyStartMinute, faceVerifyEndMinute) - usedMinute;
                 }
                 //	case2如果配置开始时间<考生已使用的考试时间<配置结束时间,则实际活体检测时间=random(配置结束时间-考试已用时间)-考试已用时间,如果结果小于1分钟则默认1分钟
-                else if (heartbeat >= faceVerifyStartMinute && heartbeat < faceVerifyEndMinute) {
-                    int verifyTime = CommonUtil.calculationRandomNumber(heartbeat, faceVerifyEndMinute) - heartbeat;
+                else if (usedMinute >= faceVerifyStartMinute && usedMinute < faceVerifyEndMinute) {
+                    int verifyTime = CommonUtil.calculationRandomNumber(usedMinute, faceVerifyEndMinute) - usedMinute;
                     return verifyTime < 1 ? 1 : verifyTime;
                 }
                 //case3如果考试已用时间>配置结束时间,则默认random(1,4)分钟后开始人脸检测
-                else if (heartbeat >= faceVerifyEndMinute) {
+                else if (usedMinute >= faceVerifyEndMinute) {
                     return CommonUtil.calculationRandomNumber(1, secondFaceCheckMinute);
                 }
             } else if (faceLivenessVerifys.size() == 1) {