소스 검색

merge from release_v4.1.3

deason 2 년 전
부모
커밋
4c4a76093b
42개의 변경된 파일1627개의 추가작업 그리고 1690개의 파일을 삭제
  1. 0 6
      examcloud-core-oe-task-api-provider/pom.xml
  2. 26 0
      examcloud-core-oe-task-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/task/controller/DevOpsController.java
  3. 0 234
      examcloud-core-oe-task-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/task/controller/ExamCaptureController.java
  4. 0 72
      examcloud-core-oe-task-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/task/controller/bean/GetExamCaptureResultDomain.java
  5. 1 55
      examcloud-core-oe-task-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/task/provider/ExamCaptureCloudServiceProvider.java
  6. 0 131
      examcloud-core-oe-task-base/pom.xml
  7. 0 35
      examcloud-core-oe-task-base/src/main/java/cn/com/qmth/examcloud/core/oe/task/base/UniqueRuleHolder.java
  8. 0 27
      examcloud-core-oe-task-dao/pom.xml
  9. 0 1
      examcloud-core-oe-task-dao/src/main/java/cn/com/qmth/examcloud/core/oe/task/dao/ignore
  10. 22 1
      examcloud-core-oe-task-service/pom.xml
  11. 356 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/CommonService.java
  12. 0 10
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/ExamCaptureQueueService.java
  13. 0 33
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/ExamCaptureService.java
  14. 11 4
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/ExamRecordDataService.java
  15. 0 2
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/ExamSyncCaptureService.java
  16. 341 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/FaceVerifyService.java
  17. 0 87
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/bean/SaveExamCaptureQueueInfo.java
  18. 0 49
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamCaptureQueueServiceImpl.java
  19. 10 403
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamCaptureServiceImpl.java
  20. 59 17
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamRecordDataServiceImpl.java
  21. 0 51
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamSyncCaptureServiceImpl.java
  22. 121 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/job/AfterHandInExamJobHandler.java
  23. 186 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/job/BeforeHandInExamJobHandler.java
  24. 297 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/job/SyncExamRecordDataJobHandler.java
  25. 1 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/AfterHandInExamExecutor.java
  26. 1 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/ClearExamDataCacheExecutor.java
  27. 1 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/DataGainExamExecutor.java
  28. 1 0
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/HandInExamExecutor.java
  29. 18 355
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/SyncExamDataExecutor.java
  30. 7 8
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/worker/BaiduFaceLivenessWorker.java
  31. 9 10
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/worker/FacePPCompareWorker.java
  32. 2 2
      examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/worker/FaceVerifyCounter.java
  33. 1 1
      examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/OETaskApp.java
  34. 0 12
      examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/Tester.java
  35. 56 0
      examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/JobConfig.java
  36. 66 0
      examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/OeTaskExecutor.java
  37. 15 15
      examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/ProcessBaiduFaceLivenessAlarmTask.java
  38. 17 17
      examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/ProcessFaceCompareAlarmTask.java
  39. 2 2
      examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/StreamTaskExecutor.java
  40. 0 48
      examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/SystemStartup.java
  41. 0 0
      examcloud-core-oe-task-starter/src/main/resources/security.properties
  42. 0 2
      pom.xml

+ 0 - 6
examcloud-core-oe-task-api-provider/pom.xml

@@ -17,12 +17,6 @@
             <artifactId>examcloud-core-oe-task-service</artifactId>
             <version>${project.version}</version>
         </dependency>
-
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-core-oe-task-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
     </dependencies>
 
 </project>

+ 26 - 0
examcloud-core-oe-task-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/task/controller/DevOpsController.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.examcloud.core.oe.task.controller;
+
+import cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@Api(tags = "运维接口")
+@RestController
+@RequestMapping("${app.api.oe.student.face}")
+public class DevOpsController {
+
+    private static final Logger log = LoggerFactory.getLogger(DevOpsController.class);
+
+    @Naked
+    @ApiOperation(value = "运维监控检测接口")
+    @GetMapping("/devops")
+    public Long devops() {
+        return System.currentTimeMillis();
+    }
+
+}

+ 0 - 234
examcloud-core-oe-task-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/task/controller/ExamCaptureController.java

@@ -1,234 +0,0 @@
-package cn.com.qmth.examcloud.core.oe.task.controller;
-
-import cn.com.qmth.examcloud.api.commons.security.bean.User;
-import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.commons.util.UrlUtil;
-import cn.com.qmth.examcloud.core.oe.student.base.bean.CompareFaceSyncInfo;
-import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamCaptureEntity;
-import cn.com.qmth.examcloud.core.oe.task.controller.bean.GetExamCaptureResultDomain;
-import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureQueueService;
-import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureService;
-import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
-import cn.com.qmth.examcloud.core.oe.task.service.ExamingSessionService;
-import cn.com.qmth.examcloud.core.oe.task.service.bean.SaveExamCaptureQueueInfo;
-import cn.com.qmth.examcloud.support.Constants;
-import cn.com.qmth.examcloud.support.cache.CacheHelper;
-import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
-import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
-import cn.com.qmth.examcloud.support.examing.ExamRecordData;
-import cn.com.qmth.examcloud.support.examing.ExamingSession;
-import cn.com.qmth.examcloud.support.examing.ExamingStatus;
-import cn.com.qmth.examcloud.web.filestorage.YunHttpRequest;
-import cn.com.qmth.examcloud.web.redis.RedisClient;
-import cn.com.qmth.examcloud.web.support.ControllerSupport;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import org.apache.commons.lang3.StringUtils;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * @author chenken
- * @date 2018年9月6日 上午10:14:23
- * @company QMTH
- * @description ExamCaptureController.java
- */
-@Api(tags = "考试抓拍")
-@RestController
-@RequestMapping("${app.api.oe.student.face}/examCaptureQueue")
-public class ExamCaptureController extends ControllerSupport {
-
-    private static final Logger log = LoggerFactory.getLogger(ExamCaptureController.class);
-
-    @Autowired
-    private ExamCaptureService examCaptureService;
-
-    @Autowired
-    private ExamingSessionService examingSessionService;
-
-    @Autowired
-    private ExamCaptureQueueService examCaptureQueueService;
-
-    @Autowired
-    private RedisClient redisClient;
-
-    @Autowired
-    private RedisTemplate<String, Object> redisTemplate;
-
-    @Autowired
-    private ExamRecordDataService examRecordDataService;
-
-    //	private static AES aes = new AES();
-
-    @ApiOperation(value = "同步比较人脸:用于进入考试")
-    @PostMapping("/compareFaceSync")
-    public CompareFaceSyncInfo compareFaceSync(@RequestParam String fileUrl, @RequestParam(required = false) String signIdentifier) {
-        Long startTime = System.currentTimeMillis();
-        User user = getAccessUser();
-        if (StringUtils.isBlank(fileUrl)) {
-            throw new StatusException("301001", "文件Url不能为空");
-        }
-        validateUpyunSign(signIdentifier, fileUrl, user.getUserId());
-
-        StudentCacheBean studentCache = CacheHelper.getStudent(user.getUserId());
-        String baseFaceToken = studentCache.getFaceToken();
-        if (StringUtils.isBlank(baseFaceToken)) {
-            throw new StatusException("301002", "学生底照的faceToken为空");
-        }
-        //fileUrl = aes.decrypt(fileUrl);
-        fileUrl = UrlUtil.decode(fileUrl);
-
-        if (this.isFaceApiLimitSeconds(user.getKey())) {
-            log.error("compareFaceSync outOfLimitSeconds...");
-            throw new StatusException("500503", "人脸验证太频繁,请稍后重试!");
-        }
-        if (this.isFaceApiLimitMinutes(user.getKey())) {
-            log.error("compareFaceSync outOfLimitMinutes...");
-            throw new StatusException("500503", "人脸验证太频繁,请10分钟后重试!");
-        }
-
-        CompareFaceSyncInfo compareFaceSyncInfo = examCaptureService.compareFaceSyncByFileUrl(user.getUserId(), baseFaceToken, fileUrl);
-        log.warn("compareFaceSyncResult isPass = {}, fileUrl = {}, errorMsg = {}", compareFaceSyncInfo.getIsPass(), fileUrl, compareFaceSyncInfo.getErrorMsg());
-
-        //将人脸同步比较的结果临时存储到redis中.开考后会清除
-        String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
-        compareFaceSyncInfo.setFileName(fileName);
-        compareFaceSyncInfo.setFileUrl(fileUrl);
-        compareFaceSyncInfo.setProcessTime(System.currentTimeMillis() - startTime);
-
-        redisClient.set(Constants.FACE_SYNC_COMPARE_RESULT_PREFIX + user.getUserId(), compareFaceSyncInfo, 5 * 60);
-        return compareFaceSyncInfo;
-    }
-
-    private boolean isFaceApiLimitSeconds(String userKey) {
-        String cacheKey = "$FACE_API_LIMIT:S_" + userKey;
-        Long count = redisTemplate.opsForValue().increment(cacheKey);
-
-        // 限制请求:1次/2秒
-        boolean limited = count > 1L;
-        if (!limited) {
-            redisTemplate.expire(cacheKey, 2, TimeUnit.SECONDS);
-        }
-
-        return limited;
-    }
-
-    private boolean isFaceApiLimitMinutes(String userKey) {
-        String cacheKey = "$FACE_API_LIMIT:M_" + userKey;
-        Long count = redisTemplate.opsForValue().increment(cacheKey);
-
-        // 限制请求:50次/10分钟
-        boolean limited = count > 50L;
-        if (!limited) {
-            redisTemplate.expire(cacheKey, 10, TimeUnit.MINUTES);
-        }
-
-        return limited;
-    }
-
-    /**
-     * 校验又拍云签名
-     *
-     * @param signIdentifier 签名标识
-     * @param fileUrl        文件路径
-     * @param userId         用户id
-     */
-    private void validateUpyunSign(String signIdentifier, String fileUrl, Long userId) {
-        //		if (StringUtils.isBlank(signIdentifier)){
-        //			throw new StatusException("300001", "签名标识不能为空");
-        //		}
-        if (!StringUtils.isBlank(signIdentifier)) {
-            String upyunSignRedisKey = Constants.EXAM_CAPTURE_PHOTO_UPYUN_SIGN_PREFIX + userId + "_" + signIdentifier;
-            YunHttpRequest upYunHttpRequest = redisClient.get(upyunSignRedisKey, YunHttpRequest.class);
-            if (upYunHttpRequest == null) {
-                throw new StatusException("301003", "无效的请求,请检查签名标识");
-            }
-            if (!upYunHttpRequest.getAccessUrl().equals(fileUrl)) {
-                throw new StatusException("301004", "文件路径格式不正确");
-            }
-        }
-
-    }
-
-    @ApiOperation(value = "获取抓拍结果")
-    @GetMapping("/getExamCaptureResult")
-    public GetExamCaptureResultDomain getExamCaptureResult(@RequestParam Long examRecordDataId, @RequestParam String fileName) {
-        if (null == examRecordDataId) {
-            throw new StatusException("301005", "examRecordDataId不能为空");
-        }
-        if (StringUtils.isBlank(fileName)) {
-            throw new StatusException("301006", "fileName不能为空");
-        }
-        ExamCaptureEntity examCaptureEntity = examCaptureService.getExamCaptureResult(examRecordDataId, fileName);
-        GetExamCaptureResultDomain domain = new GetExamCaptureResultDomain();
-        if (examCaptureEntity == null) {
-            domain.setIsCompleted(false);
-        } else {
-            domain.setIsCompleted(true);
-            domain.setIsPass(examCaptureEntity.getIsPass());
-            domain.setIsStranger(examCaptureEntity.getIsStranger());
-            domain.setExamRecordDataId(examRecordDataId);
-            domain.setFileName(fileName);
-        }
-        return domain;
-    }
-
-    @ApiOperation(value = "保存考试抓拍照片队列")
-    @PostMapping("/uploadExamCapture")
-    public String uploadExamCapture(@RequestBody SaveExamCaptureQueueInfo saveExamCaptureQueueInfo) {
-        User user = getAccessUser();
-
-        ExamingSession examingSession = examingSessionService.getExamingSession(user.getUserId());
-        //不存在考试会话,或者会话状态不正确,不允许上传图片直接返回
-        if (null == examingSession || examingSession.getExamingStatus() == ExamingStatus.INFORMAL) {
-            return null;
-        }
-
-        //参数校验
-        if (saveExamCaptureQueueInfo == null) {
-            throw new StatusException("301001", "对象不能为空");
-        }
-
-        Long examRecordDataId = saveExamCaptureQueueInfo.getExamRecordDataId();
-        if (null == examRecordDataId) {
-            throw new StatusException("301002", "examRecordDataId不能为空");
-        }
-
-        ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(examRecordDataId);
-        if (null == examRecordData) {
-            throw new StatusException("301004", "无效的考试记录id");
-        }
-        //非考试中不允许上传图片
-        if (ExamRecordStatus.EXAM_ING != examRecordData.getExamRecordStatus()) {
-            return null;
-        }
-
-        if (StringUtils.isBlank(saveExamCaptureQueueInfo.getFileUrl())) {
-            throw new StatusException("301005", "fileUrl不能为空");
-        }
-
-        //校验虚拟摄像头格式,必须 是Json数组,如果格式不正确,则置为null
-        if (StringUtils.isNoneBlank(saveExamCaptureQueueInfo.getCameraInfos())) {
-            try {
-                JSONArray jsonArray = new JSONArray(saveExamCaptureQueueInfo.getCameraInfos());
-                if (jsonArray.length() == 0) {
-                    saveExamCaptureQueueInfo.setCameraInfos(null);
-                }
-            } catch (JSONException e) {
-                saveExamCaptureQueueInfo.setCameraInfos(null);
-                LOGGER.error("ExamCaptureQueueController-uploadExamCapture-004:虚拟摄像头信息格式不正确", e);
-            }
-        }
-        validateUpyunSign(saveExamCaptureQueueInfo.getSignIdentifier(), saveExamCaptureQueueInfo.getFileUrl(), user.getUserId());
-
-        return examCaptureQueueService.saveExamCaptureQueue(saveExamCaptureQueueInfo, user.getUserId());
-    }
-
-}

+ 0 - 72
examcloud-core-oe-task-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/task/controller/bean/GetExamCaptureResultDomain.java

@@ -1,72 +0,0 @@
-package cn.com.qmth.examcloud.core.oe.task.controller.bean;
-
-import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
-
-public class GetExamCaptureResultDomain implements JsonSerializable {
-
-
-    /**
-     *
-     */
-    private static final long serialVersionUID = -7258224010169602773L;
-
-    /**
-     * 是否处理完成
-     */
-    private Boolean isCompleted;
-
-    private Long examRecordDataId;
-
-    /**
-     * 是否通过
-     */
-    private Boolean isPass;
-
-    /**
-     * 是否有陌生人
-     */
-    private Boolean isStranger;
-
-    private String fileName;
-
-    public Long getExamRecordDataId() {
-        return examRecordDataId;
-    }
-
-    public void setExamRecordDataId(Long examRecordDataId) {
-        this.examRecordDataId = examRecordDataId;
-    }
-
-    public Boolean getIsPass() {
-        return isPass;
-    }
-
-    public void setIsPass(Boolean isPass) {
-        this.isPass = isPass;
-    }
-
-    public Boolean getIsStranger() {
-        return isStranger;
-    }
-
-    public void setIsStranger(Boolean isStranger) {
-        this.isStranger = isStranger;
-    }
-
-    public String getFileName() {
-        return fileName;
-    }
-
-    public void setFileName(String fileName) {
-        this.fileName = fileName;
-    }
-
-    public Boolean getIsCompleted() {
-        return isCompleted;
-    }
-
-    public void setIsCompleted(Boolean isCompleted) {
-        this.isCompleted = isCompleted;
-    }
-
-}

+ 1 - 55
examcloud-core-oe-task-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/task/provider/ExamCaptureCloudServiceProvider.java

@@ -1,18 +1,7 @@
 package cn.com.qmth.examcloud.core.oe.task.provider;
 
-
 import cn.com.qmth.examcloud.core.oe.task.api.ExamCaptureCloudService;
-import cn.com.qmth.examcloud.core.oe.task.api.request.ExistUnhandledCaptureQueueReq;
-import cn.com.qmth.examcloud.core.oe.task.api.request.SaveExamCaptureSyncCompareResultReq;
-import cn.com.qmth.examcloud.core.oe.task.api.request.UpdateExamCaptureQueuePriorityReq;
-import cn.com.qmth.examcloud.core.oe.task.api.response.ExistUnhandledCaptureQueueResp;
-import cn.com.qmth.examcloud.core.oe.student.dao.ExamCaptureQueueRepo;
-import cn.com.qmth.examcloud.core.oe.task.service.ExamSyncCaptureService;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -24,49 +13,6 @@ import org.springframework.web.bind.annotation.RestController;
  */
 @RestController
 @RequestMapping("${$rmp.cloud.oe.student.face}/examCapture")
