deason 5 tahun lalu
induk
melakukan
2da9fc552b
100 mengubah file dengan 8727 tambahan dan 0 penghapusan
  1. 9 0
      .gitignore
  2. 19 0
      examcloud-core-oe-student-api-provider/pom.xml
  3. 446 0
      examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/controller/ExamControlController.java
  4. 295 0
      examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/controller/ExamFaceLivenessVerifyController.java
  5. 117 0
      examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/controller/ExamQuestionController.java
  6. 38 0
      examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/controller/ExamRecordPaperStructController.java
  7. 74 0
      examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/controller/ExamSmsController.java
  8. 259 0
      examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/controller/FaceBiopsyController.java
  9. 294 0
      examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/provider/ExamRecordDataCloudServiceProvider.java
  10. 134 0
      examcloud-core-oe-student-base/pom.xml
  11. 91 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/Model.java
  12. 20 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/Op.java
  13. 70 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/Order.java
  14. 146 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/Searcher.java
  15. 135 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/SpecUtils.java
  16. 265 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/SqlWrapper.java
  17. 96 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/Check.java
  18. 166 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/CommonUtil.java
  19. 101 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/DateUtils.java
  20. 248 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/FileDisposeUtil.java
  21. 95 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/HtmlUtil.java
  22. 91 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/HttpPoolUtil.java
  23. 172 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/JsonMapper.java
  24. 63 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/NewQuestionType.java
  25. 101 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/PagingAndSortingDTO.java
  26. 74 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/PagingAndSortingSpecification.java
  27. 162 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/QEncodeUtil.java
  28. 17 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/QuerySpecification.java
  29. 40 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/QuestionTypeUtil.java
  30. 63 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/RowIterator.java
  31. 180 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/Sentence.java
  32. 126 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/baiduFaceVerify/BaiduFaceVerifyUtil.java
  33. 65 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/baiduFaceVerify/Base64Util.java
  34. 29 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/baiduFaceVerify/GsonUtils.java
  35. 75 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/baiduFaceVerify/HttpUtil.java
  36. 61 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ColumnSetting.java
  37. 48 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelError.java
  38. 98 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelErrorType.java
  39. 34 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelProperty.java
  40. 95 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelReader.java
  41. 7 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelReaderHandle.java
  42. 77 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelUtils.java
  43. 93 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelWriter.java
  44. 43 0
      examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExportUtils.java
  45. 19 0
      examcloud-core-oe-student-dao/pom.xml
  46. 36 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/ExamFaceLivenessVerifyRepo.java
  47. 31 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/ExamRecordDataRepo.java
  48. 20 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/ExamRecordQuestionTempRepo.java
  49. 66 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/FaceBiopsyItemRepo.java
  50. 20 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/FaceBiopsyItemStepRepo.java
  51. 29 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/FaceBiopsyRepo.java
  52. 34 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/UniqueRuleHolder.java
  53. 149 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/ExamFaceLivenessVerifyEntity.java
  54. 217 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/ExamQuestionTempEntity.java
  55. 544 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/ExamRecordDataEntity.java
  56. 82 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/FaceBiopsyEntity.java
  57. 118 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/FaceBiopsyItemEntity.java
  58. 193 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/FaceBiopsyItemStepEntity.java
  59. 25 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/FaceBiopsyAction.java
  60. 41 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/FaceBiopsyScheme.java
  61. 17 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/FaceBiopsyType.java
  62. 52 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/FaceVerifyResult.java
  63. 17 0
      examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/ResourceType.java
  64. 24 0
      examcloud-core-oe-student-service/pom.xml
  65. 65 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/AliyunSignatureInfo.java
  66. 28 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/BatchGetUpyunSignDomain.java
  67. 28 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/BatchGetUpyunSignDomainQuery.java
  68. 105 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/CheckExamInProgressInfo.java
  69. 90 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/CheckQrCodeInfo.java
  70. 59 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/EndExamInfo.java
  71. 36 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/ExamProcessResultInfo.java
  72. 28 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/ExamQuestionAnswerExtensionInfo.java
  73. 88 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/ExamStudentQuestionInfo.java
  74. 36 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/FaceBiopsyBaseInfo.java
  75. 49 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/FaceBiopsyInfo.java
  76. 153 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/FaceBiopsyStepInfo.java
  77. 69 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetAliyunSignDomain.java
  78. 51 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetFaceVerifyTokenInfo.java
  79. 64 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetQrCodeReq.java
  80. 22 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetUploadedFileAcknowledgeStatusReq.java
  81. 68 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetUpyunSignDomain.java
  82. 35 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetUpyunSignDomainQuery.java
  83. 69 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetUpyunSignatureReq.java
  84. 30 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetYunSignDomain.java
  85. 35 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetYunSignDomainQuery.java
  86. 69 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/GetYunSignatureReq.java
  87. 49 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/SaveFaceBiopsyResultReq.java
  88. 58 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/SaveFaceBiopsyResultResp.java
  89. 60 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/SaveUploadedFileAcknowledgeStatusReq.java
  90. 61 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/SaveUploadedFileReq.java
  91. 81 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/StartExamInfo.java
  92. 52 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/UploadedFileAnswerInfo.java
  93. 60 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/UpyunSignatureInfo.java
  94. 25 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/bean/YunSignatureInfo.java
  95. 28 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/processor/HttpMethodProcessorImpl.java
  96. 29 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamBossService.java
  97. 87 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamControlService.java
  98. 85 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamFaceLivenessVerifyService.java
  99. 38 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamFileAnswerService.java
  100. 71 0
      examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamRecordDataService.java

+ 9 - 0
.gitignore

@@ -0,0 +1,9 @@
+.project
+.classpath
+.settings
+target/
+.idea/
+*.iml
+# Package Files #
+*.jar
+logs/

+ 19 - 0
examcloud-core-oe-student-api-provider/pom.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud</groupId>
+		<artifactId>examcloud-core-oe-student</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-core-oe-student-api-provider</artifactId>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-core-oe-student-service</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+	</dependencies>
+
+</project>

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

@@ -0,0 +1,446 @@
+package cn.com.qmth.examcloud.core.oe.student.api.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import javax.validation.Valid;
+
+import org.apache.commons.lang.math.RandomUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.common.collect.Maps;
+import com.mysql.cj.util.StringUtils;
+
+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.Util;
+import cn.com.qmth.examcloud.core.oe.student.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.student.bean.BatchGetUpyunSignDomain;
+import cn.com.qmth.examcloud.core.oe.student.bean.BatchGetUpyunSignDomainQuery;
+import cn.com.qmth.examcloud.core.oe.student.bean.CheckExamInProgressInfo;
+import cn.com.qmth.examcloud.core.oe.student.bean.CheckQrCodeInfo;
+import cn.com.qmth.examcloud.core.oe.student.bean.EndExamInfo;
+import cn.com.qmth.examcloud.core.oe.student.bean.ExamProcessResultInfo;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetAliyunSignDomain;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetQrCodeReq;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetUploadedFileAcknowledgeStatusReq;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetUpyunSignDomain;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetUpyunSignDomainQuery;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetYunSignDomain;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetYunSignDomainQuery;
+import cn.com.qmth.examcloud.core.oe.student.bean.GetYunSignatureReq;
+import cn.com.qmth.examcloud.core.oe.student.bean.SaveUploadedFileAcknowledgeStatusReq;
+import cn.com.qmth.examcloud.core.oe.student.bean.SaveUploadedFileReq;
+import cn.com.qmth.examcloud.core.oe.student.bean.StartExamInfo;
+import cn.com.qmth.examcloud.core.oe.student.bean.UpyunSignatureInfo;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamControlService;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamFileAnswerService;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordDataService;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamingSessionService;
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.support.enums.FileAnswerAcknowledgeStatus;
+import cn.com.qmth.examcloud.support.enums.HandInExamType;
+import cn.com.qmth.examcloud.support.examing.ExamFileAnswer;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import cn.com.qmth.examcloud.support.examing.ExamingSession;
+import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
+import cn.com.qmth.examcloud.web.filestorage.FileStorageHelper;
+import cn.com.qmth.examcloud.web.filestorage.FileStoragePathEnvInfo;
+import cn.com.qmth.examcloud.web.filestorage.FileStorageType;
+import cn.com.qmth.examcloud.web.filestorage.YunHttpRequest;
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import cn.com.qmth.examcloud.web.upyun.UpyunPathEnvironmentInfo;
+import cn.com.qmth.examcloud.web.upyun.UpyunService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@Api(tags = "在线考试控制")
+@RestController
+@RequestMapping("${app.api.oe.student}/examControl")
+public class ExamControlController extends ControllerSupport {
+    private static final String SEPARATOR = "/";
+
+    private static final String UNDERLINE = "_";
+
+    // 小程序答案上传目录
+    private static final String OE_ANSWER_FILE_PATH = "oe-answer-file";
+    @Autowired
+    private ExamControlService examControlService;
+    @Autowired
+    private ExamingSessionService examingSessionService;
+    @Autowired
+    private ExamFileAnswerService examFileAnswerService;
+    @Autowired
+    private UpyunService upyunService;
+    @Autowired
+    private RedisClient redisClient;
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+    
+    /**
+     * 开始考试
+     */
+    @ApiOperation(value = "开始考试")
+    @GetMapping("/startExam")
+    public StartExamInfo startExam(@RequestParam Long examStudentId) {
+        User user = getAccessUser();
+        String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + user.getUserId();
+        StartExamInfo startExamInfo;
+        // 开始考试上锁,分布式锁,系统在请求结束后会,自动释放锁,无需手动解锁
+        SequenceLockHelper.getLock(sequenceLockKey);
+        Check.isNull(examStudentId, "examStudentId不能为空");
+
+        startExamInfo = examControlService.startExam(examStudentId, user);
+        return startExamInfo;
+    }
+
+    /**
+     * 断点续考:检查正在进行中的考试
+     */
+    @ApiOperation(value = "断点续考:检查正在进行中的考试")
+    @GetMapping("/checkExamInProgress")
+    public ExamProcessResultInfo checkExamInProgress() {
+        User user = getAccessUser();
+        String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + user.getUserId();
+        // 系统在请求结束后会,自动释放锁,无需手动解锁
+        SequenceLockHelper.getLock(sequenceLockKey);
+        ExamProcessResultInfo res = new ExamProcessResultInfo();
+        try {
+            CheckExamInProgressInfo info = examControlService.checkExamInProgress(user.getUserId());
+            res.setCode(Constants.COMMON_SUCCESS_CODE);
+            res.setData(info);
+            return res;
+        } catch (StatusException e) {
+            if (e.getCode().equals(Constants.EXAM_RECORD_NOT_END_STATUS_CODE)) {
+                res.setCode(Constants.PROCESSING_EXAM_RECORD_CODE);
+                return res;
+            }
+            throw e;
+        } catch (Exception e) {
+            throw e;
+        }
+    }
+
+    /**
+     * 考试心跳
+     *
+     * @return 剩余时间
+     */
+    @ApiOperation(value = "考试心跳")
+    @GetMapping("/examHeartbeat")
+    public Long examHeartbeat() {
+        User user = getAccessUser();
+        return examControlService.examHeartbeat(user);
+    }
+
+    /**
+     * 结束考试:交卷..
+     */
+    @ApiOperation(value = "结束考试:交卷")
+    @GetMapping("/endExam")
+    public void endExam() {
+        User user = getAccessUser();
+        Long studentId = user.getUserId();
+        String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + user.getUserId();
+        //系统在请求结束后会,自动释放锁,无需手动解锁
+        SequenceLockHelper.getLock(sequenceLockKey);
+
+        long st = System.currentTimeMillis();
+        long startTime = System.currentTimeMillis();
+
+        ExamingSession examingSession = examingSessionService.getExamingSession(studentId);
+
+        if (log.isDebugEnabled()) {
+            log.debug("0 [END_EXAM] 交卷前处理耗时:" + (System.currentTimeMillis() - startTime) + " ms");
+        }
+        examControlService.handInExam(examingSession.getExamRecordDataId(), HandInExamType.MANUAL);
+        if (log.isDebugEnabled()) {
+            log.debug("1 [END_EXAM]合计 耗时:" + (System.currentTimeMillis() - st) + " ms");
+        }
+    }
+
+    /**
+     * 获取考试记录信息
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    @ApiOperation(value = "获取考试记录信息")
+    @GetMapping("/getEndExamInfo")
+    public EndExamInfo getEndExamInfo(@RequestParam Long examRecordDataId) {
+        return examControlService.getEndExamInfo(examRecordDataId);
+    }
+
+    /**
+     * 获取又拍云文件上传签名(微信小程序调用)
+     */
+    @ApiOperation(value = "获取又拍云文件上传签名(微信小程序调用)")
+    @PostMapping("/upyunSignature")
+    public UpyunSignatureInfo getUpyunSignature(@ModelAttribute @Valid GetYunSignatureReq req) {
+        return examControlService.getUpyunSignature(req);
+    }
+    /**
+     * 获取云存储上传签名(微信小程序调用)
+     */
+    @ApiOperation(value = "获取文件上传签名(微信小程序调用)")
+    @PostMapping("/yunSignature")
+    public GetYunSignDomain getYunSignature(@ModelAttribute @Valid GetYunSignatureReq req) {
+        
+        if(FileStorageType.UPYUN.equals(FileStorageUtil.getFileStorageType())) {
+        	GetUpyunSignDomain result = new GetUpyunSignDomain();
+        	Map<String, String> params = Maps.newHashMap();
+        	UpyunSignatureInfo info=examControlService.getUpyunSignature(req);
+        	String signIdentifier = String.valueOf(System.currentTimeMillis());
+        	params.put("authorization", info.getSignature());
+        	params.put("policy", info.getPolicy());
+        	result.setAccessUrl(FileStorageHelper.getUrl(info.getUpyunFileDomain(), info.getFilePath()));
+            result.setFormUrl(info.getUploadUrl());
+            result.setFormParams(params);
+            result.setSignIdentifier(signIdentifier);
+        	return result;
+        }
+        if(FileStorageType.ALIYUN.equals(FileStorageUtil.getFileStorageType())) {
+        	String fileSuffix = req.getFileSuffix();
+            if (StringUtils.isNullOrEmpty(fileSuffix)) {
+                throw new StatusException("5002", "文件后缀名不允许为空");
+            }
+            
+            ExamRecordData examRecordData = examRecordDataService.getExamRecordDataCache(req.getExamRecordDataId());
+            fileSuffix = fileSuffix.indexOf(".") == -1 ? "." + fileSuffix : fileSuffix;
+            
+            StringBuffer filePath = new StringBuffer();
+
+            filePath.append(OE_ANSWER_FILE_PATH).append(SEPARATOR)
+                    .append(examRecordData.getExamStudentId()).append(SEPARATOR).append(req.getExamRecordDataId())
+                    .append(SEPARATOR).append(req.getOrder()).append(SEPARATOR)
+                    .append(examRecordData.getExamStudentId()).append(UNDERLINE).append(req.getExamRecordDataId())
+                    .append(UNDERLINE).append(req.getOrder()).append(UNDERLINE).append(System.currentTimeMillis())
+                    .append(RandomUtils.nextInt(8999) + 1000);
+
+            if (!StringUtils.isNullOrEmpty(req.getExt())) {
+                filePath.append(UNDERLINE).append(req.getExt());
+            }
+            filePath.append(".").append(req.getFileSuffix());
+
+            GetAliyunSignDomain result = new GetAliyunSignDomain();
+            String signIdentifier = String.valueOf(System.currentTimeMillis());
+            FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
+            env.setRelativePath(filePath.toString());
+            YunHttpRequest aliYunHttpRequest = FileStorageUtil.getSignature(FileStorageType.ALIYUN,Constants.MINI_PROGRAM_ANWSER_SITEID, env, req.getFileMd5());
+            result.setAccessUrl(aliYunHttpRequest.getAccessUrl());
+            result.setFormUrl(aliYunHttpRequest.getFormUrl());
+            result.setFormParams(aliYunHttpRequest.getFormParams());
+            result.setSignIdentifier(signIdentifier);
+            return result;
+        }
+        throw new StatusException("5002", "未配置正确云存储类型");
+    }
+
+    /**
+     * 校验二维码(微信小程序调用)
+     */
+    @Naked
+    @ApiOperation(value = "校验二维码(微信小程序调用)")
+    @PostMapping("/checkQrCode")
+    public CheckQrCodeInfo checkQrCode(@RequestParam(required = true) String qrCode) {
+        return examControlService.checkQrCode(qrCode);
+    }
+
+    /**
+     * 保存上传的文件(微信小程序调用)
+     */
+    @ApiOperation(value = "保存上传的文件(微信小程序调用)")
+    @PostMapping("/saveUploadedFile")
+    public String saveUploadedFile(@ModelAttribute @Valid SaveUploadedFileReq req) {
+        User user = getAccessUser();
+        ExamFileAnswer examFileAnswer = new ExamFileAnswer();
+        examFileAnswer.setExamRecordDataId(req.getExamRecordDataId());
+        examFileAnswer.setExamStudentId(req.getExamStudentId());
+        examFileAnswer.setQuestionOrder(req.getOrder());
+        examFileAnswer.setFilePath(req.getFilePath());
+        examFileAnswer.setStatus(FileAnswerAcknowledgeStatus.UNCONFIRMED);
+        examFileAnswer.setTransferFileType(req.getTransferFileType());
+        String fileAnswerId = RedisKeyHelper.getBuilder().studentFileAnswerKey(req.getExamRecordDataId(), req.getOrder());
+        examFileAnswerService.saveFileAnswer(fileAnswerId, examFileAnswer);
+
+        try {
+            String fileUrl = "";
+            if (req.getFilePath().indexOf(",") > -1) {
+                for (String url : req.getFilePath().split(",")) {
+                    fileUrl += FileStorageUtil.realPath(url)+ ",";
+                }
+                fileUrl = fileUrl.substring(0, fileUrl.length() - 1);
+            } else {
+                fileUrl = FileStorageUtil.realPath(req.getFilePath());
+            }
+            examControlService.sendFileAnswerToWebSocket(req.getExamRecordDataId(), req.getOrder(),
+                    fileUrl, req.getTransferFileType(), user.getUserId(), user.getRootOrgId());
+        } catch (Exception e) {
+            examFileAnswerService.deleteFileAnswer(fileAnswerId);
+            throw new StatusException("100009", "消息通知失败", e);
+        }
+        return fileAnswerId;
+    }
+
+    //TODO 此方法有修改,微信需要修改代码
+
+    /**
+     * 查询客户端对上传的文件的响应状态(微信小程序调用)
+     */
+    @ApiOperation(value = "查询客户端对上传的文件的响应状态(微信小程序调用)")
+    @PostMapping("/getUploadedFileAcknowledgeStatus")
+    public String getUploadedFileAcknowledgeStatus(@ModelAttribute @Valid GetUploadedFileAcknowledgeStatusReq req) {
+        ExamFileAnswer fileAnswer = examFileAnswerService.getFileAnswer(req.getAcknowledgeId());
+        if (null != fileAnswer) {
+            return fileAnswer.getStatus().toString();
+        }
+        return FileAnswerAcknowledgeStatus.UNCONFIRMED.toString();
+    }
+
+    /**
+     * 修改上传音频结果推送状态
+     */
+    @ApiOperation(value = "修改上传音频结果推送状态")
+    @PostMapping("/saveUploadedFileAcknowledgeStatus")
+    public void saveUploadedFileAcknowledgeStatus(@RequestBody @Valid SaveUploadedFileAcknowledgeStatusReq req) {
+        String acknowledgeId = RedisKeyHelper.getBuilder().studentFileAnswerKey(req.getExamRecordDataId(), req.getOrder());
+        ExamFileAnswer fileAnswer = examFileAnswerService.getFileAnswer(acknowledgeId);
+        if (null == fileAnswer) {
+            throw new StatusException("100010", "无效的数据");
+        }
+
+        fileAnswer.setStatus(FileAnswerAcknowledgeStatus.valueOf(req.getAcknowledgeStatus()));
+        examFileAnswerService.saveFileAnswer(acknowledgeId, fileAnswer);
+    }
+
+    //原接口,不作改动
+    @ApiOperation(value = "获取抓拍照片的又拍云签名")
+    @GetMapping("/getCapturePhotoUpYunSign")
+    public GetUpyunSignDomain getCapturePhotoUpYunSign(GetYunSignDomainQuery query) {
+        return getUpYunSign(query);
+    }
+    
+    //又拍云签名
+    private GetUpyunSignDomain getUpYunSign(GetYunSignDomainQuery query) {
+    	String fileSuffix = query.getFileSuffix();
+        if (StringUtils.isNullOrEmpty(fileSuffix)) {
+            throw new StatusException("200001", "文件后缀名不允许为空");
+        }
+        fileSuffix = fileSuffix.indexOf(".") == -1 ? "." + fileSuffix : fileSuffix;
+
+        GetUpyunSignDomain result = new GetUpyunSignDomain();
+        User accessUser = this.getAccessUser();
+        String signIdentifier = String.valueOf(System.currentTimeMillis());
+        String upyunSignRedisKey = Constants.EXAM_CAPTURE_PHOTO_UPYUN_SIGN_PREFIX
+                + accessUser.getUserId() + "_" + signIdentifier;
+
+        UpyunPathEnvironmentInfo env = new UpyunPathEnvironmentInfo();
+        env.setRootOrgId(accessUser.getRootOrgId().toString());
+        env.setUserId(accessUser.getUserId().toString());
+        env.setFileSuffix(fileSuffix);
+        YunHttpRequest upYunHttpRequest = upyunService.buildUpYunHttpRequest(Constants.CAPTURE_PHOTO_UPYUN_SITEID, env, query.getFileMd5());
+        redisClient.set(upyunSignRedisKey, upYunHttpRequest, 60);
+        result.setAccessUrl(upYunHttpRequest.getAccessUrl());
+        result.setFormUrl(upYunHttpRequest.getFormUrl());
+        result.setFormParams(upYunHttpRequest.getFormParams());
+        result.setSignIdentifier(signIdentifier);
+        return result;
+    }
+    
+    @ApiOperation(value = "获取抓拍照片的云存储签名")
+    @GetMapping("/getCapturePhotoYunSign")
+    public GetYunSignDomain getCapturePhotoYunSign(GetYunSignDomainQuery query) {
+        if(FileStorageType.UPYUN.equals(FileStorageUtil.getFileStorageType())) {
+        	return getUpYunSign(query);
+        }
+        if(FileStorageType.ALIYUN.equals(FileStorageUtil.getFileStorageType())) {
+        	return getAliYunSign(query);
+        }
+        throw new StatusException("3002", "未配置正确云存储类型");
+    }
+
+    //阿里云签名
+    private GetAliyunSignDomain getAliYunSign(GetYunSignDomainQuery query) {
+    	String fileSuffix = query.getFileSuffix();
+        if (StringUtils.isNullOrEmpty(fileSuffix)) {
+            throw new StatusException("4001", "文件后缀名不允许为空");
+        }
+        fileSuffix = fileSuffix.indexOf(".") == -1 ? "." + fileSuffix : fileSuffix;
+
+        GetAliyunSignDomain result = new GetAliyunSignDomain();
+        User accessUser = this.getAccessUser();
+        String signIdentifier = String.valueOf(System.currentTimeMillis());
+        String aliyunSignRedisKey = Constants.EXAM_CAPTURE_PHOTO_UPYUN_SIGN_PREFIX
+                + accessUser.getUserId() + "_" + signIdentifier;
+        FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
+        env.setRootOrgId(accessUser.getRootOrgId().toString());
+        env.setUserId(accessUser.getUserId().toString());
+        env.setFileSuffix(fileSuffix);
+        YunHttpRequest aliYunHttpRequest = FileStorageUtil.getSignature(FileStorageType.ALIYUN,Constants.CAPTURE_PHOTO_UPYUN_SITEID, env, query.getFileMd5());
+        redisClient.set(aliyunSignRedisKey, aliYunHttpRequest, 60);
+        result.setAccessUrl(aliYunHttpRequest.getAccessUrl());
+        result.setFormUrl(aliYunHttpRequest.getFormUrl());
+        result.setFormParams(aliYunHttpRequest.getFormParams());
+        result.setSignIdentifier(signIdentifier);
+        return result;
+    }
+
+    @ApiOperation(value = "批量获取抓拍照片的又拍云签名")
+    @PostMapping("/batchGetCapturePhotoUpYunSign")
+    public BatchGetUpyunSignDomain batchGetCapturePhotoUpYunSign(@RequestBody BatchGetUpyunSignDomainQuery batchQuery) {
+        if (batchQuery.getQueryList() == null || batchQuery.getQueryList().isEmpty()) {
+            throw new StatusException("300001", "查询条件不允许为空");
+        }
+
+        List<GetUpyunSignDomain> signDomainList = new ArrayList<>();
+        for (GetUpyunSignDomainQuery query : batchQuery.getQueryList()) {
+            String fileSuffix = query.getFileSuffix();
+            if (StringUtils.isNullOrEmpty(fileSuffix)) {
+                throw new StatusException("", "文件后缀名不允许为空");
+            }
+            fileSuffix = fileSuffix.indexOf(".") == -1 ? "." + fileSuffix : fileSuffix;
+
+            GetUpyunSignDomain upyunSignDomain = new GetUpyunSignDomain();
+            User accessUser = this.getAccessUser();
+            String signIdentifier = String.valueOf(System.currentTimeMillis());
+            String upyunSignRedisKey = Constants.EXAM_CAPTURE_PHOTO_UPYUN_SIGN_PREFIX
+                    + accessUser.getUserId() + "_" + signIdentifier;
+
+            UpyunPathEnvironmentInfo env = new UpyunPathEnvironmentInfo();
+            env.setRootOrgId(accessUser.getRootOrgId().toString());
+            env.setUserId(accessUser.getUserId().toString());
+            env.setFileSuffix(fileSuffix);
+            YunHttpRequest upYunHttpRequest = upyunService.buildUpYunHttpRequest(Constants.CAPTURE_PHOTO_UPYUN_SITEID, env, query.getFileMd5());
+            redisClient.set(upyunSignRedisKey, upYunHttpRequest, 60);
+            upyunSignDomain.setAccessUrl(upYunHttpRequest.getAccessUrl());
+            upyunSignDomain.setFormUrl(upYunHttpRequest.getFormUrl());
+            upyunSignDomain.setFormParams(upYunHttpRequest.getFormParams());
+            upyunSignDomain.setSignIdentifier(signIdentifier);
+
+            signDomainList.add(upyunSignDomain);
+            Util.sleep(TimeUnit.MILLISECONDS, 1);
+        }
+        BatchGetUpyunSignDomain result = new BatchGetUpyunSignDomain();
+        result.setList(signDomainList);
+        return result;
+    }
+
+    @ApiOperation(value = "获取二维码")
+    @PostMapping("/getQrCode")
+    public String getQrCode(@RequestBody GetQrCodeReq req) {
+        return examControlService.getQrCode(req, getAccessUser());
+    }
+}

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

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

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