-public class ExamCaptureCloudServiceProvider extends ControllerSupport
-        implements ExamCaptureCloudService {
-
-    @Autowired
-    private ExamSyncCaptureService examSyncCaptureService;
-
-    @Autowired
-    private ExamCaptureQueueRepo examCaptureQueueRepo;
-
-    @Override
-    @ApiOperation(value = "保存抓拍照片的同步比较结果")
-    @PostMapping("/saveExamCaptureSyncCompareResult")
-    public void saveExamCaptureSyncCompareResult(@RequestBody SaveExamCaptureSyncCompareResultReq request) {
-        examSyncCaptureService.saveExamCaptureSyncCompareResult(request.getStudentId(),
-                request.getExamRecordDataId());
-    }
-
-    /**
-     * @param req 更新优先级请求参数
-     * @description 更新考试抓拍照片在队列优中的先级
-     * @date 2019/7/31 16:46
-     * @author lideyin
-     */
-    @Override
-    @ApiOperation(value = "更新考试抓拍照片在队列优中的先级")
-    @PostMapping("/updateExamCaptureQueuePriority")
-    public void updateExamCaptureQueuePriority(@RequestBody UpdateExamCaptureQueuePriorityReq req) {
-        examCaptureQueueRepo.updateExamCaptureQueuePriority(req.getPriority(), req.getExamRecordDataId());
-    }
-
-    /**
-     * 是否存在未处理的图片抓拍队列(处理完成的队列数据都会清理掉)
-     *
-     * @param req
-     * @return
-     */
-    @Override
-    @ApiOperation(value = "是否存在未处理的图片抓拍队列")
-    @PostMapping("/existsUnhandledCaptureQueue")
-    public ExistUnhandledCaptureQueueResp existsUnhandledCaptureQueue(@RequestBody ExistUnhandledCaptureQueueReq req) {
-        ExistUnhandledCaptureQueueResp resp = new ExistUnhandledCaptureQueueResp();
-        resp.setExist(examCaptureQueueRepo.existsUnhandledByExamRecordDataId(req.getExamRecordDataId()) != null);
-        return resp;
-    }
+public class ExamCaptureCloudServiceProvider extends ControllerSupport implements ExamCaptureCloudService {
 
 }

+ 0 - 131
examcloud-core-oe-task-base/pom.xml

@@ -1,131 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>examcloud-core-oe-task-base</artifactId>
-    <packaging>jar</packaging>
-
-    <parent>
-        <groupId>cn.com.qmth.examcloud</groupId>
-        <artifactId>examcloud-core-oe-task</artifactId>
-        <version>${revision}</version>
-    </parent>
-
-    <dependencies>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-mongodb</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>org.springframework.boot</groupId>
-                    <artifactId>spring-boot-starter-logging</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud</groupId>
-            <artifactId>examcloud-web</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud</groupId>
-            <artifactId>examcloud-support</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-exchange-inner-api-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-core-basic-api-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-core-examwork-api-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-core-questions-api-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-core-oe-admin-api-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-core-oe-student-api-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-ws-api-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-core-marking-api-client</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-global-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpmime</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.httpcomponents</groupId>
-            <artifactId>httpclient</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.json</groupId>
-            <artifactId>json</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>org.hibernate</groupId>
-            <artifactId>hibernate-validator</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>io.github.openfeign</groupId>
-            <artifactId>feign-okhttp</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>com.squareup.okhttp3</groupId>
-            <artifactId>okhttp</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>com.squareup.okhttp3</groupId>
-            <artifactId>logging-interceptor</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>com.upyun</groupId>
-            <artifactId>java-sdk</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.baidu.aip</groupId>
-            <artifactId>java-sdk</artifactId>
-        </dependency>
-
-        <dependency>
-            <groupId>com.esotericsoftware</groupId>
-            <artifactId>reflectasm</artifactId>
-        </dependency>
-    </dependencies>
-
-</project>

+ 0 - 35
examcloud-core-oe-task-base/src/main/java/cn/com/qmth/examcloud/core/oe/task/base/UniqueRuleHolder.java

@@ -1,35 +0,0 @@
-package cn.com.qmth.examcloud.core.oe.task.base;
-
-import cn.com.qmth.examcloud.web.jpa.UniqueRule;
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * @Description 唯一约束holder 状态码范围110XXX
- * @Author lideyin
- * @Date 2019/9/7 11:43
- * @Version 1.0
- */
-public class UniqueRuleHolder {
-
-    private static List<UniqueRule> LIST = Lists.newArrayList();
-
-    public static List<UniqueRule> getUniqueRuleList() {
-        return LIST;
-    }
-
-    static {
-        // ResourceEntity
-        LIST.add(new UniqueRule("IDX_E_O_E_A_001", "110001", "考试审核记录已存在"));
-        LIST.add(new UniqueRule("IDX_E_O_E_C_001", "110002", "抓拍照片处理结果已存在"));
-        LIST.add(new UniqueRule("IDX_E_O_E_C_Q_001", "110003", "拍拍照片队列已存在"));
-        LIST.add(new UniqueRule("IDX_E_O_E_F_A_T_001", "110004", "文件作答结果已存在"));
-        LIST.add(new UniqueRule("IDX_E_O_E_R_002", "110005", "考生已存在进行中的考试"));
-        LIST.add(new UniqueRule("IDX_E_O_E_R_D_001", "110006", "考试记录已存在"));
-        LIST.add(new UniqueRule("IDX_E_O_E_R_4_M_001", "110007", "待阅卷的考试记录已存在"));
-        LIST.add(new UniqueRule("IDX_E_O_E_S_001", "110008", "考试分数已存在"));
-        LIST.add(new UniqueRule("IDX_E_O_E_S_001", "110009", "考生已存在"));
-        LIST.add(new UniqueRule("IDX_E_O_E_O_S_H_001", "110010", "机构推分队列已存在"));
-    }
-}

+ 0 - 27
examcloud-core-oe-task-dao/pom.xml

@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <artifactId>examcloud-core-oe-task-dao</artifactId>
-    <packaging>jar</packaging>
-
-    <parent>
-        <groupId>cn.com.qmth.examcloud</groupId>
-        <artifactId>examcloud-core-oe-task</artifactId>
-        <version>${revision}</version>
-    </parent>
-
-    <dependencies>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud</groupId>
-            <artifactId>examcloud-core-oe-task-base</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud</groupId>
-            <artifactId>examcloud-core-oe-student-dao</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-    </dependencies>
-
-</project>

+ 0 - 1
examcloud-core-oe-task-dao/src/main/java/cn/com/qmth/examcloud/core/oe/task/dao/ignore

@@ -1 +0,0 @@
-ignore...

+ 22 - 1
examcloud-core-oe-task-service/pom.xml

@@ -14,8 +14,29 @@
     <dependencies>
         <dependency>
             <groupId>cn.com.qmth.examcloud</groupId>
-            <artifactId>examcloud-core-oe-task-dao</artifactId>
+            <artifactId>examcloud-core-oe-student-dao</artifactId>
             <version>${project.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>cn.com.qmth.examcloud.rpc</groupId>
+                    <artifactId>examcloud-core-oe-task-api-client</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-oe-student-api-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-oe-task-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.xuxueli</groupId>
+            <artifactId>xxl-job-core</artifactId>
+            <version>2.3.1</version>
         </dependency>
     </dependencies>
 

+ 356 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/CommonService.java

@@ -0,0 +1,356 @@
+package cn.com.qmth.examcloud.core.oe.task.service;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.*;
+import cn.com.qmth.examcloud.core.oe.student.api.ExamRecordDataCloudService;
+import cn.com.qmth.examcloud.core.oe.student.api.bean.StuExamQuestionBean;
+import cn.com.qmth.examcloud.core.oe.student.api.request.GetExamFaceLivenessVerifiesReq;
+import cn.com.qmth.examcloud.core.oe.student.api.request.GetExamRecordPaperStructReq;
+import cn.com.qmth.examcloud.core.oe.student.api.request.GetExamRecordQuestionsReq;
+import cn.com.qmth.examcloud.core.oe.student.api.request.GetFaceBiopsyReq;
+import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamFaceLivenessVerifiesResp;
+import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamRecordPaperStructResp;
+import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamRecordQuestionsResp;
+import cn.com.qmth.examcloud.core.oe.student.api.response.GetFaceBiopsyResp;
+import cn.com.qmth.examcloud.core.oe.student.dao.*;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.*;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class CommonService {
+
+    private static final Logger log = LoggerFactory.getLogger(CommonService.class);
+
+    @Autowired
+    private ExamCaptureRepo examCaptureRepo;
+
+    @Autowired
+    private ExamFaceLiveVerifyRepo examFaceLiveVerifyRepo;
+
+    @Autowired
+    private ExamSyncCaptureRepo examSyncCaptureRepo;
+
+    @Autowired
+    private ExamContinuedRecordRepo examContinuedRecordRepo;
+
+    @Autowired
+    private ExamProcessRecordRepo examProcessRecordRepo;
+
+    @Autowired
+    private ExamRecordDataCloudService examRecordDataCloudService;
+
+    public ExamRecordDataBean copyExamRecordDataFrom(ExamRecordData examRecordData) {
+        ExamRecordDataBean data = new ExamRecordDataBean();
+
+        data.setId(examRecordData.getId());
+        data.setRootOrgId(examRecordData.getRootOrgId());
+        data.setOrgId(examRecordData.getOrgId());
+        data.setCourseId(examRecordData.getCourseId());
+        data.setStudentId(examRecordData.getStudentId());
+        data.setExamStudentId(examRecordData.getExamStudentId());
+
+        data.setExamId(examRecordData.getExamId());
+        data.setExamType(examRecordData.getExamType() != null ? examRecordData.getExamType().toString() : null);
+        data.setExamStageId(examRecordData.getExamStageId());
+        data.setExamStageOrder(examRecordData.getExamStageOrder());
+        data.setBasePaperId(examRecordData.getBasePaperId());
+        data.setPaperType(examRecordData.getPaperType());
+
+        data.setStartTime(examRecordData.getStartTime());
+        data.setEndTime(examRecordData.getEndTime());
+        data.setEnterExamTime(examRecordData.getEnterExamTime());
+        data.setCleanTime(examRecordData.getCleanTime());
+        data.setLastActiveTime(examRecordData.getLastActiveTime());
+        data.setUsedExamTime(examRecordData.getUsedExamTime());
+
+        data.setContinuedCount(examRecordData.getContinuedCount());
+        data.setContinuedTime(examRecordData.getContinuedTime());
+        data.setContinued(examRecordData.getIsContinued() != null ? examRecordData.getIsContinued() : false);
+        data.setExceed(examRecordData.getIsExceed() != null ? examRecordData.getIsExceed() : false);
+
+        data.setExamRecordStatus(examRecordData.getExamRecordStatus() != null ? examRecordData.getExamRecordStatus().toString() : null);
+        data.setWarn(examRecordData.getIsWarn() != null ? examRecordData.getIsWarn() : false);
+        data.setAudit(examRecordData.getIsAudit() != null ? examRecordData.getIsAudit() : false);
+        data.setIllegality(examRecordData.getIsIllegality() != null ? examRecordData.getIsIllegality() : false);
+
+        data.setFaceVerifyResult(examRecordData.getFaceVerifyResult() != null ? examRecordData.getFaceVerifyResult().toString() : null);
+        data.setBaiduFaceLivenessSuccessPercent(examRecordData.getBaiduFaceLivenessSuccessPercent());
+        data.setFaceTotalCount(examRecordData.getFaceTotalCount());
+        data.setFaceSuccessCount(examRecordData.getFaceSuccessCount());
+        data.setFaceFailedCount(examRecordData.getFaceFailedCount());
+        data.setFaceStrangerCount(examRecordData.getFaceStrangerCount());
+        data.setFaceSuccessPercent(examRecordData.getFaceSuccessPercent());
+
+        data.setTotalScore(examRecordData.getTotalScore());
+        data.setObjectiveScore(examRecordData.getObjectiveScore());
+        data.setObjectiveAccuracy(examRecordData.getObjectiveAccuracy());
+        data.setSubjectiveScore(examRecordData.getSubjectiveScore());
+        data.setSuccPercent(examRecordData.getSuccPercent());
+        data.setAllObjectivePaper(examRecordData.getIsAllObjectivePaper());
+
+        data.setSwitchScreenCount(examRecordData.getSwitchScreenCount());
+        data.setExceedMaxSwitchScreenCount(examRecordData.getExceedMaxSwitchScreenCount());
+        return data;
+    }
+
+    public ExamRecordPaperStructBean getExamRecordPaperStruct(Long examRecordDataId) {
+        GetExamRecordPaperStructReq req = new GetExamRecordPaperStructReq();
+        req.setExamRecordDataId(examRecordDataId);
+        GetExamRecordPaperStructResp resp = examRecordDataCloudService.getExamRecordPaperStruct(req);
+
+        ExamRecordPaperStructBean bean = new ExamRecordPaperStructBean();
+        bean.setId(resp.getId());
+        bean.setDefaultPaper(resp.getDefaultPaper());
+        return bean;
+    }
+
+    public ExamRecordQuestionsBean getExamRecordQuestions(Long examRecordDataId) {
+        GetExamRecordQuestionsReq req = new GetExamRecordQuestionsReq();
+        req.setExamRecordDataId(examRecordDataId);
+        GetExamRecordQuestionsResp resp = examRecordDataCloudService.getExamRecordQuestions(req);
+
+        ExamRecordQuestionsBean bean = new ExamRecordQuestionsBean();
+        bean.setCreationTime(resp.getCreationTime());
+        bean.setExamRecordDataId(resp.getExamRecordDataId());
+
+        List<ExamQuestionBean> examQuestionBeanList = new ArrayList<>();
+        for (StuExamQuestionBean stuExamQuestionBean : resp.getExamQuestions()) {
+            ExamQuestionBean examQuestionBean = new ExamQuestionBean();
+            examQuestionBean.setExamRecordDataId(stuExamQuestionBean.getExamRecordDataId());
+            examQuestionBean.setMainNumber(stuExamQuestionBean.getMainNumber());
+            examQuestionBean.setQuestionId(stuExamQuestionBean.getQuestionId());
+            examQuestionBean.setOrder(stuExamQuestionBean.getOrder());
+            examQuestionBean.setQuestionScore(stuExamQuestionBean.getQuestionScore());
+            examQuestionBean.setQuestionType(stuExamQuestionBean.getQuestionType());
+            examQuestionBean.setCorrectAnswer(stuExamQuestionBean.getCorrectAnswer());
+            examQuestionBean.setStudentAnswer(stuExamQuestionBean.getStudentAnswer());
+            examQuestionBean.setStudentScore(stuExamQuestionBean.getStudentScore());
+            examQuestionBean.setAnswer(stuExamQuestionBean.getAnswer());
+            examQuestionBean.setSign(stuExamQuestionBean.getSign());
+            examQuestionBean.setOptionPermutation(stuExamQuestionBean.getOptionPermutation());
+            examQuestionBean.setAudioPlayTimes(stuExamQuestionBean.getAudioPlayTimes());
+
+            if (null != stuExamQuestionBean.getAnswerType()) {
+                examQuestionBean.setAnswerType(stuExamQuestionBean.getAnswerType().name());
+            }
+
+            examQuestionBeanList.add(examQuestionBean);
+        }
+
+        bean.setExamQuestionBeans(examQuestionBeanList);
+
+        return bean;
+    }
+
+    public List<ExamContinuedRecordBean> getExamContinuedRecords(Long examRecordDataId) {
+        List<ExamContinuedRecordEntity> entityList = examContinuedRecordRepo.findByExamRecordDataId(examRecordDataId);
+        if (null == entityList || entityList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        List<ExamContinuedRecordBean> resultList = new ArrayList<>();
+        for (ExamContinuedRecordEntity entity : entityList) {
+            ExamContinuedRecordBean bean = new ExamContinuedRecordBean();
+            bean.setId(entity.getId());
+            bean.setExamRecordDataId(entity.getExamRecordDataId());
+            bean.setContinuedTime(entity.getContinuedTime());
+            bean.setStartTime(entity.getStartTime());
+            bean.setUsedExamTime(entity.getUsedExamTime());
+
+            resultList.add(bean);
+        }
+
+        return resultList;
+    }
+
+    public List<ExamProcessRecordBean> getExamProcessRecords(Long examRecordDataId) {
+        List<ExamProcessRecordEntity> entityList = examProcessRecordRepo.findByExamRecordDataId(examRecordDataId);
+        if (null == entityList || entityList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        List<ExamProcessRecordBean> resultList = new ArrayList<>();
+        for (ExamProcessRecordEntity entity : entityList) {
+            ExamProcessRecordBean bean = new ExamProcessRecordBean();
+            bean.setId(entity.getId());
+            bean.setExamRecordDataId(entity.getExamRecordDataId());
+            bean.setProcessName(entity.getProcessName());
+            bean.setRecordTime(entity.getRecordTime());
+            bean.setSourceIp(entity.getSourceIp());
+
+            resultList.add(bean);
+        }
+
+        return resultList;
+    }
+
+    public ExamSyncCaptureBean getExamSyncCapture(Long examRecordDataId) {
+        ExamSyncCaptureEntity entity = examSyncCaptureRepo.findByExamRecordDataId(examRecordDataId);
+        if (null == entity) {
+            return null;
+        }
+
+        ExamSyncCaptureBean bean = new ExamSyncCaptureBean();
+        bean.setId(entity.getId());
+        bean.setExamRecordDataId(entity.getExamRecordDataId());
+        bean.setFileUrl(entity.getFileUrl());
+        bean.setFileName(entity.getFileName());
+        bean.setPass(entity.getIsPass());
+        bean.setFaceCompareResult(entity.getFaceCompareResult());
+        bean.setStranger(entity.getIsStranger());
+        bean.setLandmark(entity.getLandmark());
+        bean.setFacelivenessResult(entity.getFacelivenessResult());
+        bean.setUsedTime(entity.getUsedTime());
+        bean.setProcessTime(entity.getProcessTime());
+        bean.setHasVirtualCamera(entity.getHasVirtualCamera());
+        bean.setCameraInfos(entity.getCameraInfos());
+        bean.setExtMsg(entity.getExtMsg());
+
+        return bean;
+    }
+
+    public List<ExamCaptureBean> getExamCaptures(Long examRecordDataId) {
+        List<ExamCaptureEntity> entityList = examCaptureRepo.findByExamRecordDataId(examRecordDataId);
+        if (null == entityList || entityList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        List<ExamCaptureBean> resultList = new ArrayList<>();
+        for (ExamCaptureEntity entity : entityList) {
+            ExamCaptureBean bean = new ExamCaptureBean();
+
+            bean.setId(entity.getId());
+            bean.setExamRecordDataId(entity.getExamRecordDataId());
+            bean.setFileUrl(entity.getFileUrl());
+            bean.setFileName(entity.getFileName());
+            bean.setPass(entity.getIsPass());
+            bean.setFaceCompareResult(entity.getFaceCompareResult());
+            bean.setStranger(entity.getIsStranger());
+            bean.setLandmark(entity.getLandmark());
+            bean.setFacelivenessResult(entity.getFacelivenessResult());
+            bean.setUsedTime(entity.getUsedTime());
+            bean.setProcessTime(entity.getProcessTime());
+            bean.setHasVirtualCamera(entity.getHasVirtualCamera());
+            bean.setCameraInfos(entity.getCameraInfos());
+            bean.setExtMsg(entity.getExtMsg());
+
+            resultList.add(bean);
+        }
+
+        return resultList;
+    }
+
+    public List<ExamFaceLivenessVerifyBean> getExamFaceLivenessVerifies(Long examRecordDataId) {
+        GetExamFaceLivenessVerifiesReq req = new GetExamFaceLivenessVerifiesReq();
+        req.setExamRecordDataId(examRecordDataId);
+
+        GetExamFaceLivenessVerifiesResp resp = examRecordDataCloudService.getExamFaceLivenessVerifies(req);
+        if (null == resp.getExamFaceLivenessVerifis() || resp.getExamFaceLivenessVerifis().isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        List<ExamFaceLivenessVerifyBean> resultList = new ArrayList<>();
+        for (cn.com.qmth.examcloud.core.oe.student.api.bean.ExamFaceLivenessVerifyBean eflvb : resp.getExamFaceLivenessVerifis()) {
+            ExamFaceLivenessVerifyBean bean = new ExamFaceLivenessVerifyBean();
+            bean.setId(eflvb.getId());
+            bean.setExamRecordDataId(eflvb.getExamRecordDataId());
+            bean.setStartTime(eflvb.getStartTime());
+            bean.setUsedTime(eflvb.getUsedTime());
+            bean.setResultJson(eflvb.getResultJson());
+            bean.setVerifyResult(eflvb.getVerifyResult());
+            bean.setBizId(eflvb.getBizId());
+            bean.setIsError(eflvb.getIsError());
+            bean.setErrorMsg(eflvb.getErrorMsg());
+            bean.setOperateNum(eflvb.getOperateNum());
+
+            resultList.add(bean);
+        }
+
+        return resultList;
+    }
+
+    public FaceBiopsyBean getFaceBiopsy(Long examRecordDataId) {
+        GetFaceBiopsyReq req = new GetFaceBiopsyReq();
+        req.setExamRecordDataId(examRecordDataId);
+
+        GetFaceBiopsyResp resp = examRecordDataCloudService.getFaceBiopsy(req);
+        if (null == resp.getFaceBiopsyBean()) {
+            return null;
+        }
+
+        cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyBean faceBiopsyBean = resp.getFaceBiopsyBean();
+        FaceBiopsyBean bean = new FaceBiopsyBean();
+        bean.setErrorMsg(faceBiopsyBean.getErrorMsg());
+        bean.setExamRecordDataId(faceBiopsyBean.getExamRecordDataId());
+        bean.setResult(faceBiopsyBean.getResult());
+        bean.setRootOrgId(faceBiopsyBean.getRootOrgId());
+        bean.setVerifiedTimes(faceBiopsyBean.getVerifiedTimes());
+
+        List<FaceBiopsyItemBean> faceBiopsyItemList = new ArrayList<>();
+        for (cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyItemBean faceBiopsyItemBean : faceBiopsyBean.getFaceBiopsyItems()) {
+            FaceBiopsyItemBean itemBean = new FaceBiopsyItemBean();
+
+            itemBean.setCompleted(faceBiopsyItemBean.getCompleted());
+            itemBean.setErrorMsg(faceBiopsyItemBean.getErrorMsg());
+            itemBean.setExamRecordDataId(faceBiopsyItemBean.getExamRecordDataId());
+            itemBean.setFaceBiopsyId(faceBiopsyItemBean.getFaceBiopsyId());
+            itemBean.setFaceBiopsyType(faceBiopsyItemBean.getFaceBiopsyType());
+            itemBean.setInFreezeTime(faceBiopsyItemBean.getInFreezeTime());
+            itemBean.setResult(faceBiopsyItemBean.getResult());
+
+            List<FaceBiopsyItemStepBean> itemStepList = new ArrayList<>();
+            for (cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyItemStepBean faceBiopsyItemStepBean : faceBiopsyItemBean.getFaceBiopsyItemSteps()) {
+                FaceBiopsyItemStepBean itemStepBean = new FaceBiopsyItemStepBean();
+                itemStepBean.setAction(faceBiopsyItemStepBean.getAction());
+                itemStepBean.setActionStay(faceBiopsyItemStepBean.getActionStay());
+                itemStepBean.setErrorMsg(faceBiopsyItemStepBean.getErrorMsg());
+                itemStepBean.setExamRecordDataId(faceBiopsyItemStepBean.getExamRecordDataId());
+                itemStepBean.setFaceBiopsyItemId(faceBiopsyItemStepBean.getFaceBiopsyItemId());
+                itemStepBean.setResourceRelativePath(faceBiopsyItemStepBean.getResourceRelativePath());
+                itemStepBean.setResourceType(faceBiopsyItemStepBean.getResourceType());
+                itemStepBean.setResult(faceBiopsyItemStepBean.getResult());
+
+                itemStepList.add(itemStepBean);
+            }
+
+            itemBean.setFaceBiopsyItemSteps(itemStepList);
+        }
+
+        bean.setFaceBiopsyItems(faceBiopsyItemList);
+
+        return bean;
+    }
+
+    public List<FaceLiveVerifyBean> getFaceLiveVerifyRecords(Long examRecordDataId) {
+        List<ExamFaceLiveVerifyEntity> entities = examFaceLiveVerifyRepo.findByExamRecordDataIdAndFinished(examRecordDataId, true);
+        if (CollectionUtils.isEmpty(entities)) {
+            return null;
+        }
+
+        List<FaceLiveVerifyBean> faceLiveVerifyRecords = new ArrayList<>();
+        for (ExamFaceLiveVerifyEntity entity : entities) {
+            FaceLiveVerifyBean bean = new FaceLiveVerifyBean();
+            bean.setExamRecordDataId(entity.getExamRecordDataId());
+            bean.setStatus(entity.getStatus().name());
+            bean.setFaceCount(entity.getFaceCount());
+            bean.setSimilarity(entity.getSimilarity());
+            bean.setRealness(entity.getRealness());
+            bean.setErrorMsg(entity.getErrorMsg());
+            bean.setProcessTime(entity.getProcessTime());
+            bean.setActions(entity.getActions());
+            bean.setCreationTime(entity.getCreationTime());
+            bean.setUpdateTime(entity.getUpdateTime());
+            faceLiveVerifyRecords.add(bean);
+        }
+
+        return faceLiveVerifyRecords;
+    }
+
+}

+ 0 - 10
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/ExamCaptureQueueService.java

@@ -1,7 +1,6 @@
 package cn.com.qmth.examcloud.core.oe.task.service;
 
 import cn.com.qmth.examcloud.core.oe.student.dao.enums.ExamCaptureQueueStatus;
-import cn.com.qmth.examcloud.core.oe.task.service.bean.SaveExamCaptureQueueInfo;
 
 /**
  * @Description 图片抓拍队列
@@ -21,13 +20,4 @@ public interface ExamCaptureQueueService {
     boolean saveExamCaptureQueueEntityByFailed(Long captureQueueId, String errorMsg,
                                                ExamCaptureQueueStatus examCaptureQueueStatus);
 
-    /**
-     * 保存考试抓拍照片队列
-     *
-     * @param saveExamCaptureQueueInfo
-     * @param studentId
-     * @return
-     */
-    String saveExamCaptureQueue(SaveExamCaptureQueueInfo saveExamCaptureQueueInfo, Long studentId);
-
 }

+ 0 - 33
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/ExamCaptureService.java

@@ -1,11 +1,7 @@
 package cn.com.qmth.examcloud.core.oe.task.service;
 
-import cn.com.qmth.examcloud.core.oe.student.base.bean.CompareFaceSyncInfo;
-import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamCaptureEntity;
 import cn.com.qmth.examcloud.core.oe.task.service.bean.CalculateFaceCheckResultInfo;
 import cn.com.qmth.examcloud.core.oe.task.service.bean.ExamCaptureQueueInfo;
-import org.json.JSONException;
-import org.springframework.transaction.annotation.Transactional;
 
 /**
  * @Description 照片处理结果
@@ -15,37 +11,8 @@ import org.springframework.transaction.annotation.Transactional;
  */
 public interface ExamCaptureService {
 
-    void disposeBaiDuFaceLiveness(ExamCaptureQueueInfo examCaptureQueue) throws JSONException;
-
-    @Transactional
     void saveExamCaptureAndDeleteQueue(ExamCaptureQueueInfo examCaptureQueue);
 
-    /**
-     * 同步比较人脸:用于进入考试
-     *
-     * @param studentId     学生ID
-     * @param baseFaceToken 学生底照faceToken
-     * @param fileUrl       抓拍照片Url
-     * @return
-     */
-    CompareFaceSyncInfo compareFaceSyncByFileUrl(Long studentId, String baseFaceToken, String fileUrl);
-
-    /**
-     * 获取考试抓拍结果
-     *
-     * @param examRecordDataId
-     * @param fileName
-     * @return
-     */
-    ExamCaptureEntity getExamCaptureResult(Long examRecordDataId, String fileName);
-
-    /**
-     * 处理单个考试抓拍照片数据
-     *
-     * @param examCaptureQueueInfo
-     */
-    void disposeFaceCompare(ExamCaptureQueueInfo examCaptureQueueInfo) throws JSONException;
-
     /**
      * 计算人脸检测结果
      *

+ 11 - 4
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/ExamRecordDataService.java

@@ -1,8 +1,12 @@
 package cn.com.qmth.examcloud.core.oe.task.service;
 
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
 import cn.com.qmth.examcloud.support.enums.IsSuccess;
 import cn.com.qmth.examcloud.support.examing.ExamRecordData;
 
+import java.util.List;
+
 /**
  * @author chenken
  * @date 2018/8/15 11:16
@@ -11,7 +15,6 @@ import cn.com.qmth.examcloud.support.examing.ExamRecordData;
  */
 public interface ExamRecordDataService {
 
-
     /**
      * 保存考试记录
      *
@@ -47,12 +50,11 @@ public interface ExamRecordDataService {
 
     /**
      * 交卷后续处理
-     *
-     * @param examRecordDataId
-     * @return
      */
     ExamRecordData processAfterHandInExam(Long examRecordDataId);
 
+    ExamRecordData processAfterHandInExam(ExamRecordData examRecordData);
+
     /**
      * Description 是否开启虚拟摄像头进入待审核
      *
@@ -61,4 +63,9 @@ public interface ExamRecordDataService {
      */
     boolean isVirtualToWaiting(Long examId);
 
+    /**
+     * 按ID分片获取未同步的考试记录列表
+     */
+    List<ExamRecordDataEntity> getExamRecordDataUnSyncList(int shardTotal, int shardIndex, int batchSize, long startId, ExamRecordStatus... status);
+
 }

+ 0 - 2
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/ExamSyncCaptureService.java

@@ -8,6 +8,4 @@ package cn.com.qmth.examcloud.core.oe.task.service;
  */
 public interface ExamSyncCaptureService {
 
-    void saveExamCaptureSyncCompareResult(Long studentId, Long examRecordDataId);
-
 }

+ 341 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/FaceVerifyService.java

@@ -0,0 +1,341 @@
+package cn.com.qmth.examcloud.core.oe.task.service;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.helpers.JsonHttpResponseHolder;
+import cn.com.qmth.examcloud.core.oe.student.dao.ExamCaptureQueueRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.ExamCaptureQueueStatus;
+import cn.com.qmth.examcloud.core.oe.task.service.bean.ExamCaptureQueueInfo;
+import cn.com.qmth.examcloud.core.oe.task.service.worker.FaceVerifyCounter;
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.web.baidu.BaiduClient;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import cn.com.qmth.examcloud.web.facepp.FaceppClient;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import org.json.JSONException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+
+@Component
+public class FaceVerifyService {
+
+    private static final Logger log = LoggerFactory.getLogger(FaceVerifyService.class);
+
+    @Autowired
+    private ExamCaptureQueueRepo examCaptureQueueRepo;
+
+    @Autowired
+    private ExamCaptureService examCaptureService;
+
+    @Autowired
+    private ExamCaptureQueueService examCaptureQueueService;
+
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+
+    /**
+     * 处理单个考试抓拍照片数据
+     * 对图片进行人脸对比
+     */
+    @Transactional
+    public void disposeFaceCompare(ExamCaptureQueueInfo 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();
+            log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对开始...");
+
+            //调用face++API执行人脸比对,得到返回结果
+            JsonHttpResponseHolder jsonHttpResponseHolder = null;
+            try {
+                jsonHttpResponseHolder = FaceppClient.getClient().
+                        compareWithTokenAndImageUrl(examCaptureQueue.getBaseFaceToken(),
+                                FileStorageUtil.realPath(examCaptureQueue.getFileUrl()),
+                                FileStorageUtil.realPathBackup(examCaptureQueue.getFileUrl()));
+
+                faceCompareResult = jsonHttpResponseHolder.getRespBody();
+
+            } catch (StatusException e) {
+                //如果错误码是801,802,803直接结束,不重试
+                if (e.getCode().equals("801") || e.getCode().equals("802") || e.getCode().equals("803")) {
+                    examCaptureQueue.setFaceCompareResult(e.getDesc());
+                    examCaptureService.saveExamCaptureAndDeleteQueue(examCaptureQueue);
+
+                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对无法处理的图片地址,保存人脸检测最终结果并删除队列,errMsg=" + e.getDesc());
+                    return;
+                }
+                throw e;
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("[DISPOSE_FACE_COMPARE] 调用face++API执行人脸比对,得到返回结果faceCompareResult:" + faceCompareResult);
+            }
+            examCaptureQueue.setFaceCompareResult(faceCompareResult.toString());
+
+            //人脸比对出错的处理
+            if (faceCompareResult.containsKey(Constants.ERROR_MSG)) {
+                String errMsg = faceCompareResult.getString(Constants.ERROR_MSG);
+
+                //如果API并发次数超过上限,则保存错误信息到队列,并抛出异常,用于协调满载队列线程
+                if (errMsg.contains(Constants.FACE_COMPARE_CONCURRENCY_LIMIT_EXCEEDED)) {
+
+                    log.error("[FaceCompareQueue] FacePlusPlus_QPS_CONCURRENCY_LIMITED");
+
+                    examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue.getId(),
+                            "SatusCode:" + jsonHttpResponseHolder.getStatusCode() + " | " + faceCompareResult.toString(),
+                            ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_FAILED);
+
+                    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])) {
+                        examCaptureService.saveExamCaptureAndDeleteQueue(examCaptureQueue);
+
+                        log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对无法处理的图片,保存人脸检测最终结果并删除队列,errMsg=" + errMsg);
+
+                        return;
+                    }
+                }
+
+                //超时错误特殊处理,重试3次后
+                if (errMsg.contains(Constants.FACE_COMPARE_IMAGE_DOWNLOAD_TIMEOUT)) {
+                    faceCompareTimeOutTimes++;
+
+                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对超时,将进行第" + faceCompareTimeOutTimes + "次重试");
+
+                    //如果没有达到最大重试次数,则继续重试
+                    if (faceCompareTimeOutTimes < maxRetryTimes) {
+                        retry = true;
+                        continue;
+                    }
+                    //超过最大重试次数,则直接保存最终结果
+                    examCaptureService.saveExamCaptureAndDeleteQueue(examCaptureQueue);
+
+                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对超过最大检测次数:" + maxRetryTimes + ",停止重试,直接保存最终结果");
+                    return;
+                }
+
+                log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对出现错误,即将重试,errMsg:" + errMsg);
+                // 其它错误类型,保存错误信息到队列中,待自动重新服务处理
+                examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue.getId(),
+                        "SatusCode:" + jsonHttpResponseHolder.getStatusCode() + " | " + faceCompareResult.toString(),
+                        ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_FAILED);
+                FaceVerifyCounter.increaseFaceCompareFailedCount();//增加错误次数
+                log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对出现错误,增加错误次数后failedCount:" +
+                        FaceVerifyCounter.getFaceCompareFailedCount() + ",totalCount=" +
+                        FaceVerifyCounter.getFaceCompareCount());
+            }
+            //人脸比对没有出错的情况
+            else {
+                //face++的结果检测到人脸,才执行百度活体检测
+                if (faceCompareResult.containsKey("confidence")) {
+                    examCaptureQueue.setPass(calculateFaceCompareIsPass(faceCompareResult));
+                    examCaptureQueue.setStranger(calculateFaceCompareIsStranger(examCaptureQueue.getExamRecordDataId(),
+                            examCaptureQueue.getStudentId(), faceCompareResult));
+                    //更新队列状态为face++比对完成
+                    examCaptureQueue.setStatus(ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_COMPLETE);
+                    disposeFaceCompareSuccessful(examCaptureQueue);
+
+                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对完成,即将进行百度活体检测,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
+                }
+                //face++ 没有检测到人脸,直接保存人脸检测最终结果并删除队列
+                else {
+                    examCaptureQueue.setPass(false);
+                    examCaptureQueue.setStranger(false);
+                    examCaptureService.saveExamCaptureAndDeleteQueue(examCaptureQueue);
+
+                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对完成,且未检测到人脸,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
+                }
+            }
+        } while (retry);
+    }
+
+    /**
+     * 对照片进行百度活体检测
+     *
+     * @param examCaptureQueue 抓拍照片队列表
+     */
+    @Transactional
+    public void disposeBaiDuFaceLiveness(ExamCaptureQueueInfo examCaptureQueue) {
+        //活体检测超时次数
+        int facelivenessTimeOutTimes = 0;
+        //百度活检超时最大重试次数
+        int maxRetryTimes = PropertyHolder.getInt("baidu.faceliveness.timeOut.maxRetryTimes", 3);
+        boolean retry = false;
+        JSONObject faceLivenessResultJson;
+        do {
+            retry = false;
+
+            //仅用于日志时间计算
+            long startTime = System.currentTimeMillis();
+            log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测开始...");
+
+            //获取百度活检结果
+            JsonHttpResponseHolder jsonHttpResponseHolder = null;
+            try {
+                jsonHttpResponseHolder = BaiduClient.getClient().
+                        verifyFaceLiveness(FileStorageUtil.realPath(examCaptureQueue.getFileUrl()),
+                                FileStorageUtil.realPathBackup(examCaptureQueue.getFileUrl()));
+                faceLivenessResultJson = jsonHttpResponseHolder.getRespBody();
+            } catch (StatusException e) {
+                //如果错误码是901,902,903直接结束,不重试
+                if (e.getCode().equals("901") || e.getCode().equals("902") || e.getCode().equals("903")) {
+                    examCaptureQueue.setFacelivenessResult(e.getDesc());
+                    examCaptureService.saveExamCaptureAndDeleteQueue(examCaptureQueue);
+
+                    log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活检无法处理的图片地址,保存人脸检测最终结果并删除队列,errMsg=" + e.getDesc());
+                    return;
+                }
+                throw e;
+            }
+            //            faceLivenessResultJson = faceLivenessService.getBaiduFaceLivenessResultJson(examCaptureQueue.getFileUrl());
+
+            //如果百度活体检测执行失败,调用队列失败处理,程序退出,失败的数据,后续会有自动服务重新处理
+            if (faceLivenessResultJson.containsKey(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)) {
+                    log.error("[FaceLivenessQueue] Baidu_QPS_CONCURRENCY_LIMITED");
+
+                    examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue.getId(),
+                            "SatusCode:" + jsonHttpResponseHolder.getStatusCode() + " | " + faceLivenessResultJson.toString(),
+                            ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED);
+
+                    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])) {
+                        log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测,无法处理的图片,将保存人脸检测最终结果并删除队列,errCode=" + errCode);
+
+                        examCaptureService.saveExamCaptureAndDeleteQueue(examCaptureQueue);
+                        return;
+                    }
+                }
+
+                //超时错误特殊处理,重试3次后
+                if (errCode.equals(Constants.BAIDU_FACELIVENESS_CONNECTION_OR_READ_DATA_TIME_OUT_CODE)) {
+                    facelivenessTimeOutTimes++;
+
+                    log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测超时,将进行第" + facelivenessTimeOutTimes + "次重试");
+
+                    //如果没有达到最大重试次数,则继续重试
+                    if (facelivenessTimeOutTimes < maxRetryTimes) {
+                        retry = true;
+                        continue;
+                    }
+
+                    //超过最大重试次数,则直接保存最终结果
+                    examCaptureService.saveExamCaptureAndDeleteQueue(examCaptureQueue);
+
+                    log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测超过最大检测次数:" + maxRetryTimes + ",停止重试,直接保存最终结果");
+                    return;
+                }
+
+                log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测出现错误,即将重试,错误码:" + errCode);
+                // 其它错误类型,保存错误信息到队列中,待自动重新服务处理
+                examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue.getId(),
+                        "SatusCode:" + jsonHttpResponseHolder.getStatusCode() + " | " + faceLivenessResultJson.toString(), ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED);
+                FaceVerifyCounter.increaseFaceLivenessDetectFailedCount();//增加错误次数
+                log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测出现错误,增加错误次数后failedCount:" + FaceVerifyCounter.getFaceLivenessDetectFailedCount());
+            }
+            //百度活体检测成功,则保存最终检测结果,并删除临时的图片处理队列
+            else {
+                examCaptureQueue.setFacelivenessResult(faceLivenessResultJson.toString());
+                examCaptureService.saveExamCaptureAndDeleteQueue(examCaptureQueue);
+
+                log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测完成,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
+            }
+        } while (retry);
+    }
+
+    //人脸比较成功时的处理
+    private void disposeFaceCompareSuccessful(ExamCaptureQueueInfo examCaptureQueueInfo) {
+        examCaptureQueueRepo.saveExamCaptureQueueEntityBySuccessful(examCaptureQueueInfo.getId(),
+                examCaptureQueueInfo.getPass(), examCaptureQueueInfo.getStranger(),
+                examCaptureQueueInfo.getStatus().toString(), examCaptureQueueInfo.getFaceCompareResult(),
+                examCaptureQueueInfo.getFaceCompareStartTime(), new Date());
+    }
+
+    /**
+     * 校验是否有陌生人脸
+     */
+    private boolean calculateFaceCompareIsStranger(Long examRecordDataId, Long studentId, JSONObject jsonObject) {
+        JSONArray face2Array = jsonObject.getJSONArray("faces2");
+        //添加是否有陌生人开关功能
+        ExamRecordData examRecordDataCache = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+        //默认开启了陌生人检测
+        String isStrangerEnableStr = "true";
+        if (examRecordDataCache != null) {
+            isStrangerEnableStr = ExamCacheTransferHelper.getCachedExamProperty(examRecordDataCache.getExamId(),
+                    studentId,
+                    ExamProperties.IS_STRANGER_ENABLE.name()).getValue();
+        }
+        boolean isStranger;
+        // 如果开启了陌生人检测才记录陌生人数据,否则认为没有陌生人
+        if (Constants.isTrue.equals(isStrangerEnableStr)) {
+            isStranger = face2Array.size() > 1;//是否有陌生人
+        } else {
+            isStranger = false;
+        }
+        return isStranger;
+    }
+
+    /**
+     * 计算人脸比对是否通过
+     */
+    private boolean calculateFaceCompareIsPass(JSONObject jsonObject) {
+        //比对结果置信度,范围 [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;
+    }
+
+}

+ 0 - 87
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/bean/SaveExamCaptureQueueInfo.java

@@ -1,87 +0,0 @@
-package cn.com.qmth.examcloud.core.oe.task.service.bean;
-
-import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
-import io.swagger.annotations.ApiModelProperty;
-
-public class SaveExamCaptureQueueInfo implements JsonSerializable {
-
-    /**
-     *
-     */
-    private static final long serialVersionUID = -7434669407796418900L;
-
-    private Long examRecordDataId;
-
-    private String fileUrl;
-
-    /**
-     * 是否存在虚拟摄像头
-     */
-    private Boolean hasVirtualCamera;
-
-    /**
-     * 摄像头信息  json字符串数组
-     */
-    private String cameraInfos;
-
-    /**
-     * 其他信息
-     * Json格式
-     * {
-     * "":""
-     * }
-     */
-    private String extMsg;
-
-    @ApiModelProperty("又拍云签名唯一标识")
-    private String signIdentifier;
-
-    public String getSignIdentifier() {
-        return signIdentifier;
-    }
-
-    public void setSignIdentifier(String signIdentifier) {
-        this.signIdentifier = signIdentifier;
-    }
-
-    public Long getExamRecordDataId() {
-        return examRecordDataId;
-    }
-
-    public void setExamRecordDataId(Long examRecordDataId) {
-        this.examRecordDataId = examRecordDataId;
-    }
-
-    public String getFileUrl() {
-        return fileUrl;
-    }
-
-    public void setFileUrl(String fileUrl) {
-        this.fileUrl = fileUrl;
-    }
-
-    public Boolean getHasVirtualCamera() {
-        return hasVirtualCamera;
-    }
-
-    public void setHasVirtualCamera(Boolean hasVirtualCamera) {
-        this.hasVirtualCamera = hasVirtualCamera;
-    }
-
-    public String getCameraInfos() {
-        return cameraInfos;
-    }
-
-    public void setCameraInfos(String cameraInfos) {
-        this.cameraInfos = cameraInfos;
-    }
-
-    public String getExtMsg() {
-        return extMsg;
-    }
-
-    public void setExtMsg(String extMsg) {
-        this.extMsg = extMsg;
-    }
-
-}

+ 0 - 49
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamCaptureQueueServiceImpl.java

@@ -1,17 +1,8 @@
 package cn.com.qmth.examcloud.core.oe.task.service.impl;
 
-import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.commons.util.StringUtil;
-import cn.com.qmth.examcloud.commons.util.UrlUtil;
 import cn.com.qmth.examcloud.core.oe.student.dao.ExamCaptureQueueRepo;
-import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamCaptureQueueEntity;
 import cn.com.qmth.examcloud.core.oe.student.dao.enums.ExamCaptureQueueStatus;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureQueueService;
-import cn.com.qmth.examcloud.core.oe.task.service.bean.SaveExamCaptureQueueInfo;
-import cn.com.qmth.examcloud.support.Constants;
-import cn.com.qmth.examcloud.support.cache.CacheHelper;
-import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
-import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -46,44 +37,4 @@ public class ExamCaptureQueueServiceImpl implements ExamCaptureQueueService {
         }
     }
 
-    @Override
-    public String saveExamCaptureQueue(SaveExamCaptureQueueInfo saveExamCaptureQueueInfo, Long studentId) {
-        //查询学生底照faceToken
-        StudentCacheBean studentCache = CacheHelper.getStudent(studentId);
-        String baseFaceToken = studentCache.getFaceToken();
-        if (StringUtils.isBlank(baseFaceToken)) {
-            throw new StatusException("300002", "学生底照的faceToken为空");
-        }
-
-        String fileUrl = UrlUtil.decode(saveExamCaptureQueueInfo.getFileUrl());
-        if (!StringUtil.isAscString(fileUrl)) {
-            log.error("The fileUrl is invalid:" + saveExamCaptureQueueInfo.getFileUrl());
-            throw new StatusException("300001", "文件路径格式不正确");
-        }
-        String fileName = fileUrl.substring(fileUrl.lastIndexOf("/") + 1);
-
-        ExamCaptureQueueEntity examCaptureQueue = new ExamCaptureQueueEntity();
-        examCaptureQueue.setStudentId(studentId);
-        examCaptureQueue.setFileUrl(fileUrl);
-        examCaptureQueue.setFileName(fileName);
-        examCaptureQueue.setBaseFaceToken(baseFaceToken);
-        examCaptureQueue.setExamRecordDataId(saveExamCaptureQueueInfo.getExamRecordDataId());
-        examCaptureQueue.setHasVirtualCamera(saveExamCaptureQueueInfo.getHasVirtualCamera());
-
-        if (StringUtils.length(saveExamCaptureQueueInfo.getCameraInfos()) >= Constants.VM_CAMERA_SIZE_LIMIT) {
-            examCaptureQueue.setCameraInfos(Constants.VM_CAMERA_WARN);
-            log.warn("虚拟摄像头信息超长! " + saveExamCaptureQueueInfo.getExamRecordDataId());
-        } else {
-            examCaptureQueue.setCameraInfos(saveExamCaptureQueueInfo.getCameraInfos());
-        }
-
-        examCaptureQueue.setExtMsg(saveExamCaptureQueueInfo.getExtMsg());
-        examCaptureQueue.setStatus(ExamCaptureQueueStatus.PENDING);
-        examCaptureQueue.setErrorNum(0);
-        examCaptureQueue.setCreationTime(new Date());
-        examCaptureQueueRepo.save(examCaptureQueue);
-        return fileName;
-    }
-
-
 }