@@ -0,0 +1,117 @@
+package cn.com.qmth.examcloud.core.oe.student.api.controller;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+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.JsonUtil;
+import cn.com.qmth.examcloud.support.examing.ExamQuestion;
+import cn.com.qmth.examcloud.support.examing.ExamRecordQuestions;
+import cn.com.qmth.examcloud.core.oe.student.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.student.bean.ExamStudentQuestionInfo;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordQuestionsService;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamingSessionService;
+import cn.com.qmth.examcloud.support.examing.ExamingHeartbeat;
+import cn.com.qmth.examcloud.support.examing.ExamingSession;
+import cn.com.qmth.examcloud.support.examing.ExamingStatus;
+import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
+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;
+
+/**
+ * @author  	chenken
+ * @date    	2018年9月5日 下午4:57:05
+ * @company 	QMTH
+ * @description 考试作答记录controller
+ */
+@Api(tags = "考试过程中-试题相关接口")
+@RestController
+@RequestMapping("${app.api.oe.student}/examQuestion")
+public class ExamQuestionController extends ControllerSupport {
+	
+	@Autowired
+	private ExamRecordQuestionsService examRecordQuestionsService;
+	
+	@Autowired
+    private ExamingSessionService examingSessionService;
+	
+    @Autowired
+    private RedisClient redisClient;
+	
+	/**
+	 * 将mongodb中的答过的题和redis中的题目列表合并返回给前端
+	 * 返回给前端时注意将正确答案和得分置成null
+	 * @return
+	 */
+	@ApiOperation(value = "考试过程中-获取试题列表")
+	@GetMapping("/findExamQuestionList")
+	public List<ExamQuestion> findExamQuestionList(){
+		User user = getAccessUser();
+		ExamingSession examSessionInfo = examingSessionService.getExamingSession(user.getUserId());
+		if (examSessionInfo == null
+				|| examSessionInfo.getExamingStatus().equals(ExamingStatus.INFORMAL)) {
+			throw new StatusException("1001", "考试会话已过期,请重新开考");
+		}
+        
+        String examingHeartbeatKey = RedisKeyHelper.getBuilder().examingHeartbeatKey(examSessionInfo.getExamRecordDataId());
+        ExamingHeartbeat examingHeartbeat = redisClient.get(examingHeartbeatKey,ExamingHeartbeat.class);
+        
+		if (null != examingHeartbeat
+				&& examingHeartbeat.getCost() >= examSessionInfo.getExamDuration()) {
+			throw new StatusException("1001", "考试会话已过期,请重新开考");
+		}
+        
+		ExamRecordQuestions qers = examRecordQuestionsService.getExamRecordQuestions(examSessionInfo.getExamRecordDataId());
+		List<ExamQuestion> examQuestionList=qers.getExamQuestions();
+		for(ExamQuestion examQuestion:examQuestionList){
+			examQuestion.setCorrectAnswer(null);
+			examQuestion.setStudentScore(null);
+		}
+		return examQuestionList;
+	}
+
+	/**
+	 * 获取试题内容
+	 * @param questionId
+	 * @return
+	 */
+	@ApiOperation(value = "考试过程中-获取试题内容")
+	@GetMapping("/getQuestionContent")
+	public String getQuestionContent(@RequestParam String questionId){
+		User user = getAccessUser();
+		Check.isBlank(questionId, "questionId不能为空");
+		return examRecordQuestionsService.getQuestionContent(user.getUserId(),questionId);
+	}
+	
+	/**
+	 * 考生作答
+	 * @param examQuestionInfos
+	 */
+	@ApiOperation(value = "考试过程中-考生作答:更新试题作答信息(包括提交试题答案,更新是否标记)")
+	@PostMapping("/submitQuestionAnswer")
+	public void submitQuestionAnswer(@RequestBody List<ExamStudentQuestionInfo> examQuestionInfos){
+		if(log.isDebugEnabled()) {
+			String strJosn=JsonUtil.toJson(examQuestionInfos);
+			log.debug("ExamQuestionController--submitQuestionAnswer参数信息:"+strJosn);
+		}
+		User user = getAccessUser();
+		if(examQuestionInfos!=null && examQuestionInfos.size()>0){
+			for(ExamStudentQuestionInfo examStudentQuestionInfo:examQuestionInfos){
+				if(examStudentQuestionInfo.getOrder() == null){
+					throw new StatusException("2001", "illegal params");
+				}
+			}
+			examRecordQuestionsService.submitQuestionAnswer(user.getUserId(),examQuestionInfos);
+		}
+	}
+}

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

@@ -0,0 +1,38 @@
+package cn.com.qmth.examcloud.core.oe.student.api.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.core.oe.student.base.utils.Check;
+import cn.com.qmth.examcloud.support.examing.ExamRecordPaperStruct;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordPaperStructService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年9月5日 下午4:02:15
+ * @company 	QMTH
+ * @description 考试记录-试卷结构controller
+ */
+@Api(tags = "考试记录-试卷结构")
+@RestController
+@RequestMapping("${app.api.oe.student}/examRecordPaperStruct")
+public class ExamRecordPaperStructController extends ControllerSupport{
+
+	@Autowired
+	private ExamRecordPaperStructService examRecordPaperStructService;
+	
+	@ApiOperation(value = "获取考试记录试卷结构")
+	@GetMapping("/getExamRecordPaperStruct")
+	public ExamRecordPaperStruct getExamRecordPaperStruct(@RequestParam Long examRecordDataId){
+		Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+		return examRecordPaperStructService.getExamRecordPaperStruct(examRecordDataId);
+	}
+	
+}

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

@@ -0,0 +1,74 @@
+package cn.com.qmth.examcloud.core.oe.student.api.controller;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.basic.api.StudentCloudService;
+import cn.com.qmth.examcloud.core.basic.api.request.GetStudentReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetStudentResp;
+import cn.com.qmth.examcloud.core.oe.student.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.student.base.utils.CommonUtil;
+import cn.com.qmth.examcloud.exchange.inner.api.SmsCloudService;
+import cn.com.qmth.examcloud.exchange.inner.api.request.CheckSmsCodeReq;
+import cn.com.qmth.examcloud.exchange.inner.api.request.SendSmsCodeReq;
+import cn.com.qmth.examcloud.exchange.inner.api.response.CheckSmsCodeResp;
+import cn.com.qmth.examcloud.exchange.inner.api.response.SendSmsCodeResp;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 
+ * @author chenken
+ * @date 2018年9月5日 下午3:33:26
+ * @company QMTH
+ * @description ExamSmsController.java
+ */
+@RestController
+@Api(tags = "考试短信接口")
+@RequestMapping("${app.api.oe.student}/sms")
+public class ExamSmsController extends ControllerSupport {
+
+	@Autowired
+	private StudentCloudService studentCloudService;
+
+	@Autowired
+	SmsCloudService smsCloudService;
+
+	@ApiOperation(value = "发送短信验证码")
+	@PostMapping("/sendSmsCodeToStudent")
+	public SendSmsCodeResp sendSmsCodeToStudent() {
+		User user = getAccessUser();
+		GetStudentReq getStudentReq = new GetStudentReq();
+		getStudentReq.setStudentId(user.getUserId());
+		GetStudentResp getStudentResp = studentCloudService.getStudent(getStudentReq);
+
+		if (StringUtils.isBlank(getStudentResp.getStudentInfo().getPhoneNumber())) {
+			throw new StatusException("100001", "系统中手机号码为空,请联系管理员");
+		}
+		SendSmsCodeReq sendSmsReq = new SendSmsCodeReq();
+		sendSmsReq.setPhone(getStudentResp.getStudentInfo().getPhoneNumber());
+		sendSmsReq.setCode(CommonUtil.makeRandomNum(6));
+
+		return smsCloudService.sendSmsCode(sendSmsReq);
+	}
+
+	@ApiOperation(value = "检查验证码是否正确")
+	@PostMapping(value = "/checkSmsCode")
+	public CheckSmsCodeResp checkSmsCode(@RequestParam String phoneNumber,
+			@RequestParam String code) {
+		Check.isBlank(phoneNumber, "phoneNumber不能为空");
+		Check.isBlank(code, "code不能为空");
+		CheckSmsCodeReq checkSmsCodeReq = new CheckSmsCodeReq();
+		checkSmsCodeReq.setPhone(phoneNumber);
+		checkSmsCodeReq.setCode(code);
+		return smsCloudService.checkSmsCode(checkSmsCodeReq);
+	}
+
+}

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

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

+ 294 - 0
examcloud-core-oe-student-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/student/api/provider/ExamRecordDataCloudServiceProvider.java

@@ -0,0 +1,294 @@
+package cn.com.qmth.examcloud.core.oe.student.api.provider;
+
+import cn.com.qmth.examcloud.core.oe.student.api.ExamRecordDataCloudService;
+import cn.com.qmth.examcloud.core.oe.student.api.bean.ExamFaceLivenessVerifyBean;
+import cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyBean;
+import cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyItemBean;
+import cn.com.qmth.examcloud.core.oe.student.api.bean.FaceBiopsyItemStepBean;
+import cn.com.qmth.examcloud.core.oe.student.api.request.*;
+import cn.com.qmth.examcloud.core.oe.student.api.response.*;
+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.service.ExamControlService;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordDataService;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordPaperStructService;
+import cn.com.qmth.examcloud.core.oe.student.service.ExamRecordQuestionsService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Api(tags = "考试记录接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe.student}/examRecordData")
+public class ExamRecordDataCloudServiceProvider extends ControllerSupport implements ExamRecordDataCloudService {
+
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 6142107111834463854L;
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    @Autowired
+    private ExamFaceLivenessVerifyRepo examFaceLivenessVerifyRepo;
+    @Autowired
+    private FaceBiopsyRepo faceBiopsyRepo;
+    @Autowired
+    private FaceBiopsyItemRepo faceBiopsyItemRepo;
+    @Autowired
+    private FaceBiopsyItemStepRepo faceBiopsyItemStepRepo;
+    @Autowired
+    private ExamRecordPaperStructService examRecordPaperStructService;
+    @Autowired
+    private ExamRecordQuestionsService examRecordQuestionsService;
+    @Autowired
+    private ExamControlService examControlService;
+
+    @Override
+    @ApiOperation(value = "批量获取未同步的考试记录ID")
+    @PostMapping("/getExamRecordDataIds")
+    public GetExamRecordDataIdsResp getExamRecordDataIds(@RequestBody GetExamRecordDataIdsReq req) {
+        GetExamRecordDataIdsResp res = new GetExamRecordDataIdsResp();
+        List<Long> ids = examRecordDataService.getExamRecordDataIds(req);
+        res.setExamRecordDataIds(ids);
+        return res;
+    }
+
+    @ApiOperation(value = "批量修改考试记录batchNum")
+    @PostMapping("/updateBatchNum")
+    @Override
+    public UpdateExamRecordDataBatchNumResp updateExamRecordDataBatchNum(@RequestBody UpdateExamRecordDataBatchNumReq req) {
+        examRecordDataService.updateExamRecordDataBatchNum(req);
+        UpdateExamRecordDataBatchNumResp res = new UpdateExamRecordDataBatchNumResp();
+        return res;
+    }
+
+    /**
+     * 计算活体检测结果
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "计算活体检测结果")
+    @PostMapping("/calcFaceBiopsyResult")
+    @Override
+    public CalcFaceBiopsyResultResp calcFaceBiopsyResult(@RequestBody CalcFaceBiopsyResultReq req) {
+        return examRecordDataService.calcFaceBiopsyResult(req);
+    }
+
+    /**
+     * 计算考试分数
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "计算考试分数")
+    @PostMapping("/calcExamScore")
+    @Override
+    public CalcExamScoreResp calcExamScore(@RequestBody CalcExamScoreReq req) {
+        return examRecordDataService.calcExamScore(req);
+    }
+
+    /**
+     * 获取旧活体检测结果
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "获取旧活体检测结果")
+    @PostMapping("/getExamFaceLivenessVerifies")
+    @Override
+    public GetExamFaceLivenessVerifiesResp getExamFaceLivenessVerifies(@RequestBody GetExamFaceLivenessVerifiesReq req) {
+        List<ExamFaceLivenessVerifyEntity> entityList = examFaceLivenessVerifyRepo.findByExamRecordDataId(req.getExamRecordDataId());
+
+        List<ExamFaceLivenessVerifyBean> examFaceLivenessVerifyList = new ArrayList<>();
+
+        for (ExamFaceLivenessVerifyEntity entity : entityList) {
+            ExamFaceLivenessVerifyBean bean = new ExamFaceLivenessVerifyBean();
+            bean.setBizId(entity.getBizId());
+            bean.setIsError(entity.getIsError());
+            bean.setErrorMsg(entity.getErrorMsg());
+            bean.setExamRecordDataId(entity.getExamRecordDataId());
+            bean.setId(entity.getId());
+            bean.setOperateNum(entity.getOperateNum());
+            bean.setResultJson(entity.getResultJson());
+            bean.setStartTime(entity.getStartTime());
+            bean.setUsedTime(entity.getUsedTime());
+            if (null != entity.getVerifyResult()) {
+                bean.setVerifyResult(entity.getVerifyResult().name());
+            }
+
+            examFaceLivenessVerifyList.add(bean);
+
+        }
+
+        GetExamFaceLivenessVerifiesResp resp = new GetExamFaceLivenessVerifiesResp();
+        resp.setExamFaceLivenessVerifis(examFaceLivenessVerifyList);
+
+        return resp;
+    }
+
+    /**
+     * 获取新活检
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "获取新活检")
+    @PostMapping("/getFaceBiopsy")
+    @Override
+    public GetFaceBiopsyResp getFaceBiopsy(@RequestBody GetFaceBiopsyReq req) {
+
+        FaceBiopsyEntity entity = faceBiopsyRepo.findByExamRecordDataId(req.getExamRecordDataId());
+
+        if (null == entity) {
+            return new GetFaceBiopsyResp();
+        }
+
+        List<FaceBiopsyItemEntity> itemEntityList = faceBiopsyItemRepo.findByFaceBiopsyIdOrderByIdAsc(entity.getId());
+
+        List<FaceBiopsyItemBean> itemBeanList = new ArrayList<>();
+
+        for (FaceBiopsyItemEntity itemEntity : itemEntityList) {
+            FaceBiopsyItemBean itemBean = new FaceBiopsyItemBean();
+
+            itemBean.setCompleted(itemEntity.getCompleted());
+            itemBean.setErrorMsg(itemEntity.getErrorMsg());
+            itemBean.setExamRecordDataId(itemEntity.getExamRecordDataId());
+            itemBean.setFaceBiopsyId(itemEntity.getFaceBiopsyId());
+            itemBean.setFaceBiopsyItemSteps(null);
+            itemBean.setFaceBiopsyType(itemEntity.getFaceBiopsyType().name());
+            itemBean.setInFreezeTime(itemEntity.getInFreezeTime());
+            itemBean.setResult(itemEntity.getResult());
+
+            List<FaceBiopsyItemStepBean> itemStepBeanList = new ArrayList<>();
+
+            List<FaceBiopsyItemStepEntity> itemStepEntityList = faceBiopsyItemStepRepo.findByFaceBiopsyItemId(itemEntity.getId());
+
+            for (FaceBiopsyItemStepEntity itemStepEntity : itemStepEntityList) {
+
+                FaceBiopsyItemStepBean itemStepBean = new FaceBiopsyItemStepBean();
+
+                if (null != itemStepEntity.getAction()) {
+                    itemStepBean.setAction(itemStepEntity.getAction().name());
+                }
+
+                itemStepBean.setActionStay(itemStepEntity.getActionStay());
+                itemStepBean.setErrorMsg(itemStepEntity.getErrorMsg());
+                itemStepBean.setExamRecordDataId(itemStepEntity.getExamRecordDataId());
+                itemStepBean.setFaceBiopsyItemId(itemStepEntity.getFaceBiopsyItemId());
+                itemStepBean.setResourceRelativePath(itemStepEntity.getResourceRelativePath());
+
+                if (null != itemStepEntity.getResourceType()) {
+                    itemStepBean.setResourceType(itemStepEntity.getResourceType().name());
+                }
+
+                itemStepBean.setResult(itemStepEntity.getResult());
+
+                itemStepBeanList.add(itemStepBean);
+            }
+
+            itemBean.setFaceBiopsyItemSteps(itemStepBeanList);
+
+            itemBeanList.add(itemBean);
+        }
+
+        FaceBiopsyBean bean = new FaceBiopsyBean();
+        bean.setErrorMsg(entity.getErrorMsg());
+        bean.setExamRecordDataId(req.getExamRecordDataId());
+        bean.setResult(entity.getResult());
+        bean.setRootOrgId(entity.getRootOrgId());
+        bean.setVerifiedTimes(entity.getVerifiedTimes());
+
+        bean.setFaceBiopsyItems(itemBeanList);
+
+        GetFaceBiopsyResp resp = new GetFaceBiopsyResp();
+        resp.setFaceBiopsyBean(bean);
+
+        return resp;
+
+    }
+
+    /**
+     * 获取考试作答记录
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "获取考试作答记录")
+    @PostMapping("/getExamRecordQuestions")
+    @Override
+    public GetExamRecordQuestionsResp getExamRecordQuestions(@RequestBody GetExamRecordQuestionsReq req) {
+        return examRecordQuestionsService.getExamRecordQuestions(req);
+    }
+
+    /**
+     * 获取试卷结构
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "获取试卷结构")
+    @PostMapping("/getExamRecordPaperStruct")
+    @Override
+    public GetExamRecordPaperStructResp getExamRecordPaperStruct(@RequestBody GetExamRecordPaperStructReq req) {
+
+        return examRecordPaperStructService.getExamRecordPaperStruct(req);
+    }
+
+    /**
+     * 交卷
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "交卷")
+    @PostMapping("/handInExam")
+    @Override
+    public HandInExamResp handInExam(@RequestBody HandInExamReq req) {
+        examControlService.handInExam(req.getExamRecordDataId(), req.getHandInExamType());
+
+        return new HandInExamResp();
+    }
+
+    @ApiOperation(value = "修改考试记录状态")
+    @PostMapping("/updateExamRecordStatus")
+    @Override
+    public UpdateExamRecordStatusResp updateExamRecordStatus(@RequestBody UpdateExamRecordStatusReq req) {
+        examRecordDataService.updateExamRecordStatus(req);
+        UpdateExamRecordStatusResp res = new UpdateExamRecordStatusResp();
+        return res;
+    }
+
+    /**
+     * 获取考试记录数量
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "获取考试记录数量")
+    @PostMapping("/getExamRecordNum")
+    @Override
+    public GetExamRecordNumResp getExamRecordNum(@RequestBody GetExamRecordNumReq req) {
+        ExamRecordDataEntity query = new ExamRecordDataEntity();
+        query.setExamId(req.getExamId());
+        Example<ExamRecordDataEntity> queryExample = Example.of(query);
+        long num = examRecordDataRepo.count(queryExample);
+
+        GetExamRecordNumResp resp = new GetExamRecordNumResp();
+        resp.setNum(num);
+
+        return resp;
+    }
+}

+ 134 - 0
examcloud-core-oe-student-base/pom.xml

@@ -0,0 +1,134 @@
+<?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>
+    <parent>
+        <groupId>cn.com.qmth.examcloud</groupId>
+        <artifactId>examcloud-core-oe-student</artifactId>
+        <version>2019-SNAPSHOT</version>
+    </parent>
+    <artifactId>examcloud-core-oe-student-base</artifactId>
+
+    <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>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud</groupId>
+            <artifactId>examcloud-support</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-exchange-inner-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-basic-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-examwork-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-questions-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-oe-task-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-oe-student-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-oe-admin-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-global-api</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-ws-api-client</artifactId>
+            <version>${examcloud.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>
+            <version>20140107</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.hibernate</groupId>
+            <artifactId>hibernate-validator</artifactId>
+            <version>5.3.6.Final</version>
+        </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>
+            <version>3.16</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.baidu.aip</groupId>
+            <artifactId>java-sdk</artifactId>
+            <version>4.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.esotericsoftware</groupId>
+            <artifactId>reflectasm</artifactId>
+            <version>1.11.3</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 91 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/Model.java

@@ -0,0 +1,91 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-20 10:57:14.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.jpa;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class Model {
+
+    public static <T> T of(Optional<T> optional) {
+        try {
+            if (optional != null) {
+                return optional.get();
+            }
+        } catch (NoSuchElementException e) {
+            //ignore
+        }
+        return null;
+    }
+
+    public static String splitFieldName(String fieldName) {
+        if (fieldName == null) {
+            return null;
+        }
+        StringBuffer result = new StringBuffer();
+        char[] chars = fieldName.toCharArray();
+        final int A = 65, Z = 90, DOT = 46;
+        for (int i = 0; i < chars.length; i++) {
+            Character c = chars[i];
+            if (i == 0) {
+                result.append(c);
+                continue;
+            }
+            if (c == DOT) {
+                result.append("_");
+                continue;
+            }
+            if (c >= A && c <= Z) {
+                result.append("_");
+            }
+            result.append(c);
+        }
+        return result.toString().toUpperCase();
+    }
+
+    public static List<String> parseFields(final Class clazz) {
+        return parseFields(clazz, null);
+    }
+
+    public static List<String> parseFields(final Class clazz, final Class rootClazz) {
+        List<String> fieldNames = new ArrayList<>();
+        Field[] fields = clazz.getDeclaredFields();
+        for (Field field : fields) {
+            if (Modifier.isStatic(field.getModifiers())) {
+                continue;
+            }
+            if (Modifier.isFinal(field.getModifiers())) {
+                continue;
+            }
+            Class<?> fieldType = field.getType();
+            String fieldName = field.getName();
+            if (rootClazz != null && rootClazz.isAssignableFrom(fieldType)) {
+                List<String> subFieldNames = parseFields(fieldType, rootClazz);
+                if (subFieldNames.size() == 0) {
+                    continue;
+                }
+                fieldNames.addAll(subFieldNames.stream()
+                        .map(subName -> subName = fieldName + "." + subName)
+                        .collect(Collectors.toList()));
+            } else {
+                fieldNames.add(fieldName);
+            }
+        }
+        return fieldNames;
+    }
+
+}

+ 20 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/Op.java

@@ -0,0 +1,20 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-17 15:21:52.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.jpa;
+
+/**
+ * 操作枚举类
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public enum Op {
+
+    EQ, NOT_EQ, GT, LT, GTE, LTE, LIKE, L_LIKE, R_LIKE, IS_NULL, NOT_NULL, IN, NOT_IN
+
+}

+ 70 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/Order.java

@@ -0,0 +1,70 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-17 15:21:52.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.jpa;
+
+import org.springframework.data.domain.Sort;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 排序条件类
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class Order implements Serializable {
+    protected static final long serialVersionUID = -1L;
+    private String fieldName;
+    private Sort.Direction direction;
+
+    private List<Order> orders = new ArrayList<>();
+
+    public Order asc(String fieldName) {
+        orders.add(new Order(fieldName, Sort.Direction.ASC));
+        return this;
+    }
+
+    public Order desc(String fieldName) {
+        orders.add(new Order(fieldName, Sort.Direction.DESC));
+        return this;
+    }
+
+    public Sort build() {
+        if (orders.size() == 0) {
+            return null;
+        }
+        List<Sort.Order> list = new ArrayList<>();
+        for (Order order : orders) {
+            list.add(new Sort.Order(order.getDirection(), order.getFieldName()));
+        }
+        return new Sort(list);
+    }
+
+    private Order(String fieldName, Sort.Direction direction) {
+        if (fieldName == null || "".equals(fieldName.trim())) {
+            throw new IllegalArgumentException("FieldName must be not empty.");
+        }
+        this.fieldName = fieldName.trim();
+        this.direction = direction;
+    }
+
+    public Order() {
+
+    }
+
+    public String getFieldName() {
+        return fieldName;
+    }
+
+    public Sort.Direction getDirection() {
+        return direction;
+    }
+
+}

+ 146 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/Searcher.java

@@ -0,0 +1,146 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-17 15:21:52.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.jpa;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 查询条件类
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class Searcher implements Serializable {
+    protected static final long serialVersionUID = -1L;
+    /**
+     * 属性名
+     */
+    private String fieldName;
+    /**
+     * 属性值
+     */
+    private Object value;
+    /**
+     * 操作的表达式
+     */
+    private Op op;
+
+    private List<Searcher> searchers = new ArrayList<>();
+
+    public <T> Searcher eq(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.EQ));
+        return this;
+    }
+
+    public <T> Searcher notEq(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.NOT_EQ));
+        return this;
+    }
+
+    public <T> Searcher gt(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.GT));
+        return this;
+    }
+
+    public <T> Searcher gte(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.GTE));
+        return this;
+    }
+
+    public <T> Searcher lt(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.LT));
+        return this;
+    }
+
+    public <T> Searcher lte(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.LTE));
+        return this;
+    }
+
+    public <T> Searcher like(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.LIKE));
+        return this;
+    }
+
+    public <T> Searcher leftLike(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.L_LIKE));
+        return this;
+    }
+
+    public <T> Searcher rightLike(String fieldName, T value) {
+        searchers.add(new Searcher(fieldName, value, Op.R_LIKE));
+        return this;
+    }
+
+    public Searcher in(String fieldName, Collection<?> values) {
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("Values must be not empty.");
+        }
+        if (values.size() == 1) {
+            searchers.add(new Searcher(fieldName, values.iterator().next(), Op.EQ));
+        } else {
+            searchers.add(new Searcher(fieldName, values.toArray(), Op.IN));
+        }
+        return this;
+    }
+
+    public Searcher notIn(String fieldName, Collection<?> values) {
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("Values must be not empty.");
+        }
+        if (values.size() == 1) {
+            searchers.add(new Searcher(fieldName, values.iterator().next(), Op.NOT_EQ));
+        } else {
+            searchers.add(new Searcher(fieldName, values.toArray(), Op.NOT_IN));
+        }
+        return this;
+    }
+
+    public Searcher isNull(String fieldName) {
+        searchers.add(new Searcher(fieldName, null, Op.IS_NULL));
+        return this;
+    }
+
+    public Searcher notNull(String fieldName) {
+        searchers.add(new Searcher(fieldName, null, Op.NOT_NULL));
+        return this;
+    }
+
+    public List<Searcher> build() {
+        return searchers;
+    }
+
+    private Searcher(String fieldName, Object value, Op op) {
+        if (fieldName == null || "".equals(fieldName.trim())) {
+            throw new IllegalArgumentException("FieldName must be not empty.");
+        }
+        this.fieldName = fieldName.trim();
+        this.value = value;
+        this.op = op;
+    }
+
+    public Searcher() {
+
+    }
+
+    public String getFieldName() {
+        return fieldName;
+    }
+
+    public Object getValue() {
+        return value;
+    }
+
+    public Op getOp() {
+        return op;
+    }
+
+}