+ 10 - 403
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamCaptureServiceImpl.java

@@ -1,13 +1,9 @@
 package cn.com.qmth.examcloud.core.oe.task.service.impl;
 
 import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.commons.helpers.JsonHttpResponseHolder;
-import cn.com.qmth.examcloud.core.oe.student.base.bean.CompareFaceSyncInfo;
 import cn.com.qmth.examcloud.core.oe.student.dao.ExamCaptureQueueRepo;
 import cn.com.qmth.examcloud.core.oe.student.dao.ExamCaptureRepo;
 import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamCaptureEntity;
-import cn.com.qmth.examcloud.core.oe.student.dao.enums.ExamCaptureQueueStatus;
-import cn.com.qmth.examcloud.core.oe.task.base.ExamCaptureProcessStatisticController;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureQueueService;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureService;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
@@ -18,15 +14,9 @@ import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.examing.ExamRecordData;
-import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
-import cn.com.qmth.examcloud.web.baidu.BaiduClient;
-import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
-import cn.com.qmth.examcloud.web.facepp.FaceppClient;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
-import com.alibaba.fastjson.JSONArray;
-import com.alibaba.fastjson.JSONObject;
 import org.apache.commons.lang3.StringUtils;
 import org.json.JSONException;
 import org.slf4j.Logger;
@@ -37,7 +27,6 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
-import java.util.Date;
 import java.util.List;
 
 /**
@@ -70,269 +59,12 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
     private RedisClient redisClient;
 
     /**
-     * 对图片进行人脸对比
-     *
-     * @param examCaptureQueue
-     */
-    @Override
-    @Transactional
-    public void disposeFaceCompare(ExamCaptureQueueInfo examCaptureQueue) {
-        //将队列记录修改为处理中
-        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();
-            log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对开始...");
-
-            //调用face++API执行人脸比对,得到返回结果
-            JsonHttpResponseHolder jsonHttpResponseHolder = null;
-            try {
-                jsonHttpResponseHolder = FaceppClient.getClient().
-                        compareWithTokenAndImageUrl(examCaptureQueue.getBaseFaceToken(),
-                                FileStorageUtil.realPath(examCaptureQueue.getFileUrl()),
-                                FileStorageUtil.realPathBackup(examCaptureQueue.getFileUrl()));
-
-                faceCompareResult = jsonHttpResponseHolder.getRespBody();
-
-            } catch (StatusException e) {
-                //如果错误码是801,802,803直接结束,不重试
-                if (e.getCode().equals("801") || e.getCode().equals("802") || e.getCode().equals("803")) {
-                    examCaptureQueue.setFaceCompareResult(e.getDesc());
-                    saveExamCaptureAndDeleteQueue(examCaptureQueue);
-
-                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对无法处理的图片地址,保存人脸检测最终结果并删除队列,errMsg=" + e.getDesc());
-                    return;
-                }
-                throw e;
-            }
-
-            if (log.isDebugEnabled()) {
-                log.debug("[DISPOSE_FACE_COMPARE] 调用face++API执行人脸比对,得到返回结果faceCompareResult:" + faceCompareResult);
-            }
-            examCaptureQueue.setFaceCompareResult(faceCompareResult.toString());
-
-            //人脸比对出错的处理
-            if (faceCompareResult.containsKey(Constants.ERROR_MSG)) {
-                String errMsg = faceCompareResult.getString(Constants.ERROR_MSG);
-
-                //如果API并发次数超过上限,则保存错误信息到队列,并抛出异常,用于协调满载队列线程
-                if (errMsg.contains(Constants.FACE_COMPARE_CONCURRENCY_LIMIT_EXCEEDED)) {
-
-                    log.error("[FaceCompareQueue] FacePlusPlus_QPS_CONCURRENCY_LIMITED");
-
-                    examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue.getId(),
-                            "SatusCode:" + jsonHttpResponseHolder.getStatusCode() + " | " + faceCompareResult.toString(),
-                            ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_FAILED);
-
-                    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);
-
-                        log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对无法处理的图片,保存人脸检测最终结果并删除队列,errMsg=" + errMsg);
-
-                        return;
-                    }
-                }
-
-                //超时错误特殊处理,重试3次后
-                if (errMsg.contains(Constants.FACE_COMPARE_IMAGE_DOWNLOAD_TIMEOUT)) {
-                    faceCompareTimeOutTimes++;
-
-                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对超时,将进行第" + faceCompareTimeOutTimes + "次重试");
-
-                    //如果没有达到最大重试次数,则继续重试
-                    if (faceCompareTimeOutTimes < maxRetryTimes) {
-                        retry = true;
-                        continue;
-                    }
-                    //超过最大重试次数,则直接保存最终结果
-                    saveExamCaptureAndDeleteQueue(examCaptureQueue);
-
-                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对超过最大检测次数:" + maxRetryTimes + ",停止重试,直接保存最终结果");
-                    return;
-                }
-
-                log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对出现错误,即将重试,errMsg:" + errMsg);
-                // 其它错误类型,保存错误信息到队列中,待自动重新服务处理
-                examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue.getId(),
-                        "SatusCode:" + jsonHttpResponseHolder.getStatusCode() + " | " + faceCompareResult.toString(),
-                        ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_FAILED);
-                ExamCaptureProcessStatisticController.increaseFaceCompareFailedCount();//增加错误次数
-                log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对出现错误,增加错误次数后failedCount:" +
-                        ExamCaptureProcessStatisticController.getFaceCompareFailedCount() + ",totalCount=" +
-                        ExamCaptureProcessStatisticController.getFaceCompareCount());
-            }
-            //人脸比对没有出错的情况
-            else {
-                //face++的结果检测到人脸,才执行百度活体检测
-                if (faceCompareResult.containsKey("confidence")) {
-                    examCaptureQueue.setPass(calculateFaceCompareIsPass(faceCompareResult));
-                    examCaptureQueue.setStranger(calculateFaceCompareIsStranger(examCaptureQueue.getExamRecordDataId(),
-                            examCaptureQueue.getStudentId(), faceCompareResult));
-                    //更新队列状态为face++比对完成
-                    examCaptureQueue.setStatus(ExamCaptureQueueStatus.PROCESS_FACE_COMPARE_COMPLETE);
-                    disposeFaceCompareSuccessful(examCaptureQueue);
-
-                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对完成,即将进行百度活体检测,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
-                }
-                //face++ 没有检测到人脸,直接保存人脸检测最终结果并删除队列
-                else {
-                    examCaptureQueue.setPass(false);
-                    examCaptureQueue.setStranger(false);
-                    saveExamCaptureAndDeleteQueue(examCaptureQueue);
-
-                    log.debug("[DISPOSE_FACE_COMPARE] face++人脸比对完成,且未检测到人脸,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
-                }
-            }
-        } while (retry);
-    }
-
-    //人脸比较成功时的处理
-    public void disposeFaceCompareSuccessful(ExamCaptureQueueInfo examCaptureQueueInfo) {
-        examCaptureQueueRepo.saveExamCaptureQueueEntityBySuccessful(examCaptureQueueInfo.getId(),
-                examCaptureQueueInfo.getPass(), examCaptureQueueInfo.getStranger(),
-                examCaptureQueueInfo.getStatus().toString(), examCaptureQueueInfo.getFaceCompareResult(),
-                examCaptureQueueInfo.getFaceCompareStartTime(), new Date());
-    }
-
-    /**
-     * 对照片进行百度活体检测
+     * 保存人脸检测最终结果并删除队列
      *
      * @param examCapture      抓拍照片最终检测最终结果实体
      * @param examCaptureQueue 抓拍照片队列表
      */
     @Override
-    public void disposeBaiDuFaceLiveness(ExamCaptureQueueInfo examCaptureQueue) {
-        //活体检测超时次数
-        int facelivenessTimeOutTimes = 0;
-        //百度活检超时最大重试次数
-        int maxRetryTimes = PropertyHolder.getInt("baidu.faceliveness.timeOut.maxRetryTimes", 3);
-        boolean retry = false;
-        JSONObject faceLivenessResultJson;
-        do {
-            retry = false;
-
-            //仅用于日志时间计算
-            long startTime = System.currentTimeMillis();
-            log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测开始...");
-
-            //获取百度活检结果
-            JsonHttpResponseHolder jsonHttpResponseHolder = null;
-            try {
-                jsonHttpResponseHolder = BaiduClient.getClient().
-                        verifyFaceLiveness(FileStorageUtil.realPath(examCaptureQueue.getFileUrl()),
-                                FileStorageUtil.realPathBackup(examCaptureQueue.getFileUrl()));
-                faceLivenessResultJson = jsonHttpResponseHolder.getRespBody();
-            } catch (StatusException e) {
-                //如果错误码是901,902,903直接结束,不重试
-                if (e.getCode().equals("901") || e.getCode().equals("902") || e.getCode().equals("903")) {
-                    examCaptureQueue.setFacelivenessResult(e.getDesc());
-                    saveExamCaptureAndDeleteQueue(examCaptureQueue);
-
-                    log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活检无法处理的图片地址,保存人脸检测最终结果并删除队列,errMsg=" + e.getDesc());
-                    return;
-                }
-                throw e;
-            }
-            //            faceLivenessResultJson = faceLivenessService.getBaiduFaceLivenessResultJson(examCaptureQueue.getFileUrl());
-
-            //如果百度活体检测执行失败,调用队列失败处理,程序退出,失败的数据,后续会有自动服务重新处理
-            if (faceLivenessResultJson.containsKey(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)) {
-                    log.error("[FaceLivenessQueue] Baidu_QPS_CONCURRENCY_LIMITED");
-
-                    examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue.getId(),
-                            "SatusCode:" + jsonHttpResponseHolder.getStatusCode() + " | " + faceLivenessResultJson.toString(),
-                            ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED);
-
-                    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])) {
-                        log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测,无法处理的图片,将保存人脸检测最终结果并删除队列,errCode=" + errCode);
-
-                        saveExamCaptureAndDeleteQueue(examCaptureQueue);
-                        return;
-                    }
-                }
-
-                //超时错误特殊处理,重试3次后
-                if (errCode.equals(Constants.BAIDU_FACELIVENESS_CONNECTION_OR_READ_DATA_TIME_OUT_CODE)) {
-                    facelivenessTimeOutTimes++;
-
-                    log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测超时,将进行第" + facelivenessTimeOutTimes + "次重试");
-
-                    //如果没有达到最大重试次数,则继续重试
-                    if (facelivenessTimeOutTimes < maxRetryTimes) {
-                        retry = true;
-                        continue;
-                    }
-
-                    //超过最大重试次数,则直接保存最终结果
-                    saveExamCaptureAndDeleteQueue(examCaptureQueue);
-
-                    log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测超过最大检测次数:" + maxRetryTimes + ",停止重试,直接保存最终结果");
-                    return;
-                }
-
-                log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测出现错误,即将重试,错误码:" + errCode);
-                // 其它错误类型,保存错误信息到队列中,待自动重新服务处理
-                examCaptureQueueService.saveExamCaptureQueueEntityByFailed(examCaptureQueue.getId(),
-                        "SatusCode:" + jsonHttpResponseHolder.getStatusCode() + " | " + faceLivenessResultJson.toString(), ExamCaptureQueueStatus.PROCESS_FACELIVENESS_FAILED);
-                ExamCaptureProcessStatisticController.increaseFaceLivenessDetectFailedCount();//增加错误次数
-                log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测出现错误,增加错误次数后failedCount:" + ExamCaptureProcessStatisticController.getFaceLivenessDetectFailedCount());
-            }
-            //百度活体检测成功,则保存最终检测结果,并删除临时的图片处理队列
-            else {
-                examCaptureQueue.setFacelivenessResult(faceLivenessResultJson.toString());
-                saveExamCaptureAndDeleteQueue(examCaptureQueue);
-
-                log.debug("[DISPOSE_BAIDUFACELIVENESS] 百度活体检测完成,耗时:" + (System.currentTimeMillis() - startTime) + " ms");
-            }
-        } while (retry);
-    }
-
-    /**
-     * 保存人脸检测最终结果并删除队列
-     *
-     * @param examCapture 抓拍照片最终检测最终结果实体
-     *                    * @param examCaptureQueue 抓拍照片队列表
-     */
-    @Override
     @Transactional
     public void saveExamCaptureAndDeleteQueue(ExamCaptureQueueInfo examCaptureQueue) {
         ExamCaptureEntity examCapture = getExamCaptureFromQueue(examCaptureQueue);
@@ -350,85 +82,6 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
         examCaptureQueueRepo.deleteById(examCaptureQueue.getId());
     }
 
-    @Override
-    public CompareFaceSyncInfo compareFaceSyncByFileUrl(Long studentId, String baseFaceToken, String fileUrl) {
-        CompareFaceSyncInfo compareFaceSyncInfo = new CompareFaceSyncInfo();
-        compareFaceSyncInfo.setStudentId(studentId);
-        JSONObject facePPResult = null;
-        JsonHttpResponseHolder jsonHttpResponseHolder = null;
-        try {
-            jsonHttpResponseHolder = FaceppClient.getClient().
-                    compareWithTokenAndImageUrl(baseFaceToken,
-                            FileStorageUtil.realPath(fileUrl),
-                            FileStorageUtil.realPathBackup(fileUrl));
-            facePPResult = jsonHttpResponseHolder.getRespBody();
-        } catch (StatusException e) {
-            //如果错误码是801,802,803直接结束,不重试
-            if (e.getCode().equals("801") || e.getCode().equals("802") || e.getCode().equals("803")) {
-                compareFaceSyncInfo.setIsPass(false);
-                compareFaceSyncInfo.setExistsSystemError(true);
-                compareFaceSyncInfo.setErrorMsg(e.getDesc());
-
-                log.error("[COMPARE_FACE_SYNC] face++人脸比对无法处理的图片地址,errMsg=" + e.getDesc(), e);
-                return compareFaceSyncInfo;
-            }
-            throw e;
-        } catch (Exception e) {
-            compareFaceSyncInfo.setIsPass(false);
-            compareFaceSyncInfo.setExistsSystemError(true);
-            compareFaceSyncInfo.setErrorMsg("系统异常");
-
-            log.error("[COMPARE_FACE_SYNC] 未处理的系统异常,errMsg=" + e.getMessage(), e);
-            return compareFaceSyncInfo;
-        }
-        if (facePPResult.containsKey(Constants.ERROR_MSG)) {
-            compareFaceSyncInfo.setFaceCompareResult(facePPResult.toString());
-            compareFaceSyncInfo.setIsPass(false);
-            String errMsg = facePPResult.getString(Constants.ERROR_MSG);
-            if (errMsg.contains(Constants.FACE_COMPARE_AUTHORIZATION_ERROR)) {
-                compareFaceSyncInfo.setExistsSystemError(true);
-            }
-            if (errMsg.contains(Constants.FACE_COMPARE_CONCURRENCY_LIMIT_EXCEEDED)) {
-                log.error("[FaceCompare] FacePlusPlus_QPS_CONCURRENCY_LIMITED");
-                compareFaceSyncInfo.setExistsSystemError(true);
-            }
-            compareFaceSyncInfo.setErrorMsg("facePP called failed : " + facePPResult.toString());
-            return compareFaceSyncInfo;
-        } else {
-            compareFaceSyncInfo.setFaceCompareResult(facePPResult.toString());
-            if (facePPResult.containsKey("confidence")) {
-                double confidence = facePPResult.getDouble("confidence");
-                JSONObject thresholdsJsonObject = facePPResult.getJSONObject("thresholds");
-                double le4 = thresholdsJsonObject.getDouble("1e-4");
-                JSONArray face2Array = facePPResult.getJSONArray("faces2");
-                boolean hasStranger = face2Array.size() > 1;
-                compareFaceSyncInfo.setIsStranger(hasStranger);//是否有陌生人
-                boolean isPass = confidence >= le4;
-                compareFaceSyncInfo.setIsPass(isPass);//是否通过
-                String errorMsg = null;
-                if (hasStranger && !isPass) {
-                    errorMsg = "检测过程中相片非本人,且存在多人脸";
-                } else if (hasStranger) {
-                    errorMsg = "检测过程中多人脸失败";
-                } else if (!isPass) {
-                    errorMsg = "检测过程中相片非本人";
-                }
-                compareFaceSyncInfo.setErrorMsg(errorMsg);
-            } else {
-                compareFaceSyncInfo.setIsPass(false);
-                compareFaceSyncInfo.setErrorMsg("未检测到人脸");
-            }
-            return compareFaceSyncInfo;
-        }
-
-
-    }
-
-    @Override
-    public ExamCaptureEntity getExamCaptureResult(Long examRecordDataId, String fileName) {
-        return examCaptureRepo.findByExamRecordDataIdAndFileName(examRecordDataId, fileName);
-    }
-
     /**
      * 计算人脸检测结果
      * 相片数=0,系统判断为违纪,自动审核
@@ -560,12 +213,17 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
 
     /**
      * 计算百度活体检测成功数量
-     *
-     * @param facelivenessResult
-     * @return
      */
     private int calculateFacelivenessSuccessCount(String facelivenessResult) {
         if (StringUtils.isNotBlank(facelivenessResult)) {
+            // 兼容C端学生端情况,人脸真实性值只存在0或1两种值
+            if ("0".equals(facelivenessResult)) {
+                return 0;
+            }
+            if ("1".equals(facelivenessResult)) {
+                return 1;
+            }
+
             org.json.JSONObject jsonObject;
             try {
                 jsonObject = new org.json.JSONObject(facelivenessResult);
@@ -588,10 +246,9 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
                     }
                 }
             } catch (JSONException e) {
-                e.printStackTrace();
+                log.error(e.getMessage(), e);
                 return 0;
             }
-
         }
         return 0;
     }
@@ -624,54 +281,4 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
         return resultEntity;
     }
 
-
-    /**
-     * 校验是否有陌生人脸
-     *
-     * @param examCaptureQueue
-     * @param jsonObject
-     * @return
-     * @throws JSONException
-     */
-    private boolean calculateFaceCompareIsStranger(Long examRecordDataId, Long studentId, JSONObject jsonObject) {
-        JSONArray face2Array = jsonObject.getJSONArray("faces2");
-        //添加是否有陌生人开关功能
-        ExamRecordData examRecordDataCache = examRecordDataService.getExamRecordDataCache(examRecordDataId);
-        //默认开启了陌生人检测
-        String isStrangerEnableStr = "true";
-        if (examRecordDataCache != null) {
-            isStrangerEnableStr = ExamCacheTransferHelper.getCachedExamProperty(examRecordDataCache.getExamId(),
-                    studentId,
-                    ExamProperties.IS_STRANGER_ENABLE.name()).getValue();
-        }
-        boolean isStranger;
-        // 如果开启了陌生人检测才记录陌生人数据,否则认为没有陌生人
-        if (Constants.isTrue.equals(isStrangerEnableStr)) {
-            isStranger = face2Array.size() > 1;//是否有陌生人
-        } else {
-            isStranger = false;
-        }
-        return isStranger;
-    }
-
-    /**
-     * 计算人脸比对是否通过
-     *
-     * @param jsonObject
-     * @return
-     * @throws JSONException
-     */
-    private boolean calculateFaceCompareIsPass(JSONObject jsonObject) {
-        //比对结果置信度,范围 [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;
-    }
-
 }

+ 59 - 17
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamRecordDataServiceImpl.java

@@ -7,6 +7,8 @@ import cn.com.qmth.examcloud.core.oe.student.api.request.CalcFaceBiopsyResultReq
 import cn.com.qmth.examcloud.core.oe.student.api.response.CalcExamScoreResp;
 import cn.com.qmth.examcloud.core.oe.student.api.response.CalcFaceBiopsyResultResp;
 import cn.com.qmth.examcloud.core.oe.student.dao.ExamCaptureQueueRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamRecordDataEntity;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureService;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
 import cn.com.qmth.examcloud.core.oe.task.service.bean.CalculateFaceCheckResultInfo;
@@ -19,11 +21,19 @@ import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
 import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
+import java.util.Arrays;
+import java.util.Date;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * @Description 考试记录服务
@@ -34,12 +44,17 @@ import java.util.List;
 @Service("examRecordDataService")
 public class ExamRecordDataServiceImpl implements ExamRecordDataService {
 
+    private static final Logger log = LoggerFactory.getLogger(ExamRecordDataServiceImpl.class);
+
     @Autowired
     private RedisClient redisClient;
 
     @Autowired
     private ExamCaptureQueueRepo examCaptureQueueRepo;
 
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
     @Autowired
     private ExamCaptureService examCaptureService;
 
@@ -92,26 +107,24 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
 
     /**
      * 交卷后续处理
-     *
-     * @param examRecordDataId
-     * @return
      */
     @Override
     public ExamRecordData processAfterHandInExam(Long examRecordDataId) {
         ExamRecordData examRecordData = getExamRecordDataCache(examRecordDataId);
+        return this.processAfterHandInExam(examRecordData);
+    }
 
+    @Override
+    public ExamRecordData processAfterHandInExam(ExamRecordData examRecordData) {
         //只有开启的人脸的才会进行如下处理
-        if (FaceBiopsyHelper.isFaceEnable(examRecordData.getRootOrgId(),
-                examRecordData.getExamId(), examRecordData.getStudentId())) {
+        if (FaceBiopsyHelper.isFaceEnable(examRecordData.getRootOrgId(), examRecordData.getExamId(), examRecordData.getStudentId())) {
             // 判断是否存在未处理的图片
-            boolean existUnhandledExamCaptureQueue = (examCaptureQueueRepo
-                    .existsUnhandledByExamRecordDataId(examRecordDataId) != null);
-            if (existUnhandledExamCaptureQueue) {
-                throw new StatusException(Constants.CAPTURE_PROCESSING_STATUS_CODE, "PROCESSING");
+            if (examCaptureQueueRepo.existsUnhandledByExamRecordDataId(examRecordData.getId()) != null) {
+                throw new StatusException(Constants.CAPTURE_PROCESSING_STATUS_CODE, "抓拍照片比对尚未处理完!");
             }
 
             // 计算人脸检测结果
-            CalculateFaceCheckResultInfo faceCheckResult = examCaptureService.calculateFaceCheckResult(examRecordDataId);
+            CalculateFaceCheckResultInfo faceCheckResult = examCaptureService.calculateFaceCheckResult(examRecordData.getId());
             if (null != faceCheckResult.getFaceTotalCount()) {
                 examRecordData.setFaceTotalCount(faceCheckResult.getFaceTotalCount());
             }
@@ -133,7 +146,7 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
 
             // 计算活体检测结果
             CalcFaceBiopsyResultReq req = new CalcFaceBiopsyResultReq();
-            req.setExamRecordDataId(examRecordDataId);
+            req.setExamRecordDataId(examRecordData.getId());
             CalcFaceBiopsyResultResp calcFaceBiopsyResultResp = examRecordDataCloudService.calcFaceBiopsyResult(req);
             if (null != calcFaceBiopsyResultResp.getFaceVerifyResult()) {
                 examRecordData.setFaceVerifyResult(calcFaceBiopsyResultResp.getFaceVerifyResult());
@@ -165,7 +178,7 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
 
         //自动计算客观分
         CalcExamScoreReq cesReq = new CalcExamScoreReq();
-        cesReq.setExamRecordDataId(examRecordDataId);
+        cesReq.setExamRecordDataId(examRecordData.getId());
         CalcExamScoreResp calcExamScoreResp = examRecordDataCloudService.calcExamScore(cesReq);
         examRecordData.setObjectiveScore(calcExamScoreResp.getObjectiveScore());
         examRecordData.setObjectiveAccuracy(calcExamScoreResp.getObjectiveAccuracy());
@@ -173,13 +186,14 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
         examRecordData.setTotalScore(calcExamScoreResp.getTotalScore());
 
         // 更新考试状态
-        examRecordData.setExamRecordStatus(
-                examRecordData.getHandInExamType() == HandInExamType.MANUAL
-                        ? ExamRecordStatus.EXAM_END
-                        : ExamRecordStatus.EXAM_OVERDUE);
+        examRecordData.setExamRecordStatus(examRecordData.getHandInExamType() == HandInExamType.MANUAL ?
+                ExamRecordStatus.EXAM_END : ExamRecordStatus.EXAM_OVERDUE);
 
         //更新考试记录中的相关数据
-        this.saveExamRecordDataCache(examRecordDataId, examRecordData);
+        this.saveExamRecordDataCache(examRecordData.getId(), examRecordData);
+
+        // 更新考试记录状态
+        examRecordDataRepo.updateExamRecordStatusById(examRecordData.getExamRecordStatus(), new Date(), examRecordData.getId());
 
         return examRecordData;
     }
@@ -191,4 +205,32 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
         return !CollectionUtils.isEmpty(list) && list.get(0);
     }
 
+    /**
+     * 按ID分片获取未同步的考试记录列表
+     */
+    @Override
+    public List<ExamRecordDataEntity> getExamRecordDataUnSyncList(int shardTotal, int shardIndex, int batchSize, long startId, ExamRecordStatus... status) {
+        StringBuilder sql = new StringBuilder()
+                .append(" select id,root_org_id,student_id,exam_record_status")
+                .append(" from ec_oes_exam_record_data")
+                .append(" where sync_status='UNSYNC'");
+
+        if (ArrayUtils.isNotEmpty(status)) {
+            List<String> values = Arrays.stream(status).map(e -> "'" + e.name() + "'").collect(Collectors.toList());
+            sql.append(" and exam_record_status in (").append(StringUtils.join(values, ",")).append(")");
+        }
+
+        sql.append(" and id>").append(startId);
+
+        if (shardTotal > 1) {
+            sql.append(" and mod(id,").append(shardTotal).append(")=").append(shardIndex);
+        }
+
+        sql.append(" order by id asc");
+        sql.append(" limit ").append(batchSize);
+
+        log.debug(sql.toString());
+        return jdbcTemplate.query(sql.toString(), new BeanPropertyRowMapper(ExamRecordDataEntity.class));
+    }
+
 }

+ 0 - 51
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/impl/ExamSyncCaptureServiceImpl.java

@@ -1,15 +1,7 @@
 package cn.com.qmth.examcloud.core.oe.task.service.impl;
 
-import cn.com.qmth.examcloud.core.oe.student.base.bean.CompareFaceSyncInfo;
-import cn.com.qmth.examcloud.core.oe.student.dao.ExamSyncCaptureRepo;
-import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamSyncCaptureEntity;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamSyncCaptureService;
-import cn.com.qmth.examcloud.support.Constants;
-import cn.com.qmth.examcloud.web.redis.RedisClient;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Example;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
 
 /**
  * @Description 同步抓拍照片
@@ -21,47 +13,4 @@ import org.springframework.transaction.annotation.Transactional;
 @Service("examSyncCaptureService")
 public class ExamSyncCaptureServiceImpl implements ExamSyncCaptureService {
 
-    @Autowired
-    private ExamSyncCaptureRepo examSyncCaptureRepo;
-
-    @Autowired
-    private RedisClient redisClient;
-
-    /**
-     * 保存同步比较的抓拍照片结果
-     *
-     * @param studentId 学生id
-     */
-    @Override
-    @Transactional
-    public void saveExamCaptureSyncCompareResult(Long studentId, Long examRecordDataId) {
-        String syncCopareResultkey = Constants.FACE_SYNC_COMPARE_RESULT_PREFIX + studentId;
-        CompareFaceSyncInfo compareFaceSyncInfo =
-                redisClient.get(syncCopareResultkey, CompareFaceSyncInfo.class);
-        if (compareFaceSyncInfo == null) {
-            return;
-        }
-
-        ExamSyncCaptureEntity examCaptureEntity = new ExamSyncCaptureEntity();
-        examCaptureEntity.setExamRecordDataId(examRecordDataId);
-        examCaptureEntity.setFaceCompareResult(compareFaceSyncInfo.getFaceCompareResult());
-        examCaptureEntity.setFileName(compareFaceSyncInfo.getFileName());
-        examCaptureEntity.setFileUrl(compareFaceSyncInfo.getFileUrl());
-        examCaptureEntity.setIsStranger(compareFaceSyncInfo.getIsStranger());
-        examCaptureEntity.setIsPass(compareFaceSyncInfo.getIsPass());
-        examCaptureEntity.setProcessTime(compareFaceSyncInfo.getProcessTime());
-
-        //照片处理结果中如果已存在,则以已有的数据为准
-        ExamSyncCaptureEntity query = new ExamSyncCaptureEntity();
-        query.setExamRecordDataId(examRecordDataId);
-        query.setFileName(compareFaceSyncInfo.getFileName());
-        Example<ExamSyncCaptureEntity> example = Example.of(query);
-        if (!examSyncCaptureRepo.exists(example)) {
-            examSyncCaptureRepo.save(examCaptureEntity);
-        }
-
-        //删除redis中的同步数据
-        redisClient.delete(syncCopareResultkey);
-    }
-
 }

+ 121 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/job/AfterHandInExamJobHandler.java

@@ -0,0 +1,121 @@
+package cn.com.qmth.examcloud.core.oe.task.service.job;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import cn.com.qmth.examcloud.web.exception.SequenceLockException;
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 处理交卷后考试数据任务
+ */
+@Component
+public class AfterHandInExamJobHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(AfterHandInExamJobHandler.class);
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+
+    public void run(int shardTotal, int shardIndex, String jobParam) throws Exception {
+        int batchNo = 0, batchSize = 200;
+        int sumTodoSize = 0;
+        Long startId = 0L;
+        while (true) {
+            List<ExamRecordDataEntity> todoList = examRecordDataService.getExamRecordDataUnSyncList(
+                    shardTotal, shardIndex, batchSize, startId, ExamRecordStatus.EXAM_HAND_IN, ExamRecordStatus.EXAM_AUTO_HAND_IN);
+
+            batchNo++;
+            sumTodoSize += todoList.size();
+            log.warn("TODO_LIST_2 shardTotal:{}, shardIndex:{}, sumTodoSize:{}, curTodoSize:{}, batchNo:{}, startId:{}",
+                    shardTotal, shardIndex, sumTodoSize, todoList.size(), batchNo, startId);
+
+            if (CollectionUtils.isEmpty(todoList)) {
+                break;
+            }
+
+            int curTodoIndex = 0;
+            for (ExamRecordDataEntity data : todoList) {
+                final String lockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + data.getStudentId();
+                try {
+                    SequenceLockHelper.getLockSimple(lockKey);
+
+                    // 处理业务
+                    curTodoIndex++;
+                    this.handler(data.getId(), todoList.size(), curTodoIndex);
+                } catch (Exception e) {
+                    if (e instanceof InterruptedException) {
+                        // 若线程终止,则抛出交由任务调度中心处理
+                        log.warn("当前任务线程被终止!examRecordDataId:{}, error:{}", data.getId(), e.getMessage());
+                        throw e;
+                    } else if (e instanceof SequenceLockException) {
+                        // 若锁问题,下次会继续执行
+                        log.warn("当前考试记录数据获取锁失败!examRecordDataId:{}, redisKey:{}", data.getId(), lockKey);
+                    } else {
+                        // 若异常,下次会继续执行(需要排查原因)
+                        log.error("当前考试记录数据处理失败!examRecordDataId:{}, error:{}", data.getId(), e.getMessage(), e);
+                    }
+                } finally {
+                    SequenceLockHelper.releaseLockSimple(lockKey);
+                }
+            }
+
+            startId = todoList.get(todoList.size() - 1).getId();
+            todoList.clear();
+        }
+    }
+
+    /**
+     * 仅处理已交卷的考试记录
+     */
+    private void handler(Long examRecordDataId, int curTodoSize, int curTodoIndex) throws Exception {
+        ExamRecordData examRecordDataCache = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+        if (examRecordDataCache == null) {
+            log.warn("redisKey OE_ERD:{} value not exist, Set examRecordStatus=EXAM_ERROR", examRecordDataId);
+            examRecordDataRepo.updateExamRecordStatusById(ExamRecordStatus.EXAM_ERROR, new Date(), examRecordDataId);
+            return;
+        }
+
+        if (ExamRecordStatus.EXAM_HAND_IN != examRecordDataCache.getExamRecordStatus()
+                && ExamRecordStatus.EXAM_AUTO_HAND_IN != examRecordDataCache.getExamRecordStatus()) {
+            log.warn("Mysql&Redis examRecordStatus value not equal, should EXAM_HAND_IN or EXAM_AUTO_HAND_IN but {}, examRecordDataId:{}",
+                    examRecordDataCache.getExamRecordStatus(), examRecordDataId);
+            // 状态出现不一致情况时,则按Redis中的值为准,下次会按实际状态执行
+            examRecordDataRepo.updateExamRecordStatusById(examRecordDataCache.getExamRecordStatus(), new Date(), examRecordDataId);
+            return;
+        }
+
+        // 交卷时间(手动交卷取endTime,自动交卷取cleanTime)
+        Date handInTime = examRecordDataCache.getEndTime() != null ? examRecordDataCache.getEndTime() : examRecordDataCache.getCleanTime();
+        long diff = (System.currentTimeMillis() - handInTime.getTime()) / 1000;
+        log.warn("交卷类型:{} 交卷距离现在已{}秒, examRecordDataId:{}, studentId:{}, curTodoSize:{}, curTodoIndex:{}",
+                examRecordDataCache.getHandInExamType(), diff, examRecordDataId, examRecordDataCache.getStudentId(), curTodoSize, curTodoIndex);
+
+        try {
+            // 处理人脸、活体、违纪、算分等
+            long startTime = System.currentTimeMillis();
+            examRecordDataService.processAfterHandInExam(examRecordDataCache);
+            long cost = (System.currentTimeMillis() - startTime) / 1000;
+            log.warn("processAfterHandInExam ok, examRecordDataId:{}, studentId:{}, examType:{}, cost:{}s",
+                    examRecordDataCache.getId(), examRecordDataCache.getStudentId(), examRecordDataCache.getExamType(), cost);
+        } catch (Exception e) {
+            log.error("processAfterHandInExam fail, examRecordDataId:{}, error:{}", examRecordDataCache.getId(), e.getMessage());
+            throw e;
+        }
+    }
+
+}

+ 186 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/job/BeforeHandInExamJobHandler.java

@@ -0,0 +1,186 @@
+package cn.com.qmth.examcloud.core.oe.task.service.job;
+
+import cn.com.qmth.examcloud.core.oe.student.api.ExamRecordDataCloudService;
+import cn.com.qmth.examcloud.core.oe.student.api.request.HandInExamReq;
+import cn.com.qmth.examcloud.core.oe.student.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
+import cn.com.qmth.examcloud.core.oe.task.service.ExamingSessionService;
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.support.enums.HandInExamType;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import cn.com.qmth.examcloud.support.examing.ExamingActivityTime;
+import cn.com.qmth.examcloud.support.examing.ExamingSession;
+import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
+import cn.com.qmth.examcloud.web.exception.SequenceLockException;
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 处理交卷前考试数据任务
+ */
+@Component
+public class BeforeHandInExamJobHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(BeforeHandInExamJobHandler.class);
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+
+    @Autowired
+    private ExamingSessionService examingSessionService;
+
+    @Autowired
+    private ExamRecordDataCloudService examRecordDataCloudService;
+
+    @Autowired
+    private RedisClient redisClient;
+
+    public void run(int shardTotal, int shardIndex, String jobParam) throws Exception {
+        int batchNo = 0, batchSize = 200;
+        int sumTodoSize = 0;
+        Long startId = 0L;
+        while (true) {
+            List<ExamRecordDataEntity> todoList = examRecordDataService.getExamRecordDataUnSyncList(
+                    shardTotal, shardIndex, batchSize, startId, ExamRecordStatus.EXAM_ING);
+
+            batchNo++;
+            sumTodoSize += todoList.size();
+            log.warn("TODO_LIST_1 shardTotal:{}, shardIndex:{}, sumTodoSize:{}, curTodoSize:{}, batchNo:{}, startId:{}",
+                    shardTotal, shardIndex, sumTodoSize, todoList.size(), batchNo, startId);
+
+            if (CollectionUtils.isEmpty(todoList)) {
+                break;
+            }
+
+            for (ExamRecordDataEntity data : todoList) {
+                final String lockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + data.getStudentId();
+                try {
+                    SequenceLockHelper.getLockSimple(lockKey);
+
+                    // 处理业务
+                    this.handler(data.getId());
+                } catch (Exception e) {
+                    if (e instanceof InterruptedException) {
+                        // 若线程终止,则抛出交由任务调度中心处理
+                        log.warn("当前任务线程被终止!examRecordDataId:{}, error:{}", data.getId(), e.getMessage());
+                        throw e;
+                    } else if (e instanceof SequenceLockException) {
+                        // 若锁问题,下次会继续执行
+                        log.warn("当前考试记录数据获取锁失败!examRecordDataId:{}, redisKey:{}", data.getId(), lockKey);
+                    } else {
+                        // 若异常,下次会继续执行(需要排查原因)
+                        log.error("当前考试记录数据处理失败!examRecordDataId:{}, error:{}", data.getId(), e.getMessage(), e);
+                    }
+                } finally {
+                    SequenceLockHelper.releaseLockSimple(lockKey);
+                }
+            }
+
+            startId = todoList.get(todoList.size() - 1).getId();
+            todoList.clear();
+        }
+    }
+
+    /**
+     * 仅处理正在进行中的考试记录
+     */
+    private void handler(Long examRecordDataId) throws Exception {
+        ExamRecordData examRecordDataCache = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+        if (examRecordDataCache == null) {
+            log.warn("redisKey OE_ERD:{} value not exist, Set examRecordStatus=EXAM_ERROR", examRecordDataId);
+            examRecordDataRepo.updateExamRecordStatusById(ExamRecordStatus.EXAM_ERROR, new Date(), examRecordDataId);
+            return;
+        }
+
+        if (ExamRecordStatus.EXAM_ING != examRecordDataCache.getExamRecordStatus()) {
+            log.warn("Mysql&Redis examRecordStatus value not equal, should EXAM_ING but {}, examRecordDataId:{}",
+                    examRecordDataCache.getExamRecordStatus(), examRecordDataId);
+            // 状态出现不一致情况时,则按Redis中的值为准,下次会按实际状态执行
+            examRecordDataRepo.updateExamRecordStatusById(examRecordDataCache.getExamRecordStatus(), new Date(), examRecordDataId);
+            return;
+        }
+
+        long diff = System.currentTimeMillis() - examRecordDataCache.getEnterExamTime().getTime();
+        if (diff <= 180000L) {
+            // 跳过刚进入考试3分钟内的记录
+            log.debug("enterExamTime just 3 minutes, skip... examRecordDataId:{}", examRecordDataId);
+            return;
+        }
+
+        ExamingSession examingSession = examingSessionService.getExamingSession(examRecordDataCache.getStudentId());
+        if (examingSession == null) {
+            // 考试会话不存在,则自动交卷
+            log.warn("redisKey OE_SESSION:{} value not exist, do autoHandInExam, examRecordDataId:{}", examRecordDataCache.getStudentId(), examRecordDataId);
+            this.autoHandInExam(examRecordDataCache);
+            return;
+        }
+
+        // 超过断点续考时间,则自动交卷
+        if (this.overBreakpointTime(examingSession)) {
+            log.warn("overBreakpointTime do autoHandInExam, examRecordDataId:{}, studentId:{}", examRecordDataCache.getId(), examRecordDataCache.getStudentId());
+            this.autoHandInExam(examRecordDataCache);
+            return;
+        }
+
+        // 异常情况超过半天仍未交卷,则自动交卷
+        if (diff >= 6L * 3600000L) {
+            log.warn("enterExamTime over 6 hours... do autoHandInExam, examRecordDataId:{}, studentId:{}", examRecordDataCache.getId(), examRecordDataCache.getStudentId());
+            this.autoHandInExam(examRecordDataCache);
+        }
+    }
+
+    /**
+     * 是否超过断点续考时间
+     */
+    private boolean overBreakpointTime(ExamingSession examingSession) throws Exception {
+        long now = System.currentTimeMillis();
+
+        String activeTimeKey = RedisKeyHelper.getBuilder().examingActiveTimeKey(examingSession.getExamRecordDataId());
+        ExamingActivityTime activeTimeCache = redisClient.get(activeTimeKey, ExamingActivityTime.class);
+
+        long activeTime;
+        if (activeTimeCache == null) {
+            log.warn("redisKey OE_ACTIVE:{} value not exist, examReconnectTime:{} minutes",
+                    examingSession.getExamRecordDataId(), examingSession.getExamReconnectTime());
+            activeTime = System.currentTimeMillis();
+        } else {
+            activeTime = activeTimeCache.getActiveTime();
+        }
+
+        return (now - activeTime) >= examingSession.getExamReconnectTime() * 60L * 1000L;
+    }
+
+    /**
+     * 自动交卷
+     */
+    private void autoHandInExam(ExamRecordData examRecordDataCache) throws Exception {
+        try {
+            HandInExamReq req = new HandInExamReq();
+            req.setExamRecordDataId(examRecordDataCache.getId());
+            req.setHandInExamType(HandInExamType.AUTO);
+
+            long startTime = System.currentTimeMillis();
+            examRecordDataCloudService.handInExam(req);
+            long cost = (System.currentTimeMillis() - startTime) / 1000;
+            log.warn("autoHandInExam ok, examRecordDataId:{}, studentId:{}, examType:{}, cost:{}s",
+                    examRecordDataCache.getId(), examRecordDataCache.getStudentId(), examRecordDataCache.getExamType(), cost);
+        } catch (Exception e) {
+            log.error("autoHandInExam fail, examRecordDataId:{}, error:{}", examRecordDataCache.getId(), e.getMessage());
+            throw e;
+        }
+    }
+
+}

+ 297 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/job/SyncExamRecordDataJobHandler.java

@@ -0,0 +1,297 @@
+package cn.com.qmth.examcloud.core.oe.task.service.job;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamScoreNoticeQueueCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.SyncExamDataCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamCaptureBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamSyncCaptureBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.AddExamScoreNoticeQueueReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.SyncExamDataReq;
+import cn.com.qmth.examcloud.core.oe.student.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.task.service.CommonService;
+import cn.com.qmth.examcloud.core.oe.task.service.ExamBossService;
+import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
+import cn.com.qmth.examcloud.core.oe.task.service.ExamingSessionService;
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.support.enums.SyncStatus;
+import cn.com.qmth.examcloud.support.examing.ExamBoss;
+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.support.redis.RedisKeyHelper;
+import cn.com.qmth.examcloud.web.exception.SequenceLockException;
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 同步考试记录数据任务
+ */
+@Component
+public class SyncExamRecordDataJobHandler {
+
+    private static final Logger log = LoggerFactory.getLogger(SyncExamRecordDataJobHandler.class);
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+
+    @Autowired
+    private ExamBossService examBossService;
+
+    @Autowired
+    private SyncExamDataCloudService syncExamDataCloudService;
+
+    @Autowired
+    private ExamScoreNoticeQueueCloudService examScoreNoticeQueueCloudService;
+
+    @Autowired
+    private ExamingSessionService examingSessionService;
+
+    @Autowired
+    private CommonService commonService;
+
+    @Autowired
+    private RedisClient redisClient;
+
+    public void run(int shardTotal, int shardIndex, String jobParam) throws Exception {
+        int batchNo = 0, batchSize = 200;
+        int sumTodoSize = 0;
+        Long startId = 0L;
+        while (true) {
+            List<ExamRecordDataEntity> todoList = examRecordDataService.getExamRecordDataUnSyncList(
+                    shardTotal, shardIndex, batchSize, startId, ExamRecordStatus.EXAM_END, ExamRecordStatus.EXAM_OVERDUE);
+
+            batchNo++;
+            sumTodoSize += todoList.size();
+            log.warn("TODO_LIST_3 shardTotal:{}, shardIndex:{}, sumTodoSize:{}, curTodoSize:{}, batchNo:{}, startId:{}",
+                    shardTotal, shardIndex, sumTodoSize, todoList.size(), batchNo, startId);
+
+            if (CollectionUtils.isEmpty(todoList)) {
+                break;
+            }
+
+            for (ExamRecordDataEntity data : todoList) {
+                final String lockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + data.getStudentId();
+                try {
+                    SequenceLockHelper.getLockSimple(lockKey);
+
+                    // 处理业务
+                    this.handler(data.getId());
+                } catch (Exception e) {
+                    if (e instanceof InterruptedException) {
+                        // 若线程终止,则抛出交由任务调度中心处理
+                        log.warn("当前任务线程被终止!examRecordDataId:{}, error:{}", data.getId(), e.getMessage());
+                        throw e;
+                    } else if (e instanceof SequenceLockException) {
+                        // 若锁问题,下次会继续执行
+                        log.warn("当前考试记录数据获取锁失败!examRecordDataId:{}, redisKey:{}", data.getId(), lockKey);
+                    } else {
+                        // 若异常,下次会继续执行(需要排查原因)
+                        log.error("当前考试记录数据处理失败!examRecordDataId:{}, error:{}", data.getId(), e.getMessage(), e);
+                    }
+                } finally {
+                    SequenceLockHelper.releaseLockSimple(lockKey);
+                }
+            }
+
+            startId = todoList.get(todoList.size() - 1).getId();
+            todoList.clear();
+        }
+    }
+
+    /**
+     * 仅处理已结束的考试记录
+     */
+    private void handler(Long examRecordDataId) throws Exception {
+        ExamRecordData examRecordDataCache = examRecordDataService.getExamRecordDataCache(examRecordDataId);
+        if (examRecordDataCache == null) {
+            log.warn("redisKey OE_ERD:{} value not exist, Set examRecordStatus=EXAM_ERROR", examRecordDataId);
+            examRecordDataRepo.updateExamRecordStatusById(ExamRecordStatus.EXAM_ERROR, new Date(), examRecordDataId);
+            return;
+        }
+
+        if (ExamRecordStatus.EXAM_END != examRecordDataCache.getExamRecordStatus()
+                && ExamRecordStatus.EXAM_OVERDUE != examRecordDataCache.getExamRecordStatus()) {
+            log.warn("Mysql&Redis examRecordStatus value not equal, should EXAM_END or EXAM_OVERDUE but {}, examRecordDataId:{}",
+                    examRecordDataCache.getExamRecordStatus(), examRecordDataId);
+            // 状态出现不一致情况时,则按Redis中的值为准,下次会按实际状态执行
+            examRecordDataRepo.updateExamRecordStatusById(examRecordDataCache.getExamRecordStatus(), new Date(), examRecordDataId);
+            return;
+        }
+
+        if (SyncStatus.SYNCED == examRecordDataCache.getSyncStatus()) {
+            log.warn("syncExamData already synced, examRecordDataId:{}, studentId:{}", examRecordDataId, examRecordDataCache.getStudentId());
+            this.afterSyncExamData(examRecordDataCache);
+            return;
+        }
+
+        log.info("syncExamData start, examRecordDataId:{}, studentId:{}", examRecordDataId, examRecordDataCache.getStudentId());
+
+        SyncExamDataReq syncReq = new SyncExamDataReq();
+        // 获取试卷结构(Redis中)
+        syncReq.setExamRecordPaperStruct(commonService.getExamRecordPaperStruct(examRecordDataId));
+        // 获取考试作答记录(Redis中)
+        syncReq.setExamRecordQuestions(commonService.getExamRecordQuestions(examRecordDataId));
+        // 获取考试过程记录(Mysql中)
+        syncReq.setExamProcessRecords(commonService.getExamProcessRecords(examRecordDataId));
+        // 获取断点续考记录(Mysql中)
+        syncReq.setExamContinuedRecords(commonService.getExamContinuedRecords(examRecordDataId));
+
+        Long rootOrgId = examRecordDataCache.getRootOrgId();
+        Long examId = examRecordDataCache.getExamId();
+        Long studentId = examRecordDataCache.getStudentId();
+
+        // 是否开启人脸比对检测
+        if (FaceBiopsyHelper.isFaceEnable(rootOrgId, examId, studentId)) {
+            // 获取同步抓拍照片处理结果(Mysql中)
+            ExamSyncCaptureBean syncCapture = commonService.getExamSyncCapture(examRecordDataId);
+            syncReq.setExamSyncCapture(syncCapture);
+
+            // 获取间隔抓拍照片处理记录(Mysql中)
+            List<ExamCaptureBean> captures = commonService.getExamCaptures(examRecordDataId);
+            syncReq.setExamCaptures(captures);
+
+            // 是否开启“虚拟摄像头进入待审” VIRTUAL_CAMERA_AUDIT_ENABLED
+            boolean virtualCameraAuditEnabled = examRecordDataService.isVirtualToWaiting(examId);
+            if (virtualCameraAuditEnabled) {
+                boolean hasVirtualCamera = captures.stream().anyMatch(e -> e.getHasVirtualCamera() != null && e.getHasVirtualCamera());
+                if (syncCapture == null || hasVirtualCamera) {
+                    // 没有同步抓拍照片处理结果 或 存在虚拟摄像头,则进入待审
+                    examRecordDataCache.setIsWarn(true);
+                }
+            }
+
+            // 是否开启人脸活体检测
+            if (FaceBiopsyHelper.isFaceVerify(rootOrgId, examId, studentId)) {
+                // S1 FaceID活体检测方案(即旧活体检测方案)
+                syncReq.setExamFaceLivenessVerifies(commonService.getExamFaceLivenessVerifies(examRecordDataId));
+                // S2 ElectronClient 自研活体检测方案
+                syncReq.setFaceBiopsy(commonService.getFaceBiopsy(examRecordDataId));
+                // S3 C端活体检测方案
+                syncReq.setFaceLiveVerifyRecords(commonService.getFaceLiveVerifyRecords(examRecordDataId));
+            }
+        }
+
+        // 考试记录信息
+        syncReq.setExamRecordData(commonService.copyExamRecordDataFrom(examRecordDataCache));
+
+        long startTime = System.currentTimeMillis();
+        syncExamDataCloudService.syncExamData(syncReq);
+        long cost = (System.currentTimeMillis() - startTime) / 1000;
+        log.warn("syncExamData ok, examRecordDataId:{}, studentId:{}, examType:{}, cost:{}s",
+                examRecordDataId, examRecordDataCache.getStudentId(), examRecordDataCache.getExamType(), cost);
+
+        // 同步成功后,更新同步状态
+        examRecordDataCache.setSyncStatus(SyncStatus.SYNCED);
+        examRecordDataService.saveExamRecordDataCache(examRecordDataId, examRecordDataCache);
+
+        this.afterSyncExamData(examRecordDataCache);
+
+        // 是否推送分数
+        String isPushScore = ExamCacheTransferHelper.getCachedExamProperty(examId, studentId, ExamProperties.PUSH_SCORE.name()).getValue();
+        if (Constants.isTrue.equals(isPushScore)) {
+            AddExamScoreNoticeQueueReq req = new AddExamScoreNoticeQueueReq();
+            req.setRootOrgId(rootOrgId);
+
+            // 添加至成绩通知队列
+            examScoreNoticeQueueCloudService.addExamScoreNoticeQueue(req);
+            log.info("addExamScoreNoticeQueue ok, examRecordDataId:{}, studentId:{}", examRecordDataId, studentId);
+        }
+    }
+
+    private void afterSyncExamData(ExamRecordData examRecordDataCache) {
+        // 更新数据库的同步状态
+        examRecordDataRepo.updateSyncStatusById(SyncStatus.SYNCED, examRecordDataCache.getId());
+
+        // 考生已考完结次数累加
+        ExamBoss examBoss = examBossService.getExamBoss(examRecordDataCache.getExamStudentId());
+        if (null != examBoss) {
+            examBoss.setEndCount(examBoss.getEndCount() + 1);
+            examBossService.saveExamBoss(examRecordDataCache.getExamStudentId(), examBoss);
+            log.info("ExamBoss startCount:{}, endCount:{}, examRecordDataId:{}, studentId:{}", examBoss.getStartCount(),
+                    examBoss.getEndCount(), examRecordDataCache.getId(), examRecordDataCache.getStudentId());
+        }
+
+        this.clearUnusefulCacheData(examRecordDataCache);
+    }
+
+    /**
+     * 清理不再使用的缓存数据
+     */
+    private void clearUnusefulCacheData(ExamRecordData examRecordDataCache) {
+        if (SyncStatus.SYNCED != examRecordDataCache.getSyncStatus()) {
+            return;
+        }
+
+        Long studentId = examRecordDataCache.getStudentId();
+        Long examStudentId = examRecordDataCache.getExamStudentId();
+        Long examRecordDataId = examRecordDataCache.getId();
+
+        // 清除当前考生的考试次数相关缓存
+        final String examBossKey = RedisKeyHelper.getBuilder().examBossKey(examStudentId);
+        ExamBoss examBoss = redisClient.get(examBossKey, ExamBoss.class);
+        if (examBoss != null) {
+            if (examBoss.getStartCount() == examBoss.getEndCount()) {
+                // 如果开考次数 等于 考试完结次数,则删除该缓存
+                redisClient.delete(examBossKey);
+            } else {
+                // 否则仅移除当前的考试记录ID
+                List<Long> examRecordDataIds = examBoss.getExamRecordDataIds();
+                if (CollectionUtils.isNotEmpty(examRecordDataIds)) {
+                    examRecordDataIds.removeIf(e -> examRecordDataId.equals(e));
+                    examBoss.setExamRecordDataIds(examRecordDataIds);
+                    redisClient.set(examBossKey, examBoss, 2592000);
+                }
+            }
+        }
+
+        // 清除当前“考试活动时间”缓存
+        redisClient.delete(RedisKeyHelper.getBuilder().examingActiveTimeKey(examRecordDataId));
+
+        // 清除心跳缓存
+        redisClient.delete(RedisKeyHelper.getBuilder().examingHeartbeatKey(examRecordDataId));
+
+        // 清除当前学生的考试会话缓存
+        ExamingSession examingSession = examingSessionService.getExamingSession(studentId);
+        if (examingSession != null && examingSession.getExamRecordDataId().equals(examRecordDataId)) {
+            examingSessionService.deleteExamingSession(studentId);
+        }
+
+        // 清除试题作答记录、作答文件记录缓存
+        Integer questionCount = examRecordDataCache.getQuestionCount();
+        for (int n = 1; n <= questionCount; n++) {
+            redisClient.delete(RedisKeyHelper.getBuilder().studentAnswerKey(examRecordDataId, n));
+            redisClient.delete(RedisKeyHelper.getBuilder().studentFileAnswerKey(examRecordDataId, n));
+        }
+
+        // 清除“网考试卷结构”缓存
+        redisClient.delete(RedisKeyHelper.getBuilder().studentPaperKey(examRecordDataId));
+
+        // 清除“违纪非法请求数据”缓存
+        redisClient.delete(Constants.OE_DISCIPLINE_ILLEGAL_DATA + examRecordDataId);
+
+        // 清除“违纪非法考生端应用”缓存
+        redisClient.delete(Constants.OE_DISCIPLINE_ILLEGAL_CLIENT + examRecordDataId);
+
+        // 最后设置过期“当前考试记录”缓存
+        final String examRecordDataKey = RedisKeyHelper.getBuilder().examRecordDataKey(examRecordDataId);
+        redisClient.expire(examRecordDataKey, 180, TimeUnit.SECONDS);
+
+        log.warn("clearUnusefulCacheData finished, examRecordDataId:{}, studentId:{}", examRecordDataId, studentId);
+    }
+
+}

+ 1 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/AfterHandInExamExecutor.java

@@ -24,6 +24,7 @@ import java.util.List;
  * @Date 2019/12/17 16:39
  * @Version 1.0
  */
+@Deprecated
 @Component
 public class AfterHandInExamExecutor implements NodeExecuter<Long, ExamRecordData, Long, ExamRecordData> {
 

+ 1 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/ClearExamDataCacheExecutor.java

@@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit;
  * @Date 2019/12/24 18:07
  * @Version 1.0
  */
+@Deprecated
 @Component
 public class ClearExamDataCacheExecutor implements NodeExecuter<Long, ExamRecordData, Long, ExamRecordData> {
 

+ 1 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/DataGainExamExecutor.java

@@ -31,6 +31,7 @@ import java.util.List;
  * @Date 2019/12/17 16:39
  * @Version 1.0
  */
+@Deprecated
 @Component
 public class DataGainExamExecutor implements NodeExecuter<Long, ExamRecordData, Long, ExamRecordData> {
 

+ 1 - 0
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/HandInExamExecutor.java

@@ -34,6 +34,7 @@ import java.util.List;
  * @Date 2019/12/17 16:39
  * @Version 1.0
  */
+@Deprecated
 @Component
 public class HandInExamExecutor implements NodeExecuter<Long, ExamRecordData, Long, ExamRecordData> {
 

+ 18 - 355
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/pipeline/SyncExamDataExecutor.java

@@ -4,21 +4,15 @@ import cn.com.qmth.examcloud.commons.helpers.KeyValuePair;
 import cn.com.qmth.examcloud.commons.helpers.ObjectHolder;
 import cn.com.qmth.examcloud.commons.helpers.pipeline.NodeExecuter;
 import cn.com.qmth.examcloud.commons.helpers.pipeline.TaskContext;
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
 import cn.com.qmth.examcloud.core.oe.admin.api.ExamScoreNoticeQueueCloudService;
 import cn.com.qmth.examcloud.core.oe.admin.api.SyncExamDataCloudService;
-import cn.com.qmth.examcloud.core.oe.admin.api.bean.*;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamCaptureBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamSyncCaptureBean;
 import cn.com.qmth.examcloud.core.oe.admin.api.request.AddExamScoreNoticeQueueReq;
 import cn.com.qmth.examcloud.core.oe.admin.api.request.SyncExamDataReq;
 import cn.com.qmth.examcloud.core.oe.student.api.ExamRecordDataCloudService;
-import cn.com.qmth.examcloud.core.oe.student.api.bean.StuExamQuestionBean;
-import cn.com.qmth.examcloud.core.oe.student.api.request.*;
-import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamFaceLivenessVerifiesResp;
-import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamRecordPaperStructResp;
-import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamRecordQuestionsResp;
-import cn.com.qmth.examcloud.core.oe.student.api.response.GetFaceBiopsyResp;
-import cn.com.qmth.examcloud.core.oe.student.dao.*;
-import cn.com.qmth.examcloud.core.oe.student.dao.entity.*;
+import cn.com.qmth.examcloud.core.oe.student.api.request.UpdatePartialExamRecordReq;
+import cn.com.qmth.examcloud.core.oe.task.service.CommonService;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamBossService;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamRecordDataService;
 import cn.com.qmth.examcloud.support.Constants;
@@ -34,9 +28,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
-import org.springframework.util.CollectionUtils;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -45,24 +37,18 @@ import java.util.List;
  * @Date 2019/12/19 16:22
  * @Version 1.0
  */
+@Deprecated
 @Component
 public class SyncExamDataExecutor implements NodeExecuter<Long, ExamRecordData, Long, ExamRecordData> {
 
+    private static final Logger LOG = LoggerFactory.getLogger(SyncExamDataExecutor.class);
+
     @Autowired
     private SyncExamDataCloudService syncExamDataCloudService;
 
     @Autowired
     private ExamRecordDataService examRecordDataService;
 
-    @Autowired
-    private ExamCaptureRepo examCaptureRepo;
-
-    @Autowired
-    private ExamFaceLiveVerifyRepo examFaceLiveVerifyRepo;
-
-    @Autowired
-    private ExamSyncCaptureRepo examSyncCaptureRepo;
-
     @Autowired
     private ExamRecordDataCloudService examRecordDataCloudService;
 
@@ -73,24 +59,8 @@ public class SyncExamDataExecutor implements NodeExecuter<Long, ExamRecordData,
     private ExamBossService examBossService;
 
     @Autowired
-    private ExamContinuedRecordRepo examContinuedRecordRepo;
-
-    @Autowired
-    private ExamProcessRecordRepo examProcessRecordRepo;
+    private CommonService commonService;
 
-    private static final Logger LOG = LoggerFactory.getLogger(SyncExamDataExecutor.class);
-
-    /**
-     * 执行
-     *
-     * @param key
-     * @param examRecordData
-     * @param outList
-     * @param removable
-     * @param context
-     * @throws Exception
-     * @author WANGWEI
-     */
     @Override
     public void execute(Long key, ExamRecordData examRecordData,
                         List<KeyValuePair<Long, ExamRecordData>> outList,
@@ -135,22 +105,22 @@ public class SyncExamDataExecutor implements NodeExecuter<Long, ExamRecordData,
                 //同步数据
                 SyncExamDataReq syncReq = new SyncExamDataReq();
 
-                syncReq.setExamRecordPaperStruct(getExamRecordPaperStruct(examRecordDataId));
+                syncReq.setExamRecordPaperStruct(commonService.getExamRecordPaperStruct(examRecordDataId));
 
-                syncReq.setExamRecordQuestions(getExamRecordQuestions(examRecordDataId));
+                syncReq.setExamRecordQuestions(commonService.getExamRecordQuestions(examRecordDataId));
 
-                syncReq.setExamContinuedRecords(getExamContinuedRecords(examRecordDataId));
+                syncReq.setExamContinuedRecords(commonService.getExamContinuedRecords(examRecordDataId));
 
-                syncReq.setExamProcessRecords(getExamProcessRecords(examRecordDataId));
+                syncReq.setExamProcessRecords(commonService.getExamProcessRecords(examRecordDataId));
 
                 //开启人脸检测相关数据赋值
                 Long rootOrgId = examRecordData.getRootOrgId();
                 Long examId = examRecordData.getExamId();
                 if (FaceBiopsyHelper.isFaceEnable(rootOrgId, examId, studentId)) {
-                    List<ExamCaptureBean> captures = getExamCaptures(examRecordDataId);
+                    List<ExamCaptureBean> captures = commonService.getExamCaptures(examRecordDataId);
                     syncReq.setExamCaptures(captures);
 
-                    ExamSyncCaptureBean syncCapture = getExamSyncCapture(examRecordDataId);
+                    ExamSyncCaptureBean syncCapture = commonService.getExamSyncCapture(examRecordDataId);
                     syncReq.setExamSyncCapture(syncCapture);
 
                     //虚拟摄像头进入待审核,且有虚拟摄像头的或者同步没有照片的,更新缓存
@@ -161,15 +131,15 @@ public class SyncExamDataExecutor implements NodeExecuter<Long, ExamRecordData,
                     }
 
                     if (FaceBiopsyHelper.isFaceVerify(rootOrgId, examId, studentId)) {
-                        syncReq.setExamFaceLivenessVerifies(getExamFaceLivenessVerifies(examRecordDataId));
+                        syncReq.setExamFaceLivenessVerifies(commonService.getExamFaceLivenessVerifies(examRecordDataId));
 
-                        syncReq.setFaceBiopsy(getFaceBiopsy(examRecordDataId));
+                        syncReq.setFaceBiopsy(commonService.getFaceBiopsy(examRecordDataId));
 
-                        syncReq.setFaceLiveVerifyRecords(loadFaceLiveVerifyRecords(examRecordDataId));
+                        syncReq.setFaceLiveVerifyRecords(commonService.getFaceLiveVerifyRecords(examRecordDataId));
                     }
                 }
 
-                syncReq.setExamRecordData(copyExamRecordDataFrom(examRecordData));
+                syncReq.setExamRecordData(commonService.copyExamRecordDataFrom(examRecordData));
 
                 long startTime = System.currentTimeMillis();
                 //同步数据
@@ -225,313 +195,6 @@ public class SyncExamDataExecutor implements NodeExecuter<Long, ExamRecordData,
         }
     }
 
-    private List<ExamContinuedRecordBean> getExamContinuedRecords(Long examRecordDataId) {
-        List<ExamContinuedRecordEntity> entityList = examContinuedRecordRepo.findByExamRecordDataId(examRecordDataId);
-        if (null == entityList || entityList.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        List<ExamContinuedRecordBean> resultList = new ArrayList<>();
-        for (ExamContinuedRecordEntity entity : entityList) {
-            ExamContinuedRecordBean bean = new ExamContinuedRecordBean();
-            bean.setId(entity.getId());
-            bean.setExamRecordDataId(entity.getExamRecordDataId());
-            bean.setContinuedTime(entity.getContinuedTime());
-            bean.setStartTime(entity.getStartTime());
-            bean.setUsedExamTime(entity.getUsedExamTime());
-
-            resultList.add(bean);
-        }
-
-        return resultList;
-    }
-
-    private List<ExamProcessRecordBean> getExamProcessRecords(Long examRecordDataId) {
-        List<ExamProcessRecordEntity> entityList = examProcessRecordRepo.findByExamRecordDataId(examRecordDataId);
-        if (null == entityList || entityList.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        List<ExamProcessRecordBean> resultList = new ArrayList<>();
-        for (ExamProcessRecordEntity entity : entityList) {
-            ExamProcessRecordBean bean = new ExamProcessRecordBean();
-            bean.setId(entity.getId());
-            bean.setExamRecordDataId(entity.getExamRecordDataId());
-            bean.setProcessName(entity.getProcessName());
-            bean.setRecordTime(entity.getRecordTime());
-            bean.setSourceIp(entity.getSourceIp());
-
-            resultList.add(bean);
-        }
-
-        return resultList;
-    }
-
-    private FaceBiopsyBean getFaceBiopsy(Long examRecordDataId) {
-        GetFaceBiopsyReq req = new GetFaceBiopsyReq();
-        req.setExamRecordDataId(examRecordDataId);
-
-        GetFaceBiopsyResp resp = examRecordDataCloudService.getFaceBiopsy(req);
-
-        if (null == resp.getFaceBiopsyBean()) {
-            return null;
-        }
-
-        cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyBean faceBiopsyBean = resp.getFaceBiopsyBean();
-
-        FaceBiopsyBean bean = new FaceBiopsyBean();
-        bean.setErrorMsg(faceBiopsyBean.getErrorMsg());
-        bean.setExamRecordDataId(faceBiopsyBean.getExamRecordDataId());
-        bean.setResult(faceBiopsyBean.getResult());
-        bean.setRootOrgId(faceBiopsyBean.getRootOrgId());
-        bean.setVerifiedTimes(faceBiopsyBean.getVerifiedTimes());
-
-        List<FaceBiopsyItemBean> faceBiopsyItemList = new ArrayList<>();
-
-        for (cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyItemBean faceBiopsyItemBean : faceBiopsyBean.getFaceBiopsyItems()) {
-            FaceBiopsyItemBean itemBean = new FaceBiopsyItemBean();
-
-            itemBean.setCompleted(faceBiopsyItemBean.getCompleted());
-            itemBean.setErrorMsg(faceBiopsyItemBean.getErrorMsg());
-            itemBean.setExamRecordDataId(faceBiopsyItemBean.getExamRecordDataId());
-            itemBean.setFaceBiopsyId(faceBiopsyItemBean.getFaceBiopsyId());
-            itemBean.setFaceBiopsyType(faceBiopsyItemBean.getFaceBiopsyType());
-            itemBean.setInFreezeTime(faceBiopsyItemBean.getInFreezeTime());
-            itemBean.setResult(faceBiopsyItemBean.getResult());
-
-            List<FaceBiopsyItemStepBean> itemStepList = new ArrayList<>();
-            for (cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyItemStepBean faceBiopsyItemStepBean : faceBiopsyItemBean.getFaceBiopsyItemSteps()) {
-                FaceBiopsyItemStepBean itemStepBean = new FaceBiopsyItemStepBean();
-                itemStepBean.setAction(faceBiopsyItemStepBean.getAction());
-                itemStepBean.setActionStay(faceBiopsyItemStepBean.getActionStay());
-                itemStepBean.setErrorMsg(faceBiopsyItemStepBean.getErrorMsg());
-                itemStepBean.setExamRecordDataId(faceBiopsyItemStepBean.getExamRecordDataId());
-                itemStepBean.setFaceBiopsyItemId(faceBiopsyItemStepBean.getFaceBiopsyItemId());
-                itemStepBean.setResourceRelativePath(faceBiopsyItemStepBean.getResourceRelativePath());
-                itemStepBean.setResourceType(faceBiopsyItemStepBean.getResourceType());
-                itemStepBean.setResult(faceBiopsyItemStepBean.getResult());
-
-                itemStepList.add(itemStepBean);
-            }
-
-            itemBean.setFaceBiopsyItemSteps(itemStepList);
-
-        }
-
-        bean.setFaceBiopsyItems(faceBiopsyItemList);
-
-        return bean;
-
-    }
-
-    private List<FaceLiveVerifyBean> loadFaceLiveVerifyRecords(Long examRecordDataId) {
-        List<ExamFaceLiveVerifyEntity> entities = examFaceLiveVerifyRepo.findByExamRecordDataIdAndFinished(examRecordDataId, true);
-        if (CollectionUtils.isEmpty(entities)) {
-            return null;
-        }
-
-        List<FaceLiveVerifyBean> faceLiveVerifyRecords = new ArrayList<>();
-        for (ExamFaceLiveVerifyEntity entity : entities) {
-            FaceLiveVerifyBean bean = new FaceLiveVerifyBean();
-            bean.setExamRecordDataId(entity.getExamRecordDataId());
-            bean.setStatus(entity.getStatus().name());
-            bean.setFaceCount(entity.getFaceCount());
-            bean.setSimilarity(entity.getSimilarity());
-            bean.setRealness(entity.getRealness());
-            bean.setErrorMsg(entity.getErrorMsg());
-            bean.setProcessTime(entity.getProcessTime());
-            bean.setActions(entity.getActions());
-            bean.setCreationTime(entity.getCreationTime());
-            bean.setUpdateTime(entity.getUpdateTime());
-            faceLiveVerifyRecords.add(bean);
-        }
-
-        return faceLiveVerifyRecords;
-    }
-
-    private ExamSyncCaptureBean getExamSyncCapture(Long examRecordDataId) {
-        ExamSyncCaptureEntity entity = examSyncCaptureRepo.findByExamRecordDataId(examRecordDataId);
-        if (null == entity) {
-            return null;
-        }
-
-        ExamSyncCaptureBean bean = new ExamSyncCaptureBean();
-        bean.setId(entity.getId());
-        bean.setExamRecordDataId(entity.getExamRecordDataId());
-        bean.setFileUrl(entity.getFileUrl());
-        bean.setFileName(entity.getFileName());
-        bean.setPass(entity.getIsPass());
-        bean.setFaceCompareResult(entity.getFaceCompareResult());
-        bean.setStranger(entity.getIsStranger());
-        bean.setLandmark(entity.getLandmark());
-        bean.setFacelivenessResult(entity.getFacelivenessResult());
-        bean.setUsedTime(entity.getUsedTime());
-        bean.setProcessTime(entity.getProcessTime());
-        bean.setHasVirtualCamera(entity.getHasVirtualCamera());
-        bean.setCameraInfos(entity.getCameraInfos());
-        bean.setExtMsg(entity.getExtMsg());
-
-        return bean;
-    }
-
-    private ExamRecordQuestionsBean getExamRecordQuestions(Long examRecordDataId) {
-        GetExamRecordQuestionsReq req = new GetExamRecordQuestionsReq();
-        req.setExamRecordDataId(examRecordDataId);
-        GetExamRecordQuestionsResp resp = examRecordDataCloudService.getExamRecordQuestions(req);
-
-        ExamRecordQuestionsBean bean = new ExamRecordQuestionsBean();
-        bean.setCreationTime(resp.getCreationTime());
-        bean.setExamRecordDataId(resp.getExamRecordDataId());
-
-        List<ExamQuestionBean> examQuestionBeanList = new ArrayList<>();
-        for (StuExamQuestionBean stuExamQuestionBean : resp.getExamQuestions()) {
-            ExamQuestionBean examQuestionBean = new ExamQuestionBean();
-            examQuestionBean.setExamRecordDataId(stuExamQuestionBean.getExamRecordDataId());
-            examQuestionBean.setMainNumber(stuExamQuestionBean.getMainNumber());
-            examQuestionBean.setQuestionId(stuExamQuestionBean.getQuestionId());
-            examQuestionBean.setOrder(stuExamQuestionBean.getOrder());
-            examQuestionBean.setQuestionScore(stuExamQuestionBean.getQuestionScore());
-            examQuestionBean.setQuestionType(stuExamQuestionBean.getQuestionType());
-            examQuestionBean.setCorrectAnswer(stuExamQuestionBean.getCorrectAnswer());
-            examQuestionBean.setStudentAnswer(stuExamQuestionBean.getStudentAnswer());
-            examQuestionBean.setStudentScore(stuExamQuestionBean.getStudentScore());
-            examQuestionBean.setAnswer(stuExamQuestionBean.getAnswer());
-            examQuestionBean.setSign(stuExamQuestionBean.getSign());
-            examQuestionBean.setOptionPermutation(stuExamQuestionBean.getOptionPermutation());
-            examQuestionBean.setAudioPlayTimes(stuExamQuestionBean.getAudioPlayTimes());
-
-            if (null != stuExamQuestionBean.getAnswerType()) {
-                examQuestionBean.setAnswerType(stuExamQuestionBean.getAnswerType().name());
-            }
-
-            examQuestionBeanList.add(examQuestionBean);
-        }
-
-        bean.setExamQuestionBeans(examQuestionBeanList);
-
-        return bean;
-    }
-
-    private ExamRecordPaperStructBean getExamRecordPaperStruct(Long examRecordDataId) {
-        GetExamRecordPaperStructReq req = new GetExamRecordPaperStructReq();
-        req.setExamRecordDataId(examRecordDataId);
-        GetExamRecordPaperStructResp resp = examRecordDataCloudService.getExamRecordPaperStruct(req);
-
-        ExamRecordPaperStructBean bean = new ExamRecordPaperStructBean();
-        bean.setId(resp.getId());
-        bean.setDefaultPaper(resp.getDefaultPaper());
-
-        return bean;
-    }
-
-    private List<ExamFaceLivenessVerifyBean> getExamFaceLivenessVerifies(Long examRecordDataId) {
-        GetExamFaceLivenessVerifiesReq req = new GetExamFaceLivenessVerifiesReq();
-        req.setExamRecordDataId(examRecordDataId);
-
-        GetExamFaceLivenessVerifiesResp resp = examRecordDataCloudService.getExamFaceLivenessVerifies(req);
-        if (null == resp.getExamFaceLivenessVerifis() || resp.getExamFaceLivenessVerifis().isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        debugLog("the result joson of method getExamFaceLivenessVerifies is :" + JsonUtil.toJson(resp), examRecordDataId);
-
-        List<ExamFaceLivenessVerifyBean> resultList = new ArrayList<>();
-        for (cn.com.qmth.examcloud.core.oe.student.api.bean.ExamFaceLivenessVerifyBean eflvb : resp.getExamFaceLivenessVerifis()) {
-            ExamFaceLivenessVerifyBean bean = new ExamFaceLivenessVerifyBean();
-            bean.setId(eflvb.getId());
-            bean.setExamRecordDataId(eflvb.getExamRecordDataId());
-            bean.setStartTime(eflvb.getStartTime());
-            bean.setUsedTime(eflvb.getUsedTime());
-            bean.setResultJson(eflvb.getResultJson());
-            bean.setVerifyResult(eflvb.getVerifyResult());
-            bean.setBizId(eflvb.getBizId());
-            bean.setIsError(eflvb.getIsError());
-            bean.setErrorMsg(eflvb.getErrorMsg());
-            bean.setOperateNum(eflvb.getOperateNum());
-
-            resultList.add(bean);
-        }
-
-        return resultList;
-    }
-
-    private List<ExamCaptureBean> getExamCaptures(Long examRecordDataId) {
-        List<ExamCaptureEntity> entityList = examCaptureRepo.findByExamRecordDataId(examRecordDataId);
-        if (null == entityList || entityList.isEmpty()) {
-            return new ArrayList<>();
-        }
-
-        List<ExamCaptureBean> resultList = new ArrayList<>();
-        for (ExamCaptureEntity entity : entityList) {
-            ExamCaptureBean bean = new ExamCaptureBean();
-
-            bean.setId(entity.getId());
-            bean.setExamRecordDataId(entity.getExamRecordDataId());
-            bean.setFileUrl(entity.getFileUrl());
-            bean.setFileName(entity.getFileName());
-            bean.setPass(entity.getIsPass());
-            bean.setFaceCompareResult(entity.getFaceCompareResult());
-            bean.setStranger(entity.getIsStranger());
-            bean.setLandmark(entity.getLandmark());
-            bean.setFacelivenessResult(entity.getFacelivenessResult());
-            bean.setUsedTime(entity.getUsedTime());
-            bean.setProcessTime(entity.getProcessTime());
-            bean.setHasVirtualCamera(entity.getHasVirtualCamera());
-            bean.setCameraInfos(entity.getCameraInfos());
-            bean.setExtMsg(entity.getExtMsg());
-
-            resultList.add(bean);
-        }
-
-        return resultList;
-    }
-
-    private ExamRecordDataBean copyExamRecordDataFrom(ExamRecordData examRecordData) {
-        ExamRecordDataBean data = new ExamRecordDataBean();
-        data.setId(examRecordData.getId());
-        data.setExamId(examRecordData.getExamId());
-        data.setExamType(examRecordData.getExamType() == null ? null : examRecordData.getExamType().toString());
-        data.setExamStudentId(examRecordData.getExamStudentId());
-        data.setStudentId(examRecordData.getStudentId());
-        data.setCourseId(examRecordData.getCourseId());
-        data.setOrgId(examRecordData.getOrgId());
-        data.setRootOrgId(examRecordData.getRootOrgId());
-        data.setBasePaperId(examRecordData.getBasePaperId());
-        data.setPaperType(examRecordData.getPaperType());
-        data.setExamRecordStatus(examRecordData.getExamRecordStatus() == null ? null : examRecordData.getExamRecordStatus().toString());
-        data.setStartTime(examRecordData.getStartTime());
-        data.setEndTime(examRecordData.getEndTime());
-        data.setCleanTime(examRecordData.getCleanTime());
-        data.setWarn(examRecordData.getIsWarn() == null ? false : examRecordData.getIsWarn());
-        data.setAudit(examRecordData.getIsAudit() == null ? false : examRecordData.getIsAudit());
-        data.setIllegality(examRecordData.getIsIllegality() == null ? false : examRecordData.getIsIllegality());
-        data.setUsedExamTime(examRecordData.getUsedExamTime());
-        data.setContinued(examRecordData.getIsContinued() == null ? false : examRecordData.getIsContinued());
-        data.setContinuedCount(examRecordData.getContinuedCount());
-        data.setExceed(examRecordData.getIsExceed() == null ? false : examRecordData.getIsExceed());
-        data.setFaceSuccessCount(examRecordData.getFaceSuccessCount());
-        data.setFaceFailedCount(examRecordData.getFaceFailedCount());
-        data.setFaceStrangerCount(examRecordData.getFaceStrangerCount());
-        data.setFaceTotalCount(examRecordData.getFaceTotalCount());
-        data.setFaceSuccessPercent(examRecordData.getFaceSuccessPercent());
-        data.setFaceVerifyResult(examRecordData.getFaceVerifyResult() == null ? null : examRecordData.getFaceVerifyResult().toString());
-        data.setBaiduFaceLivenessSuccessPercent(examRecordData.getBaiduFaceLivenessSuccessPercent());
-        data.setTotalScore(examRecordData.getTotalScore());
-        data.setObjectiveScore(examRecordData.getObjectiveScore());
-        data.setObjectiveAccuracy(examRecordData.getObjectiveAccuracy());
-        data.setSubjectiveScore(examRecordData.getSubjectiveScore());
-        data.setSuccPercent(examRecordData.getSuccPercent());
-        data.setAllObjectivePaper(examRecordData.getIsAllObjectivePaper());
-        data.setExamStageId(examRecordData.getExamStageId());
-        data.setExamStageOrder(examRecordData.getExamStageOrder());
-        data.setLastActiveTime(examRecordData.getLastActiveTime());
-        data.setContinuedTime(examRecordData.getContinuedTime());
-        data.setEnterExamTime(examRecordData.getEnterExamTime());
-        data.setSwitchScreenCount(examRecordData.getSwitchScreenCount());
-        data.setExceedMaxSwitchScreenCount(examRecordData.getExceedMaxSwitchScreenCount());
-        return data;
-    }
-
     /**
      * 设置并保存考试记录的同步状态
      *

+ 7 - 8
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/worker/BaiduFaceLivenessWorker.java

@@ -4,10 +4,9 @@ import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.helpers.concurrency.simple.Worker;
 import cn.com.qmth.examcloud.commons.helpers.concurrency.simple.WorkerController;
 import cn.com.qmth.examcloud.commons.util.Util;
-import cn.com.qmth.examcloud.core.oe.task.base.ExamCaptureProcessStatisticController;
 import cn.com.qmth.examcloud.core.oe.student.dao.enums.ExamCaptureQueueStatus;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureQueueService;
-import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureService;
+import cn.com.qmth.examcloud.core.oe.task.service.FaceVerifyService;
 import cn.com.qmth.examcloud.core.oe.task.service.bean.ExamCaptureQueueInfo;
 import cn.com.qmth.examcloud.support.Constants;
 import cn.com.qmth.examcloud.web.support.SpringContextHolder;
@@ -30,13 +29,13 @@ public class BaiduFaceLivenessWorker implements Worker<ExamCaptureQueueInfo> {
     public void process(WorkerController controller, ExamCaptureQueueInfo element) {
         ThreadContext.put("TRACE_ID", "Q_" + element.getId());
         ThreadContext.put("CALLER", "BAIDU_WORKER");
-        ExamCaptureProcessStatisticController.increaseFaceLivenessDetectCount();
+        FaceVerifyCounter.increaseFaceLivenessDetectCount();
         if (log.isDebugEnabled()) {
             log.debug("[BAIDU_FACELIVENESS_WORKER.] 图片处理数量+1,count= "
-                    + ExamCaptureProcessStatisticController.getFaceLivenessDetectCount());
+                    + FaceVerifyCounter.getFaceLivenessDetectCount());
         }
-        ExamCaptureService examCaptureService = SpringContextHolder
-                .getBean(ExamCaptureService.class);
+
+        FaceVerifyService examCaptureService = SpringContextHolder.getBean(FaceVerifyService.class);
         ExamCaptureQueueService examCaptureQueueService = SpringContextHolder
                 .getBean(ExamCaptureQueueService.class);
         try {
@@ -56,12 +55,12 @@ public class BaiduFaceLivenessWorker implements Worker<ExamCaptureQueueInfo> {
                 controller.addConcurrencyWarn();
                 log.debug("[BAIDU_FACELIVENESS_WORKER.] 超过并发次数 ");
             } else {
-                ExamCaptureProcessStatisticController.increaseFaceLivenessDetectCount();
+                FaceVerifyCounter.increaseFaceLivenessDetectCount();
                 log.error("[BAIDU_FACELIVENESS_WORKER.] 自定义异常 " + e.getDesc(), e);
             }
             Util.sleep(TimeUnit.MICROSECONDS, 50);
         } catch (Exception e) {
-            ExamCaptureProcessStatisticController.increaseFaceLivenessDetectCount();
+            FaceVerifyCounter.increaseFaceLivenessDetectCount();
             while (true) {
                 // 异常处理
                 if (examCaptureQueueService.saveExamCaptureQueueEntityByFailed(element.getId(), "系统异常",

+ 9 - 10
examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/worker/FacePPCompareWorker.java

@@ -4,10 +4,9 @@ import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.helpers.concurrency.simple.Worker;
 import cn.com.qmth.examcloud.commons.helpers.concurrency.simple.WorkerController;
 import cn.com.qmth.examcloud.commons.util.Util;
-import cn.com.qmth.examcloud.core.oe.task.base.ExamCaptureProcessStatisticController;
 import cn.com.qmth.examcloud.core.oe.student.dao.enums.ExamCaptureQueueStatus;
 import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureQueueService;
-import cn.com.qmth.examcloud.core.oe.task.service.ExamCaptureService;
+import cn.com.qmth.examcloud.core.oe.task.service.FaceVerifyService;
 import cn.com.qmth.examcloud.core.oe.task.service.bean.ExamCaptureQueueInfo;
 import cn.com.qmth.examcloud.support.Constants;
 import cn.com.qmth.examcloud.web.support.SpringContextHolder;
@@ -32,13 +31,13 @@ public class FacePPCompareWorker implements Worker<ExamCaptureQueueInfo> {
         ThreadContext.put("TRACE_ID", "Q_" + element.getId());
         ThreadContext.put("CALLER", "FACEPP_WORKER");
         // 图片处理数量+1
-        ExamCaptureProcessStatisticController.increaseFaceCompareCount();
+        FaceVerifyCounter.increaseFaceCompareCount();
         if (log.isDebugEnabled()) {
             log.debug("[FACEPP_COMPARE_WORKER.] 图片处理数量+1,count= "
-                    + ExamCaptureProcessStatisticController.getFaceCompareCount());
+                    + FaceVerifyCounter.getFaceCompareCount());
         }
-        ExamCaptureService examCaptureService = SpringContextHolder
-                .getBean(ExamCaptureService.class);
+        FaceVerifyService examCaptureService = SpringContextHolder.getBean(FaceVerifyService.class);
+
         ExamCaptureQueueService examCaptureQueueService = SpringContextHolder
                 .getBean(ExamCaptureQueueService.class);
         try {
@@ -59,14 +58,14 @@ public class FacePPCompareWorker implements Worker<ExamCaptureQueueInfo> {
                 controller.addConcurrencyWarn();
                 log.debug("[FACEPP_COMPARE_WORKER.] 超过并发次数 ");
             } else {
-                ExamCaptureProcessStatisticController.increaseFaceCompareFailedCount();
+                FaceVerifyCounter.increaseFaceCompareFailedCount();
                 log.error("[FACEPP_COMPARE_WORKER.] 自定义异常 " +
-                        ",failedCount=" + ExamCaptureProcessStatisticController.getFaceCompareFailedCount(), e);
+                        ",failedCount=" + FaceVerifyCounter.getFaceCompareFailedCount(), e);
             }
 
             Util.sleep(TimeUnit.MICROSECONDS, 50);
         } catch (Exception e) {
-            ExamCaptureProcessStatisticController.increaseFaceCompareFailedCount();
+            FaceVerifyCounter.increaseFaceCompareFailedCount();
             // 异常处理
             while (true) {
                 // 异常处理
@@ -77,7 +76,7 @@ public class FacePPCompareWorker implements Worker<ExamCaptureQueueInfo> {
                 Util.sleep(TimeUnit.MILLISECONDS, 500);
             }
             log.error("[FACEPP_COMPARE_WORKER.] 系统异常 " +
-                    ",failedCount=" + ExamCaptureProcessStatisticController.getFaceCompareFailedCount(), e);
+                    ",failedCount=" + FaceVerifyCounter.getFaceCompareFailedCount(), e);
             Util.sleep(TimeUnit.MICROSECONDS, 50);
         } finally {
             ThreadContext.clearAll();

+ 2 - 2
examcloud-core-oe-task-base/src/main/java/cn/com/qmth/examcloud/core/oe/task/base/ExamCaptureProcessStatisticController.java → examcloud-core-oe-task-service/src/main/java/cn/com/qmth/examcloud/core/oe/task/service/worker/FaceVerifyCounter.java

@@ -1,4 +1,4 @@
-package cn.com.qmth.examcloud.core.oe.task.base;
+package cn.com.qmth.examcloud.core.oe.task.service.worker;
 
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -8,7 +8,7 @@ import java.util.concurrent.atomic.AtomicInteger;
  * @Date 2019/9/9 17:05
  * @Version 1.0
  */
-public class ExamCaptureProcessStatisticController {
+public class FaceVerifyCounter {
 
     //人脸比对处理数量
     private static AtomicInteger faceCompareCount = new AtomicInteger(0);

+ 1 - 1
examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/OETaskApp.java

@@ -1,6 +1,6 @@
 package cn.com.qmth.examcloud.core.oe.task.starter;
 
-import cn.com.qmth.examcloud.core.oe.task.base.UniqueRuleHolder;
+import cn.com.qmth.examcloud.core.oe.student.dao.UniqueRuleHolder;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 import cn.com.qmth.examcloud.web.jpa.DataIntegrityViolationTransverter;
 import org.springframework.boot.SpringApplication;

+ 0 - 12
examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/Tester.java

@@ -1,12 +0,0 @@
-package cn.com.qmth.examcloud.core.oe.task.starter;
-
-import org.springframework.stereotype.Component;
-
-@Component
-public class Tester {
-
-    public void test() {
-
-    }
-
-}

+ 56 - 0
examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/JobConfig.java

@@ -0,0 +1,56 @@
+package cn.com.qmth.examcloud.core.oe.task.starter.config;
+
+import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JobConfig {
+
+    private static final Logger log = LoggerFactory.getLogger(JobConfig.class);
+
+    @Value("${xxl.job.admin.addresses}")
+    private String adminAddresses;
+
+    @Value("${xxl.job.accessToken}")
+    private String accessToken;
+
+    @Value("${xxl.job.executor.appname}")
+    private String appname;
+
+    @Value("${xxl.job.executor.address}")
+    private String address;
+
+    @Value("${xxl.job.executor.ip}")
+    private String ip;
+
+    @Value("${xxl.job.executor.port}")
+    private int port;
+
+    @Value("${xxl.job.executor.logpath}")
+    private String logPath;
+
+    @Value("${xxl.job.executor.logretentiondays}")
+    private int logRetentionDays;
+
+    @Bean
+    public XxlJobSpringExecutor xxlJobExecutor() {
+        log.info("job config init..");
+
+        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
+        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
+        xxlJobSpringExecutor.setAppname(appname);
+        xxlJobSpringExecutor.setAddress(address);
+        xxlJobSpringExecutor.setIp(ip);
+        xxlJobSpringExecutor.setPort(port);
+        xxlJobSpringExecutor.setAccessToken(accessToken);
+        xxlJobSpringExecutor.setLogPath(logPath);
+        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
+
+        return xxlJobSpringExecutor;
+    }
+
+}

+ 66 - 0
examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/OeTaskExecutor.java

@@ -0,0 +1,66 @@
+package cn.com.qmth.examcloud.core.oe.task.starter.config;
+
+import cn.com.qmth.examcloud.core.oe.task.service.job.AfterHandInExamJobHandler;
+import cn.com.qmth.examcloud.core.oe.task.service.job.BeforeHandInExamJobHandler;
+import cn.com.qmth.examcloud.core.oe.task.service.job.SyncExamRecordDataJobHandler;
+import com.xxl.job.core.context.XxlJobHelper;
+import com.xxl.job.core.handler.annotation.XxlJob;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 网考任务执行器
+ */
+@Component
+public class OeTaskExecutor {
+
+    private static final Logger log = LoggerFactory.getLogger(OeTaskExecutor.class);
+
+    @Autowired
+    private BeforeHandInExamJobHandler beforeHandInExamJobHandler;
+
+    @Autowired
+    private AfterHandInExamJobHandler afterHandInExamJobHandler;
+
+    @Autowired
+    private SyncExamRecordDataJobHandler syncExamRecordDataJobHandler;
+
+    /**
+     * 1、处理交卷前考试数据任务
+     */
+    @XxlJob("beforeHandInExamJobHandler")
+    public void beforeHandInExamJobHandler() throws Exception {
+        int shardIndex = XxlJobHelper.getShardIndex();
+        int shardTotal = XxlJobHelper.getShardTotal();
+        String jobParam = XxlJobHelper.getJobParam();
+        XxlJobHelper.log("shardTotal:{}, shardIndex:{}", shardTotal, shardIndex);
+        beforeHandInExamJobHandler.run(shardTotal, shardIndex, jobParam);
+    }
+
+    /**
+     * 2、处理交卷后考试数据任务
+     */
+    @XxlJob("afterHandInExamJobHandler")
+    public void afterHandInExamJobHandler() throws Exception {
+        int shardIndex = XxlJobHelper.getShardIndex();
+        int shardTotal = XxlJobHelper.getShardTotal();
+        String jobParam = XxlJobHelper.getJobParam();
+        XxlJobHelper.log("shardTotal:{}, shardIndex:{}", shardTotal, shardIndex);
+        afterHandInExamJobHandler.run(shardTotal, shardIndex, jobParam);
+    }
+
+    /**
+     * 3、同步考试记录数据任务
+     */
+    @XxlJob("syncExamRecordDataJobHandler")
+    public void syncExamRecordDataJobHandler() throws Exception {
+        int shardIndex = XxlJobHelper.getShardIndex();
+        int shardTotal = XxlJobHelper.getShardTotal();
+        String jobParam = XxlJobHelper.getJobParam();
+        XxlJobHelper.log("shardTotal:{}, shardIndex:{}", shardTotal, shardIndex);
+        syncExamRecordDataJobHandler.run(shardTotal, shardIndex, jobParam);
+    }
+
+}

+ 15 - 15
examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/ProcessBaiduFaceLivenessAlarmTask.java

@@ -2,7 +2,7 @@ package cn.com.qmth.examcloud.core.oe.task.starter.config;
 
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.Util;
-import cn.com.qmth.examcloud.core.oe.task.base.ExamCaptureProcessStatisticController;
+import cn.com.qmth.examcloud.core.oe.task.service.worker.FaceVerifyCounter;
 import cn.com.qmth.examcloud.exchange.inner.api.SmsCloudService;
 import cn.com.qmth.examcloud.exchange.inner.api.request.SendSmsReq;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
@@ -76,9 +76,9 @@ public class ProcessBaiduFaceLivenessAlarmTask implements ApplicationRunner {
         if (LOG.isDebugEnabled()) {
             LOG.debug("[FACE_LIVENESS_ALARM] 进入活体检测" + System.currentTimeMillis()
                     + "....totalCount="
-                    + ExamCaptureProcessStatisticController.getFaceLivenessDetectCount()
+                    + FaceVerifyCounter.getFaceLivenessDetectCount()
                     + " ,failCount="
-                    + ExamCaptureProcessStatisticController.getFaceLivenessDetectFailedCount());
+                    + FaceVerifyCounter.getFaceLivenessDetectFailedCount());
         }
 
         // 默认每分钟失败率超过50%则发短信报警,且总数不少于10次则短信报警
@@ -87,18 +87,18 @@ public class ProcessBaiduFaceLivenessAlarmTask implements ApplicationRunner {
                     .getSysProperty("capture.faceLiveness.smsAssemblyCode");
             if (!faceLivenessSmsAssemblyCodeProperty.getHasValue()) {
                 LOG.error("[FACE_LIVENESS_ALARM.] 未配置人脸比对的短信模板代码,totalCount="
-                        + ExamCaptureProcessStatisticController.getFaceCompareCount()
+                        + FaceVerifyCounter.getFaceCompareCount()
                         + ",errorCount="
-                        + ExamCaptureProcessStatisticController.getFaceCompareFailedCount());
+                        + FaceVerifyCounter.getFaceCompareFailedCount());
                 throw new StatusException("300003", "未配置人脸活体检测的短信模板代码");
             }
             SysPropertyCacheBean smsPhoneProperty = CacheHelper
                     .getSysProperty("capture.sms.phones");
             if (!smsPhoneProperty.getHasValue()) {
                 LOG.error("[FACE_LIVENESS_ALARM.] 未配置图片处理失败的通知手机号,totalCount="
-                        + ExamCaptureProcessStatisticController.getFaceCompareCount()
+                        + FaceVerifyCounter.getFaceCompareCount()
                         + ",errorCount="
-                        + ExamCaptureProcessStatisticController.getFaceCompareFailedCount());
+                        + FaceVerifyCounter.getFaceCompareFailedCount());
                 throw new StatusException("300004", "未配置图片处理失败的通知手机号");
             }
 
@@ -111,9 +111,9 @@ public class ProcessBaiduFaceLivenessAlarmTask implements ApplicationRunner {
 
             HashMap<String, String> params = new HashMap<>();
             params.put("totalCount", String
-                    .valueOf(ExamCaptureProcessStatisticController.getFaceLivenessDetectCount()));
+                    .valueOf(FaceVerifyCounter.getFaceLivenessDetectCount()));
             params.put("errorCount", String.valueOf(
-                    ExamCaptureProcessStatisticController.getFaceLivenessDetectFailedCount()));
+                    FaceVerifyCounter.getFaceLivenessDetectFailedCount()));
             sendSmsReq.setParams(params);
             try {
                 smsCloudService.sendSms(sendSmsReq);
@@ -122,7 +122,7 @@ public class ProcessBaiduFaceLivenessAlarmTask implements ApplicationRunner {
             }
         }
         // 每1分钟重置一次总数量与失败数量
-        ExamCaptureProcessStatisticController.resetAllFaceLivenessDetectCount();
+        FaceVerifyCounter.resetAllFaceLivenessDetectCount();
 
     }
 
@@ -132,15 +132,15 @@ public class ProcessBaiduFaceLivenessAlarmTask implements ApplicationRunner {
      * @return boolean
      */
     private boolean needSmsAlarm() {
-        if (ExamCaptureProcessStatisticController.getFaceCompareCount() == 0) {
+        if (FaceVerifyCounter.getFaceCompareCount() == 0) {
             return false;
         }
         SysPropertyCacheBean expressionProperty = CacheHelper.getSysProperty("capture.baidu.expression.alarm");
         if (expressionProperty.getHasValue()) {
             String expression = expressionProperty.getValue().toString();
             Map<String, Object> env = Maps.newHashMap();
-            env.put("totalCount", ExamCaptureProcessStatisticController.getFaceLivenessDetectCount());
-            env.put("failedCount", ExamCaptureProcessStatisticController.getFaceLivenessDetectFailedCount());
+            env.put("totalCount", FaceVerifyCounter.getFaceLivenessDetectCount());
+            env.put("failedCount", FaceVerifyCounter.getFaceLivenessDetectFailedCount());
             try {
                 return (Boolean) AviatorEvaluator.execute(expression, env, true);
             } catch (Exception e) {
@@ -149,8 +149,8 @@ public class ProcessBaiduFaceLivenessAlarmTask implements ApplicationRunner {
 
         } else {
             // 默认每分钟失败率超过50%则发短信报警,且失败总数不少于10次则短信报警
-            return ExamCaptureProcessStatisticController.getFaceLivenessDetectFailedCount() > 10 &&
-                    ExamCaptureProcessStatisticController.getFaceLivenessDetectFailurePercent() > 50;
+            return FaceVerifyCounter.getFaceLivenessDetectFailedCount() > 10 &&
+                    FaceVerifyCounter.getFaceLivenessDetectFailurePercent() > 50;
         }
     }
 

+ 17 - 17
examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/ProcessFaceCompareAlarmTask.java

@@ -2,7 +2,7 @@ package cn.com.qmth.examcloud.core.oe.task.starter.config;
 
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.Util;
-import cn.com.qmth.examcloud.core.oe.task.base.ExamCaptureProcessStatisticController;
+import cn.com.qmth.examcloud.core.oe.task.service.worker.FaceVerifyCounter;
 import cn.com.qmth.examcloud.exchange.inner.api.SmsCloudService;
 import cn.com.qmth.examcloud.exchange.inner.api.request.SendSmsReq;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
@@ -76,8 +76,8 @@ public class ProcessFaceCompareAlarmTask implements ApplicationRunner {
         if (LOG.isDebugEnabled()) {
             LOG.debug("[FACE_COMPARE_ALARM] 进入人脸" + System.currentTimeMillis()
                     + "....totalCount="
-                    + ExamCaptureProcessStatisticController.getFaceCompareCount() + " ,failCount="
-                    + ExamCaptureProcessStatisticController.getFaceCompareFailedCount());
+                    + FaceVerifyCounter.getFaceCompareCount() + " ,failCount="
+                    + FaceVerifyCounter.getFaceCompareFailedCount());
         }
 
         // 默认每分钟失败率超过50%则发短信报警,且总数不少于10次则短信报警
@@ -87,9 +87,9 @@ public class ProcessFaceCompareAlarmTask implements ApplicationRunner {
 
             if (!faceCompareSmsAssemblyCodeProperty.getHasValue()) {
                 LOG.error("[FACE_COMPARE_ALARM.] 未配置人脸比对的短信模板代码,totalCount="
-                        + ExamCaptureProcessStatisticController.getFaceCompareCount()
+                        + FaceVerifyCounter.getFaceCompareCount()
                         + ",errorCount="
-                        + ExamCaptureProcessStatisticController.getFaceCompareFailedCount());
+                        + FaceVerifyCounter.getFaceCompareFailedCount());
                 throw new StatusException("300001", "未配置人脸比对的短信模板代码");
             }
             SysPropertyCacheBean smsPhoneProperty = CacheHelper
@@ -97,9 +97,9 @@ public class ProcessFaceCompareAlarmTask implements ApplicationRunner {
             if (!smsPhoneProperty.getHasValue()) {
                 if (LOG.isErrorEnabled()) {
                     LOG.error("[FACE_COMPARE_ALARM.] 未配置图片处理失败的通知手机号,totalCount="
-                            + ExamCaptureProcessStatisticController.getFaceCompareCount()
+                            + FaceVerifyCounter.getFaceCompareCount()
                             + ",errorCount="
-                            + ExamCaptureProcessStatisticController.getFaceCompareFailedCount());
+                            + FaceVerifyCounter.getFaceCompareFailedCount());
                 }
                 throw new StatusException("300002", "未配置图片处理失败的通知手机号");
             }
@@ -115,16 +115,16 @@ public class ProcessFaceCompareAlarmTask implements ApplicationRunner {
 
             HashMap<String, String> params = new HashMap<>();
             params.put("totalCount",
-                    String.valueOf(ExamCaptureProcessStatisticController.getFaceCompareCount()));
+                    String.valueOf(FaceVerifyCounter.getFaceCompareCount()));
             params.put("errorCount", String
-                    .valueOf(ExamCaptureProcessStatisticController.getFaceCompareFailedCount()));
+                    .valueOf(FaceVerifyCounter.getFaceCompareFailedCount()));
             sendSmsReq.setParams(params);
             try {
                 if (LOG.isDebugEnabled()) {
                     LOG.debug("[FACE_COMPARE_ALARM.] 开始调用发送短信接口,totalCount="
-                            + ExamCaptureProcessStatisticController.getFaceCompareCount()
+                            + FaceVerifyCounter.getFaceCompareCount()
                             + ",errorCount="
-                            + ExamCaptureProcessStatisticController.getFaceCompareFailedCount());
+                            + FaceVerifyCounter.getFaceCompareFailedCount());
                 }
                 smsCloudService.sendSms(sendSmsReq);
             } catch (Exception e) {
@@ -132,7 +132,7 @@ public class ProcessFaceCompareAlarmTask implements ApplicationRunner {
             }
         }
         // 每1分钟重置一次总数量与失败数量
-        ExamCaptureProcessStatisticController.resetAllFaceCompareCount();
+        FaceVerifyCounter.resetAllFaceCompareCount();
     }
 
     /**
@@ -141,15 +141,15 @@ public class ProcessFaceCompareAlarmTask implements ApplicationRunner {
      * @return boolean
      */
     private boolean needSmsAlarm() {
-        if (ExamCaptureProcessStatisticController.getFaceCompareCount() == 0) {
+        if (FaceVerifyCounter.getFaceCompareCount() == 0) {
             return false;
         }
         SysPropertyCacheBean expressionProperty = CacheHelper.getSysProperty("capture.faceCompare.expression.alarm");
         if (expressionProperty.getHasValue()) {
             String expression = expressionProperty.getValue().toString();
             Map<String, Object> env = Maps.newHashMap();
-            env.put("totalCount", ExamCaptureProcessStatisticController.getFaceCompareCount());
-            env.put("failedCount", ExamCaptureProcessStatisticController.getFaceCompareFailedCount());
+            env.put("totalCount", FaceVerifyCounter.getFaceCompareCount());
+            env.put("failedCount", FaceVerifyCounter.getFaceCompareFailedCount());
             try {
                 return (Boolean) AviatorEvaluator.execute(expression, env, true);
             } catch (Exception e) {
@@ -158,8 +158,8 @@ public class ProcessFaceCompareAlarmTask implements ApplicationRunner {
 
         } else {
             // 默认每分钟失败率超过50%则发短信报警,且失败总数不少于10次则短信报警
-            return ExamCaptureProcessStatisticController.getFaceCompareFailedCount() > 10 &&
-                    ExamCaptureProcessStatisticController.getFaceLivenessDetectFailurePercent() > 50;
+            return FaceVerifyCounter.getFaceCompareFailedCount() > 10 &&
+                    FaceVerifyCounter.getFaceLivenessDetectFailurePercent() > 50;
         }
     }
 

+ 2 - 2
examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/StreamTaskExecutor.java

@@ -21,6 +21,7 @@ import org.springframework.stereotype.Component;
  * @Date 2019/12/24 15:52
  * @Version 1.0
  */
+@Deprecated
 @Component
 @Order(300)
 public class StreamTaskExecutor implements ApplicationRunner {
@@ -61,7 +62,7 @@ public class StreamTaskExecutor implements ApplicationRunner {
         LOG.warn("examcloud.record.data.sync.enable = " + recordDataSyncEnable);
 
         if (recordDataSyncEnable) {
-            initExecutor();
+            // initExecutor();
         }
     }
 
@@ -110,5 +111,4 @@ public class StreamTaskExecutor implements ApplicationRunner {
         node5.start();
     }
 
-
 }

+ 0 - 48
examcloud-core-oe-task-starter/src/main/java/cn/com/qmth/examcloud/core/oe/task/starter/config/SystemStartup.java

@@ -1,48 +0,0 @@
-// package cn.com.qmth.examcloud.core.oe.task.starter.config;
-//
-// import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
-// import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
-// import org.apache.commons.lang3.StringUtils;
-// import org.springframework.beans.factory.annotation.Autowired;
-// import org.springframework.boot.ApplicationArguments;
-// import org.springframework.boot.ApplicationRunner;
-// import org.springframework.cloud.client.ServiceInstance;
-// import org.springframework.cloud.client.discovery.DiscoveryClient;
-// import org.springframework.core.annotation.Order;
-// import org.springframework.stereotype.Component;
-//
-// import java.util.List;
-//
-// /**
-//  * 系统启动
-//  *
-//  * @author WANGWEI
-//  * @date 2018年11月29日
-//  * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
-//  */
-// @Component
-// @Order(99)
-// public class SystemStartup implements ApplicationRunner {
-//
-//     @Autowired
-//     private DiscoveryClient discoveryClient;
-//
-//     public void start() {
-//
-//         String appName = PropertyHolder.getString("spring.application.name");
-//
-//         if (StringUtils.isNotBlank(appName)) {
-//             List<ServiceInstance> instances = discoveryClient.getInstances(appName);
-//             if (!instances.isEmpty()) {
-//                 throw new ExamCloudRuntimeException("multiple instances!");
-//             }
-//         }
-//
-//     }
-//
-//     @Override
-//     public void run(ApplicationArguments args) throws Exception {
-//         start();
-//     }
-//
-// }

+ 0 - 0
examcloud-core-oe-task-starter/src/main/resources/security.properties


+ 0 - 2
pom.xml

@@ -12,8 +12,6 @@
     </parent>
 
     <modules>
-        <module>examcloud-core-oe-task-base</module>
-        <module>examcloud-core-oe-task-dao</module>
         <module>examcloud-core-oe-task-service</module>
         <module>examcloud-core-oe-task-api-provider</module>
         <module>examcloud-core-oe-task-starter</module>