+ 135 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/SpecUtils.java

@@ -0,0 +1,135 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-17 15:21:52.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.jpa;
+
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.data.jpa.domain.Specifications;
+
+import javax.persistence.criteria.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JPA查询工具类
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class SpecUtils {
+    /**
+     * 默认当前页数
+     */
+    public static final int DEFAULT_PAGE_NO = 1;
+    /**
+     * 默认每页条数
+     */
+    public static final int DEFAULT_PAGE_SIZE = 10;
+
+    public static Pageable buildPageable(Integer pageNo, Integer pageSize) {
+        return buildPageable(pageNo, pageSize, Sort.unsorted());
+    }
+
+    public static Pageable buildPageable(Integer pageNo, Integer pageSize, Sort sort) {
+        if (pageNo == null || pageNo < 1) {
+            pageNo = DEFAULT_PAGE_NO;
+        }
+        if (pageSize == null || pageSize < 1) {
+            pageSize = DEFAULT_PAGE_SIZE;
+        }
+        return PageRequest.of (pageNo - 1, pageSize, sort);
+    }
+
+    public static <T> Specification<T> buildSearchers(final Class<T> clazz, final List<Searcher> searchers) {
+        return buildSearchers(clazz, searchers, false);
+    }
+
+    public static <T> Specification<T> buildSearchers(final Class<T> clazz, final List<Searcher> searchers, final boolean isOR) {
+        if (searchers == null || searchers.size() == 0) {
+            return null;
+        }
+        return new Specification<T>() {
+            @Override
+            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
+                List<Predicate> predicates = new ArrayList<>();
+                for (Searcher filter : searchers) {
+                    if (filter.getFieldName() == null || "".equals(filter.getFieldName())) {
+                        continue;
+                    }
+                    String[] names = filter.getFieldName().split("\\.");
+                    Path expression = root.get(names[0]);
+                    for (int i = 1; i < names.length; i++) {
+                        expression = expression.get(names[i]);
+                    }
+                    switch (filter.getOp()) {
+                        case EQ:
+                            predicates.add(builder.equal(expression, filter.getValue()));
+                            break;
+                        case NOT_EQ:
+                            predicates.add(builder.notEqual(expression, filter.getValue()));
+                            break;
+                        case GT:
+                            predicates.add(builder.greaterThan(expression, (Comparable) filter.getValue()));
+                            break;
+                        case LT:
+                            predicates.add(builder.lessThan(expression, (Comparable) filter.getValue()));
+                            break;
+                        case GTE:
+                            predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) filter.getValue()));
+                            break;
+                        case LTE:
+                            predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) filter.getValue()));
+                            break;
+                        case LIKE:
+                            predicates.add(builder.like(expression, "%" + filter.getValue() + "%"));
+                            break;
+                        case L_LIKE:
+                            predicates.add(builder.like(expression, "%" + filter.getValue()));
+                            break;
+                        case R_LIKE:
+                            predicates.add(builder.like(expression, filter.getValue() + "%"));
+                            break;
+                        case IS_NULL:
+                            predicates.add(builder.isNull(expression));
+                            break;
+                        case NOT_NULL:
+                            predicates.add(builder.isNotNull(expression));
+                            break;
+                        case IN:
+                            predicates.add(expression.in((Object[]) filter.getValue()));
+                            break;
+                        case NOT_IN:
+                            predicates.add(expression.in((Object[]) filter.getValue()).not());
+                            break;
+                    }
+                }
+                if (!predicates.isEmpty()) {
+                    if (isOR) {
+                        //将所有条件用 or 联合起来
+                        return builder.or(predicates.toArray(new Predicate[predicates.size()]));
+                    } else {
+                        //将所有条件用 and 联合起来
+                        return builder.and(predicates.toArray(new Predicate[predicates.size()]));
+                    }
+                }
+                return builder.conjunction();
+            }
+        };
+    }
+
+    public static Specification andMerge(Specification target1, Specification target2) {
+        return Specifications.where(target1).and(target2);
+    }
+
+    public static Specification orMerge(Specification target1, Specification target2) {
+        return Specifications.where(target1).or(target2);
+    }
+
+}

+ 265 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/jpa/SqlWrapper.java

@@ -0,0 +1,265 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-28 09:38:53.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.jpa;
+
+import java.util.Collection;
+
+/**
+ * 原生SQL包装类
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/28
+ */
+public class SqlWrapper {
+    private static final String SELECT = "SELECT ";
+    private static final String UPDATE = "UPDATE ";
+    private static final String DELETE = "DELETE ";
+    private static final String SET = " SET ";
+    private static final String COUNT = " COUNT ";
+    private static final String SUM = " SUM ";
+    private static final String DISTINCT = " DISTINCT ";
+    private static final String FROM = " FROM ";
+    private static final String WHERE = " WHERE ";
+    private static final String INNER_JOIN = " INNER JOIN ";
+    private static final String LEFT_JOIN = " LEFT JOIN ";
+    private static final String UNION_ALL = " UNION ALL ";
+    private static final String UNION = " UNION ";
+    private static final String ON = " ON ";
+    private static final String AS = " AS ";
+    private static final String AND = " AND ";
+    private static final String OR = " OR ";
+    private static final String LIKE = " LIKE ";
+    private static final String IN = " IN ";
+    private static final String NOT_IN = " NOT IN ";
+    private static final String IS_NULL = " IS NULL ";
+    private static final String IS_NOT_NULL = " IS NOT NULL ";
+    private static final String GROUP_BY = " GROUP BY ";
+    private static final String ORDER_BY = " ORDER BY ";
+    private static final String ASC = " ASC ";
+    private static final String DESC = " DESC ";
+
+    private StringBuilder sql = new StringBuilder();
+
+    public SqlWrapper select() {
+        sql.append(SELECT).append("*");
+        return this;
+    }
+
+    public SqlWrapper select(String columns) {
+        /* 多个按逗号分隔 */
+        sql.append(SELECT).append(columns);
+        return this;
+    }
+
+    public SqlWrapper delete() {
+        sql.append(DELETE);
+        return this;
+    }
+
+    public SqlWrapper update(String tableName) {
+        sql.append(UPDATE).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper set() {
+        sql.append(SET);
+        return this;
+    }
+
+    public SqlWrapper count(String fieldName) {
+        sql.append(COUNT).append("(").append(fieldName).append(")");
+        return this;
+    }
+
+    public SqlWrapper sum(String fieldName) {
+        sql.append(SUM).append("(").append(fieldName).append(")");
+        return this;
+    }
+
+    public SqlWrapper distinct(String fieldName) {
+        sql.append(DISTINCT).append(fieldName);
+        return this;
+    }
+
+    public SqlWrapper from(String tableName) {
+        sql.append(FROM).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper innerJoin(String tableName) {
+        sql.append(INNER_JOIN).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper leftJoin(String tableName) {
+        sql.append(LEFT_JOIN).append(tableName);
+        return this;
+    }
+
+    public SqlWrapper on(String fieldName, String refFieldName) {
+        sql.append(ON).append(fieldName).append(" = ").append(refFieldName);
+        return this;
+    }
+
+    public SqlWrapper where() {
+        sql.append(WHERE);
+        return this;
+    }
+
+    public SqlWrapper and() {
+        sql.append(AND);
+        return this;
+    }
+
+    public SqlWrapper or() {
+        sql.append(OR);
+        return this;
+    }
+
+    public SqlWrapper like(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(LIKE).append("'%").append(value).append("%'");
+        return this;
+    }
+
+    public SqlWrapper like(String fieldName, Number value) {
+        sql.append(fieldName).append(LIKE).append("'%").append(value).append("%'");
+        return this;
+    }
+
+    public SqlWrapper eq(String fieldName, Number value) {
+        sql.append(fieldName).append(" = ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper eq(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" = ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper notEq(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" != ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper notEq(String fieldName, Number value) {
+        sql.append(fieldName).append(" != ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper gt(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" > ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper gt(String fieldName, Number value) {
+        sql.append(fieldName).append(" > ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper gte(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" >= ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper gte(String fieldName, Number value) {
+        sql.append(fieldName).append(" >= ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper lt(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" < ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper lt(String fieldName, Number value) {
+        sql.append(fieldName).append(" < ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper lte(String fieldName, CharSequence value) {
+        sql.append(fieldName).append(" <= ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper lte(String fieldName, Number value) {
+        sql.append(fieldName).append(" <= ").append("'").append(value).append("'");
+        return this;
+    }
+
+    public SqlWrapper in(String fieldName, Collection<?> values) {
+        String str = this.spilt(values);
+        sql.append(fieldName).append(IN).append("(").append(str).append(")");
+        return this;
+    }
+
+    public SqlWrapper notIn(String fieldName, Collection<?> values) {
+        String str = this.spilt(values);
+        sql.append(fieldName).append(NOT_IN).append("(").append(str).append(")");
+        return this;
+    }
+
+    public SqlWrapper isNull(String fieldName) {
+        sql.append(fieldName).append(IS_NULL);
+        return this;
+    }
+
+    public SqlWrapper isNotNull(String fieldName) {
+        sql.append(fieldName).append(IS_NOT_NULL);
+        return this;
+    }
+
+    public SqlWrapper orderBy(String columns, boolean isDesc) {
+        /* 多个按逗号分隔 */
+        if (isDesc) {
+            sql.append(ORDER_BY).append(columns).append(DESC);
+        } else {
+            sql.append(ORDER_BY).append(columns).append(ASC);
+        }
+        return this;
+    }
+
+    public SqlWrapper groupBy(String columns) {
+        /* 多个按逗号分隔 */
+        sql.append(GROUP_BY).append(columns);
+        return this;
+    }
+
+    public SqlWrapper as(String name) {
+        sql.append(AS).append(name);
+        return this;
+    }
+
+    public SqlWrapper append(String str) {
+        sql.append(str);
+        return this;
+    }
+
+    public String build() {
+        return sql.toString();
+    }
+
+    private String spilt(Collection<?> values) {
+        if (values == null || values.size() == 0) {
+            throw new IllegalArgumentException("Values must be not empty.");
+        }
+        int index = 0, total = values.size();
+        StringBuilder str = new StringBuilder();
+        for (Object value : values) {
+            if (!(value instanceof CharSequence || value instanceof Number)) {
+                throw new IllegalArgumentException("Values must be charSequence or number.");
+            }
+            str.append(value.toString());
+            if (index < (total - 1)) {
+                str.append(",");
+            }
+            index++;
+        }
+        return str.toString();
+    }
+
+}

+ 96 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/Check.java

@@ -0,0 +1,96 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-22 11:21:09.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+
+import static cn.com.qmth.examcloud.support.Constants.OE_CODE_400;
+
+/**
+ * 参数校验类
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/22
+ */
+public class Check {
+
+    public static void isNull(Object obj, String message) {
+        if (obj == null) {
+            throw new StatusException(OE_CODE_400, message);
+        }
+    }
+
+    public static void isEmpty(Object obj, String message) {
+        if (isEmpty(obj)) {
+            throw new StatusException(OE_CODE_400, message);
+        }
+    }
+
+    public static void isEmpty(Object[] array, String message) {
+        if (isEmpty(array)) {
+            throw new StatusException(OE_CODE_400, message);
+        }
+    }
+
+    public static void isEmpty(Collection<?> collection, String message) {
+        if (isEmpty(collection)) {
+            throw new StatusException(OE_CODE_400, message);
+        }
+    }
+
+    public static void isEmpty(Map<?, ?> map, String message) {
+        if (isEmpty(map)) {
+            throw new StatusException(OE_CODE_400, message);
+        }
+    }
+
+    public static void isBlank(CharSequence str, String message) {
+        if (isBlank(str)) {
+            throw new StatusException(OE_CODE_400, message);
+        }
+    }
+
+    public static void isFalse(Boolean expression, String message) {
+        if (expression == null || !expression) {
+            throw new StatusException(OE_CODE_400, message);
+        }
+    }
+
+    private static boolean isEmpty(Object obj) {
+        if (obj == null) {
+            return true;
+        } else if (obj instanceof CharSequence) {
+            return ((CharSequence) obj).length() == 0;
+        } else if (obj.getClass().isArray()) {
+            return Array.getLength(obj) == 0;
+        } else if (obj instanceof Collection) {
+            return ((Collection) obj).isEmpty();
+        } else {
+            return obj instanceof Map ? ((Map) obj).isEmpty() : false;
+        }
+    }
+
+    private static boolean isBlank(CharSequence cs) {
+        int strLen;
+        if (cs != null && (strLen = cs.length()) != 0) {
+            for (int i = 0; i < strLen; ++i) {
+                if (!Character.isWhitespace(cs.charAt(i))) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return true;
+        }
+    }
+
+}

+ 166 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/CommonUtil.java

@@ -0,0 +1,166 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.lang3.StringUtils;
+
+import java.lang.reflect.Field;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+
+/**
+ * @author  	chenken
+ * @date    	2017年12月13日 下午3:39:56
+ * @company 	QMTH
+ * @description CommonUtil.java
+ */
+public class CommonUtil {
+	public static double nullToZero(Double score) {
+        if (score == null) {
+            return 0D;
+        } else {
+            return score;
+        }
+    }
+	/**
+	 * 计算number1 - number2之间的随机数
+	 * @param number1
+	 * @param number2
+	 * @return
+	 */
+	public static int calculationRandomNumber(int number1,int number2){
+		if(number1>number2){
+			throw new RuntimeException("number1 must less than number2");
+		}
+		return (int)(Math.random()*(number2-number1+1)+number1);
+	}
+
+
+	/**
+	 * 格式化日期精确到秒
+	 * @param date	日期
+	 * @param format yyyy-MM-dd  yyyy-MM-dd HH:mm:ss
+	 * @return
+	 */
+	public static String getDateStrWithSecond(Date date){
+		if(date == null){
+			return null;
+		}
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+		return sdf.format(date);
+	}
+	
+	/**
+	 * 格式化日期精确到日
+	 * @param date	日期
+	 * @param format yyyy-MM-dd  yyyy-MM-dd HH:mm:ss
+	 * @return
+	 */
+	public static String getDateStrWithoutSecond(Date date){
+		if(date == null){
+			return null;
+		}
+		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+		return sdf.format(date);
+	}
+	
+	/**
+	 * 生成随机数
+	 * @param several  个数
+	 * @return
+	 */
+	public static String makeRandomNum(int several){
+		StringBuffer sb = new StringBuffer();
+		for(int i = 0;i<several;i++){
+			sb.append(new Random().nextInt(10));
+		}
+		return sb.toString();
+	}
+	
+	/**
+     * 重新排列 Integer数组
+     * @param arr
+     * @return
+     */
+	public static Integer[] reorderArray(Integer[] arr){
+    	int temp;
+		for(int i = 0;i<arr.length;i++){
+		     int e=(int) Math.round((arr.length-1)*Math.random());
+		     temp = arr[e];
+		     arr[e] = arr[arr.length-1];
+		     arr[arr.length-1] = temp;
+		}
+		return arr;
+    }
+	
+	public static boolean isTrue(Object obj){
+		if(obj == null){
+			return false;
+		}
+		if(obj instanceof Boolean){
+			return (boolean) obj;
+		}else{
+			return false;
+		}
+	}
+	
+    public static Map<String, Object> getKeyAndValue(Object obj) {
+        Map<String, Object> map = new HashMap<String, Object>();
+        // 得到类对象
+        Class<?> userCla = (Class<?>) obj.getClass();
+        /* 得到类中的所有属性集合 */
+        Field[] fs = userCla.getDeclaredFields();
+        for (int i = 0; i < fs.length; i++) {
+            Field f = fs[i];
+            f.setAccessible(true); // 设置些属性是可以访问的
+            Object val = new Object();
+            try {
+                val = f.get(obj);
+                // 得到此属性的值
+                map.put(f.getName(), val);// 设置键值
+            } catch (IllegalArgumentException e) {
+                e.printStackTrace();
+            } catch (IllegalAccessException e) {
+                e.printStackTrace();
+            }
+        }
+        System.out.println("单个对象的所有键值==反射==" + map.toString());
+        return map;
+    }
+	
+    /**
+     * 判断字符串是不是json格式
+     * @param content
+     * @return
+     */
+    public static boolean isJson(String content) {
+        try {
+            JSONObject.parseObject(content);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+    
+    /**
+     * 判断字符串是否为null,或者为 "null"
+     * @param content
+     * @return
+     */
+    public static boolean isBlank(String content){
+    	if(StringUtils.isBlank(content) || "null".equals(content)){
+    		return true;
+    	}
+    	return false;
+    }
+    
+	public static void main(String[] args) {
+		for(int i = 0;i<200;i++){
+			System.out.println(i+":"+calculationRandomNumber(1,2));
+		}
+	}
+}
+

+ 101 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/DateUtils.java

@@ -0,0 +1,101 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-30 15:54:06.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * @author: fengdesheng
+ * @since: 2018/8/30
+ */
+public class DateUtils {
+    public static final String PATTERN = "yyyy-MM-dd";
+    public static final String FULL_PATTERN = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * 日期字符串转日期对象
+     *
+     * @param dateStr 支持格式:yyyy/MM/dd 或 yyyy-MM-dd
+     * @return
+     */
+    public static Date parse(String dateStr) {
+        if (dateStr == null) {
+            return null;
+        }
+        try {
+            dateStr = dateStr.replaceAll("/", "-");
+            if (dateStr.length() == 10) {
+                return new SimpleDateFormat(PATTERN).parse(dateStr);
+            }
+            return new SimpleDateFormat(FULL_PATTERN).parse(dateStr);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static String format(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return new SimpleDateFormat(FULL_PATTERN).format(date);
+    }
+
+    /**
+     * 计算时长
+     */
+    public static String diff(long diff) {
+        long nd = 1000 * 24 * 60 * 60;//一天的毫秒数
+        long nh = 1000 * 60 * 60;//一小时的毫秒数
+        long nm = 1000 * 60;//一分钟的毫秒数
+        long ns = 1000;//一秒钟的毫秒数
+        long day = diff / nd;//计算差多少天
+        long hour = diff % nd / nh;//计算差多少小时
+        long min = diff % nd % nh / nm;//计算差多少分钟
+        long sec = diff % nd % nh % nm / ns;//计算差多少秒//输出结果
+
+        StringBuilder sb = new StringBuilder();
+        if (day != 0) {
+            sb.append(day + "d ");
+        }
+        if (hour != 0) {
+            sb.append(hour + "h");
+        }
+        if (min != 0) {
+            sb.append(min + "m");
+        }
+        if (sec != 0) {
+            sb.append(sec + "s");
+        }
+        return sb.toString();
+    }
+    /**
+     * 在指定日期添加固定小时数
+     * @param date 日期
+     * @param hours 小时数(可为负数)
+     * @return
+     */
+    public static Date addHours(Date date,int hours) {
+        return addDateByField(date, Calendar.HOUR_OF_DAY,hours);
+    }
+    /**
+     * 给日期指定区块添加相应的数值
+     * @param date 日期
+     * @param field 日期区块
+     * @param amount 数值(可为负数)
+     * @return
+     */
+    public static Date addDateByField(Date date,int field,int amount) {
+        Calendar ca = Calendar.getInstance();
+        ca.setTime(date);
+        ca.add(field,amount);
+        return ca.getTime();
+    }
+}

+ 248 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/FileDisposeUtil.java

@@ -0,0 +1,248 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+
+/**
+ * @author chenken
+ * @date 2017年7月17日 上午9:36:32
+ * @company QMTH
+ * @description FileUtil.java
+ */
+public class FileDisposeUtil {
+	
+	private static final Logger logger = LoggerFactory.getLogger(FileDisposeUtil.class);
+	
+	/**
+	 * 将网络文件保存到本地
+	 * @param fileUrl		   网络文件URL
+	 * @param localFilePath  例如D:/123.txt
+	 * @return
+	 */
+	public static boolean saveUrlAs(String fileUrl,String localFilePath) {
+		HttpURLConnection connection = null;
+		FileOutputStream fileOutputStream = null;
+		DataOutputStream dataOutputStream = null;
+		DataInputStream dataInputStream = null;
+		try {
+			URL url = new URL(fileUrl);
+			connection = (HttpURLConnection) url.openConnection();
+			dataInputStream = new DataInputStream(connection.getInputStream());
+			fileOutputStream = new FileOutputStream(localFilePath);
+			dataOutputStream = new DataOutputStream(fileOutputStream);
+			byte[] buffer = new byte[4096];
+			int count = 0;
+			while ((count = dataInputStream.read(buffer)) > 0) {
+				dataOutputStream.write(buffer, 0, count);
+			}
+			return true;
+		} catch (Exception e) {
+			return false;
+		}finally {
+			try {
+				if(fileOutputStream!=null){
+					fileOutputStream.flush();
+					fileOutputStream.close();
+					fileOutputStream = null;
+				}
+				if (dataOutputStream != null) {
+					dataOutputStream.flush();
+					dataOutputStream.close();
+					dataOutputStream = null;
+				}
+				if (dataInputStream != null) {
+					dataInputStream.close();
+					dataInputStream = null;
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+			if (connection != null) {
+				connection.disconnect();
+				connection = null;
+			}
+		}
+	}
+	
+	/**
+	 * 下载服务器上的文件
+	 * @param filename		文件名称
+	 * @param fullFilePath	文件全路径
+	 * @param response
+	 */
+	public static void downloadFile(String filename,String fullFilePath,HttpServletResponse response){
+		FileInputStream input = null;
+		OutputStream output = null;
+		try {
+	        //设置文件MIME类型  
+			response.setContentType(getContentType(filename));
+	        response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(filename,"UTF-8"));
+	        //读取目标文件,通过response将目标文件写到客户端  
+	        input =  new FileInputStream(fullFilePath);
+			output =  response.getOutputStream();  
+	        //写文件  
+			byte[] b = new byte[2048];
+            int len;
+            while ((len = input.read(b)) != -1) {
+            	output.write(b, 0, len);
+            } 
+            response.setHeader("Content-Length", String.valueOf(input.getChannel().size()));
+	        input.close();  
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+	/**
+	 * 获得文件MIME类型
+	 * @param filename
+	 * @return
+	 */
+	public static String getContentType(String filename){
+		String type = null;
+		Path path = Paths.get(filename);
+		try {
+			type = Files.probeContentType(path);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return type;
+	}
+
+	/**
+	 * 将存放在sourceFilePath目录下的源文件,打包成fileName名称的zip文件,并存放到zipFilePath路径下
+	 * 
+	 * @param sourceFilePath
+	 *            :待压缩的文件夹路径
+	 * @param zipFilePath
+	 *            :压缩后zip文件的存放路径
+	 * @param fileName
+	 *            :zip文件的名称
+	 * @return
+	 */
+	public static boolean fileToZip(String sourceFilePath, String zipFilePath,String fileName) {
+		logger.info("压缩"+sourceFilePath+"目录开始");
+		boolean flag = false;
+		File sourceFile = new File(sourceFilePath);
+		FileInputStream fis = null;
+		BufferedInputStream bis = null;
+		FileOutputStream fos = null;
+		ZipOutputStream zos = null;
+		if (sourceFile.exists() == false) {
+			logger.error("待压缩的文件目录:" + sourceFilePath + "不存在.");
+		} else {
+			try {
+				File zipFile = new File(zipFilePath+File.separator+fileName+".zip");
+				if (zipFile.exists()) {
+					logger.error(zipFilePath + "目录下存在名字为:"+fileName+".zip"+"打包文件.");
+				} else {
+					File[] sourceFiles = sourceFile.listFiles();
+					if (null == sourceFiles || sourceFiles.length < 1) {
+						logger.error("待压缩的文件目录:" + sourceFilePath+ "里面不存在文件,无需压缩.");
+					} else {
+						fos = new FileOutputStream(zipFile);
+						zos = new ZipOutputStream(new BufferedOutputStream(fos));
+						byte[] bufs = new byte[1024 * 10];
+						for (int i = 0; i < sourceFiles.length; i++) {
+							try{
+								//创建ZIP实体,并添加进压缩包
+								String fileEncode = System.getProperty("file.encoding");
+								String name = new String(sourceFiles[i].getName().getBytes(fileEncode),"UTF-8");
+								ZipEntry zipEntry = new ZipEntry(name);
+								zos.putNextEntry(zipEntry);
+								//读取待压缩的文件并写进压缩包里
+								fis = new FileInputStream(sourceFiles[i]);
+								bis = new BufferedInputStream(fis, 1024 * 10);
+								int read = 0;
+								while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
+									zos.write(bufs, 0, read);
+								}
+								zos.flush();
+							}catch(Exception e){
+								e.printStackTrace();
+							}finally{
+								IOUtils.closeQuietly(bis);
+								IOUtils.closeQuietly(fis);
+							}
+						}
+						flag = true;
+					}
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			} finally {
+				IOUtils.closeQuietly(bis);
+				IOUtils.closeQuietly(fis);
+				IOUtils.closeQuietly(zos);
+				IOUtils.closeQuietly(fos);
+			}
+		}
+		logger.info("压缩"+sourceFilePath+"目录完成");
+		return flag;
+	}
+	
+	public static void createDirectory(String downloadDirectory) {
+		File directory = new File(downloadDirectory);
+		if(!directory.exists()){
+			directory.mkdirs();
+		}else{
+			FileUtils.deleteQuietly(directory);
+			directory.mkdirs();
+		}
+	}
+	
+	/**
+	 * 获得文件的byte数组
+	 * @param filePath
+	 * @return
+	 * @throws IOException
+	 */
+	public static byte[] getBytes(String filePath) throws IOException{  
+    	File file = new File(filePath);
+        if (!file.exists()) {
+            throw new FileNotFoundException(filePath);
+        }
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(((int) file.length()));
+        BufferedInputStream in = null;
+        try {
+            in = new BufferedInputStream(new FileInputStream(file));
+            int bufSize = 1024;
+            byte[] buffer = new byte[bufSize];
+            int len = 0;
+            while (-1 != (len = in.read(buffer, 0, bufSize))) {
+                bos.write(buffer, 0, len);
+            }
+            return bos.toByteArray();
+        } finally {
+            try {
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            bos.close();
+        }
+    }
+	
+	public static void main(String[] args) {
+		System.out.println(System.getProperty("sun.jnu.encoding"));
+		System.out.println(System.getProperty("file.encoding"));
+	}
+}

+ 95 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/HtmlUtil.java

@@ -0,0 +1,95 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import org.springframework.util.StringUtils;
+
+/**
+ * Created by yuanpan on 2017/5/16.
+ */
+public class HtmlUtil {
+
+    /**
+     * 去掉html中的span
+     *
+     * @param html
+     * @return
+     */
+    public static String removeSpan(String html) {
+        if (StringUtils.isEmpty(html)) {
+            return "";
+        } else {
+            return html.replaceAll("<span>", "").replaceAll("</span>", "");
+        }
+    }
+
+    public static String ToCH(int intInput) {
+        String si = String.valueOf(intInput);
+        String sd = "";
+        if (si.length() == 1) // 個
+        {
+            sd += GetCH(intInput);
+            return sd;
+        } else if (si.length() == 2)// 十
+        {
+            if (si.substring(0, 1).equals("1"))
+                sd += "十";
+            else
+                sd += (GetCH(intInput / 10) + "十");
+            sd += ToCH(intInput % 10);
+        } else if (si.length() == 3)// 百
+        {
+            sd += (GetCH(intInput / 100) + "百");
+            if (String.valueOf(intInput % 100).length() < 2)
+                sd += "零";
+            sd += ToCH(intInput % 100);
+        } else if (si.length() == 4)// 千
+        {
+            sd += (GetCH(intInput / 1000) + "千");
+            if (String.valueOf(intInput % 1000).length() < 3)
+                sd += "零";
+            sd += ToCH(intInput % 1000);
+        } else if (si.length() == 5)// 萬
+        {
+            sd += (GetCH(intInput / 10000) + "萬");
+            if (String.valueOf(intInput % 10000).length() < 4)
+                sd += "零";
+            sd += ToCH(intInput % 10000);
+        }
+        return sd;
+    }
+
+    private static String GetCH(int input) {
+        String sd = "";
+        switch (input) {
+            case 1:
+                sd = "一";
+                break;
+            case 2:
+                sd = "二";
+                break;
+            case 3:
+                sd = "三";
+                break;
+            case 4:
+                sd = "四";
+                break;
+            case 5:
+                sd = "五";
+                break;
+            case 6:
+                sd = "六";
+                break;
+            case 7:
+                sd = "七";
+                break;
+            case 8:
+                sd = "八";
+                break;
+            case 9:
+                sd = "九";
+                break;
+            default:
+                break;
+        }
+        return sd;
+    }
+}

+ 91 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/HttpPoolUtil.java

@@ -0,0 +1,91 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.springframework.stereotype.Component;
+
+
+/** 
+* 连接池工具类 
+*/ 
+@Component
+public class HttpPoolUtil { 
+	private static Log logger = LogFactory.getLog(HttpPoolUtil.class); 
+	
+	public static final String UTF8 = "UTF-8"; 
+	public static volatile boolean isClosed = false; 
+	
+	public static final int maxTotalPool = 200;
+	public static final int MAX_TIMEOUT = 15000;
+	public static final int RequestTimeout = 5000;
+	
+	private static RequestConfig requestConfig; 
+	private static HttpClientBuilder httpClientBuilder; 
+	private static PoolingHttpClientConnectionManager poolConnManager;
+	
+	static { 
+		// 设置连接池 
+		poolConnManager = new PoolingHttpClientConnectionManager(); 
+		poolConnManager.setMaxTotal(maxTotalPool);//设置连接池大小 
+		poolConnManager.setDefaultMaxPerRoute(maxTotalPool); 
+		
+		RequestConfig.Builder configBuilder = RequestConfig.custom(); 
+		// 设置连接超时 
+		configBuilder.setConnectTimeout(MAX_TIMEOUT); 
+		// 设置读取超时 
+		configBuilder.setSocketTimeout(MAX_TIMEOUT); 
+		// 设置从连接池获取连接实例的超时 
+		configBuilder.setConnectionRequestTimeout(RequestTimeout); 
+		// 在提交请求之前 测试连接是否可用 
+		//configBuilder.setStaleConnectionCheckEnabled(true); 
+		requestConfig = configBuilder.build(); 
+		// 
+		httpClientBuilder = HttpClients.custom()
+									   .setConnectionManager(poolConnManager)
+									   .setDefaultRequestConfig(requestConfig); 
+		System.out.println(">>>>>>>>>>> PoolingHttpClientConnectionManager初始化成功 >>>>>>>>>>>");
+	}
+	
+	/** 
+	* 获取HttpClient客户端 
+	* @return httpClient 
+	*/ 
+	/*public static CloseableHttpClient getClient() { 
+		CloseableHttpClient httpClient = HttpClients.custom() 
+		.setConnectionManager(poolConnManager) 
+		.setDefaultRequestConfig(requestConfig) 
+		.build(); 
+		if(null == httpClient){ 
+			httpClient = HttpClients.createDefault(); 
+		} 
+		return httpClient; 
+	}*/
+	
+	/** 
+	* 从http连接池里获取客户端实例 
+	* @return httpClient 
+	*/ 
+	public static CloseableHttpClient getHttpClient() { 
+		CloseableHttpClient httpClient = httpClientBuilder.build(); 
+		if(null == httpClient){ 
+			logger.info("---------HttpClients.createDefault()---------"); 
+			httpClient = HttpClients.createDefault(); 
+		} 
+		return httpClient; 
+	}
+	
+	/** 
+	* 关闭连接池资源 
+	*/ 
+	public synchronized static void closePool() { 
+		if( !isClosed ){ 
+			isClosed = true; 
+			poolConnManager.close(); 
+		} 
+	}
+} 

+ 172 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/JsonMapper.java

@@ -0,0 +1,172 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-21 14:12:14.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.util.JSONPObject;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 封装Jackson,实现JSON 与 Java Object互相转换的Mapper
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/20
+ */
+@SuppressWarnings("unchecked")
+public class JsonMapper {
+    private static Logger log = LoggerFactory.getLogger(JsonMapper.class);
+    private ObjectMapper mapper;
+
+    public JsonMapper() {
+        this(null);
+    }
+
+    public JsonMapper(Include include) {
+        mapper = new ObjectMapper();
+        if (include != null) {
+            mapper.setSerializationInclusion(include);
+        }
+        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+    }
+
+    public static JsonMapper nonEmptyMapper() {
+        return new JsonMapper(Include.NON_EMPTY);
+    }
+
+    public static JsonMapper nonNullMapper() {
+        return new JsonMapper(Include.NON_NULL);
+    }
+
+    public static JsonMapper nonDefaultMapper() {
+        return new JsonMapper(Include.NON_DEFAULT);
+    }
+
+    public String toJson(Object object) {
+        try {
+            return mapper.writeValueAsString(object);
+        } catch (IOException e) {
+            log.error("write to json string error:" + object);
+            return null;
+        }
+    }
+
+    public <T> T fromJson(String jsonString, Class<T> clazz) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return mapper.readValue(jsonString, clazz);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    public <T> T fromJson(String jsonString, JavaType javaType) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return (T) mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    public <T> T fromJson(String jsonString, TypeReference javaType) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return (T) mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    public <T> List<T> toList(String jsonString, Class<T> bean) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            JavaType javaType = constructCollectionType(List.class, bean);
+            return mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    public <T> Map<String, T> toHashMap(String jsonString, Class<T> bean) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            JavaType javaType = constructMapType(HashMap.class, String.class, bean);
+            return mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error:", e);
+            return null;
+        }
+    }
+
+    public JavaType constructCollectionType(Class<? extends Collection> collectionClass, Class<?> elementClass) {
+        return mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass);
+    }
+
+    public JavaType constructMapType(Class<? extends Map> mapClass, Class<?> keyClass, Class<?> valueClass) {
+        return mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass);
+    }
+
+    public void update(String jsonString, Object object) {
+        try {
+            mapper.readerForUpdating(object).readValue(jsonString);
+        } catch (JsonProcessingException e) {
+            log.error("update json string:" + jsonString + " to object:" + object + " error.");
+        } catch (IOException e) {
+            log.error("update json string:" + jsonString + " to object:" + object + " error.");
+        }
+    }
+
+    public String toJsonP(String functionName, Object object) {
+        return toJson(new JSONPObject(functionName, object));
+    }
+
+    public void enableEnumUseToString() {
+        mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+        mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+    }
+
+    public ObjectMapper getMapper() {
+        return mapper;
+    }
+
+    public JsonNode getNode(String jsonStr) {
+        try {
+            //mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
+            return mapper.readTree(jsonStr);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+}

+ 63 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/NewQuestionType.java

@@ -0,0 +1,63 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-09-12 09:55:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+public enum NewQuestionType {
+
+    SINGLE_CHOICE("单选题", true),
+
+    MULTIPLE_CHOICE("多选题", true),
+
+    TRUE_OR_FALSE("判断题", true),
+
+    FILL_UP("填空题", false),
+
+    ESSAY("问答题", false),
+
+    NESTED("套题", false);
+
+    /**
+     * 转换来源于题库的题型值
+     */
+    public static NewQuestionType convert(String value) {
+        if (value == null) {
+            return null;
+        }
+        if ("SINGLE_ANSWER_QUESTION".equals(value)) {
+            return SINGLE_CHOICE;
+        } else if ("MULTIPLE_ANSWER_QUESTION".equals(value)) {
+            return MULTIPLE_CHOICE;
+        } else if ("BOOL_ANSWER_QUESTION".equals(value)) {
+            return TRUE_OR_FALSE;
+        } else if ("FILL_BLANK_QUESTION".equals(value)) {
+            return FILL_UP;
+        } else if ("TEXT_ANSWER_QUESTION".equals(value)) {
+            return ESSAY;
+        } else if ("NESTED_ANSWER_QUESTION".equals(value)) {
+            return NESTED;
+        }
+        return null;
+    }
+
+    private boolean objective;
+    private String desc;
+
+    NewQuestionType(String desc, boolean objective) {
+        this.objective = objective;
+        this.desc = desc;
+    }
+
+    public boolean isObjective() {
+        return objective;
+    }
+
+    public String getDesc() {
+        return desc;
+    }
+
+}

+ 101 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/PagingAndSortingDTO.java

@@ -0,0 +1,101 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+/**
+ * 分页DTO
+ *
+ * @author xudengqi
+ * 2016年8月18日
+ */
+public class PagingAndSortingDTO {
+
+    private int page;
+
+    private int size;
+
+    private int pageCount;
+
+    private String[] properties;
+
+    private String order;
+
+    private long totalCount;
+
+    private Object data;
+
+    protected PagingAndSortingDTO() {
+    }
+
+    public PagingAndSortingDTO(int page, int size) {
+        this.page = page;
+        this.size = size;
+    }
+
+    public PagingAndSortingDTO(Object data) {
+        this.data = data;
+    }
+
+    public PagingAndSortingDTO(int page, int size, int pageCount, long totalCount, Object data) {
+        this.page = page;
+        this.size = size;
+        this.pageCount = pageCount;
+        this.data = data;
+        this.totalCount = totalCount;
+    }
+
+    public int getPage() {
+        return page;
+    }
+
+    public void setPage(int page) {
+        this.page = page;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public void setSize(int size) {
+        this.size = size;
+    }
+
+    public int getPageCount() {
+        return pageCount;
+    }
+
+    public void setPageCount(int pageCount) {
+        this.pageCount = pageCount;
+    }
+
+    public String[] getProperties() {
+        return properties;
+    }
+
+    public void setProperties(String[] properties) {
+        this.properties = properties;
+    }
+
+    public String getOrder() {
+        return order;
+    }
+
+    public void setOrder(String order) {
+        this.order = order;
+    }
+
+    public long getTotalCount() {
+        return totalCount;
+    }
+
+    public void setTotalCount(long totalCount) {
+        this.totalCount = totalCount;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+
+}

+ 74 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/PagingAndSortingSpecification.java

@@ -0,0 +1,74 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+
+/**
+ * 构建动态查询、分页、排序
+ *
+ * @author xudengqi
+ * 2016年8月18日
+ */
+public abstract class PagingAndSortingSpecification implements QuerySpecification {
+
+    private Integer page;
+    private Integer size;
+    private String order;
+    private String[] properties;
+
+    public Integer getPage() {
+        return page;
+    }
+
+    public void setPage(Integer page) {
+        this.page = page;
+    }
+
+    public Integer getSize() {
+        return size;
+    }
+
+    public void setSize(Integer size) {
+        this.size = size;
+    }
+
+    public String getOrder() {
+        return order;
+    }
+
+    public void setOrder(String order) {
+        this.order = order;
+    }
+
+    public String[] getProperties() {
+        return properties;
+    }
+
+    public void setProperties(String[] properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public abstract Specification<?> getSpecification();
+
+    @Override
+    public PageRequest getPageRequest() {
+        PageRequest pageRequest = null;
+
+        if (order == null || order.trim().length() == 0) {
+            order = "ASC";
+        }
+        if (properties != null) {
+            Sort sort = new Sort(Sort.Direction.valueOf(order), properties);
+            pageRequest = new PageRequest(page, size, sort);
+        } else if (page != null && size != null) {
+            pageRequest = new PageRequest(page, size);
+        } else {
+            //TODO 暂时返还一条数据
+            pageRequest = new PageRequest(0, 1);
+        }
+        return pageRequest;
+    }
+
+}

+ 162 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/QEncodeUtil.java

@@ -0,0 +1,162 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.spec.SecretKeySpec;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Base64;
+
+/**
+ * 编码工具类
+ * 1.将byte[]转为各种进制的字符串
+ * 2.base 64 encode
+ * 3.base 64 decode
+ * 4.获取byte[]的md5值
+ * 5.获取字符串md5值
+ * 6.结合base64实现md5加密
+ * 7.AES加密
+ * 8.AES加密为base 64 code
+ * 9.AES解密
+ * 10.将base 64 code AES解密
+ * @author uikoo9
+ * @version 0.0.7.20140601
+ */
+public class QEncodeUtil {
+	
+	public static void main(String[] args) throws Exception {
+		String content = "abcdefg";
+		System.out.println("加密前:" + content);
+ 
+		String key = "123456";
+		System.out.println("加密密钥和解密密钥:" + key);
+		
+		String encrypt = aesEncrypt(content, key);
+		System.out.println("加密后:" + encrypt);
+		
+		String decrypt = aesDecrypt(encrypt, key);
+		System.out.println("解密后:" + decrypt);
+	}
+	
+	/**
+	 * 将byte[]转为各种进制的字符串
+	 * @param bytes byte[]
+	 * @param radix 可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,超出范围后变为10进制
+	 * @return 转换后的字符串
+	 */
+	public static String binary(byte[] bytes, int radix){
+		return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数
+	}
+	
+	/**
+	 * base 64 encode
+	 * @param bytes 待编码的byte[]
+	 * @return 编码后的base 64 code
+	 */
+	public static String base64Encode(byte[] bytes){
+		return Base64.getEncoder().encodeToString(bytes);
+	}
+	
+	/**
+	 * base 64 decode
+	 * @param base64Code 待解码的base 64 code
+	 * @return 解码后的byte[]
+	 * @throws Exception
+	 */
+	public static byte[] base64Decode(String base64Code) throws Exception{
+		return StringUtils.isEmpty(base64Code) ? null : Base64.getDecoder().decode(base64Code);
+	}
+	
+	/**
+	 * 获取byte[]的md5值
+	 * @param bytes byte[]
+	 * @return md5
+	 * @throws Exception
+	 */
+	public static byte[] md5(byte[] bytes) throws Exception {
+		MessageDigest md = MessageDigest.getInstance("MD5");
+		md.update(bytes);
+		
+		return md.digest();
+	}
+	
+	/**
+	 * 获取字符串md5值
+	 * @param msg 
+	 * @return md5
+	 * @throws Exception
+	 */
+	public static byte[] md5(String msg) throws Exception {
+		return StringUtils.isEmpty(msg) ? null : md5(msg.getBytes());
+	}
+	
+	/**
+	 * 结合base64实现md5加密
+	 * @param msg 待加密字符串
+	 * @return 获取md5后转为base64
+	 * @throws Exception
+	 */
+	public static String md5Encrypt(String msg) throws Exception{
+		return StringUtils.isEmpty(msg) ? null : base64Encode(md5(msg));
+	}
+	
+	/**
+	 * AES加密
+	 * @param content 待加密的内容
+	 * @param encryptKey 加密密钥
+	 * @return 加密后的byte[]
+	 * @throws Exception
+	 */
+	public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {
+		KeyGenerator kgen = KeyGenerator.getInstance("AES");
+		kgen.init(128, new SecureRandom(encryptKey.getBytes()));
+ 
+		Cipher cipher = Cipher.getInstance("AES");
+		cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(kgen.generateKey().getEncoded(), "AES"));
+		
+		return cipher.doFinal(content.getBytes("utf-8"));
+	}
+	
+	/**
+	 * AES加密为base 64 code
+	 * @param content 待加密的内容
+	 * @param encryptKey 加密密钥
+	 * @return 加密后的base 64 code
+	 * @throws Exception
+	 */
+	public static String aesEncrypt(String content, String encryptKey) throws Exception {
+		return base64Encode(aesEncryptToBytes(content, encryptKey));
+	}
+	
+	/**
+	 * AES解密
+	 * @param encryptBytes 待解密的byte[]
+	 * @param decryptKey 解密密钥
+	 * @return 解密后的String
+	 * @throws Exception
+	 */
+	public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {
+		KeyGenerator kgen = KeyGenerator.getInstance("AES");
+		kgen.init(128, new SecureRandom(decryptKey.getBytes()));
+		
+		Cipher cipher = Cipher.getInstance("AES");
+		cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(kgen.generateKey().getEncoded(), "AES"));
+		byte[] decryptBytes = cipher.doFinal(encryptBytes);
+		
+		return new String(decryptBytes);
+	}
+	
+	/**
+	 * 将base 64 code AES解密
+	 * @param encryptStr 待解密的base 64 code
+	 * @param decryptKey 解密密钥
+	 * @return 解密后的string
+	 * @throws Exception
+	 */
+	public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {
+		return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);
+	}
+}

+ 17 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/QuerySpecification.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.jpa.domain.Specification;
+
+/**
+ * 通过Specification动态构建动态查询、分页、排序
+ *
+ * @author xudengqi
+ * 2016年8月18日
+ */
+public interface QuerySpecification {
+
+    Specification<?> getSpecification();
+
+    PageRequest getPageRequest();
+}

+ 40 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/QuestionTypeUtil.java

@@ -0,0 +1,40 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年9月1日 下午2:24:07
+ * @company 	QMTH
+ * @description QuestionTypeUtil.java
+ */
+public class QuestionTypeUtil {
+
+	/**
+	 * 是否是客观题,客观题返回true
+	 * @param questionType
+	 * @return
+	 */
+	public static boolean isObjectiveQuestion(QuestionType questionType){
+		if(questionType==QuestionType.SINGLE_CHOICE
+    			||questionType==QuestionType.MULTIPLE_CHOICE
+    				||questionType==QuestionType.TRUE_OR_FALSE){
+    		return true;
+    	}
+		return false;
+	}
+	
+	/**
+	 * 是否是选择题,单选多选返回true
+	 * @param questionType
+	 * @return
+	 */
+	public static boolean isChoiceQuestion(QuestionType questionType){
+		if(questionType==QuestionType.SINGLE_CHOICE
+    			||questionType==QuestionType.MULTIPLE_CHOICE){
+    		return true;
+    	}
+		return false;
+	}
+}

+ 63 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/RowIterator.java

@@ -0,0 +1,63 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import com.esotericsoftware.reflectasm.ConstructorAccess;
+import com.esotericsoftware.reflectasm.FieldAccess;
+import org.apache.poi.ss.formula.functions.T;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+import java.util.function.Consumer;
+
+/**
+ * Created by zhengmin on 2016/8/19.
+ */
+public class RowIterator implements Iterator<T>{
+
+    private Class<T> clazz;
+
+    private XSSFWorkbook workbook;
+    private XSSFSheet sheet;
+    private int size = 0;
+    private int times = 0;
+
+    public RowIterator(InputStream inputStream,int sheetIndex,Class<T> clazz) throws IOException {
+        this.clazz = clazz;
+        this.workbook = new XSSFWorkbook(inputStream);
+        this.sheet = workbook.getSheetAt(sheetIndex);
+        this.size = sheet.getLastRowNum();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return times <= size;
+    }
+
+    @Override
+    public T next() {
+        XSSFRow row = this.sheet.getRow(times);
+        ConstructorAccess<T> access = ConstructorAccess.get(clazz);
+        T instance = access.newInstance();
+        FieldAccess fa = FieldAccess.get(clazz);
+        fa.set(instance,0,row.getCell(0).getStringCellValue());
+        times++;
+        return instance;
+    }
+
+    @Override
+    public void remove() {
+
+    }
+
+    @Override
+    public void forEachRemaining(Consumer<? super T> action) {
+
+    }
+
+    public int getSize(){
+        return this.size;
+    }
+}

+ 180 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/Sentence.java

@@ -0,0 +1,180 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-09-12 09:55:46.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.student.base.utils;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Mysql相关语句处理工具类
+ *
+ * @author: fengdesheng
+ * @since: 2018/9/5
+ */
+public class Sentence {
+    private static final String YYYY_MM_DD = "yyyy-MM-dd";
+    private static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+    /**
+     * 处理字符串
+     */
+    public static String dealString(Object value, String defaultValue) {
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof CharSequence) {
+            return String.valueOf(value);
+        }
+        throw new IllegalArgumentException("[" + value + "] Can't convert to String.");
+    }
+
+    public static String dealString(Object value) {
+        return dealString(value, null);
+    }
+
+    /**
+     * 处理Integer
+     */
+    public static Integer dealInteger(Object value, Integer defaultValue) {
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof Number) {
+            return ((Number) value).intValue();
+        }
+        throw new IllegalArgumentException("[" + value + "] Can't convert to Integer.");
+    }
+
+    public static Integer dealInteger(Object value) {
+        return dealInteger(value, null);
+    }
+
+    /**
+     * 处理Long
+     */
+    public static Long dealLong(Object value, Long defaultValue) {
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof Number) {
+            return ((Number) value).longValue();
+        }
+        throw new IllegalArgumentException("[" + value + "] Can't convert to Long.");
+    }
+
+    public static Long dealLong(Object value) {
+        return dealLong(value, null);
+    }
+
+    /**
+     * 处理Double
+     */
+    public static Double dealDouble(Object value, Double defaultValue) {
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof Number) {
+            return ((Number) value).doubleValue();
+        }
+        throw new IllegalArgumentException("[" + value + "] Can't convert to Double.");
+    }
+
+    public static Double dealDouble(Object value) {
+        return dealDouble(value, null);
+    }
+
+    /**
+     * 处理Float
+     */
+    public static Float dealFloat(Object value, Float defaultValue) {
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof Number) {
+            return ((Number) value).floatValue();
+        }
+        throw new IllegalArgumentException("[" + value + "] Can't convert to Float.");
+    }
+
+    public static Float dealFloat(Object value) {
+        return dealFloat(value, null);
+    }
+
+    /**
+     * 处理Boolean
+     */
+    public static Boolean dealBoolean(Object value, Boolean defaultValue) {
+        if (value == null) {
+            return defaultValue;
+        }
+        if (value instanceof Boolean) {
+            return (Boolean) value;
+        }
+        String str = String.valueOf(value);
+        final String trueStr = "1";
+        if (trueStr.equals(str)) {
+            return true;
+        }
+        final String falseStr = "0";
+        if (falseStr.equals(str)) {
+            return false;
+        }
+        throw new IllegalArgumentException("[" + value + "] Can't convert to Boolean.");
+    }
+
+    public static Boolean dealBoolean(Object value) {
+        return dealBoolean(value, null);
+    }
+
+    /**
+     * 处理日期
+     */
+    public static String dealDate(Object value, Date defaultValue) {
+        if (value == null) {
+            if (defaultValue != null) {
+                return formatDate(defaultValue);
+            }
+            return null;
+        }
+        if (value instanceof Date) {
+            return formatDate((Date) value);
+        }
+        //判断是否为日期格式
+        String dateStr = String.valueOf(value);
+        Date date = parseDate(dateStr);
+        if (date != null) {
+            return formatDate(date);
+        }
+        throw new IllegalArgumentException("[" + value + "] Can't convert to Date.");
+    }
+
+    public static String dealDate(Object value) {
+        return dealDate(value, null);
+    }
+
+    public static Date parseDate(String dateStr) {
+        if (dateStr == null) {
+            return null;
+        }
+        try {
+            final int len = 10;
+            if (dateStr.length() == len) {
+                return new SimpleDateFormat(YYYY_MM_DD).parse(dateStr);
+            }
+            return new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS).parse(dateStr);
+        } catch (ParseException e) {
+            return null;
+        }
+    }
+
+    public static String formatDate(Date date) {
+        return new SimpleDateFormat(YYYY_MM_DD_HH_MM_SS).format(date);
+    }
+
+}

+ 126 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/baiduFaceVerify/BaiduFaceVerifyUtil.java

@@ -0,0 +1,126 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.baiduFaceVerify;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年10月8日 上午9:43:04
+ * @company 	QMTH
+ * @description 百度活体检测工具
+ */
+public class BaiduFaceVerifyUtil {
+	
+	/**
+     * 获取API访问token
+     * 该token有一定的有效期,需要自行管理,当失效时需重新获取.
+     * @param ak - 百度云官网获取的 API Key
+     * @param sk - 百度云官网获取的 Securet Key
+     * @return assess_token 示例:
+     * "24.460da4889caad24cccdb1fea17221975.2592000.1491995545.282335-1234567"
+     */
+    public static String getAccessToken(String ak, String sk) {
+        // 获取token地址
+        String authHost = "https://aip.baidubce.com/oauth/2.0/token?";
+        String getAccessTokenUrl = authHost
+                // 1. grant_type为固定参数
+                + "grant_type=client_credentials"
+                // 2. 官网获取的 API Key
+                + "&client_id=" + ak
+                // 3. 官网获取的 Secret Key
+                + "&client_secret=" + sk;
+        try {
+            URL realUrl = new URL(getAccessTokenUrl);
+            // 打开和URL之间的连接
+            HttpURLConnection connection = (HttpURLConnection) realUrl.openConnection();
+            connection.setRequestMethod("GET");
+            connection.connect();
+            // 获取所有响应头字段
+            /*Map<String, List<String>> map = connection.getHeaderFields();
+            // 遍历所有的响应头字段
+            for (String key : map.keySet()) {
+                System.err.println(key + "--->" + map.get(key));
+            }*/
+            // 定义 BufferedReader输入流来读取URL的响应
+            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
+            String result = "";
+            String line;
+            while ((line = in.readLine()) != null) {
+                result += line;
+            }
+            /**
+             * 返回结果
+             */
+            return result;
+        } catch (Exception e) {
+            e.printStackTrace(System.err);
+        }
+        return null;
+    }
+	
+    /**
+     * 重要提示代码中所需工具类
+     * FileUtil,Base64Util,HttpUtil,GsonUtils请从
+     * https://ai.baidu.com/file/658A35ABAB2D404FBF903F64D47C1F72
+     * https://ai.baidu.com/file/C8D81F3301E24D2892968F09AE1AD6E2
+     * https://ai.baidu.com/file/544D677F5D4E4F17B4122FBD60DB82B3
+     * https://ai.baidu.com/file/470B3ACCA3FE43788B5A963BF0B625F3
+     * 下载
+     * param 必须是json数组
+     */
+     public static String faceVerify(String accessToken,String imageUrl) {
+         // 请求url
+         try {
+             String param = getFaceVerifyParam(imageUrl);
+             return HttpUtil.post(getFaceVerifyUrl(), accessToken, "application/json", param);
+         } catch (Exception e) {
+             e.printStackTrace();
+         }
+         return null;
+     }
+     
+     /**
+      * 得到活体检测参数
+      * @param imageUrl
+      * @return
+      */
+     private static String getFaceVerifyParam(String imageUrl){
+    	JsonObject jsonObject = new JsonObject();
+     	jsonObject.addProperty("image",imageUrl);
+     	jsonObject.addProperty("image_type","URL");
+     	JsonArray jsonArray = new JsonArray();
+     	jsonArray.add(jsonObject);
+     	return jsonArray.toString();
+     }
+    
+    /*public static void main(String[] args) {
+    	String accessToken = getAccessToken();
+    	String imageUrl = "https://ecs-test-static.qmth.com.cn/capture_photo/6/1537953767307.jpg";
+    	System.out.println(accessToken);
+	}*/
+
+    /**
+     * 获取百度活体检测url
+     * @return
+     */
+    private static String getFaceVerifyUrl(){
+        SysPropertyCacheBean sysProperty = CacheHelper.getSysProperty("baidu.faceLiveness.url");
+        if (sysProperty.getHasValue()){
+            Object value = sysProperty.getValue();
+            if (null!=value){
+                String url = value.toString();
+                return url;
+            }
+        }
+        throw new StatusException("300005","未找到百度活体检测的配置路径");
+    }
+}

+ 65 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/baiduFaceVerify/Base64Util.java

@@ -0,0 +1,65 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.baiduFaceVerify;
+
+/**
+ * Base64 工具类
+ */
+public class Base64Util {
+    private static final char last2byte = (char) Integer.parseInt("00000011", 2);
+    private static final char last4byte = (char) Integer.parseInt("00001111", 2);
+    private static final char last6byte = (char) Integer.parseInt("00111111", 2);
+    private static final char lead6byte = (char) Integer.parseInt("11111100", 2);
+    private static final char lead4byte = (char) Integer.parseInt("11110000", 2);
+    private static final char lead2byte = (char) Integer.parseInt("11000000", 2);
+    private static final char[] encodeTable = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
+
+    public Base64Util() {
+    }
+
+    public static String encode(byte[] from) {
+        StringBuilder to = new StringBuilder((int) ((double) from.length * 1.34D) + 3);
+        int num = 0;
+        char currentByte = 0;
+
+        int i;
+        for (i = 0; i < from.length; ++i) {
+            for (num %= 8; num < 8; num += 6) {
+                switch (num) {
+                    case 0:
+                        currentByte = (char) (from[i] & lead6byte);
+                        currentByte = (char) (currentByte >>> 2);
+                    case 1:
+                    case 3:
+                    case 5:
+                    default:
+                        break;
+                    case 2:
+                        currentByte = (char) (from[i] & last6byte);
+                        break;
+                    case 4:
+                        currentByte = (char) (from[i] & last4byte);
+                        currentByte = (char) (currentByte << 2);
+                        if (i + 1 < from.length) {
+                            currentByte = (char) (currentByte | (from[i + 1] & lead2byte) >>> 6);
+                        }
+                        break;
+                    case 6:
+                        currentByte = (char) (from[i] & last2byte);
+                        currentByte = (char) (currentByte << 4);
+                        if (i + 1 < from.length) {
+                            currentByte = (char) (currentByte | (from[i + 1] & lead4byte) >>> 4);
+                        }
+                }
+
+                to.append(encodeTable[currentByte]);
+            }
+        }
+
+        if (to.length() % 4 != 0) {
+            for (i = 4 - to.length() % 4; i > 0; --i) {
+                to.append("=");
+            }
+        }
+
+        return to.toString();
+    }
+}

+ 29 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/baiduFaceVerify/GsonUtils.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package cn.com.qmth.examcloud.core.oe.student.base.utils.baiduFaceVerify;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+
+import java.lang.reflect.Type;
+
+/**
+ * Json工具类.
+ */
+public class GsonUtils {
+    private static Gson gson = new GsonBuilder().create();
+
+    public static String toJson(Object value) {
+    	return gson.toJsonTree(value).getAsString();
+    }
+
+    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonParseException {
+        return gson.fromJson(json, classOfT);
+    }
+
+    public static <T> T fromJson(String json, Type typeOfT) throws JsonParseException {
+        return (T) gson.fromJson(json, typeOfT);
+    }
+}

+ 75 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/baiduFaceVerify/HttpUtil.java

@@ -0,0 +1,75 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.baiduFaceVerify;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+/**
+ * http 工具类
+ */
+public class HttpUtil {
+
+    public static String post(String requestUrl, String accessToken, String params)
+            throws Exception {
+        String contentType = "application/x-www-form-urlencoded";
+        return HttpUtil.post(requestUrl, accessToken, contentType, params);
+    }
+
+    public static String post(String requestUrl, String accessToken, String contentType, String params)
+            throws Exception {
+        String encoding = "UTF-8";
+        if (requestUrl.contains("nlp")) {
+            encoding = "GBK";
+        }
+        return HttpUtil.post(requestUrl, accessToken, contentType, params, encoding);
+    }
+
+    public static String post(String requestUrl, String accessToken, String contentType, String params, String encoding)
+            throws Exception {
+        String url = requestUrl + "?access_token=" + accessToken;
+        return HttpUtil.postGeneralUrl(url, contentType, params, encoding);
+    }
+
+    public static String postGeneralUrl(String generalUrl, String contentType, String params, String encoding)
+            throws Exception {
+        URL url = new URL(generalUrl);
+        // 打开和URL之间的连接
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        connection.setRequestMethod("POST");
+        // 设置通用的请求属性
+        connection.setRequestProperty("Content-Type", contentType);
+        connection.setRequestProperty("Connection", "Keep-Alive");
+        connection.setUseCaches(false);
+        connection.setDoOutput(true);
+        connection.setDoInput(true);
+
+        // 得到请求的输出流对象
+        DataOutputStream out = new DataOutputStream(connection.getOutputStream());
+        out.write(params.getBytes(encoding));
+        out.flush();
+        out.close();
+
+        // 建立实际的连接
+        connection.connect();
+        // 获取所有响应头字段
+        /*Map<String, List<String>> headers = connection.getHeaderFields();
+        // 遍历所有的响应头字段
+        for (String key : headers.keySet()) {
+            System.err.println(key + "--->" + headers.get(key));
+        }*/
+        // 定义 BufferedReader输入流来读取URL的响应
+        BufferedReader in = null;
+        in = new BufferedReader(
+                new InputStreamReader(connection.getInputStream(), encoding));
+        String result = "";
+        String getLine;
+        while ((getLine = in.readLine()) != null) {
+            result += getLine;
+        }
+        in.close();
+        //System.err.println("result:" + result);
+        return result;
+    }
+}

+ 61 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ColumnSetting.java

@@ -0,0 +1,61 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+
+/**
+ * Created by zhengmin on 2016/6/22.
+ */
+public class ColumnSetting implements Comparable<ColumnSetting> {
+
+    private String header;
+    private String getMethodName;
+    private int width;
+    private int index;
+
+    public ColumnSetting(String header, String getMethodName, int width, int index) {
+        this.header = header;
+        this.getMethodName = getMethodName;
+        this.width = width;
+        this.index = index;
+    }
+
+    public String getHeader() {
+        return header;
+    }
+
+    public void setHeader(String header) {
+        this.header = header;
+    }
+
+    public String getGetMethodName() {
+        return getMethodName;
+    }
+
+    public void setGetMethodName(String getMethodName) {
+        this.getMethodName = getMethodName;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+
+    @Override
+    public int compareTo(ColumnSetting columnSetting) {
+        if (index < columnSetting.getIndex())
+            return - 1 ;
+        if (index > columnSetting.getIndex())
+            return 1 ;
+        return 0 ;
+    }
+}

+ 48 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelError.java

@@ -0,0 +1,48 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+/**
+ * 
+ * @Description: excel导入错误
+ * @author ting.yin
+ * @date 2016年8月19日
+ */
+public class ExcelError {
+
+	/**
+	 * 错误行数
+	 */
+	private int row;
+
+	/**
+	 * 错误类型
+	 */
+	private String excelErrorType;
+
+	public ExcelError() {
+
+	}
+
+	public ExcelError(int row, String excelErrorType) {
+		this.row = row;
+		this.excelErrorType = excelErrorType;
+	}
+	
+	public ExcelError(String excelErrorType) {
+		this.excelErrorType = excelErrorType;
+	}
+	public int getRow() {
+		return row;
+	}
+
+	public void setRow(int row) {
+		this.row = row;
+	}
+
+	public String getExcelErrorType() {
+		return excelErrorType;
+	}
+
+	public void setExcelErrorType(String excelErrorType) {
+		this.excelErrorType = excelErrorType;
+	}
+
+}

+ 98 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelErrorType.java

@@ -0,0 +1,98 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+
+/**
+ * 
+ * @Description: excel导入错误类型
+ * @author ting.yin
+ * @date 2016年8月19日
+ */
+public enum ExcelErrorType {
+
+	NOT_CAMPUS("学习中心不存在"),
+
+	NOT_EXAM_SITE("学习中心下的考点不存在"),
+	
+	EXAM_SITE_CODE("考点代码为空或格式错误,只能包含数字,英文字母"),
+	
+	EXAM_SITE_CODE_LENGTH("考点代码必须在1-15个字符"),
+	
+	EXAM_SITE_NAME_LENGTH("考点名称必须在1-50个字符"),
+	
+	CAMPUS_CODE("学习中心代码为空或格式错误,只能包含数字,英文字母"),
+	
+	CAMPUS_CODE_LENGTH("学习中心代码必须在1-15个字符"),
+	
+	CAMPUS_NAME_LENGTH("学习中心名必须在1-50个字符"),
+
+	COURSE_CODE("课程代码格式错误,只能包含数字,英文字母"),
+
+	SPECIALTY_CODE("专业代码格式错误,只能包含数字,英文字母"),
+	
+	ONLY_EXAM_NUMBER("准考证号重复"),
+	
+	STUDENT_NAME_NULL("考生姓名不能为空"),
+	
+	STUDENT_NAME_LENGTH("考生姓名必须在1-50个字符"),
+	
+	STUDENT_SEX_NULL("考生性别不能为空"),
+	
+	STUDENT_CODE_NULL("考生学号不能为空"),
+	
+	STUDENT_CODE_LENGTH("考生学号必须在6-15个字符"),
+	
+	CAMPUS_NAME_NULL("学习中心名称不能为空"),
+	
+	COURSE_NAME_NULL("课程名称不能为空"),
+	
+	COURSE_CODE_NULL("课程代码不能为空"),
+	
+	SPECIALTY_NAME_NULL("专业名称不能为空"),
+	
+	SPECIALTY_CODE_NULL("专业代码不能为空"),
+	
+	EXCEL_FORMAT_ERROR("Excel文件格式错误"),
+	
+	EXAM_SITE_NAME_NULL("考点名称不能为空"),
+	
+	GRADE_NULL("年级不能为空"),
+	
+	GRADE_FORMAT("年级必须是数字格式"),
+	
+	STUDENT_EXIST("已有相同考生学号,课程代码,批次的考生存在"),
+	
+	EXCEL_HYPERLINK_FORMAT_ERROR("Excel文件包含有超链接格式的文本"),
+	
+	STUDENT_NAME_CHECK("考生名称不能为空且长度在1-50个字符"),
+	
+	STUDENT_CODE_CHECK("考生学号不能为空且长度在6-15个字符"),
+	
+	STUDENT_GRADE_CHECK("年级不能为空且只能是数字格式"),
+	
+	CAMPUS_NAME_CHECK("学习中心名称不能为空且长度在1-50个字符"),
+	
+	CAMPUS_CODE_CHECK("学习中心代码不能为空且长度在1-15个字符且只能包含数字和英文字母"),
+	
+	COURSE_NAME_CHECK("课程名称不能为空且长度在1-50个字符"),
+	
+	COURSE_CODE_CHECK("课程代码不能为空且长度在1-15个字符且只能包含数字和英文字母"),
+	
+	SPECIALTY_NAME_CHECK("专业名称不能为空且长度在1-50个字符"),
+	
+	SPECIALTY_CODE_CHECK("专业代码不能为空且长度在1-15个字符且只能包含数字和英文字母")
+	
+	;
+	
+	
+	
+	
+	private String name;
+
+	public String getName() {
+		return name;
+	}
+
+	private ExcelErrorType(String name) {
+		this.name = name;
+	}
+
+}

+ 34 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelProperty.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Created by zhengmin on 2016/6/22.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelProperty {
+	/**
+	 * 导出时列名
+	 */
+    String name() default "";
+    
+	/**
+	 * 导出时列宽
+	 */
+    int width() default 0;
+	/**
+	 * 排序
+	 */
+    int index();
+    /**
+     * 类型
+     * 0:导入(读excel)
+     * 1:导出(写excel)
+     * 2:导入&导出
+     */
+    int type() default 2;
+}

+ 95 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelReader.java

@@ -0,0 +1,95 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+
+import com.esotericsoftware.reflectasm.ConstructorAccess;
+import com.esotericsoftware.reflectasm.FieldAccess;
+import org.apache.poi.ss.usermodel.*;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 
+ * @Description: 读取excel
+ * @author ting.yin
+ * @date 2016年8月19日
+ */
+public class ExcelReader extends ExcelUtils {
+
+	public ExcelReader(Class<?> dataClass) {
+		super(dataClass);
+	}
+
+	/**
+	 * 
+	 * @param inputStream
+	 *            输入流
+	 * @param handle
+	 *            单个对象处理器
+	 * @return 错误信息集合
+	 */
+	public List<ExcelError> reader(InputStream inputStream,
+			ExcelReaderHandle handle) {
+		List<ExcelError> excelErrors = new ArrayList<ExcelError>();
+		try {
+			Workbook wb = WorkbookFactory.create(inputStream);
+			Sheet sheet = wb.getSheetAt(0);
+			for (int i = 1; i <= sheet.getLastRowNum(); i++) {
+				Row row = sheet.getRow(i);
+				if (row == null) {
+					return excelErrors;
+				}
+				Object dto = ConstructorAccess.get(getDataClass()).newInstance();
+				FieldAccess access = FieldAccess.get(getDataClass());
+				try {
+					for (int j = 0; j < row.getLastCellNum(); j++) {
+						Cell cell = row.getCell(j);
+						Object obj = convert(cell);
+						ColumnSetting columnSetting = this.getColumnSettings().get(j);
+						access.set(dto ,columnSetting.getHeader(), obj);
+					}
+				}catch (Exception e) {
+					ExcelError error2 = new ExcelError(ExcelErrorType.EXCEL_FORMAT_ERROR.getName());
+					error2.setRow(i+1);
+					excelErrors.add(error2);
+					e.printStackTrace();
+					break;
+				}
+				ExcelError error = handle.handle(dto);
+				if (error != null) {
+					error.setRow(i+1);
+					excelErrors.add(error);
+				}
+			}
+
+		} catch (Exception e) {
+			ExcelError error2 = new ExcelError(ExcelErrorType.EXCEL_HYPERLINK_FORMAT_ERROR.getName());
+			excelErrors.add(error2);
+			e.printStackTrace();
+		} finally {
+			try {
+				inputStream.close();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+		return excelErrors;
+	}
+
+	@SuppressWarnings("deprecation")
+	private Object convert(Cell cell) {
+		if(cell!=null){
+			switch (cell.getCellType()) {
+			case Cell.CELL_TYPE_STRING:
+				return cell.getStringCellValue().toString();
+			case Cell.CELL_TYPE_BOOLEAN:
+				return String.valueOf(cell.getBooleanCellValue());
+			case Cell.CELL_TYPE_NUMERIC:
+				return Math.round(cell.getNumericCellValue());
+			}
+		}
+		
+		return null;
+	}
+}

+ 7 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelReaderHandle.java

@@ -0,0 +1,7 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+
+public interface ExcelReaderHandle {
+	
+	ExcelError handle(Object dto);
+
+}

+ 77 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelUtils.java

@@ -0,0 +1,77 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by zhengmin on 2016/8/17.
+ */
+public abstract class ExcelUtils {
+
+    private Class<?> dataClass;
+    private List<ColumnSetting> columnSettings;
+
+    public ExcelUtils(Class<?> dataClass){
+        this.dataClass = dataClass;
+        this.columnSettings = getColumnSettings(dataClass);
+    }
+
+    public Class<?> getDataClass() {
+        return dataClass;
+    }
+
+    public void setDataClass(Class<?> dataClass) {
+        this.dataClass = dataClass;
+    }
+
+    public List<ColumnSetting> getColumnSettings() {
+        return columnSettings;
+    }
+
+    public void setColumnSettings(List<ColumnSetting> columnSettings) {
+        this.columnSettings = columnSettings;
+    }
+
+    /**
+     * 提取ExcelProperty注解类的字段信息
+     * @param dataClass 需要解析 写入excel的数据类型
+     * @return
+     */
+    protected List<ColumnSetting> getColumnSettings(Class<?> dataClass){
+        List<ColumnSetting> columnSettings = new ArrayList<>();
+        //先在方法上找ExcelProperty注解
+        Method[] methods = dataClass.getDeclaredMethods();
+        for(Method method : methods){
+            ExcelProperty exportProperty = method.getAnnotation(ExcelProperty.class);
+            if(exportProperty != null && exportProperty.name().trim().length() > 0){
+                ColumnSetting columnSetting = new ColumnSetting(exportProperty.name(),method.getName(),
+                        exportProperty.width(),exportProperty.index());
+                columnSettings.add(columnSetting);
+            }
+        }
+        //如果方法上找不到注解,再到属性上找 
+        if(columnSettings.size() == 0){
+        	Field[] fields = dataClass.getDeclaredFields();
+        	for(Field field:fields){
+        		ExcelProperty exportProperty = field.getAnnotation(ExcelProperty.class);
+        		if(exportProperty != null && exportProperty.name().trim().length() > 0){
+                    ColumnSetting columnSetting = new ColumnSetting(exportProperty.name(),"get"+toUpperCaseFirstOne(field.getName()),
+                            exportProperty.width(),exportProperty.index());
+                    columnSettings.add(columnSetting);
+                }
+        	}
+        }
+        Collections.sort(columnSettings);
+        return columnSettings;
+    }
+    
+    private static String toUpperCaseFirstOne(String s){
+	  if(Character.isUpperCase(s.charAt(0)))
+	    return s;
+	  else
+	    return (new StringBuilder()).append(Character.toUpperCase(s.charAt(0))).append(s.substring(1)).toString();
+	}
+}

+ 93 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExcelWriter.java

@@ -0,0 +1,93 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFColor;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import java.awt.*;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+
+public class ExcelWriter extends ExcelUtils {
+	
+	private XSSFWorkbook workbook;//工作簿
+	
+	private Sheet  sheet;	//工作表
+	
+	private Row row = null;// 创建一行
+	
+	private Cell cell = null;
+	
+	private XSSFCellStyle style;
+	
+	private XSSFCellStyle style2;
+
+    public ExcelWriter(Class<?> dataClass,String sheetName) {
+        super(dataClass);
+        // 声明一个工作薄
+        //workbook = new SXSSFWorkbook(100);//使用该方法会有权限问题
+        workbook = new XSSFWorkbook();
+        // 生成一个表格
+        sheet =  workbook.createSheet(sheetName);
+        // 设置表格默认列宽度为15个字节
+        sheet.setDefaultColumnWidth((short) 15);
+    }
+    
+    private List<ColumnSetting> createColumnSettings(){
+    	List<ColumnSetting> columnSettings = this.getColumnSettings();
+        // 产生表格标题行
+        row =  sheet.createRow(0);
+        for (short i = 0; i < columnSettings.size(); i++) {
+            cell =  row.createCell(i);
+            style =  workbook.createCellStyle();
+            style.setFillForegroundColor(new XSSFColor(new Color(227, 239, 217)));
+            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);  
+            cell.setCellStyle(style);
+            XSSFRichTextString text = new XSSFRichTextString(columnSettings.get(i).getHeader());
+            cell.setCellValue(text);
+            if (columnSettings.get(i).getWidth() > 0) {
+                sheet.setColumnWidth(i, columnSettings.get(i).getWidth() * 256);
+            }
+        }
+        return columnSettings;
+    }
+
+    /**
+     * 写入excel
+     *
+     * @param dataset   数据集合
+     * @param out       输出流
+     * @throws SecurityException 
+     * @throws NoSuchMethodException 
+     * @throws InvocationTargetException 
+     * @throws IllegalArgumentException 
+     * @throws IllegalAccessException 
+     */
+    public void write(Collection<?> dataset, OutputStream out) throws Exception {
+    	List<ColumnSetting> columnSettings = this.createColumnSettings();
+        int index = 0;
+        for (Object obj : dataset) {
+            index++;
+            row = sheet.createRow(index);//创建行
+            // 利用反射,根据javabean属性的先后顺序,动态调用getXxx()方法得到属性值
+            for (short i = 0; i < columnSettings.size(); i++) {
+                cell = row.createCell(i);//创建列
+                cell.setCellStyle(style2);
+                String methodName = columnSettings.get(i).getGetMethodName();
+                Method method = this.getDataClass().getMethod(methodName, new Class[]{});
+                Object value = method.invoke(obj, new Object[] {});
+                cell.setCellValue(value==null?"":value.toString());
+            }
+        }
+        workbook.write(out);
+    }
+    
+}

+ 43 - 0
examcloud-core-oe-student-base/src/main/java/cn/com/qmth/examcloud/core/oe/student/base/utils/excel/ExportUtils.java

@@ -0,0 +1,43 @@
+package cn.com.qmth.examcloud.core.oe.student.base.utils.excel;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.util.Collection;
+
+/*
+ * excel导出工具
+ */
+public class ExportUtils {
+
+	private static final Logger log = LoggerFactory.getLogger(ExportUtils.class);
+	
+    private static final String DEFALUT_CONTENT_TYPE = "application/vnd.ms-excel";
+
+    private static final String DEFALUT_EXT = ".xlsx";
+
+    public static void exportEXCEL(String fileName,Class<?> dataClass,
+                             Collection<?> dataset,HttpServletResponse response) throws Exception {
+    	log.info("导出Excel开始...");
+        response.setHeader("Content-Disposition","inline;filename="+URLEncoder.encode(fileName, "UTF-8")+DEFALUT_EXT);
+        response.setContentType(DEFALUT_CONTENT_TYPE);
+        ServletOutputStream outputStream = response.getOutputStream();
+    	
+        ExcelWriter excelExporter = new ExcelWriter(dataClass,"sheet1");
+        excelExporter.write(dataset,outputStream);
+        outputStream.flush();
+        outputStream.close();
+        log.info("导出Excel结束");
+    }
+    
+    public static void makeExcel(Class<?> dataClass,Collection<?> dataset,OutputStream outputStream ) throws Exception {
+		log.info("生成Excel开始...");
+		ExcelWriter excelExporter = new ExcelWriter(dataClass,"sheet1");
+		excelExporter.write(dataset,outputStream);
+		log.info("生成Excel结束");
+	}
+}

+ 19 - 0
examcloud-core-oe-student-dao/pom.xml

@@ -0,0 +1,19 @@
+<?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>
+    <parent>
+        <groupId>cn.com.qmth.examcloud</groupId>
+        <artifactId>examcloud-core-oe-student</artifactId>
+        <version>2019-SNAPSHOT</version>
+    </parent>
+    <artifactId>examcloud-core-oe-student-dao</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud</groupId>
+            <artifactId>examcloud-core-oe-student-base</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+    </dependencies>
+</project>

+ 36 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/ExamFaceLivenessVerifyRepo.java

@@ -0,0 +1,36 @@
+package cn.com.qmth.examcloud.core.oe.student.dao;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamFaceLivenessVerifyEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @Description 人脸活体检测
+ * @Author lideyin
+ * @Date 2019/12/9 18:00
+ * @Version 1.0
+ */
+@Repository
+public interface ExamFaceLivenessVerifyRepo extends JpaRepository<ExamFaceLivenessVerifyEntity, Long>, JpaSpecificationExecutor<ExamFaceLivenessVerifyEntity> {
+    /**
+     * 使用examRecordId查询
+     * @param examRecordDataId
+     * @return
+     */
+    @Query(nativeQuery = true, value = "select * from ec_oes_exam_face_liveness_verify t where t.exam_record_data_id = ?1 and t.is_error = 0 order by id asc")
+    List<ExamFaceLivenessVerifyEntity> findByExamRecordDataIdOrderById(Long examRecordDataId);
+
+    /**
+     * 取出is_error=1的最新的一条
+     * @param examRecordDataId
+     * @return
+     */
+    @Query(nativeQuery = true, value = "select * from ec_oes_exam_face_liveness_verify t where t.exam_record_data_id = ?1 and t.is_error = 1 order by id desc limit 1")
+    ExamFaceLivenessVerifyEntity findErrorFaceVerifyByExamRecordDataId(Long examRecordDataId);
+
+    List<ExamFaceLivenessVerifyEntity> findByExamRecordDataId(Long examRecordDataId);
+}

+ 31 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/ExamRecordDataRepo.java

@@ -0,0 +1,31 @@
+package cn.com.qmth.examcloud.core.oe.student.dao;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamRecordDataEntity;
+
+/**
+ * @Description 考试记录
+ * @Author lideyin
+ * @Date 2019/12/9 18:00
+ * @Version 1.0
+ */
+@Repository
+public interface ExamRecordDataRepo extends JpaRepository<ExamRecordDataEntity, Long>, JpaSpecificationExecutor<ExamRecordDataEntity> {
+    @Query(value = "select *  from ec_oes_exam_record_data where (batch_num is null or batch_num!=?1) and id>?2  order by id limit ?3",nativeQuery = true)
+    List<ExamRecordDataEntity> getLimitExamRecordDataList(Long batchNum, Long startId, Integer size);
+    
+    @Modifying
+    @Query(value = "update ec_oes_exam_record_data set batch_num=?1 where id=?2", nativeQuery = true)
+    int updateBatchNumById(Long batchNum, Long id);
+    
+    @Modifying
+    @Query(value = "update ec_oes_exam_record_data set exam_record_status=?1 where id=?2", nativeQuery = true)
+    int updateExamRecordStatusById(String examRecordStatus, Long id);
+}

+ 20 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/ExamRecordQuestionTempRepo.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.examcloud.core.oe.student.dao;
+
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamQuestionTempEntity;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年9月3日 上午10:48:39
+ * @company 	QMTH
+ * @description ExamRecordQuestionsRepo.java
+ */
+@Repository
+public interface ExamRecordQuestionTempRepo  extends MongoRepository<ExamQuestionTempEntity, String>,QueryByExampleExecutor<ExamQuestionTempEntity>{
+	
+
+}

+ 66 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/FaceBiopsyItemRepo.java

@@ -0,0 +1,66 @@
+package cn.com.qmth.examcloud.core.oe.student.dao;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.FaceBiopsyItemEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import javax.transaction.Transactional;
+import java.util.List;
+
+/**
+ * @Description 人脸检测明细仓库
+ * @Author lideyin
+ * @Date 2019/7/17 18:58
+ * @Version 1.0
+ */
+@Repository
+public interface FaceBiopsyItemRepo extends JpaRepository<FaceBiopsyItemEntity, Long>, JpaSpecificationExecutor<FaceBiopsyItemEntity> {
+
+    List<FaceBiopsyItemEntity> findByExamRecordDataIdOrderByIdAsc(Long examRecordDataId);
+
+    /**
+     * 根据活检结果id,按明细id正序获取活检明细
+     * @param faceBiopsyId 活检最终结果表id
+     * @return
+     */
+    List<FaceBiopsyItemEntity> findByFaceBiopsyIdOrderByIdAsc(Long faceBiopsyId);
+
+    /**
+     * 根据活检结果id,按明细id正序获取第一条活检明细(即第一次活检)
+     * @param faceBiopsyId 活检最终结果表id
+     * @return
+     */
+    FaceBiopsyItemEntity findFirstByFaceBiopsyIdOrderByIdAsc(Long faceBiopsyId);
+
+    /**
+     * 根据活检结果id,按明细id倒序获取第一条活检明细(即最后一次活检)
+     * @param faceBiopsyId 活检最终结果表id
+     * @return
+     */
+    FaceBiopsyItemEntity findFirstByFaceBiopsyIdOrderByIdDesc(Long faceBiopsyId);
+
+    /**
+     * 根据考试记录id,按明细id正序获取第一条活检明细(即第一次活检)
+     * @param examRecordDataId 考试记录id
+     * @return
+     */
+    FaceBiopsyItemEntity findFirstByExamRecordDataIdOrderByIdAsc(Long examRecordDataId);
+
+    /**
+     * 根据考试记录id和人检检测完成状态获取数据
+     *
+     * @param examRecordDataId 考试记录id
+     * @param completed        是否检测完成
+     * @return
+     */
+    List<FaceBiopsyItemEntity> findByExamRecordDataIdAndCompleted(Long examRecordDataId, Boolean completed);
+
+    @Transactional
+    @Modifying
+    @Query(nativeQuery = true,
+            value = "update ec_oes_exam_face_biopsy_item set result=?2,error_msg=?3,completed=?4 where id=?1")
+    void updateFaceBiopsyItemResult(Long id, boolean result, String errorMsg, boolean completed);
+}

+ 20 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/FaceBiopsyItemStepRepo.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.examcloud.core.oe.student.dao;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.FaceBiopsyItemStepEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @Description 人脸检测明细仓库
+ * @Author lideyin
+ * @Date 2019/7/17 18:58
+ * @Version 1.0
+ */
+@Repository
+public interface FaceBiopsyItemStepRepo extends JpaRepository<FaceBiopsyItemStepEntity, Long>, JpaSpecificationExecutor<FaceBiopsyItemStepEntity> {
+	List<FaceBiopsyItemStepEntity> findByFaceBiopsyItemId(Long faceBiopsyItemId);
+
+}

+ 29 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/FaceBiopsyRepo.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.core.oe.student.dao;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.FaceBiopsyEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import javax.transaction.Transactional;
+
+/**
+ * @Description 人脸检测结果仓库
+ * @Author lideyin
+ * @Date 2019/7/17 18:58
+ * @Version 1.0
+ */
+@Repository
+public interface FaceBiopsyRepo extends JpaRepository<FaceBiopsyEntity, Long>, JpaSpecificationExecutor<FaceBiopsyEntity> {
+
+
+    FaceBiopsyEntity findByExamRecordDataId(Long examRecordDataId);
+
+    @Transactional
+    @Modifying
+    @Query(nativeQuery = true,
+            value = "update ec_oes_exam_face_biopsy set result=?2,error_msg=?3 where exam_record_data_id=?1")
+    void updateFaceBiopsyResult(Long examRecordDataId, boolean result, String errorMsg);
+}

+ 34 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/UniqueRuleHolder.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.core.oe.student.dao;
+
+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", "机构推分队列已存在"));
+    }
+}

+ 149 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/ExamFaceLivenessVerifyEntity.java

@@ -0,0 +1,149 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.entity;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceVerifyResult;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+import javax.persistence.*;
+import java.util.Date;
+
+
+/**
+ * @author  	chenken
+ * @date    	2018年2月5日 上午8:28:34
+ * @company 	QMTH
+ * @description 人脸活体检测
+ */
+@Entity
+@Table(name="ec_oes_exam_face_liveness_verify",indexes ={
+@Index(name = "IDX_E_O_E_F_L_V_001",columnList = "examRecordDataId, isError")
+} )
+public class ExamFaceLivenessVerifyEntity extends JpaEntity{
+	
+	private static final long serialVersionUID = -3428631813990503829L;
+	/**
+	 * 主键ID
+	 */
+	@Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+	/**
+	 * 考试记录 DataID
+	 */
+	private Long examRecordDataId;
+	/**
+	 * 验证考试时间
+	 */
+	private Date startTime;
+	/**
+	 * 验证使用时间
+	 */
+	private Long usedTime;
+	/**
+	 * faceId返回json:notify_url返回
+	 * 网页端活体检测及比对返回值说明:https://faceid.com/pages/documents/5680508
+	 */
+	@Column(length = 2000)
+	private String resultJson;
+	/**
+	 * 验证结果
+	 */
+	@Enumerated(EnumType.STRING)
+	private FaceVerifyResult verifyResult;
+	
+	/**
+	 * https://api.megvii.com/faceid/liveness/v2/get_result 
+	 * 用于活体结果反查
+	 */
+	private String bizId;
+	
+	/**
+	 * 发生错误
+	 */
+	private Boolean isError;
+	/**
+	 * 错误信息
+	 */
+	@Column(length = 2000)
+	private String errorMsg;
+	
+	/**
+	 * 当前记录操作次数
+	 */
+	private Integer operateNum;
+	
+	public ExamFaceLivenessVerifyEntity(){}
+	
+	public Long getId() {
+		return id;
+	}
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public Date getStartTime() {
+		return startTime;
+	}
+	public void setStartTime(Date startTime) {
+		this.startTime = startTime;
+	}
+	public Long getUsedTime() {
+		return usedTime;
+	}
+	public void setUsedTime(Long usedTime) {
+		this.usedTime = usedTime;
+	}
+	public String getResultJson() {
+		return resultJson;
+	}
+	public void setResultJson(String resultJson) {
+		this.resultJson = resultJson;
+	}
+	public FaceVerifyResult getVerifyResult() {
+		return verifyResult;
+	}
+	public void setVerifyResult(FaceVerifyResult verifyResult) {
+		this.verifyResult = verifyResult;
+	}
+
+	public String getBizId() {
+		return bizId;
+	}
+
+	public void setBizId(String bizId) {
+		this.bizId = bizId;
+	}
+
+	public Boolean getIsError() {
+		return isError;
+	}
+
+	public void setIsError(Boolean isError) {
+		this.isError = isError;
+	}
+
+	public String getErrorMsg() {
+		return errorMsg;
+	}
+
+	public void setErrorMsg(String errorMsg) {
+		this.errorMsg = errorMsg;
+	}
+
+	public Integer getOperateNum() {
+		return operateNum;
+	}
+
+	public void setOperateNum(Integer operateNum) {
+		this.operateNum = operateNum;
+	}
+	
+}
+

+ 217 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/ExamQuestionTempEntity.java

@@ -0,0 +1,217 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.entity;
+
+import java.io.Serializable;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Id;
+
+import org.springframework.data.mongodb.core.mapping.Document;
+
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
+import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
+
+/**
+ * @author chenken
+ * @date 2018/8/17 10:18
+ * @company QMTH
+ * @description 考生单题作答记录,存储过长的作答
+ */
+@Document(collection="examRecordQuestionTemp")
+public class ExamQuestionTempEntity implements Serializable{
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = -6141069483774400912L;
+	
+	@Id
+	private String id;
+	/**
+	 * 考试记录Data Id
+	 */
+    private Long examRecordDataId;
+    /**
+     * 大题号
+     */
+    private Integer mainNumber;
+    /**
+     * 原题ID
+     */
+    private String questionId;
+    /**
+     * 顺序
+     */
+    private Integer order;
+    /**
+     * 小题分数
+     */
+    private Double questionScore;
+    /**
+     * 小题类型
+     */
+    private QuestionType questionType;
+    /**
+     * 标准答案
+     */
+    private String correctAnswer;
+    /**
+     * 考生作答
+     */
+    private String studentAnswer;
+    /**
+     * 学生小题得分
+     */
+    private Double studentScore;
+    /**
+     * 是否作答
+     */
+    private Boolean isAnswer;
+    /**
+     * 是否标记
+     */
+    private Boolean isSign;
+    
+    /**
+	 * 选项排序值
+	 */
+	private Integer[] optionPermutation;
+	
+	/**
+	 * 音频播放次数
+	 */
+	private String audioPlayTimes;
+	/**
+	 * 题目作答类型
+	 */
+	@Enumerated(EnumType.STRING)
+	private AnswerType answerType;	
+    
+	public ExamQuestionTempEntity() {}
+	
+	public String getId() {
+		return id;
+	}
+	public void setId(String id) {
+		this.id = id;
+	}
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+	public Integer getMainNumber() {
+		return mainNumber;
+	}
+	/**
+	 * 设置 大题号
+	 * @param mainNumber
+	 */
+	public void setMainNumber(Integer mainNumber) {
+		this.mainNumber = mainNumber;
+	}
+	public String getQuestionId() {
+		return questionId;
+	}
+	/**
+	 * 设置题库试题ID
+	 * @param questionId
+	 */
+	public void setQuestionId(String questionId) {
+		this.questionId = questionId;
+	}
+	public Integer getOrder() {
+		return order;
+	}
+	/**
+	 * 设置小题号
+	 * @param order
+	 */
+	public void setOrder(Integer order) {
+		this.order = order;
+	}
+	public String getStudentAnswer() {
+		return studentAnswer;
+	}
+	public void setStudentAnswer(String studentAnswer) {
+		this.studentAnswer = studentAnswer;
+	}
+	public Double getStudentScore() {
+		return studentScore;
+	}
+	/**
+	 * 设置考生得分
+	 * @param studentScore
+	 */
+	public void setStudentScore(Double studentScore) {
+		this.studentScore = studentScore;
+	}
+	public Double getQuestionScore() {
+		return questionScore;
+	}
+	/**
+	 * 设置试题分数
+	 * @param questionScore
+	 */
+	public void setQuestionScore(Double questionScore) {
+		this.questionScore = questionScore;
+	}
+	public QuestionType getQuestionType() {
+		return questionType;
+	}
+	/**
+	 * 设置题型
+	 * @param questionType
+	 */
+	public void setQuestionType(QuestionType questionType) {
+		this.questionType = questionType;
+	}
+	public Boolean getIsAnswer() {
+		return isAnswer;
+	}
+	public void setIsAnswer(Boolean isAnswer) {
+		this.isAnswer = isAnswer;
+	}
+	public Boolean getIsSign() {
+		return isSign;
+	}
+	public void setIsSign(Boolean isSign) {
+		this.isSign = isSign;
+	}
+
+	public String getCorrectAnswer() {
+		return correctAnswer;
+	}
+
+	public void setCorrectAnswer(String correctAnswer) {
+		this.correctAnswer = correctAnswer;
+	}
+
+	public Integer[] getOptionPermutation() {
+		return optionPermutation;
+	}
+
+	public void setOptionPermutation(Integer[] optionPermutation) {
+		this.optionPermutation = optionPermutation;
+	}
+
+	public String getAudioPlayTimes() {
+		return audioPlayTimes;
+	}
+
+	public void setAudioPlayTimes(String audioPlayTimes) {
+		this.audioPlayTimes = audioPlayTimes;
+	}
+
+	public AnswerType getAnswerType() {
+		return answerType;
+	}
+
+	public void setAnswerType(AnswerType answerType) {
+		this.answerType = answerType;
+	}
+
+	
+	
+}

+ 544 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/ExamRecordDataEntity.java

@@ -0,0 +1,544 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.support.enums.IsSuccess;
+import cn.com.qmth.examcloud.support.enums.SyncStatus;
+import org.hibernate.annotations.DynamicInsert;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.support.enums.HandInExamType;
+import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * @author chenken
+ * @date 2018年8月13日 上午11:41:34
+ * @company QMTH
+ * @description 考试记录数据表, 与ec_oe_exam_record一对一关系
+ */
+@Entity
+@Table(name = "ec_oes_exam_record_data", indexes = {
+        @Index(name = "IDX_E_O_E_R_D_001", columnList = "examStudentId"),
+        @Index(name = "IDX_E_O_E_R_D_002", columnList = "studentId"),
+        @Index(name = "IDX_E_O_E_R_D_003", columnList = "examId"),
+        @Index(name = "IDX_E_O_E_R_D_004", columnList = "courseId"),
+        @Index(name = "IDX_E_O_E_R_D_005", columnList = "batchNum"),
+})
+@DynamicInsert
+public class ExamRecordDataEntity extends JpaEntity {
+    /**
+     *
+     */
+    private static final long serialVersionUID = -242327915801750970L;
+    /**
+     * 主键
+     */
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+    /**
+     * 考试ID
+     */
+    private Long examId;
+    /**
+     * 考试类型
+     */
+    @Column(length = 20)
+    @Enumerated(EnumType.STRING)
+    private ExamType examType;
+    /**
+     * 考生ID
+     */
+    private Long examStudentId;
+    /**
+     * 学生ID
+     */
+    private Long studentId;
+    /**
+     * 课程ID
+     */
+    private Long courseId;
+
+    /**
+     * 学习中心ID
+     */
+    private Long orgId;
+    /**
+     * 顶级机构ID
+     */
+    private Long rootOrgId;
+    /**
+     * 基础试卷ID
+     */
+    private String basePaperId;
+
+    /**
+     * 试卷类型
+     */
+    private String paperType;
+
+    /**
+     * 考试开始时间
+     */
+    private Date startTime;
+    /**
+     * 考试结束时间
+     */
+    private Date endTime;
+
+    /**
+     * 考试被清理时间
+     */
+    private Date cleanTime;
+
+    /**
+     * 考试时长
+     */
+    private Long usedExamTime;
+    /**
+     * 是否断点续考
+     */
+    private Boolean isContinued;
+    /**
+     * 断点续考次数
+     */
+    private Integer continuedCount;
+    /**
+     * 是否达到最大断点限制
+     */
+    private Boolean isExceed;
+    /**
+     * 抓拍比对成功次数
+     */
+    private Integer faceSuccessCount;
+    /**
+     * 抓拍比对失败次数
+     */
+    private Integer faceFailedCount;
+    /**
+     * 抓拍存在陌生人的次数
+     */
+    private Integer faceStrangerCount;
+    /**
+     * 抓拍比对总次数
+     */
+    private Integer faceTotalCount;
+
+    /**
+     * 考试记录状态
+     */
+    @Column(length = 20)
+    @Enumerated(EnumType.STRING)
+    private ExamRecordStatus examRecordStatus;
+
+    /**
+     * 交卷类型
+     */
+    @Column(length = 20)
+    @Enumerated(EnumType.STRING)
+    private HandInExamType handInExamType;
+
+    /**
+     *
+     * 活体检测结果
+     */
+    @Column(length = 20)
+    @Enumerated(EnumType.STRING)
+    private IsSuccess faceVerifyResult;
+
+    /**
+     * 是否异常数据
+     */
+    private Boolean isWarn;
+    /**
+     * 是否被审核过
+     */
+    private Boolean isAudit;
+    /**
+     * 是否违纪
+     */
+    @Column(name = "is_illegality")
+    private Boolean isIllegality;
+
+    /**
+     * 总分
+     */
+    private Double totalScore;
+
+    /**
+     * 客观题得分总分
+     */
+    private Double objectiveScore;
+
+    /**
+     * 客观题答对的比率
+     * (客观题答对的题数/客观题总题数)*100  取2位小数
+     */
+    private Double objectiveAccuracy;
+
+    /**
+     * 主观题得分总分
+     */
+    private Double subjectiveScore;
+
+    /**
+     * 答题正确率
+     */
+    private Double succPercent;
+
+    /**
+     * 抓拍比对成功比率
+     */
+    private Double faceSuccessPercent;
+
+    /**
+     * 百度人脸活体检测通过率
+     */
+    private Double baiduFaceLivenessSuccessPercent;
+
+    /**
+     * 数据同步状态
+     */
+    @Column(length = 20)
+    @Enumerated(EnumType.STRING)
+    private SyncStatus syncStatus;
+
+    /**
+     * 批次号,自动服务遍历数据用
+     */
+    private Long batchNum;
+    
+    /**
+     * 试卷题目数量(校验提交答案的order)
+     */
+    private Integer questionCount;
+    
+    /**
+     * 是否是全客观题卷  1:是   0:否
+     */
+    private Boolean isAllObjectivePaper;
+    
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public ExamType getExamType() {
+        return examType;
+    }
+
+    public void setExamType(ExamType examType) {
+        this.examType = examType;
+    }
+
+    public Long getExamStudentId() {
+        return examStudentId;
+    }
+
+    public void setExamStudentId(Long examStudentId) {
+        this.examStudentId = examStudentId;
+    }
+
+    public Long getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(Long studentId) {
+        this.studentId = studentId;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public String getBasePaperId() {
+        return basePaperId;
+    }
+
+    public void setBasePaperId(String basePaperId) {
+        this.basePaperId = basePaperId;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+
+    public Long getUsedExamTime() {
+        return usedExamTime;
+    }
+
+    public void setUsedExamTime(Long usedExamTime) {
+        this.usedExamTime = usedExamTime;
+    }
+
+    public Boolean getIsContinued() {
+        return isContinued;
+    }
+
+    public void setIsContinued(Boolean isContinued) {
+        this.isContinued = isContinued;
+    }
+
+    public Integer getContinuedCount() {
+        return continuedCount;
+    }
+
+    public void setContinuedCount(Integer continuedCount) {
+        this.continuedCount = continuedCount;
+    }
+
+    public Integer getFaceSuccessCount() {
+        return faceSuccessCount;
+    }
+
+    public void setFaceSuccessCount(Integer faceSuccessCount) {
+        this.faceSuccessCount = faceSuccessCount;
+    }
+
+    public Integer getFaceFailedCount() {
+        return faceFailedCount;
+    }
+
+    public void setFaceFailedCount(Integer faceFailedCount) {
+        this.faceFailedCount = faceFailedCount;
+    }
+
+    public Integer getFaceStrangerCount() {
+        return faceStrangerCount;
+    }
+
+    public void setFaceStrangerCount(Integer faceStrangerCount) {
+        this.faceStrangerCount = faceStrangerCount;
+    }
+
+    public Integer getFaceTotalCount() {
+        return faceTotalCount;
+    }
+
+    public void setFaceTotalCount(Integer faceTotalCount) {
+        this.faceTotalCount = faceTotalCount;
+    }
+
+    public Boolean getIsExceed() {
+        return isExceed;
+    }
+
+    public void setIsExceed(Boolean isExceed) {
+        this.isExceed = isExceed;
+    }
+
+    public ExamRecordStatus getExamRecordStatus() {
+        return examRecordStatus;
+    }
+
+    public void setExamRecordStatus(ExamRecordStatus examRecordStatus) {
+        this.examRecordStatus = examRecordStatus;
+    }
+
+    public HandInExamType getHandInExamType() {
+        return handInExamType;
+    }
+
+    public void setHandInExamType(HandInExamType handInExamType) {
+        this.handInExamType = handInExamType;
+    }
+
+    public IsSuccess getFaceVerifyResult() {
+        return faceVerifyResult;
+    }
+
+    public void setFaceVerifyResult(IsSuccess faceVerifyResult) {
+        this.faceVerifyResult = faceVerifyResult;
+    }
+
+    public Date getCleanTime() {
+        return cleanTime;
+    }
+
+    public void setCleanTime(Date cleanTime) {
+        this.cleanTime = cleanTime;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Double getObjectiveScore() {
+        return objectiveScore;
+    }
+
+    public void setObjectiveScore(Double objectiveScore) {
+        this.objectiveScore = objectiveScore;
+    }
+
+    public Double getObjectiveAccuracy() {
+        return objectiveAccuracy;
+    }
+
+    public void setObjectiveAccuracy(Double objectiveAccuracy) {
+        this.objectiveAccuracy = objectiveAccuracy;
+    }
+
+    public Double getSubjectiveScore() {
+        return subjectiveScore;
+    }
+
+    public void setSubjectiveScore(Double subjectiveScore) {
+        this.subjectiveScore = subjectiveScore;
+    }
+
+    public Double getSuccPercent() {
+        return succPercent;
+    }
+
+    public void setSuccPercent(Double succPercent) {
+        this.succPercent = succPercent;
+    }
+
+    public Boolean getIsWarn() {
+        return isWarn;
+    }
+
+    public void setIsWarn(Boolean warn) {
+        isWarn = warn;
+    }
+
+    public Boolean getIsAudit() {
+        return isAudit;
+    }
+
+    public void setIsAudit(Boolean audit) {
+        isAudit = audit;
+    }
+
+    public Boolean getIsIllegality() {
+        return isIllegality;
+    }
+
+    public void setIsIllegality(Boolean illegality) {
+        isIllegality = illegality;
+    }
+
+    public SyncStatus getSyncStatus() {
+        return syncStatus;
+    }
+
+    
+    public void setSyncStatus(SyncStatus syncStatus) {
+        this.syncStatus = syncStatus;
+    }
+
+    
+    public Long getBatchNum() {
+        return batchNum;
+    }
+
+    
+    public void setBatchNum(Long batchNum) {
+        this.batchNum = batchNum;
+    }
+
+    public Double getFaceSuccessPercent() {
+        return faceSuccessPercent;
+    }
+
+    public void setFaceSuccessPercent(Double faceSuccessPercent) {
+        this.faceSuccessPercent = faceSuccessPercent;
+    }
+
+    public Double getBaiduFaceLivenessSuccessPercent() {
+        return baiduFaceLivenessSuccessPercent;
+    }
+
+    public void setBaiduFaceLivenessSuccessPercent(Double baiduFaceLivenessSuccessPercent) {
+        this.baiduFaceLivenessSuccessPercent = baiduFaceLivenessSuccessPercent;
+    }
+
+    
+    public Integer getQuestionCount() {
+        return questionCount;
+    }
+
+    
+    public void setQuestionCount(Integer questionCount) {
+        this.questionCount = questionCount;
+    }
+
+    
+    public Boolean getIsAllObjectivePaper() {
+        return isAllObjectivePaper;
+    }
+
+    
+    public void setIsAllObjectivePaper(Boolean isAllObjectivePaper) {
+        this.isAllObjectivePaper = isAllObjectivePaper;
+    }
+    
+    
+}

+ 82 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/FaceBiopsyEntity.java

@@ -0,0 +1,82 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.entity;
+
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+
+import javax.persistence.*;
+
+/**
+ * @Description 人脸活体检测结果实体
+ * @Author lideyin
+ * @Date 2019/7/17 15:15
+ * @Version 1.0
+ */
+@Entity
+@Table(name = "EC_OES_EXAM_FACE_BIOPSY", indexes = {
+        @Index(name = "IDX_E_F_B_001001", columnList = "examRecordDataId", unique = true)
+})
+public class FaceBiopsyEntity extends WithIdJpaEntity {
+    /**
+     * 组织机构id
+     */
+    @Column(nullable = false)
+    private Long rootOrgId;
+    /**
+     * 考试记录id
+     */
+    @Column(nullable = false)
+    private Long examRecordDataId;
+    /**
+     * 检测结果
+     */
+    private Boolean result;
+
+    /**
+     * 已检测次数
+     */
+    private int verifiedTimes;
+    /**
+     * 错误信息
+     */
+    @Column(length = 500)
+    private String errorMsg;
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Boolean getResult() {
+        return result;
+    }
+
+    public void setResult(Boolean result) {
+        this.result = result;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+
+    public int getVerifiedTimes() {
+        return verifiedTimes;
+    }
+
+    public void setVerifiedTimes(int verifiedTimes) {
+        this.verifiedTimes = verifiedTimes;
+    }
+}

+ 118 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/FaceBiopsyItemEntity.java

@@ -0,0 +1,118 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.entity;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceBiopsyType;
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+
+import javax.persistence.*;
+
+/**
+ * @Description 人脸活体检测明细
+ * @Author lideyin
+ * @Date 2019/7/17 16:30
+ * @Version 1.0
+ */
+@Entity
+@Table(name = "EC_OES_EXAM_FACE_BIOPSY_ITEM", indexes = {
+        @Index(name = "IDX_E_F_B_I_001001", columnList = "examRecordDataId"),
+        @Index(name = "IDX_E_F_B_I_001002", columnList = "faceBiopsyId")
+})
+public class FaceBiopsyItemEntity extends WithIdJpaEntity {
+
+    private static final long serialVersionUID = -8411713406344895994L;
+    /**
+     * 考试记录id
+     */
+    @Column(nullable = false)
+    private Long examRecordDataId;
+
+    /**
+     * 人脸识别结果id
+     */
+    @Column(nullable = false)
+    private Long faceBiopsyId;
+
+    /**
+     * 人脸识别类型
+     */
+    @Column(nullable = false)
+    @Enumerated(EnumType.STRING)
+    private FaceBiopsyType faceBiopsyType;
+
+    /**
+     * 检测是否已完成
+     */
+    @Column(nullable = false)
+    private Boolean completed;
+
+    /**
+     * 检测结果
+     */
+    private Boolean result;
+    /**
+     * 错误信息
+     */
+    @Column(length = 500)
+    private String errorMsg;
+
+    /**
+     * 是否在冻结时间内
+     */
+    @Column(nullable = false)
+    private Boolean inFreezeTime;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Long getFaceBiopsyId() {
+        return faceBiopsyId;
+    }
+
+    public void setFaceBiopsyId(Long faceBiopsyId) {
+        this.faceBiopsyId = faceBiopsyId;
+    }
+
+    public FaceBiopsyType getFaceBiopsyType() {
+        return faceBiopsyType;
+    }
+
+    public void setFaceBiopsyType(FaceBiopsyType faceBiopsyType) {
+        this.faceBiopsyType = faceBiopsyType;
+    }
+
+    public Boolean getCompleted() {
+        return completed;
+    }
+
+    public void setCompleted(Boolean completed) {
+        this.completed = completed;
+    }
+
+    public Boolean getResult() {
+        return result;
+    }
+
+    public void setResult(Boolean result) {
+        this.result = result;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+
+    public Boolean getInFreezeTime() {
+        return inFreezeTime;
+    }
+
+    public void setInFreezeTime(Boolean inFreezeTime) {
+        this.inFreezeTime = inFreezeTime;
+    }
+}

+ 193 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/entity/FaceBiopsyItemStepEntity.java

@@ -0,0 +1,193 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.entity;
+
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceBiopsyAction;
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.ResourceType;
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+import javax.persistence.*;
+
+/**
+ * @Description 人脸活体检测步骤
+ * @Author lideyin
+ * @Date 2019/7/17 16:31
+ * @Version 1.0
+ */
+@Entity
+@Table(name = "EC_OES_EXAM_FACE_BIOPSY_ITEM_STEP", indexes = {
+        @Index(name = "IDX_E_F_B_I_S_001001", columnList = "examRecordDataId"),
+        @Index(name = "IDX_E_F_B_I_S_001002", columnList = "faceBiopsyItemId")
+})
+public class FaceBiopsyItemStepEntity extends WithIdJpaEntity {
+    /**
+     * 考试记录id
+     */
+    @Column(nullable = false)
+    private Long examRecordDataId;
+    /**
+     * 人脸活体检测明细id
+     */
+    @Column(nullable = false)
+    private Long faceBiopsyItemId;
+
+    /**
+     * 步骤动作
+     */
+    @Column(nullable = false, length = 20)
+    @Enumerated(EnumType.STRING)
+    private FaceBiopsyAction action;
+
+    /**
+     * 资源文件相对路径
+     */
+    @Column(length = 200)
+    private String resourceRelativePath;
+    /**
+     * 资源文件类型
+     */
+    @Column(length = 20)
+    @Enumerated(EnumType.STRING)
+    private ResourceType resourceType;
+
+    /**
+     * 动作时长
+     */
+    private Integer actionStay;
+
+    /**
+     * 检测结果
+     */
+    private Boolean result;
+    /**
+     * 错误信息
+     */
+    @Column(length = 500)
+    private String errorMsg;
+    /**
+     * 扩展属性1
+     */
+    @Column(length = 2000)
+    private String ext1;
+    /**
+     * 扩展属性2
+     */
+    @Column(length = 2000)
+    private String ext2;
+    /**
+     * 扩展属性3
+     */
+    @Column(length = 2000)
+    private String ext3;
+    /**
+     * 扩展属性4
+     */
+    @Column(length = 2000)
+    private String ext4;
+    /**
+     * 扩展属性5
+     */
+    @Column(length = 2000)
+    private String ext5;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Long getFaceBiopsyItemId() {
+        return faceBiopsyItemId;
+    }
+
+    public void setFaceBiopsyItemId(Long faceBiopsyItemId) {
+        this.faceBiopsyItemId = faceBiopsyItemId;
+    }
+
+    public FaceBiopsyAction getAction() {
+        return action;
+    }
+
+    public void setAction(FaceBiopsyAction action) {
+        this.action = action;
+    }
+
+    public String getResourceRelativePath() {
+        return resourceRelativePath;
+    }
+
+    public void setResourceRelativePath(String resourceRelativePath) {
+        this.resourceRelativePath = resourceRelativePath;
+    }
+
+    public ResourceType getResourceType() {
+        return resourceType;
+    }
+
+    public void setResourceType(ResourceType resourceType) {
+        this.resourceType = resourceType;
+    }
+
+    public Integer getActionStay() {
+        return actionStay;
+    }
+
+    public void setActionStay(Integer actionStay) {
+        this.actionStay = actionStay;
+    }
+
+    public Boolean getResult() {
+        return result;
+    }
+
+    public void setResult(Boolean result) {
+        this.result = result;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+
+    public String getExt1() {
+        return ext1;
+    }
+
+    public void setExt1(String ext1) {
+        this.ext1 = ext1;
+    }
+
+    public String getExt2() {
+        return ext2;
+    }
+
+    public void setExt2(String ext2) {
+        this.ext2 = ext2;
+    }
+
+    public String getExt3() {
+        return ext3;
+    }
+
+    public void setExt3(String ext3) {
+        this.ext3 = ext3;
+    }
+
+    public String getExt4() {
+        return ext4;
+    }
+
+    public void setExt4(String ext4) {
+        this.ext4 = ext4;
+    }
+
+    public String getExt5() {
+        return ext5;
+    }
+
+    public void setExt5(String ext5) {
+        this.ext5 = ext5;
+    }
+}

+ 25 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/FaceBiopsyAction.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.enums;
+/*
+ * @Description 人脸活体检测动作
+ * @Author lideyin
+ * @Date 2019/7/17 16:46
+ * @Version 1.0
+ */
+public enum FaceBiopsyAction {
+    /**
+     * 人脸比对
+     */
+    FACE_COMPARE,
+    /**
+     * 开心
+     */
+    HAPPY,
+    /**
+     * 严肃
+     */
+    SERIOUS,
+    /**
+     * 视频比对
+     */
+    VIDEO_COMPARE
+}

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

@@ -0,0 +1,41 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.enums;
+
+/**
+ * @Description 活体检测方案枚举
+ * @Author lideyin
+ * @Date 2019/11/6 15:03
+ * @Version 1.0
+ */
+public enum FaceBiopsyScheme {
+    /**
+     * FaceID活体检测方案(即旧活体检测方案)
+     */
+    FACE_ID("S1", "FaceID活体检测方案"),
+    /**
+     * 新活体检测方案(暂时无法给出具体命名,以后有需要再改动)
+     */
+    NEW("S2", "新活体检测方案");
+    private String code;
+    private String desc;
+
+    FaceBiopsyScheme(String code, String desc) {
+        this.code = code;
+        this.desc = desc;
+    }
+
+    /**
+     * 获取活检方案代码
+     * @return
+     */
+    public String getCode(){
+        return this.code;
+    }
+
+    /**
+     * 获取活检方案描述
+     * @return
+     */
+    public String getDesc(){
+        return this.desc;
+    }
+}

+ 17 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/FaceBiopsyType.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.enums;
+/*
+ * @Description 人脸活体检测类型
+ * @Author lideyin
+ * @Date 2019/7/17 16:46
+ * @Version 1.0
+ */
+public enum FaceBiopsyType {
+    /**
+     * faceid人脸活体识别技术
+     */
+    FACE_ID,
+    /**
+     * 人脸移动识别技术
+     */
+    FACE_MOTION
+}

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

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

+ 17 - 0
examcloud-core-oe-student-dao/src/main/java/cn/com/qmth/examcloud/core/oe/student/dao/enums/ResourceType.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.examcloud.core.oe.student.dao.enums;
+/*
+ * @Description 资源类型
+ * @Author lideyin
+ * @Date 2019/7/17 16:46
+ * @Version 1.0
+ */
+public enum ResourceType {
+    /**
+     * 图片
+     */
+    PIC,
+    /**
+     * 视频
+     */
+    VIDEO
+}

+ 24 - 0
examcloud-core-oe-student-service/pom.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud</groupId>
+		<artifactId>examcloud-core-oe-student</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-core-oe-student-service</artifactId>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-core-oe-student-dao</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.reports</groupId>
+			<artifactId>examcloud-reports-commons</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+	</dependencies>
+
+</project>

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

@@ -0,0 +1,65 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import java.util.Map;
+
+import cn.com.qmth.examcloud.web.filestorage.FileStorageType;
+import io.swagger.annotations.ApiModelProperty;
+
+public class AliyunSignatureInfo extends YunSignatureInfo {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 2365956212905035110L;
+	
+	@ApiModelProperty("阿里云读取文件地址")
+	private String accessUrl;
+
+	@ApiModelProperty("阿里云签名唯一标识")
+	private String signIdentifier;
+
+	@ApiModelProperty("阿里云上传form请求地址 POST")
+	private String formUrl;
+
+	@ApiModelProperty("form表单参数(上传文件的参数名为'file')")
+	private Map<String, String> formParams;
+	
+	
+
+	public AliyunSignatureInfo() {
+		super();
+		super.setFsType(FileStorageType.ALIYUN);
+	}
+
+	public String getAccessUrl() {
+		return accessUrl;
+	}
+
+	public void setAccessUrl(String accessUrl) {
+		this.accessUrl = accessUrl;
+	}
+
+	public String getSignIdentifier() {
+		return signIdentifier;
+	}
+
+	public void setSignIdentifier(String signIdentifier) {
+		this.signIdentifier = signIdentifier;
+	}
+
+	public String getFormUrl() {
+		return formUrl;
+	}
+
+	public void setFormUrl(String formUrl) {
+		this.formUrl = formUrl;
+	}
+
+	public Map<String, String> getFormParams() {
+		return formParams;
+	}
+
+	public void setFormParams(Map<String, String> formParams) {
+		this.formParams = formParams;
+	}
+}

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

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.List;
+
+/**
+ * @Description 获取又拍云签名实体
+ * @Author lideyin
+ * @Date 2019/7/29 13:51
+ * @Version 1.0
+ */
+public class BatchGetUpyunSignDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = -1590654532824096979L;
+
+	@ApiModelProperty("又拍云签名集合")
+	private List<GetUpyunSignDomain> list;
+
+	public List<GetUpyunSignDomain> getList() {
+		return list;
+	}
+
+	public void setList(List<GetUpyunSignDomain> list) {
+		this.list = list;
+	}
+}

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

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @Description 获取又拍云签名查询实体
+ * @Author lideyin
+ * @Date 2019/7/29 13:52
+ * @Version 1.0
+ */
+public class BatchGetUpyunSignDomainQuery implements Serializable {
+
+    private static final long serialVersionUID = 9042153672752879732L;
+
+    @ApiModelProperty("又拍云签名查询参数集合")
+    private List<GetUpyunSignDomainQuery> queryList;
+
+    public List<GetUpyunSignDomainQuery> getQueryList() {
+        return queryList;
+    }
+
+    public void setQueryList(List<GetUpyunSignDomainQuery> queryList) {
+        this.queryList = queryList;
+    }
+}

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

@@ -0,0 +1,105 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+public class CheckExamInProgressInfo implements JsonSerializable{
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 5411680698118472710L;
+
+	//已断点次数
+	private Integer interruptNum;
+	
+	//最大断点次数限制
+	private Integer maxInterruptNum;
+	
+	//是否达到最大断点限制
+	private Boolean isExceed;
+	
+	private Long examRecordDataId;
+	
+	private Long examId;
+	/**
+	 * 使用时间
+	 */
+	private Long usedTime;
+	/**
+	 * 活体检测启动分钟数
+	 */
+	private Integer faceVerifyMinute;
+	
+    /**
+     * 考试类型
+     */
+    private ExamType examType;
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getUsedTime() {
+		return usedTime;
+	}
+
+	public void setUsedTime(Long usedTime) {
+		this.usedTime = usedTime;
+	}
+
+	public Integer getFaceVerifyMinute() {
+		return faceVerifyMinute;
+	}
+
+	public void setFaceVerifyMinute(Integer faceVerifyMinute) {
+		this.faceVerifyMinute = faceVerifyMinute;
+	}
+
+	public Integer getInterruptNum() {
+		return interruptNum;
+	}
+
+	public void setInterruptNum(Integer interruptNum) {
+		this.interruptNum = interruptNum;
+	}
+
+	public Integer getMaxInterruptNum() {
+		return maxInterruptNum;
+	}
+
+	public void setMaxInterruptNum(Integer maxInterruptNum) {
+		this.maxInterruptNum = maxInterruptNum;
+	}
+
+	public Boolean getIsExceed() {
+		return isExceed;
+	}
+
+	public void setIsExceed(Boolean isExceed) {
+		this.isExceed = isExceed;
+	}
+
+    
+    public ExamType getExamType() {
+        return examType;
+    }
+
+    
+    public void setExamType(ExamType examType) {
+        this.examType = examType;
+    }
+
+}

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

@@ -0,0 +1,90 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+public class CheckQrCodeInfo implements JsonSerializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 7830558100815856011L;
+	@ApiModelProperty(value = "登录信息key")
+	private String key;
+	@ApiModelProperty(value = "登录信息token")
+	private String token;
+	@ApiModelProperty(value = "考生ID")
+	private Long examStudentId;
+	@ApiModelProperty(value = "考试记录DataID")
+	private Long examRecordDataId;
+	@ApiModelProperty(value = "考试试题题序")
+	private Integer questionOrder;
+	@ApiModelProperty(value = "考试试题大题号")
+    private Integer questionMainNumber;
+	@ApiModelProperty(value = "课程ID")
+	private Long courseId;
+	@ApiModelProperty(value = "课程名称")
+	private String courseName;
+	@ApiModelProperty(value = "小题显示序号")
+    private Integer subNumber;
+	public String getKey() {
+		return key;
+	}
+	public void setKey(String key) {
+		this.key = key;
+	}
+	public String getToken() {
+		return token;
+	}
+	public void setToken(String token) {
+		this.token = token;
+	}
+
+	public Long getExamStudentId() {
+		return examStudentId;
+	}
+
+	public void setExamStudentId(Long examStudentId) {
+		this.examStudentId = examStudentId;
+	}
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+	public String getCourseName() {
+		return courseName;
+	}
+	public void setCourseName(String courseName) {
+		this.courseName = courseName;
+	}
+	public Integer getQuestionOrder() {
+		return questionOrder;
+	}
+	public void setQuestionOrder(Integer questionOrder) {
+		this.questionOrder = questionOrder;
+	}
+	public Integer getQuestionMainNumber() {
+		return questionMainNumber;
+	}
+	public void setQuestionMainNumber(Integer questionMainNumber) {
+		this.questionMainNumber = questionMainNumber;
+	}
+	public Integer getSubNumber() {
+		return subNumber;
+	}
+	public void setSubNumber(Integer subNumber) {
+		this.subNumber = subNumber;
+	}
+	
+}

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

@@ -0,0 +1,59 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * @Description 考试结束信息
+ * @Author lideyin
+ * @Date 2019/12/13 14:17
+ * @Version 1.0
+ */
+public class EndExamInfo implements JsonSerializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 3567311334163339241L;
+
+	private Long examRecordDataId;
+	
+	private Boolean isWarn;
+	
+	private Double objectiveScore;
+	
+	private Double objectiveAccuracy;
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public Boolean getIsWarn() {
+		return isWarn;
+	}
+
+	public void setIsWarn(Boolean isWarn) {
+		this.isWarn = isWarn;
+	}
+
+	public Double getObjectiveScore() {
+		return objectiveScore;
+	}
+
+	public void setObjectiveScore(Double objectiveScore) {
+		this.objectiveScore = objectiveScore;
+	}
+
+	public Double getObjectiveAccuracy() {
+		return objectiveAccuracy;
+	}
+
+	public void setObjectiveAccuracy(Double objectiveAccuracy) {
+		this.objectiveAccuracy = objectiveAccuracy;
+	}
+	
+	
+}

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

@@ -0,0 +1,36 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * @Description 考试处理结果
+ * @Author lideyin
+ * @Date 2019/8/9 19:49
+ * @Version 1.0
+ */
+public class ExamProcessResultInfo implements JsonSerializable {
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 759446787600457180L;
+
+    private String code;
+
+    private Object data;
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public Object getData() {
+        return data;
+    }
+
+    public void setData(Object data) {
+        this.data = data;
+    }
+}

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

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * 考生作答记录扩展类
+ * @author lideyin
+ * @date 2019年5月14日 下午3:29:59
+ * @description
+ */
+public class ExamQuestionAnswerExtensionInfo implements Serializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 6459207019695307248L;
+	@ApiModelProperty(value = "小题显示序号")
+    private Integer subNumber;
+	public Integer getSubNumber() {
+		return subNumber;
+	}
+	public void setSubNumber(Integer subNumber) {
+		this.subNumber = subNumber;
+	}
+	
+}

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

@@ -0,0 +1,88 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年9月25日 上午9:43:19
+ * @company 	QMTH
+ * @description 考生作答信息 
+ */
+public class ExamStudentQuestionInfo implements JsonSerializable{
+	
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 8080615797817377990L;
+
+	/**
+	 * 题目序号
+	 */
+	private Integer order;
+	
+	/**
+	 * 是否标记
+	 */
+	private Boolean isSign;
+	
+	/**
+	 * 考生作答
+	 */
+	private String studentAnswer;
+	
+	/**
+	 * 音频播放次数
+	 */
+	private String audioPlayTimes;
+	/**
+	 * 题目作答类型
+	 */
+	@Enumerated(EnumType.STRING)
+	private AnswerType answerType;
+
+	public Integer getOrder() {
+		return order;
+	}
+
+	public void setOrder(Integer order) {
+		this.order = order;
+	}
+
+	public String getStudentAnswer() {
+		return studentAnswer;
+	}
+
+	public void setStudentAnswer(String studentAnswer) {
+		this.studentAnswer = studentAnswer;
+	}
+
+	public Boolean getIsSign() {
+		return isSign;
+	}
+
+	public void setIsSign(Boolean isSign) {
+		this.isSign = isSign;
+	}
+
+	public String getAudioPlayTimes() {
+		return audioPlayTimes;
+	}
+
+	public void setAudioPlayTimes(String audioPlayTimes) {
+		this.audioPlayTimes = audioPlayTimes;
+	}
+
+	public AnswerType getAnswerType() {
+		return answerType;
+	}
+
+	public void setAnswerType(AnswerType answerType) {
+		this.answerType = answerType;
+	}
+	
+}

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

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

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

@@ -0,0 +1,49 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.List;
+
+/**
+ * @Description 人脸活体检测基本信息
+ * @Author lideyin
+ * @Date 2019/10/14 11:21
+ * @Version 1.0
+ */
+public class FaceBiopsyInfo implements JsonSerializable {
+    private static final long serialVersionUID = -8323306595159483818L;
+
+    @ApiModelProperty(value = "活体检测开始分钟数", required = true)
+    private Integer faceVerifyMinute;
+
+    @ApiModelProperty(value = "人脸活体检测明细id", required = true)
+    private Long faceBiopsyItemId;
+
+    @ApiModelProperty(value = "人脸活体检测步骤", required = true)
+    private List<FaceBiopsyStepInfo> verifySteps;
+
+    public Integer getFaceVerifyMinute() {
+        return faceVerifyMinute;
+    }
+
+    public void setFaceVerifyMinute(Integer faceVerifyMinute) {
+        this.faceVerifyMinute = faceVerifyMinute;
+    }
+
+    public Long getFaceBiopsyItemId() {
+        return faceBiopsyItemId;
+    }
+
+    public void setFaceBiopsyItemId(Long faceBiopsyItemId) {
+        this.faceBiopsyItemId = faceBiopsyItemId;
+    }
+
+    public List<FaceBiopsyStepInfo> getVerifySteps() {
+        return verifySteps;
+    }
+
+    public void setVerifySteps(List<FaceBiopsyStepInfo> verifySteps) {
+        this.verifySteps = verifySteps;
+    }
+}

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

@@ -0,0 +1,153 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceBiopsyAction;
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.ResourceType;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+
+/**
+ * @Description 人脸活体检测步骤
+ * @Author lideyin
+ * @Date 2019/10/14 11:21
+ * @Version 1.0
+ */
+public class FaceBiopsyStepInfo implements JsonSerializable {
+	private static final long serialVersionUID = -2108253508943543996L;
+
+	@ApiModelProperty(value = "步骤id",required = true)
+	private Long stepId;
+
+	@ApiModelProperty(value = "具体动作",required = true)
+	@Enumerated(EnumType.STRING)
+	private FaceBiopsyAction action;
+
+	@ApiModelProperty(value = "动作时长")
+	private Integer stay;
+
+	@ApiModelProperty(value = "资源文件路径")
+	private String resourceUrl;
+
+	@ApiModelProperty(value = "资源文件类型")
+	@Enumerated(EnumType.STRING)
+	private ResourceType resourceType;
+
+	@ApiModelProperty(value = "执行结果")
+	private Boolean result;
+
+	@ApiModelProperty(value = "指令是否超时")
+	private Boolean timeout;
+
+	@ApiModelProperty(value = "是否有陌生人脸即多张人脸")
+	private Boolean stranger;
+
+	@ApiModelProperty(value = "是否晃出摄像头")
+	private Boolean waggleOutCamera;
+
+	@ApiModelProperty(value = "是否检测到人脸")
+	private Boolean hasFace;
+
+	@ApiModelProperty(value = "指令执行结果json串")
+	private String resultJson;
+
+	@ApiModelProperty(value = "错误描述")
+	private String errorMsg;
+
+	public Long getStepId() {
+		return stepId;
+	}
+
+	public void setStepId(Long stepId) {
+		this.stepId = stepId;
+	}
+
+	public FaceBiopsyAction getAction() {
+		return action;
+	}
+
+	public void setAction(FaceBiopsyAction action) {
+		this.action = action;
+	}
+
+	public Integer getStay() {
+		return stay;
+	}
+
+	public void setStay(Integer stay) {
+		this.stay = stay;
+	}
+
+	public String getResourceUrl() {
+		return resourceUrl;
+	}
+
+	public void setResourceUrl(String resourceUrl) {
+		this.resourceUrl = resourceUrl;
+	}
+
+	public ResourceType getResourceType() {
+		return resourceType;
+	}
+
+	public void setResourceType(ResourceType resourceType) {
+		this.resourceType = resourceType;
+	}
+
+	public Boolean getResult() {
+		return result;
+	}
+
+	public void setResult(Boolean result) {
+		this.result = result;
+	}
+
+	public String getResultJson() {
+		return resultJson;
+	}
+
+	public void setResultJson(String resultJson) {
+		this.resultJson = resultJson;
+	}
+
+	public String getErrorMsg() {
+		return errorMsg;
+	}
+
+	public void setErrorMsg(String errorMsg) {
+		this.errorMsg = errorMsg;
+	}
+
+	public Boolean getTimeout() {
+		return timeout;
+	}
+
+	public void setTimeout(Boolean timeout) {
+		this.timeout = timeout;
+	}
+
+	public Boolean getStranger() {
+		return stranger;
+	}
+
+	public void setStranger(Boolean stranger) {
+		this.stranger = stranger;
+	}
+
+	public Boolean getWaggleOutCamera() {
+		return waggleOutCamera;
+	}
+
+	public void setWaggleOutCamera(Boolean waggleOutCamera) {
+		this.waggleOutCamera = waggleOutCamera;
+	}
+
+	public Boolean getHasFace() {
+		return hasFace;
+	}
+
+	public void setHasFace(Boolean hasFace) {
+		this.hasFace = hasFace;
+	}
+}

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

@@ -0,0 +1,69 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import java.util.Map;
+
+import cn.com.qmth.examcloud.web.filestorage.FileStorageType;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 获取阿里云签名实体
+ */
+public class GetAliyunSignDomain extends GetYunSignDomain {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -6317148733119502392L;
+
+	@ApiModelProperty("阿里云读取文件地址")
+	private String accessUrl;
+
+	@ApiModelProperty("阿里云签名唯一标识")
+	private String signIdentifier;
+
+	@ApiModelProperty("阿里云上传form请求地址 POST")
+	private String formUrl;
+
+	@ApiModelProperty("form表单参数(上传文件的参数名为'file')")
+	private Map<String, String> formParams;
+	
+	
+
+	public GetAliyunSignDomain() {
+		super();
+		super.setFsType(FileStorageType.ALIYUN);
+	}
+
+	public String getAccessUrl() {
+		return accessUrl;
+	}
+
+	public void setAccessUrl(String accessUrl) {
+		this.accessUrl = accessUrl;
+	}
+
+	public String getSignIdentifier() {
+		return signIdentifier;
+	}
+
+	public void setSignIdentifier(String signIdentifier) {
+		this.signIdentifier = signIdentifier;
+	}
+
+	public String getFormUrl() {
+		return formUrl;
+	}
+
+	public void setFormUrl(String formUrl) {
+		this.formUrl = formUrl;
+	}
+
+	public Map<String, String> getFormParams() {
+		return formParams;
+	}
+
+	public void setFormParams(Map<String, String> formParams) {
+		this.formParams = formParams;
+	}
+
+}

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

@@ -0,0 +1,51 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+public class GetFaceVerifyTokenInfo implements JsonSerializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -4053674655722904852L;
+
+	private Boolean success;
+	
+	private String faceLivenessToken;
+
+	private Long faceVerifyId;
+	
+	private String errorMsg;
+
+	public Boolean getSuccess() {
+		return success;
+	}
+
+	public void setSuccess(Boolean success) {
+		this.success = success;
+	}
+
+	public String getFaceLivenessToken() {
+		return faceLivenessToken;
+	}
+
+	public void setFaceLivenessToken(String faceLivenessToken) {
+		this.faceLivenessToken = faceLivenessToken;
+	}
+
+	public String getErrorMsg() {
+		return errorMsg;
+	}
+
+	public void setErrorMsg(String errorMsg) {
+		this.errorMsg = errorMsg;
+	}
+
+	public Long getFaceVerifyId() {
+		return faceVerifyId;
+	}
+
+	public void setFaceVerifyId(Long faceVerifyId) {
+		this.faceVerifyId = faceVerifyId;
+	}
+}

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

@@ -0,0 +1,64 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+public class GetQrCodeReq implements JsonSerializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -8684452576786540515L;
+	@NotNull(message = "考生ID不能为空")
+	@ApiModelProperty(value = "考生ID")
+	private Long examStudentId;
+	@NotNull(message = "考试记录DataID不能为空")
+	@ApiModelProperty(required = true,value = "考试记录DataID")
+	private Long examRecordDataId;
+	@NotNull(message = "题号不能为空")
+	@ApiModelProperty(required = true,value = "考试试题号")
+	private Integer order;
+	@ApiModelProperty(required = true,value = "传输文件类型")
+	private String transferFileType;
+
+	@ApiModelProperty(required = true,value = "是否用来测试环境")
+	private boolean testEnv;
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public Integer getOrder() {
+		return order;
+	}
+	public void setOrder(Integer order) {
+		this.order = order;
+	}
+	public Long getExamStudentId() {
+		return examStudentId;
+	}
+	public void setExamStudentId(Long examStudentId) {
+		this.examStudentId = examStudentId;
+	}
+
+	public String getTransferFileType() {
+		return transferFileType;
+	}
+
+	public void setTransferFileType(String transferFileType) {
+		this.transferFileType = transferFileType;
+	}
+
+	public boolean isTestEnv() {
+		return testEnv;
+	}
+
+	public void setTestEnv(boolean testEnv) {
+		this.testEnv = testEnv;
+	}
+}

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

@@ -0,0 +1,22 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+public class GetUploadedFileAcknowledgeStatusReq implements JsonSerializable {
+
+    private static final long serialVersionUID = 2015767610417418141L;
+    @NotNull(message = "响应id不允许为空")
+    @ApiModelProperty(required = true, value = "响应id不允许为空")
+    private String acknowledgeId;
+
+    public String getAcknowledgeId() {
+        return acknowledgeId;
+    }
+
+    public void setAcknowledgeId(String acknowledgeId) {
+        this.acknowledgeId = acknowledgeId;
+    }
+}

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

@@ -0,0 +1,68 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import java.util.Map;
+
+import cn.com.qmth.examcloud.web.filestorage.FileStorageType;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 获取又拍云签名实体
+ * @Author lideyin
+ * @Date 2019/7/29 13:51
+ * @Version 1.0
+ */
+public class GetUpyunSignDomain extends GetYunSignDomain {
+	private static final long serialVersionUID = -7428336128716146142L;
+
+	@ApiModelProperty("又拍云读取文件地址")
+	private String accessUrl;
+
+	@ApiModelProperty("又拍云签名唯一标识")
+	private String signIdentifier;
+
+	@ApiModelProperty("又拍云上传form请求地址 POST")
+	private String formUrl;
+
+	@ApiModelProperty("form表单参数(上传文件的参数名为'file')")
+	private Map<String, String> formParams;
+	
+	
+
+	public GetUpyunSignDomain() {
+		super();
+		super.setFsType(FileStorageType.UPYUN);
+	}
+
+	public String getAccessUrl() {
+		return accessUrl;
+	}
+
+	public void setAccessUrl(String accessUrl) {
+		this.accessUrl = accessUrl;
+	}
+
+	public String getSignIdentifier() {
+		return signIdentifier;
+	}
+
+	public void setSignIdentifier(String signIdentifier) {
+		this.signIdentifier = signIdentifier;
+	}
+
+	public String getFormUrl() {
+		return formUrl;
+	}
+
+	public void setFormUrl(String formUrl) {
+		this.formUrl = formUrl;
+	}
+
+	public Map<String, String> getFormParams() {
+		return formParams;
+	}
+
+	public void setFormParams(Map<String, String> formParams) {
+		this.formParams = formParams;
+	}
+
+}

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

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * @Description 获取又拍云签名查询实体
+ * @Author lideyin
+ * @Date 2019/7/29 13:52
+ * @Version 1.0
+ */
+public class GetUpyunSignDomainQuery implements Serializable {
+    private static final long serialVersionUID = 6594663402697883755L;
+    @ApiModelProperty("文件后缀名,示例:jpg")
+    private String fileSuffix;
+    @ApiModelProperty("文件md5加密值")
+    private String fileMd5;
+
+    public String getFileSuffix() {
+        return fileSuffix;
+    }
+
+    public void setFileSuffix(String fileSuffix) {
+        this.fileSuffix = fileSuffix;
+    }
+
+    public String getFileMd5() {
+        return fileMd5;
+    }
+
+    public void setFileMd5(String fileMd5) {
+        this.fileMd5 = fileMd5;
+    }
+}

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

@@ -0,0 +1,69 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+public class GetUpyunSignatureReq implements JsonSerializable {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = -3761016079101373608L;
+    @NotNull(message = "考试记录DataID不能为空")
+    @ApiModelProperty(required = true, value = "考试记录DataID")
+    private Long examRecordDataId;
+    @NotNull(message = "题号不能为空")
+    @ApiModelProperty(required = true, value = "考试试题号")
+    private Integer order;
+    @NotNull(message = "文件MD5不能为空")
+    @ApiModelProperty(required = true, value = "文件MD5")
+    private String fileMd5;
+    @NotNull(message = "文件后缀不能为空")
+    @ApiModelProperty(required = true, value = "文件后缀")
+    private String fileSuffix;
+    @ApiModelProperty(value = "文件名自定义参数")
+    private String ext;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Integer getOrder() {
+        return order;
+    }
+
+    public void setOrder(Integer order) {
+        this.order = order;
+    }
+
+    public String getFileMd5() {
+        return fileMd5;
+    }
+
+    public void setFileMd5(String fileMd5) {
+        this.fileMd5 = fileMd5;
+    }
+
+    public String getFileSuffix() {
+        return fileSuffix;
+    }
+
+    public void setFileSuffix(String fileSuffix) {
+        this.fileSuffix = fileSuffix;
+    }
+
+    public String getExt() {
+        return ext;
+    }
+
+    public void setExt(String ext) {
+        this.ext = ext;
+    }
+
+}

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

@@ -0,0 +1,30 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.web.filestorage.FileStorageType;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 获取云存储签名实体
+ * @Version 1.0
+ */
+public class GetYunSignDomain implements JsonSerializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 5956527846413583514L;
+
+	@ApiModelProperty("云存储类型")
+	private FileStorageType fsType;
+
+	public FileStorageType getFsType() {
+		return fsType;
+	}
+
+	public void setFsType(FileStorageType fsType) {
+		this.fsType = fsType;
+	}
+
+
+}

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

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * @Description 获取云存储签名查询实体
+ */
+public class GetYunSignDomainQuery implements Serializable {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 245797819495925975L;
+	@ApiModelProperty("文件后缀名,示例:jpg")
+	private String fileSuffix;
+	@ApiModelProperty("文件md5加密值")
+	private String fileMd5;
+
+	public String getFileSuffix() {
+		return fileSuffix;
+	}
+
+	public void setFileSuffix(String fileSuffix) {
+		this.fileSuffix = fileSuffix;
+	}
+
+	public String getFileMd5() {
+		return fileMd5;
+	}
+
+	public void setFileMd5(String fileMd5) {
+		this.fileMd5 = fileMd5;
+	}
+}

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

@@ -0,0 +1,69 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+public class GetYunSignatureReq implements JsonSerializable {
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = 4677351126981131069L;
+	@NotNull(message = "考试记录DataID不能为空")
+    @ApiModelProperty(required = true, value = "考试记录DataID")
+    private Long examRecordDataId;
+    @NotNull(message = "题号不能为空")
+    @ApiModelProperty(required = true, value = "考试试题号")
+    private Integer order;
+    @NotNull(message = "文件MD5不能为空")
+    @ApiModelProperty(required = true, value = "文件MD5")
+    private String fileMd5;
+    @NotNull(message = "文件后缀不能为空")
+    @ApiModelProperty(required = true, value = "文件后缀")
+    private String fileSuffix;
+    @ApiModelProperty(value = "文件名自定义参数")
+    private String ext;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Integer getOrder() {
+        return order;
+    }
+
+    public void setOrder(Integer order) {
+        this.order = order;
+    }
+
+    public String getFileMd5() {
+        return fileMd5;
+    }
+
+    public void setFileMd5(String fileMd5) {
+        this.fileMd5 = fileMd5;
+    }
+
+    public String getFileSuffix() {
+        return fileSuffix;
+    }
+
+    public void setFileSuffix(String fileSuffix) {
+        this.fileSuffix = fileSuffix;
+    }
+
+    public String getExt() {
+        return ext;
+    }
+
+    public void setExt(String ext) {
+        this.ext = ext;
+    }
+
+}

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

@@ -0,0 +1,49 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.List;
+
+/**
+ * @Description 保存人脸活体检测结果请求对象
+ * @Author lideyin
+ * @Date 2019/10/14 11:21
+ * @Version 1.0
+ */
+public class SaveFaceBiopsyResultReq implements JsonSerializable {
+	private static final long serialVersionUID = 3996944262550668002L;
+
+	@ApiModelProperty(value = "考试记录id",required = true)
+	private Long examRecordDataId;
+
+	@ApiModelProperty(value = "人脸活体检测明细id",required = true)
+	private Long faceBiopsyItemId;
+
+	@ApiModelProperty(value = "实际活体检测步骤",required = true)
+	private List<FaceBiopsyStepInfo> verifySteps;
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public Long getFaceBiopsyItemId() {
+		return faceBiopsyItemId;
+	}
+
+	public void setFaceBiopsyItemId(Long faceBiopsyItemId) {
+		this.faceBiopsyItemId = faceBiopsyItemId;
+	}
+
+	public List<FaceBiopsyStepInfo> getVerifySteps() {
+		return verifySteps;
+	}
+
+	public void setVerifySteps(List<FaceBiopsyStepInfo> verifySteps) {
+		this.verifySteps = verifySteps;
+	}
+}

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

@@ -0,0 +1,58 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * @Description 人脸活体检测结果
+ * @Author lideyin
+ * @Date 2019/10/14 11:21
+ * @Version 1.0
+ */
+public class SaveFaceBiopsyResultResp implements JsonSerializable {
+	private static final long serialVersionUID = 3996944262550668002L;
+
+	@ApiModelProperty(value = "检测结果",required = true)
+	private Boolean verifyResult;
+
+	@ApiModelProperty(value = "错误消息",required = false)
+	private String errorMessage;
+
+	@ApiModelProperty(value = "是否结束考试",required = true)
+	private Boolean  endExam;
+
+	@ApiModelProperty(value = "是否需要下一次活检",required = true)
+	private Boolean  needNextVerify;
+
+	public Boolean getVerifyResult() {
+		return verifyResult;
+	}
+
+	public void setVerifyResult(Boolean verifyResult) {
+		this.verifyResult = verifyResult;
+	}
+
+	public String getErrorMessage() {
+		return errorMessage;
+	}
+
+	public void setErrorMessage(String errorMessage) {
+		this.errorMessage = errorMessage;
+	}
+
+	public Boolean getEndExam() {
+		return endExam;
+	}
+
+	public void setEndExam(Boolean endExam) {
+		this.endExam = endExam;
+	}
+
+	public Boolean getNeedNextVerify() {
+		return needNextVerify;
+	}
+
+	public void setNeedNextVerify(Boolean needNextVerify) {
+		this.needNextVerify = needNextVerify;
+	}
+}

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

@@ -0,0 +1,60 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+public class SaveUploadedFileAcknowledgeStatusReq implements JsonSerializable {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 2015767610417418141L;
+    @NotNull(message = "考试记录DataID不能为空")
+    @ApiModelProperty(required = true, value = "考试记录DataID")
+    private Long examRecordDataId;
+    @NotNull(message = "题号不能为空")
+    @ApiModelProperty(required = true, value = "考试试题号")
+    private Integer order;
+    @NotNull(message = "文件路径(含文件名)不能为空")
+    @ApiModelProperty(required = true, value = "文件路径(含文件名)")
+    private String filePath;
+
+    @NotNull(message = "答复状态不允许为空")
+    @ApiModelProperty(required = true,value = "答复状态(CONFIRMED:已确认,DISCARDED:已弃用)")
+    private String acknowledgeStatus;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+
+    public Integer getOrder() {
+        return order;
+    }
+
+    public void setOrder(Integer order) {
+        this.order = order;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    public String getAcknowledgeStatus() {
+        return acknowledgeStatus;
+    }
+
+    public void setAcknowledgeStatus(String acknowledgeStatus) {
+        this.acknowledgeStatus = acknowledgeStatus;
+    }
+}

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

@@ -0,0 +1,61 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+
+public class SaveUploadedFileReq implements JsonSerializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 5983488494596822561L;
+	@NotNull(message = "考生ID不能为空")
+	@ApiModelProperty(required = true,value = "考生ID")
+	private Long examStudentId;
+	@NotNull(message = "考试记录DataID不能为空")
+	@ApiModelProperty(required = true,value = "考试记录DataID")
+	private Long examRecordDataId;
+	@NotNull(message = "题号不能为空")
+	@ApiModelProperty(required = true,value = "考试试题号")
+	private Integer order;
+	@NotNull(message = "文件路径(含文件名)不能为空")
+	@ApiModelProperty(required = true,value = "文件路径(含文件名)")
+	private String filePath;
+	@ApiModelProperty(required = true,value = "传输文件类型(用于标识,通过二维码传输文件的类型)")
+	private String transferFileType;
+	public Long getExamStudentId() {
+		return examStudentId;
+	}
+	public void setExamStudentId(Long examStudentId) {
+		this.examStudentId = examStudentId;
+	}
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public Integer getOrder() {
+		return order;
+	}
+	public void setOrder(Integer order) {
+		this.order = order;
+	}
+	public String getFilePath() {
+		return filePath;
+	}
+	public void setFilePath(String filePath) {
+		this.filePath = filePath;
+	}
+
+	public String getTransferFileType() {
+		return transferFileType;
+	}
+
+	public void setTransferFileType(String transferFileType) {
+		this.transferFileType = transferFileType;
+	}
+}

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

@@ -0,0 +1,81 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年9月27日 下午4:44:15
+ * @company 	QMTH
+ * @description StartExamInfo.java
+ */
+public class StartExamInfo implements JsonSerializable{
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 7964522965649314640L;
+	
+	private Long examRecordDataId;
+	
+	private String courseCode;
+	
+	private String courseName;
+	
+	private String studentCode;
+	
+	private String studentName;
+	
+	/**
+	 * 考试时长
+	 */
+	private Integer duration;
+	/**
+	 * 活体检测开始分钟数
+	 */
+	private Integer faceVerifyMinute;
+	
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+	public Integer getDuration() {
+		return duration;
+	}
+	public void setDuration(Integer duration) {
+		this.duration = duration;
+	}
+	public Integer getFaceVerifyMinute() {
+		return faceVerifyMinute;
+	}
+	public void setFaceVerifyMinute(Integer faceVerifyMinute) {
+		this.faceVerifyMinute = faceVerifyMinute;
+	}
+	public String getCourseCode() {
+		return courseCode;
+	}
+	public void setCourseCode(String courseCode) {
+		this.courseCode = courseCode;
+	}
+	public String getCourseName() {
+		return courseName;
+	}
+	public void setCourseName(String courseName) {
+		this.courseName = courseName;
+	}
+	public String getStudentCode() {
+		return studentCode;
+	}
+	public void setStudentCode(String studentCode) {
+		this.studentCode = studentCode;
+	}
+	public String getStudentName() {
+		return studentName;
+	}
+	public void setStudentName(String studentName) {
+		this.studentName = studentName;
+	}
+	
+}

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

@@ -0,0 +1,52 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class UploadedFileAnswerInfo extends ExamQuestionAnswerExtensionInfo{
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -8684452576786540515L;
+	@ApiModelProperty(value = "考试记录DataID")
+	private Long examRecordDataId;
+//	@ApiModelProperty(value = "考试试题ID")
+//	private String questionId;
+	@ApiModelProperty(value = "考生答案")
+	private String studentAnswer;
+	@ApiModelProperty(value = "大题号")
+	private Integer mainNumber;
+	@ApiModelProperty(value = "小题号")
+    private Integer order;
+	
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+//	public String getQuestionId() {
+//		return questionId;
+//	}
+//	public void setQuestionId(String questionId) {
+//		this.questionId = questionId;
+//	}
+	public String getStudentAnswer() {
+		return studentAnswer;
+	}
+	public void setStudentAnswer(String studentAnswer) {
+		this.studentAnswer = studentAnswer;
+	}
+	public Integer getMainNumber() {
+		return mainNumber;
+	}
+	public void setMainNumber(Integer mainNumber) {
+		this.mainNumber = mainNumber;
+	}
+	public Integer getOrder() {
+		return order;
+	}
+	public void setOrder(Integer order) {
+		this.order = order;
+	}
+}

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

@@ -0,0 +1,60 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.web.filestorage.FileStorageType;
+import io.swagger.annotations.ApiModelProperty;
+
+public class UpyunSignatureInfo extends YunSignatureInfo {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -2401837848131443152L;
+	@ApiModelProperty(value = "上传到又拍云的url")
+	private String uploadUrl;
+	@ApiModelProperty(value = "又拍云签名")
+	private String signature;
+	@ApiModelProperty(value = "又拍云policy")
+	private String policy;
+	@ApiModelProperty(value = "上传到又拍云的文件路径(含文件名)")
+	private String filePath;
+	@ApiModelProperty(value = "取又拍云文件时的域名")
+	private String upyunFileDomain;
+	
+	public UpyunSignatureInfo() {
+		super();
+		super.setFsType(FileStorageType.UPYUN);
+	}
+
+	
+	public String getSignature() {
+		return signature;
+	}
+	public void setSignature(String signature) {
+		this.signature = signature;
+	}
+	public String getPolicy() {
+		return policy;
+	}
+	public void setPolicy(String policy) {
+		this.policy = policy;
+	}
+	public String getFilePath() {
+		return filePath;
+	}
+	public void setFilePath(String filePath) {
+		this.filePath = filePath;
+	}
+	public String getUploadUrl() {
+		return uploadUrl;
+	}
+	public void setUploadUrl(String uploadUrl) {
+		this.uploadUrl = uploadUrl;
+	}
+	public String getUpyunFileDomain() {
+		return upyunFileDomain;
+	}
+	public void setUpyunFileDomain(String upyunFileDomain) {
+		this.upyunFileDomain = upyunFileDomain;
+	}
+	
+}

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

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.core.oe.student.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.web.filestorage.FileStorageType;
+import io.swagger.annotations.ApiModelProperty;
+
+public class YunSignatureInfo implements JsonSerializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 6721564120024116705L;
+	@ApiModelProperty("云存储类型")
+	private FileStorageType fsType;
+
+	public FileStorageType getFsType() {
+		return fsType;
+	}
+
+	public void setFsType(FileStorageType fsType) {
+		this.fsType = fsType;
+	}
+
+	
+}

+ 28 - 0
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/processor/HttpMethodProcessorImpl.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.oe.student.processor;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
+import cn.com.qmth.examcloud.web.support.HttpMethodProcessor;
+
+@Component
+public class HttpMethodProcessorImpl implements HttpMethodProcessor {
+
+	@Override
+	public void beforeMethod(HttpServletRequest request, Object[] args) {
+
+	}
+
+	@Override
+	public void onSuccess(HttpServletRequest request, Object[] args, Object ret) {
+		ReportsUtil.sendReport(false);
+	}
+
+	@Override
+	public void onException(HttpServletRequest request, Object[] args, Throwable e) {
+		ReportsUtil.sendReport(true);
+	}
+
+}

+ 29 - 0
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamBossService.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.core.oe.student.service;
+
+import cn.com.qmth.examcloud.support.examing.ExamBoss;
+
+/**
+ * @author chenken
+ *
+ */
+public interface ExamBossService {
+
+    /**
+     * 保存
+     * @param timeout   秒
+     */
+    public void saveExamBoss(Long examStudentId,ExamBoss eb);
+
+    /**
+     * 获取
+     * @param examStudentId
+     * @return
+     */
+    public ExamBoss getExamBoss(Long examStudentId);
+
+    /**
+     * 删除
+     * @param examStudentId
+     */
+    public void deleteExamBoss(Long examStudentId);
+}

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

@@ -0,0 +1,87 @@
+package cn.com.qmth.examcloud.core.oe.student.service;
+
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.oe.student.bean.*;
+import cn.com.qmth.examcloud.support.enums.HandInExamType;
+
+/**
+ * @author chenken
+ * @date 2018年8月13日 下午2:09:38
+ * @company QMTH
+ * @description 在线考试控制服务接口
+ */
+public interface ExamControlService {
+
+    /**
+     * 开始考试
+     *
+     * @param examStudentId
+     * @param user
+     */
+    StartExamInfo startExam(Long examStudentId, User user);
+
+    /**
+     * 交卷
+     *
+     * @param examRecordDataId 考试记录id
+     * @param handInExamType   交卷类型
+     */
+    void handInExam(Long examRecordDataId, HandInExamType handInExamType);
+
+    /**
+     * 断点续考:检查正在进行中的考试
+     *
+     * @param studentId
+     */
+    CheckExamInProgressInfo checkExamInProgress(Long studentId);
+
+    /**
+     * 考试心跳
+     *
+     * @param
+     */
+    long examHeartbeat(User user);
+
+    /**
+     * 获取考试结束后的相关信息
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    EndExamInfo getEndExamInfo(Long examRecordDataId);
+
+    CheckQrCodeInfo checkQrCode(String qrCode);
+
+    /**
+     * 发送消息到websocket
+     *
+     * @param examRecordDataId 考试记录id
+     * @param order            题序号
+     * @param fileUrl          文件路径
+     * @param transferFileType 传输文件类型
+     * @throws Exception
+     */
+    void sendFileAnswerToWebSocket(Long examRecordDataId, Integer order,
+                                   String fileUrl, String transferFileType, Long userId, Long rootOrgId) throws Exception;
+
+    /**
+     * 通过websocket发送二维码扫描信息
+     *
+     * @param examRecordDataId
+     * @param clientId
+     * @throws Exception
+     */
+    void sendScanQrCodeToWebSocket(String clientId, Long examRecordDataId, Integer order,
+                                   Long userId, Long rootOrgId) throws Exception;
+
+    UpyunSignatureInfo getUpyunSignature(GetYunSignatureReq req);
+
+    /**
+     * 获取二维码
+     * @param req
+     * @param user
+     * @return
+     */
+    String getQrCode(GetQrCodeReq req, User user);
+}

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

@@ -0,0 +1,85 @@
+package cn.com.qmth.examcloud.core.oe.student.service;
+
+import cn.com.qmth.examcloud.core.oe.student.bean.GetFaceVerifyTokenInfo;
+import cn.com.qmth.examcloud.core.oe.student.dao.entity.ExamFaceLivenessVerifyEntity;
+
+import java.util.List;
+
+
+/**
+ * @Description 活体检测服务接口
+ * @Author lideyin
+ * @Date 2019/12/12 15:56
+ * @Version 1.0
+ */
+public interface ExamFaceLivenessVerifyService {
+
+    /**
+     * 保存活体检测信息
+     *
+     * @param examRecordDataId
+     */
+    ExamFaceLivenessVerifyEntity saveFaceVerify(Long examRecordDataId);
+
+    /**
+     * 使用考试记录id查询人脸活体检测信息
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    List<ExamFaceLivenessVerifyEntity> listFaceVerifyByExamRecordId(Long examRecordDataId);
+
+    /**
+     * 人脸检测完成后回调处理
+     *
+     * @param resultJson
+     * @return
+     */
+    ExamFaceLivenessVerifyEntity faceIdNotify(String resultJson);
+
+    /**
+     * 向faceId发起检测请求,返回token
+     *
+     * @return
+     */
+    GetFaceVerifyTokenInfo getFaceVerifyToken(Long studentId, String bizNo);
+
+    ExamFaceLivenessVerifyEntity saveFaceVerifyByExamRecordDataId(Long examRecordDataId);
+
+    /**
+     * 根据ID查询
+     *
+     * @param id
+     * @return
+     */
+    ExamFaceLivenessVerifyEntity findFaceVerifyById(Long id);
+
+    /**
+     * 人脸检测超时处理
+     *
+     * @param examRecordDataId
+     */
+    void faceTestTimeOut(Long examRecordDataId);
+
+    /**
+     * 人脸活体检测结束处理
+     *
+     * @param examRecordDataId
+     * @param studentId
+     * @param result
+     */
+    void faceTestEndHandle(Long examRecordDataId, Long studentId, String result);
+
+    /**
+     * 断点续考,获取活体检测开启时间
+     *
+     * @param examId
+     * @param examRecordDataId
+     * @param usedMinute 考试已用的分钟数
+     * @return
+     */
+    Integer getFaceLivenessVerifyMinute(Long rootOrgId, Long orgId, Long examId,
+                                        Long studentId, Long examRecordDataId, Integer usedMinute);
+
+}
+

+ 38 - 0
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamFileAnswerService.java

@@ -0,0 +1,38 @@
+package cn.com.qmth.examcloud.core.oe.student.service;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.oe.student.bean.FaceBiopsyInfo;
+import cn.com.qmth.examcloud.core.oe.student.bean.SaveFaceBiopsyResultReq;
+import cn.com.qmth.examcloud.core.oe.student.bean.SaveFaceBiopsyResultResp;
+import cn.com.qmth.examcloud.core.oe.student.bean.SaveUploadedFileReq;
+import cn.com.qmth.examcloud.core.oe.student.dao.enums.FaceBiopsyType;
+import cn.com.qmth.examcloud.support.examing.ExamFileAnswer;
+
+/**
+ * @Description 文件作答接口
+ * @Author lideyin
+ * @Date 2019/12/13 17:31
+ * @Version 1.0
+ */
+public interface ExamFileAnswerService {
+
+    /**
+     * 保存文件作答结果
+     * @param examFileAnswer
+     * @return 返回主键id
+     */
+    void saveFileAnswer(String fileAnswerId,ExamFileAnswer examFileAnswer);
+
+    /**
+     * 删除文件作答记录
+     * @param fileAnswerId
+     */
+    void deleteFileAnswer(String fileAnswerId);
+
+    /**
+     *  获取文件作答结果
+     * @param fileAnswerId
+     * @return
+     */
+    ExamFileAnswer getFileAnswer(String fileAnswerId);
+}

+ 71 - 0
examcloud-core-oe-student-service/src/main/java/cn/com/qmth/examcloud/core/oe/student/service/ExamRecordDataService.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.examcloud.core.oe.student.service;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.oe.student.api.request.CalcExamScoreReq;
+import cn.com.qmth.examcloud.core.oe.student.api.request.CalcFaceBiopsyResultReq;
+import cn.com.qmth.examcloud.core.oe.student.api.request.GetExamRecordDataIdsReq;
+import cn.com.qmth.examcloud.core.oe.student.api.request.UpdateExamRecordDataBatchNumReq;
+import cn.com.qmth.examcloud.core.oe.student.api.request.UpdateExamRecordStatusReq;
+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.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import cn.com.qmth.examcloud.support.examing.ExamingSession;
+
+/**
+ * @author chenken
+ * @date 2018/8/15 11:16
+ * @company QMTH
+ * @description 考试记录数据服务接口
+ */
+public interface ExamRecordDataService {
+
+    /**
+     *
+     * @param examingSession
+     * @param examBean
+     * @param courseBean
+     * @param basePaperId
+     * @param isFullyObjective
+     * @return
+     */
+    ExamRecordData createExamRecordData(ExamingSession examingSession, ExamSettingsCacheBean examBean, CourseCacheBean courseBean,
+                                        String basePaperId,boolean isFullyObjective);
+
+    /**
+     *
+     * @param examRecordDataId
+     * @param data
+     */
+    void saveExamRecordDataCache(Long examRecordDataId, ExamRecordData data);
+
+    /**
+     * 获取
+     * @param examRecordDataId
+     * @return
+     */
+    ExamRecordData getExamRecordDataCache(Long examRecordDataId);
+
+    /**
+     * 删除
+     * @param examRecordDataId
+     */
+    void deleteExamRecordDataCache(Long examRecordDataId);
+    
+    List<Long> getExamRecordDataIds(GetExamRecordDataIdsReq req);
+    
+    void updateExamRecordDataBatchNum(UpdateExamRecordDataBatchNumReq req);
+    
+    void updateExamRecordStatus(UpdateExamRecordStatusReq req);
+
+    /**
+     * 计算活体检测结果
+     * @param req
+     * @return
+     */
+    CalcFaceBiopsyResultResp calcFaceBiopsyResult(CalcFaceBiopsyResultReq req);
+
+    CalcExamScoreResp calcExamScore(CalcExamScoreReq req);
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini