deason 5 年之前
當前提交
ecc0823793
共有 100 個文件被更改,包括 10613 次插入0 次删除
  1. 12 0
      .gitignore
  2. 19 0
      examcloud-core-oe-admin-api-provider/pom.xml
  3. 61 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamAuditBeanConvert.java
  4. 20 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamCaptureBeanConvert.java
  5. 61 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamRecordDataBeanConvert.java
  6. 350 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamRecordDataDomain.java
  7. 20 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamScoreBeanConvert.java
  8. 114 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamStudentBeanConvert.java
  9. 96 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/IllegallyTypeDomain.java
  10. 55 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/BaseInfoController.java
  11. 168 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamAuditController.java
  12. 57 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamCaptureController.java
  13. 99 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamControlController.java
  14. 171 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordController.java
  15. 57 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordDataController.java
  16. 49 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordPaperStructController.java
  17. 55 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordQuestionsController.java
  18. 192 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamScoreController.java
  19. 245 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamStudentController.java
  20. 360 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/IllegallyTypeController.java
  21. 144 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/OfflineExamController.java
  22. 83 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/PracticeController.java
  23. 75 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamCourseCloudServiceProvider.java
  24. 711 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordCloudServiceProvider.java
  25. 302 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordForMarkingCloudServiceProvider.java
  26. 44 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordPaperStructProvider.java
  27. 290 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordQuestionsCloudServiceProvider.java
  28. 65 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreCloudServiceProvider.java
  29. 474 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreDataCloudServiceProvider.java
  30. 82 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreNoticeQueueCloudServiceProvider.java
  31. 108 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreObtainQueueCloudServiceProvider.java
  32. 212 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamStudentCloudServiceProvider.java
  33. 180 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamStudentDataCloudServiceProvider.java
  34. 39 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/OeAdminScorePushCloudServiceProvider.java
  35. 154 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/SyncCloudServiceProvider.java
  36. 673 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/SyncExamDataCloudServiceProvider.java
  37. 134 0
      examcloud-core-oe-admin-base/pom.xml
  38. 182 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/Constants.java
  39. 91 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/jpa/Model.java
  40. 20 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/jpa/Op.java
  41. 70 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/jpa/Order.java
  42. 146 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/jpa/Searcher.java
  43. 135 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/jpa/SpecUtils.java
  44. 265 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/jpa/SqlWrapper.java
  45. 96 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/Check.java
  46. 167 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/CommonUtil.java
  47. 101 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/DateUtils.java
  48. 259 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/FileDisposeUtil.java
  49. 95 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/HtmlUtil.java
  50. 91 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/HttpPoolUtil.java
  51. 172 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/JsonMapper.java
  52. 63 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/NewQuestionType.java
  53. 101 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/PagingAndSortingDTO.java
  54. 74 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/PagingAndSortingSpecification.java
  55. 163 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/QEncodeUtil.java
  56. 17 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/QuerySpecification.java
  57. 40 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/QuestionTypeUtil.java
  58. 63 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/RowIterator.java
  59. 180 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/Sentence.java
  60. 127 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/baiduFaceVerify/BaiduFaceVerifyUtil.java
  61. 65 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/baiduFaceVerify/Base64Util.java
  62. 29 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/baiduFaceVerify/GsonUtils.java
  63. 77 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/baiduFaceVerify/HttpUtil.java
  64. 61 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ColumnSetting.java
  65. 48 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelError.java
  66. 98 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelErrorType.java
  67. 34 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelProperty.java
  68. 100 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelReader.java
  69. 7 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelReaderHandle.java
  70. 77 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelUtils.java
  71. 93 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelWriter.java
  72. 44 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExportUtils.java
  73. 19 0
      examcloud-core-oe-admin-dao/pom.xml
  74. 24 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamAuditRepo.java
  75. 115 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureQueueRepo.java
  76. 32 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureRepo.java
  77. 43 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamFaceLivenessVerifyRepo.java
  78. 44 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamFileAnswerTempRepo.java
  79. 98 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordDataRepo.java
  80. 18 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordDataSyncRepo.java
  81. 47 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordForMarkingRepo.java
  82. 20 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordPaperStructRepo.java
  83. 29 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordQuestionsRepo.java
  84. 24 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamScoreNoticeQueueRepo.java
  85. 36 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamScoreObtainQueueRepo.java
  86. 25 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamScorePushQueueRepo.java
  87. 30 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamScoreRepo.java
  88. 20 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamStudentFinalScoreRepo.java
  89. 101 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamStudentRepo.java
  90. 17 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamSyncCaptureRepo.java
  91. 38 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamingRecordRepo.java
  92. 67 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/FaceBiopsyItemRepo.java
  93. 21 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/FaceBiopsyItemStepRepo.java
  94. 30 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/FaceBiopsyRepo.java
  95. 26 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/HandInExamRecordRepo.java
  96. 23 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/IllegallyTypeRepo.java
  97. 13 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/OfflineFileRepo.java
  98. 20 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/OrgScoreHandleRepo.java
  99. 34 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/UniqueRuleHolder.java
  100. 117 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamAuditEntity.java

+ 12 - 0
.gitignore

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

+ 19 - 0
examcloud-core-oe-admin-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-admin</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-core-oe-admin-api-provider</artifactId>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-core-oe-admin-service</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+    </dependencies>
+
+</project>

+ 61 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamAuditBeanConvert.java

@@ -0,0 +1,61 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-29 10:28:31.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.DisciplineType;
+
+/**
+ * 类信息转换
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class ExamAuditBeanConvert implements JsonSerializable {
+	
+	private Long examRecordDataId;
+	
+	private String disciplineDetail;
+	
+	private Boolean isPass;
+	
+	private DisciplineType disciplineType;
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+	public String getDisciplineDetail() {
+		return disciplineDetail;
+	}
+
+	public void setDisciplineDetail(String disciplineDetail) {
+		this.disciplineDetail = disciplineDetail;
+	}
+
+	public Boolean getIsPass() {
+		return isPass;
+	}
+
+	public void setIsPass(Boolean isPass) {
+		this.isPass = isPass;
+	}
+
+	public DisciplineType getDisciplineType() {
+		return disciplineType;
+	}
+
+	public void setDisciplineType(DisciplineType disciplineType) {
+		this.disciplineType = disciplineType;
+	}
+	
+}

+ 20 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamCaptureBeanConvert.java

@@ -0,0 +1,20 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 17:26:26.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 类信息转换
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class ExamCaptureBeanConvert implements JsonSerializable {
+
+}

+ 61 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamRecordDataBeanConvert.java

@@ -0,0 +1,61 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-29 10:49:29.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+
+/**
+ * 类信息转换
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class ExamRecordDataBeanConvert implements JsonSerializable {
+    /**
+     * 将数据库实体转换为业务实体
+     * @param erdEntity
+     * @return
+     */
+    public static ExamRecordDataDomain copyFrom(ExamRecordDataEntity erdEntity){
+        if (erdEntity==null){
+            return null;
+        }
+
+        ExamRecordDataDomain result = new ExamRecordDataDomain();
+        result.setAllObjectivePaper(erdEntity.getIsAllObjectivePaper());
+        result.setAudit(erdEntity.getIsAudit());
+        result.setBaiduFaceLivenessSuccessPercent(erdEntity.getBaiduFaceLivenessSuccessPercent());
+        result.setCleanTime(erdEntity.getCleanTime());
+        result.setContinued(erdEntity.getIsContinued());
+        result.setContinuedCount(erdEntity.getContinuedCount());
+        result.setCreationTime(erdEntity.getCreationTime());
+        result.setEndTime(erdEntity.getEndTime());
+        result.setExamOrder(erdEntity.getExamOrder());
+        result.setExamOrder(erdEntity.getExamOrder());
+        result.setExamRecordQuestionsId(erdEntity.getExamRecordQuestionsId());
+        result.setExamRecordStatus(erdEntity.getExamRecordStatus());
+        result.setExceed(erdEntity.getIsExceed());
+        result.setFaceFailedCount(erdEntity.getFaceFailedCount());
+        result.setFaceLandmarkVal(erdEntity.getFaceLandmarkVal());
+        result.setFaceStrangerCount(erdEntity.getFaceStrangerCount());
+        result.setFaceSuccessCount(erdEntity.getFaceSuccessCount());
+        result.setFaceSuccessPercent(erdEntity.getFaceSuccessPercent());
+        result.setFaceTotalCount(erdEntity.getFaceTotalCount());
+        result.setFaceVerifyResult(erdEntity.getFaceVerifyResult());
+        result.setId(erdEntity.getId());
+        result.setIllegality(erdEntity.getIsIllegality());
+        result.setReexamine(erdEntity.getIsReexamine());
+        result.setStartTime(erdEntity.getStartTime());
+        result.setUpdateTime(erdEntity.getUpdateTime());
+        result.setUsedExamTime(erdEntity.getUsedExamTime());
+        result.setWarn(erdEntity.getIsWarn());
+        return result;
+    }
+}

+ 350 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamRecordDataDomain.java

@@ -0,0 +1,350 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.IsSuccess;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import java.util.Date;
+
+/**
+ * @Description 考试记录Data实体
+ * @Author lideyin
+ * @Date 2019/8/20 18:40
+ * @Version 1.0
+ */
+public class ExamRecordDataDomain implements JsonSerializable {
+    private static final long serialVersionUID = 5819718857965384297L;
+    private Long id;
+
+    /**
+     * 考试作答记录id
+     */
+    private String examRecordQuestionsId;
+    /**
+     * 考试记录状态(考试中,考试结束,考试过期,考试作废)
+     */
+    @Enumerated(EnumType.STRING)
+    private ExamRecordStatus examRecordStatus;
+    /**
+     * 考试开始时间
+     */
+    private Date startTime;
+    /**
+     * 考试结束时间
+     */
+    private Date endTime;
+    /**
+     * 考试被清理时间
+     */
+    private Date cleanTime;
+    /**
+     * 是否异常数据
+     */
+    private Boolean isWarn;
+    /**
+     * 是否被审核过
+     */
+    private Boolean isAudit;
+    /**
+     * 是否违纪
+     */
+    private Boolean isIllegality;
+
+    /**
+     * 考试时长
+     */
+    private Long usedExamTime;
+    /**
+     * 第几次考试
+     */
+    private Integer examOrder;
+    /**
+     * 是否为重考
+     */
+    private Boolean isReexamine;
+    /**
+     * 是否断点续考
+     */
+    private Boolean isContinued;
+    /**
+     * 是否是全客观题卷  1:是   0:否
+     */
+    private Boolean isAllObjectivePaper;
+    /**
+     * 断点续考次数
+     */
+    private Integer continuedCount;
+    /**
+     * 是否达到最大断点限制
+     */
+    private Boolean isExceed;
+    /**
+     * 抓拍比对成功次数
+     */
+    private Integer faceSuccessCount;
+    /**
+     * 抓拍比对失败次数
+     */
+    private Integer faceFailedCount;
+    /**
+     * 抓拍存在陌生人的次数
+     */
+    private Integer faceStrangerCount;
+    /**
+     * 抓拍比对总次数
+     */
+    private Integer faceTotalCount;
+    /**
+     * 抓拍比对成功比率
+     */
+    private Double faceSuccessPercent;
+    /**
+     * @see cn.com.qmth.examcloud.core.oe.admin.dao.enums.IsSuccess
+     * 活体检测结果
+     */
+    @Enumerated(EnumType.STRING)
+    private IsSuccess faceVerifyResult;
+
+    /**
+     * 百度人脸活体检测通过率
+     */
+    private Double baiduFaceLivenessSuccessPercent;
+    /**
+     * 人脸五官坐标比对值
+     */
+    private Double faceLandmarkVal;
+
+    /**
+     * 考试记录对象//TODO 合并后,此实体不再需要,需要前台一起修改,暂时先这么处理
+     */
+    private ExamRecordDataEntity examRecord;
+
+    /**
+     * 修改时间
+     */
+    private Date updateTime;
+
+    /**
+     * 创建时间
+     */
+    private Date creationTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getExamRecordQuestionsId() {
+        return examRecordQuestionsId;
+    }
+
+    public void setExamRecordQuestionsId(String examRecordQuestionsId) {
+        this.examRecordQuestionsId = examRecordQuestionsId;
+    }
+
+    public ExamRecordStatus getExamRecordStatus() {
+        return examRecordStatus;
+    }
+
+    public void setExamRecordStatus(ExamRecordStatus examRecordStatus) {
+        this.examRecordStatus = examRecordStatus;
+    }
+
+    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 Date getCleanTime() {
+        return cleanTime;
+    }
+
+    public void setCleanTime(Date cleanTime) {
+        this.cleanTime = cleanTime;
+    }
+
+    public Boolean getWarn() {
+        return isWarn;
+    }
+
+    public void setWarn(Boolean warn) {
+        isWarn = warn;
+    }
+
+    public Boolean getAudit() {
+        return isAudit;
+    }
+
+    public void setAudit(Boolean audit) {
+        isAudit = audit;
+    }
+
+    public Boolean getIllegality() {
+        return isIllegality;
+    }
+
+    public void setIllegality(Boolean illegality) {
+        isIllegality = illegality;
+    }
+
+    public Long getUsedExamTime() {
+        return usedExamTime;
+    }
+
+    public void setUsedExamTime(Long usedExamTime) {
+        this.usedExamTime = usedExamTime;
+    }
+
+    public Integer getExamOrder() {
+        return examOrder;
+    }
+
+    public void setExamOrder(Integer examOrder) {
+        this.examOrder = examOrder;
+    }
+
+    public Boolean getReexamine() {
+        return isReexamine;
+    }
+
+    public void setReexamine(Boolean reexamine) {
+        isReexamine = reexamine;
+    }
+
+    public Boolean getContinued() {
+        return isContinued;
+    }
+
+    public void setContinued(Boolean continued) {
+        isContinued = continued;
+    }
+
+    public Boolean getAllObjectivePaper() {
+        return isAllObjectivePaper;
+    }
+
+    public void setAllObjectivePaper(Boolean allObjectivePaper) {
+        isAllObjectivePaper = allObjectivePaper;
+    }
+
+    public Integer getContinuedCount() {
+        return continuedCount;
+    }
+
+    public void setContinuedCount(Integer continuedCount) {
+        this.continuedCount = continuedCount;
+    }
+
+    public Boolean getExceed() {
+        return isExceed;
+    }
+
+    public void setExceed(Boolean exceed) {
+        isExceed = exceed;
+    }
+
+    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 Double getFaceSuccessPercent() {
+        return faceSuccessPercent;
+    }
+
+    public void setFaceSuccessPercent(Double faceSuccessPercent) {
+        this.faceSuccessPercent = faceSuccessPercent;
+    }
+
+    public IsSuccess getFaceVerifyResult() {
+        return faceVerifyResult;
+    }
+
+    public void setFaceVerifyResult(IsSuccess faceVerifyResult) {
+        this.faceVerifyResult = faceVerifyResult;
+    }
+
+    public Double getBaiduFaceLivenessSuccessPercent() {
+        return baiduFaceLivenessSuccessPercent;
+    }
+
+    public void setBaiduFaceLivenessSuccessPercent(Double baiduFaceLivenessSuccessPercent) {
+        this.baiduFaceLivenessSuccessPercent = baiduFaceLivenessSuccessPercent;
+    }
+
+    public Double getFaceLandmarkVal() {
+        return faceLandmarkVal;
+    }
+
+    public void setFaceLandmarkVal(Double faceLandmarkVal) {
+        this.faceLandmarkVal = faceLandmarkVal;
+    }
+
+    public ExamRecordDataEntity getExamRecord() {
+        return examRecord;
+    }
+
+    public void setExamRecord(ExamRecordDataEntity examRecord) {
+        this.examRecord = examRecord;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public void setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+    }
+}

+ 20 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamScoreBeanConvert.java

@@ -0,0 +1,20 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 10:23:05.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 类信息转换
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class ExamScoreBeanConvert implements JsonSerializable {
+
+}

+ 114 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamStudentBeanConvert.java

@@ -0,0 +1,114 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-17 11:36:14.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.ExamStudentSyncAllDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.ExamStudentSyncPartDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentPartInfo;
+import cn.com.qmth.examcloud.global.api.request.SyncExamStudentReq;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 类信息转换
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public class ExamStudentBeanConvert implements JsonSerializable {
+
+    public static ExamStudentInfo of(ExamStudentSyncAllDataBean bean) {
+        if (bean == null) {
+            return null;
+        }
+        ExamStudentInfo info = new ExamStudentInfo();
+        info.setExamStudentId(bean.getExamStudentId());
+        info.setExamId(bean.getExamId());
+        info.setStudentId(bean.getStudentId());
+        info.setStudentCode(bean.getStudentCode());
+        info.setStudentName(bean.getStudentName());
+        info.setIdentityNumber(bean.getIdentityNumber());
+        info.setCourseId(bean.getCourseId());
+        info.setCourseCode(bean.getCourseCode());
+        info.setCourseName(bean.getCourseName());
+        info.setCourseLevel(bean.getCourseLevel());
+        info.setOrgId(bean.getOrgId());
+        info.setOrgCode(bean.getOrgCode());
+        info.setOrgName(bean.getOrgName());
+        info.setRootOrgId(bean.getRootOrgId());
+        info.setSpecialtyCode(bean.getSpecialtyCode());
+        info.setSpecialtyName(bean.getSpecialtyName());
+        info.setPaperType(bean.getPaperType());
+        info.setInfoCollector(bean.getInfoCollector());
+        info.setFinished(bean.getFinished());
+        info.setUsedNum(0);
+        info.setExtraNum(0);
+        return info;
+    }
+
+    public static ExamStudentPartInfo of(ExamStudentSyncPartDataBean bean) {
+        if (bean == null) {
+            return null;
+        }
+        ExamStudentPartInfo info = new ExamStudentPartInfo();
+        info.setStudentId(bean.getStudentId());
+        info.setStudentCode(bean.getStudentCode());
+        info.setStudentName(bean.getStudentName());
+        info.setIdentityNumber(bean.getIdentityNumber());
+        return info;
+    }
+
+    public static List<ExamStudentInfo> of(ExamStudentSyncAllDataReq req) {
+        if (req == null) {
+            return Lists.newArrayList();
+        }
+        List<ExamStudentSyncAllDataBean> beans = req.getExamStudents();
+        return beans.stream().map(bean -> of(bean)).collect(Collectors.toList());
+    }
+
+    public static List<ExamStudentPartInfo> of(ExamStudentSyncPartDataReq req) {
+        if (req == null) {
+            return Lists.newArrayList();
+        }
+        List<ExamStudentSyncPartDataBean> beans = req.getExamStudents();
+        return beans.stream().map(bean -> of(bean)).collect(Collectors.toList());
+    }
+
+    public static ExamStudentInfo of(SyncExamStudentReq req) {
+        ExamStudentInfo info = new ExamStudentInfo();
+        info.setExamStudentId(req.getId());
+        info.setExamId(req.getExamId());
+        info.setStudentId(req.getStudentId());
+        info.setStudentCode(req.getStudentCode());
+        info.setStudentName(req.getStudentName());
+        info.setIdentityNumber(req.getIdentityNumber());
+        info.setCourseId(req.getCourseId());
+        info.setCourseCode(req.getCourseCode());
+        info.setCourseName(req.getCourseName());
+        info.setCourseLevel(req.getCourseLevel());
+        info.setOrgId(req.getOrgId());
+        info.setOrgCode(req.getOrgCode());
+        info.setOrgName(req.getOrgName());
+        info.setRootOrgId(req.getRootOrgId());
+        info.setSpecialtyName(req.getSpecialtyName());
+        info.setPaperType(req.getPaperType());
+        info.setInfoCollector(req.getInfoCollector());
+        info.setGrade(req.getGrade());
+        info.setExamSiteName(req.getExamSite());
+        info.setFinished(false);
+        info.setExtraNum(0);
+        info.setUsedNum(0);
+        info.setEnable(req.getEnable());
+        return info;
+    }
+
+}

+ 96 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/IllegallyTypeDomain.java

@@ -0,0 +1,96 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+import java.util.Date;
+
+public class IllegallyTypeDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = 239263456816448160L;
+
+	private Long id;
+
+	private String code;
+
+	private String name;
+
+	private Long rootOrgId;
+
+	private Boolean enable;
+
+	/**
+	 * 排序号
+	 */
+	private Integer sortNo;
+
+	/**
+	 * 数据分类
+	 */
+	private String dataCategory;
+
+	private Date updateTime;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public Integer getSortNo() {
+		return sortNo;
+	}
+
+	public void setSortNo(Integer sortNo) {
+		this.sortNo = sortNo;
+	}
+
+	public String getDataCategory() {
+		return dataCategory;
+	}
+
+	public void setDataCategory(String dataCategory) {
+		this.dataCategory = dataCategory;
+	}
+
+	public Date getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Date updateTime) {
+		this.updateTime = updateTime;
+	}
+}

+ 55 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/BaseInfoController.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.RestController;
+
+import cn.com.qmth.examcloud.core.basic.api.OrgCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.OrgBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetOrgsReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgsResp;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+
+/**
+ * 
+ * @author chenken
+ *
+ */
+@Api(tags = "考试记录审核相关接口")
+@RestController
+@RequestMapping("${$rmp.ctr.oe}/baseInfo")
+public class BaseInfoController extends ControllerSupport {
+	
+	@Autowired
+	private OrgCloudService orgCloudService;
+
+	@GetMapping("/org/list")
+	public List<OrgBean> queryOrg(){
+		List<OrgBean> orgBeanList = new ArrayList<OrgBean>();
+		Long start = 1l;
+        GetOrgsReq req = new GetOrgsReq();
+        req.setRootOrgId(this.getRootOrgId());
+        while(true){
+        	req.setStart(start);
+     		GetOrgsResp getOrgsResp = orgCloudService.getOrgs(req);
+     		List<OrgBean> subOrgBeanList = getOrgsResp.getOrgBeanList();
+     		for(OrgBean orgBean:subOrgBeanList){
+ 				if(orgBean.getEnable()){
+ 					orgBeanList.add(orgBean);
+ 				}
+     		}
+     		if(start.equals(getOrgsResp.getNext())){
+             	break;
+            }else {
+             	start = getOrgsResp.getNext();
+ 			}
+        }
+		return orgBeanList;
+	}
+	
+}

+ 168 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamAuditController.java

@@ -0,0 +1,168 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-27 16:16:29.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.base.Constants;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.JsonMapper;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExportUtils;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.AuditStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.DisciplineType;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.SelectType;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamAuditService;
+import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 考试记录审核相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/27
+ */
+@RestController
+@Api(tags = "考试记录审核相关接口")
+@RequestMapping("${$rmp.ctr.oe}/exam/audit")
+public class ExamAuditController extends ControllerSupport {
+
+    @Autowired
+    private ExamAuditService examAuditService;
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private GainBaseDataService gainBaseDataService;
+
+    @PostMapping("/list")
+    @ApiOperation(value = "查询“监考已审”列表(分页)")
+    public Page<ExamAuditInfo> getExamAuditList(@RequestBody ExamAuditQuery query) {
+        User user = getAccessUser();
+        query.setRootOrgId(user.getRootOrgId());
+        Page<ExamAuditInfo> examAuditList = examAuditService.getExamAuditList(query);
+
+        examAuditList.getContent().forEach(p -> {
+            p.setIdentityNumber(IdentityNumberHelper.conceal(user.getRootOrgId(), p.getIdentityNumber()));
+        });
+        return examAuditList;
+    }
+
+    @PostMapping("/discipline/list")
+    @ApiOperation(value = "查询“违纪名单”列表(分页)")
+    public Page<ExamAuditInfo> getExamAuditUnPassList(@RequestBody ExamAuditQuery query) {
+        query.setStatus(AuditStatus.UN_PASS.name());
+        User user = getAccessUser();
+        query.setRootOrgId(user.getRootOrgId());
+        Page<ExamAuditInfo> examAuditList = examAuditService.getExamAuditList(query);
+
+        examAuditList.getContent().forEach(p -> {
+            p.setIdentityNumber(IdentityNumberHelper.conceal(user.getRootOrgId(), p.getIdentityNumber()));
+        });
+        return examAuditList;
+    }
+
+    @Naked
+    @GetMapping("/discipline/list/export")
+    @ApiOperation(value = "导出“违纪名单”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
+    public void exportExamAuditUnPassList(@RequestParam String query, HttpServletResponse response) throws Exception {
+        ExamAuditQuery newQuery = new JsonMapper().fromJson(query, ExamAuditQuery.class);
+        Check.isNull(newQuery, "请求参数不能为空!");
+        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
+
+        newQuery.setStatus(AuditStatus.UN_PASS.name());
+        newQuery.setSelectType(SelectType.EXPORT);
+        Page<ExamAuditInfo> page = examAuditService.getExamAuditList(newQuery);
+        List<ExamAuditExcel> list = ExamAuditEntityConvert.ofExcel(page);
+        ExportUtils.exportEXCEL("违纪名单列表", ExamAuditExcel.class, list, response);
+    }
+
+    @PostMapping(value = "/single/audit")
+    @ApiOperation(value = "监考待审-单个审核")
+    public void singleAudit(@RequestParam(required = true) Long examRecordDataId,
+                            @RequestParam(required = false) String disciplineDetail,
+                            @RequestParam(required = true) Boolean isPass,
+                            @RequestParam(required = false) DisciplineType disciplineType) {
+        if (examRecordDataId == null) {
+            throw new StatusException("singleAudit-001", "examRecordDataId不能为空");
+        }
+        if (isPass == null) {
+            throw new StatusException("singleAudit-002", "isPass不能为空");
+        }
+        User user = getAccessUser();
+        examAuditService.singleAudit(examRecordDataId, isPass, disciplineDetail, disciplineType, user);
+    }
+
+    @PostMapping(value = "/batch/audit")
+    @ApiOperation(value = "监考待审-批量审核")
+    public void batchAudit(@RequestParam List<Long> examRecordDataIds, @RequestParam Boolean isPass) {
+        if (examRecordDataIds == null || examRecordDataIds.size() == 0) {
+            throw new StatusException("batchAudit-001", "examRecordDataIds不能为空");
+        }
+        if (isPass == null) {
+            throw new StatusException("batchAudit-002", "isPass不能为空");
+        }
+        User user = getAccessUser();
+        examAuditService.batchAudit(examRecordDataIds, isPass, user);
+    }
+
+    @PostMapping(value = "/redoAudit")
+    @ApiOperation(value = "重新审核")
+    public void redoAudit(@RequestBody RedoAuditInfo redoAuditInfo) {
+        User user = getAccessUser();
+        if (redoAuditInfo.getExamRecordDataIds() == null || redoAuditInfo.getExamRecordDataIds().size() == 0) {
+            throw new StatusException("redoAudit-001", "examRecordDataIds不能为空");
+        }
+        if (redoAuditInfo.getIsPass() == null) {
+            throw new StatusException("redoAudit-002", "isPass不能为空");
+        }
+        if (!redoAuditInfo.getIsPass() && StringUtils.isBlank(redoAuditInfo.getDisciplineType())) {
+            throw new StatusException("redoAudit-003", "审核为不通过时违纪类型不能为空");
+        }
+        Set<Long> examIds = new HashSet<Long>();
+        for (Long examRecordDataId : redoAuditInfo.getExamRecordDataIds()) {
+            ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+            examIds.add(examRecordData.getExamId().longValue());
+        }
+        if (examIds.size() > 1) {
+            throw new StatusException("redoAudit-004", "考试ID中包含了多个批次");
+        }
+        long examId = examIds.iterator().next();
+
+        //原需求:已经生成了评卷任务,不能重新审为不通过,但是可以审核为已通过
+        //20191220,需求临时调整:已经生成了评卷任务,均不可重审
+        String markingTaskBuilt =
+                ExamCacheTransferHelper.getDefaultCachedExamProperty(examId, ExamProperties.MARKING_TASK_BUILDED.name()).getValue();
+        if (StringUtils.isNotBlank(markingTaskBuilt) && Constants.isTrue.equals(markingTaskBuilt)) {
+//            if (!redoAuditInfo.getIsPass()) {
+            throw new StatusException("redoAudit-005", "该考试评卷任务已生成,不允许重审");
+//            }
+        }
+        examAuditService.redoAudit(redoAuditInfo, user);
+    }
+
+}

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

@@ -0,0 +1,57 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 17:26:51.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+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.core.oe.admin.service.ExamCaptureService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examcapture.ExamCaptureAuditInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examcapture.ExamCaptureInfo;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 考试抓拍检测相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/27
+ */
+@RestController
+@Api(tags = "考试抓拍检测相关接口")
+@RequestMapping("${$rmp.ctr.oe}/exam/capture")
+public class ExamCaptureController extends ControllerSupport {
+    @Autowired
+    private ExamCaptureService examCaptureService;
+
+    @PostMapping("/audit/detail")
+    @ApiOperation(value = "抓拍详情-审核详细信息")
+    public ExamCaptureAuditInfo getAuditDetail(@RequestParam Long examRecordDataId) {
+        ExamCaptureAuditInfo examCaptureAuditDetail = examCaptureService.getExamCaptureAuditDetail(examRecordDataId);
+
+        if (null != examCaptureAuditDetail) {
+            examCaptureAuditDetail.setIdentityNumber(IdentityNumberHelper.conceal(getRootOrgId(),
+                    examCaptureAuditDetail.getIdentityNumber()));
+        }
+        return examCaptureAuditDetail;
+    }
+
+    @PostMapping("/list")
+    @ApiOperation(value = "抓拍详情-抓拍记录列表")
+    public List<ExamCaptureInfo> list(@RequestParam Long examRecordDataId) {
+        return examCaptureService.getExamCaptureList(examRecordDataId);
+    }
+
+}

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

@@ -0,0 +1,99 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+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.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.OnHandExamInfo;
+import cn.com.qmth.examcloud.core.oe.student.api.ExamRecordDataCloudService;
+import cn.com.qmth.examcloud.core.oe.student.api.request.GetExamRecordNumReq;
+import cn.com.qmth.examcloud.core.oe.student.api.response.GetExamRecordNumResp;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Date;
+import java.util.List;
+
+@Api(tags = "在线考试控制")
+@RestController
+@RequestMapping("${$rmp.ctr.oe}/examControl")
+public class ExamControlController extends ControllerSupport {
+
+    @Autowired
+    private ExamStudentService examStudentService;
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    @Autowired
+    private ExamRecordDataCloudService examRecordDataCloudService;
+
+    /**
+     * 获取在线考试待考列表
+     *
+     * @return
+     */
+    @ApiOperation(value = "获取在线考试待考列表")
+    @GetMapping("/queryExamList")
+    public List<OnHandExamInfo> queryExamList() {
+        User user = getAccessUser();
+        return examStudentService.queryOnlineExamList(user.getUserId(), ExamType.ONLINE);
+    }
+
+    /**
+     * 获取在线作业待考列表
+     *
+     * @return
+     */
+    @ApiOperation(value = "获取在线作业待考列表")
+    @GetMapping("/queryHomeworkList")
+    public List<OnHandExamInfo> queryHomeworkList() {
+        User user = getAccessUser();
+        return examStudentService.queryOnlineExamList(user.getUserId(),ExamType.ONLINE_HOMEWORK);
+    }
+
+    /**
+     * 获取考试同步完成百分比
+     *
+     * @return
+     */
+    @ApiOperation(value = "获取在线考试待考列表")
+    @GetMapping("/getExamSyncPercentage")
+    public long getExamSyncPercentage(@RequestParam("examId") Long examId) {
+        ExamSettingsCacheBean cachedExam =
+                ExamCacheTransferHelper.getDefaultCachedExam(examId);
+
+        Date now = new Date();
+        if (now.compareTo(cachedExam.getEndTime()) < 0) {
+            throw new StatusException("100001", "考试未结束,请考试结束后再试");
+        }
+
+        //已同步的考试记录数量
+        ExamRecordDataEntity query = new ExamRecordDataEntity();
+        query.setExamId(examId);
+        Example<ExamRecordDataEntity> queryExample = Example.of(query);
+        long syncedNum = examRecordDataRepo.count(queryExample);
+
+        //合计考试记录数量
+        GetExamRecordNumReq req = new GetExamRecordNumReq();
+        req.setExamId(examId);
+        GetExamRecordNumResp resp = examRecordDataCloudService.getExamRecordNum(req);
+        long totalNum = resp.getNum();
+
+        if (totalNum == 0) {
+            return 0;
+        }
+
+        return ((100 * syncedNum) / totalNum) > 100 ? 100 : ((100 * syncedNum) / totalNum);
+    }
+
+}

+ 171 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordController.java

@@ -0,0 +1,171 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-27 14:52:17.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+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.enums.ExamType;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.JsonMapper;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExportUtils;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordForMarkingRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordForMarkingEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamCaptureService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamStudentQuestionScoreInfo;
+import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 考试记录相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/27
+ */
+@RestController
+@Api(tags = "考试记录相关接口")
+@RequestMapping("${$rmp.ctr.oe}/exam/record")
+public class ExamRecordController extends ControllerSupport {
+    @Autowired
+    private ExamRecordService examRecordService;
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    @Autowired
+    private ExamCaptureService examCaptureService;
+    
+    @Autowired
+    private ExamRecordForMarkingRepo examRecordForMarkingRepo;
+
+
+    @PostMapping("/waiting/audit/list")
+    @ApiOperation(value = "查询“监考待审”列表(分页)")
+    public Page<ExamRecordInfo> getExamRecordWaitingAuditList(@RequestBody ExamRecordQuery query) {
+        Page<ExamRecordInfo> examRecordWaitingAuditList = examRecordService.getExamRecordWaitingAuditList(query);
+        examRecordWaitingAuditList.getContent().forEach(p -> {
+            p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
+        });
+        List<ExamRecordInfo> examRecordInfoList=examRecordWaitingAuditList.getContent();
+        if (examRecordInfoList != null && examRecordInfoList.size() > 0) {
+            String examType = examRecordInfoList.get(0).getExamType();
+
+            for (ExamRecordInfo examRecordInfo : examRecordInfoList) {
+                if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
+                    examRecordInfo.setVirtualCameraNames(examCaptureService.getVirtualCameraNames(examRecordInfo.getDataId()));
+                }
+            }
+        }
+        return examRecordWaitingAuditList;
+    }
+
+    @PostMapping("/detail/check")
+    @ApiOperation(value = "查询“考试明细” 是否存在待审数据")
+    public Long existsWarnExamRecordDetail(@RequestBody ExamRecordQuery query) {
+        return examRecordService.existsWarnExamRecordDetail(query);
+    }
+
+    @PostMapping("/detail/list")
+    @ApiOperation(value = "查询“考试明细”列表(分页)")
+    public Page<ExamRecordInfo> getExamRecordDetailList(@RequestBody ExamRecordQuery query) {
+        Page<ExamRecordInfo> examRecordInfoPage = examRecordService.getExamRecordDetailListForPage(query);
+        List<ExamRecordInfo> examRecordInfoList = examRecordInfoPage.getContent();
+        if (examRecordInfoList != null && examRecordInfoList.size() > 0) {
+            String examType = examRecordInfoList.get(0).getExamType();
+
+            for (ExamRecordInfo examRecordInfo : examRecordInfoList) {
+                examRecordInfo.setIdentityNumber((IdentityNumberHelper.conceal(examRecordInfo.getRootOrgId(),
+                        examRecordInfo.getIdentityNumber())));
+
+                if (ExamType.OFFLINE.name() == examType) {
+                    long examRecordDataId = examRecordInfo.getDataId();
+                    ExamRecordForMarkingEntity examRecordForMarkingEntity = examRecordForMarkingRepo.findByExamRecordDataId(examRecordDataId);
+                    if (examRecordForMarkingEntity != null) {
+                        examRecordInfo.setOfflineFileUrl(
+                                FileStorageUtil.realPath(examRecordForMarkingEntity.getOfflineFileUrl()));
+                    }
+                }
+                if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
+                    examRecordInfo.setVirtualCameraNames(examCaptureService.getVirtualCameraNames(examRecordInfo.getDataId()));
+                }
+            }
+        }
+        return examRecordInfoPage;
+    }
+
+    @Naked
+    @GetMapping("/detail/list/export")
+    @ApiOperation(value = "导出“考试明细”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
+    public void exportExamRecordDetailList(@RequestParam String query, HttpServletResponse response) throws Exception {
+        ExamRecordQuery newQuery = new JsonMapper().fromJson(query, ExamRecordQuery.class);
+        Check.isNull(newQuery, "请求参数不能为空!");
+        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
+        List<ExamRecordInfo> examRecordInfoList = examRecordService.getExamRecordDetailList(newQuery);
+        if (examRecordInfoList != null && examRecordInfoList.size() > 0) {
+            String examType = examRecordInfoList.get(0).getExamType();
+
+            for (ExamRecordInfo examRecordInfo : examRecordInfoList) {
+                if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
+                    examRecordInfo.setVirtualCameraNames(examCaptureService.getVirtualCameraNames(examRecordInfo.getDataId()));
+                }
+            }
+        }
+        ExportUtils.exportEXCEL("考试明细列表", ExamRecordInfo.class, examRecordInfoList, response);
+    }
+
+    @PostMapping(value = "/refresh/capture/statistic")
+    @ApiOperation(value = "监考待审-重新统计", notes = "根据人脸识别阀值重新计算数据")
+    public ResponseEntity<String> refreshCaptureStatistic(@RequestParam Long examId) {
+        //todo
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @PostMapping(value = "/clean/capture/statistic")
+    @ApiOperation(value = "监考待审-清理数据")
+    public ResponseEntity<String> cleanCaptureStatistic() {
+        //todo
+        //examRecordService.cleanCaptureStatistic();
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @GetMapping("/select/byExamStudentId")
+    @ApiOperation(value = "根据考生ID查询考试记录")
+    public List<ExamRecordDataEntity> getExamRecordByExamStudentId(@RequestParam Long examStudentId) {
+        List<ExamRecordDataEntity> examRecordEntityList = examRecordDataRepo.findByExamStudentId(examStudentId);
+        return examRecordEntityList;
+    }
+
+    @GetMapping("/detail/examStudentQuestionScoreList")
+    @ApiOperation(value = "导出考生作答分数明细列表(Excel)", notes = "参数examId和examId必传")
+    public void getExamStudentQuestionScoreList(@RequestParam Long examId, @RequestParam String courseCode, HttpServletResponse response) throws Exception {
+        Check.isNull(examId, "考试id不能为空!");
+        Check.isBlank(courseCode, "课程代码不能空!");
+        List<ExamStudentQuestionScoreInfo> examRecordInfoList = examRecordService.getExamStudentQuestionScoreList(examId, courseCode);
+        ExportUtils.exportEXCEL("考试明细列表", ExamStudentQuestionScoreInfo.class, examRecordInfoList, response);
+    }
+}

+ 57 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordDataController.java

@@ -0,0 +1,57 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordDataBeanConvert;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordDataDomain;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataSyncService;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import io.swagger.annotations.Api;
+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 java.util.ArrayList;
+import java.util.List;
+
+
+@RestController
+@Api(tags = "考试记录相关接口")
+@RequestMapping("${$rmp.ctr.oe}/exam/record/data")
+public class ExamRecordDataController {
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    
+    @Autowired
+    private ExamRecordDataSyncService examRecordDataSyncService;
+
+    @GetMapping("/findExamRecordDataEntity")
+    public ExamRecordDataDomain findExamRecordDataEntity(@RequestParam Long examRecordDataId,@RequestParam(required = false) String fromCache) {
+        if(fromCache!=null) {
+            examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
+            if(examRecordDataId==null) {
+                throw new StatusException("1001", "未找到数据");
+            }
+        }
+        ExamRecordDataEntity examRecordDataEntity = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+        ExamRecordDataDomain domain = ExamRecordDataBeanConvert.copyFrom(examRecordDataEntity);
+        domain.setExamRecord(examRecordDataEntity);
+        return domain;
+    }
+
+    @GetMapping("/findByExamStudentId")
+    public List<ExamRecordDataDomain> findByExamStudentId(@RequestParam Long examStudentId) {
+        List<ExamRecordDataEntity> examRecordEntities = examRecordDataRepo.findByExamStudentId(examStudentId);
+        List<ExamRecordDataDomain> examRecordDataList = new ArrayList<ExamRecordDataDomain>();
+        for (ExamRecordDataEntity examRecordData : examRecordEntities) {
+            ExamRecordDataDomain domain = ExamRecordDataBeanConvert.copyFrom(examRecordData);
+            domain.setExamRecord(examRecordData);
+            examRecordDataList.add(domain);
+        }
+        return examRecordDataList;
+    }
+}

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

@@ -0,0 +1,49 @@
+package cn.com.qmth.examcloud.core.oe.admin.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.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordPaperStructEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataSyncService;
+import cn.com.qmth.examcloud.core.oe.admin.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("${$rmp.ctr.oe}/examRecordPaperStruct")
+public class ExamRecordPaperStructController extends ControllerSupport{
+
+	@Autowired
+	private ExamRecordPaperStructService examRecordPaperStructService;
+	
+    @Autowired
+    private ExamRecordDataSyncService examRecordDataSyncService;
+	
+	@ApiOperation(value = "获取考试记录试卷结构")
+	@GetMapping("/getExamRecordPaperStruct")
+	public ExamRecordPaperStructEntity getExamRecordPaperStruct(@RequestParam Long examRecordDataId,@RequestParam(required = false) String fromCache){
+		Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+		if(fromCache!=null) {
+            examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
+            if(examRecordDataId==null) {
+                throw new StatusException("1001", "未找到数据");
+            }
+        }
+		return examRecordPaperStructService.getExamRecordPaperStruct(examRecordDataId);
+	}
+	
+}

+ 55 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordQuestionsController.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
+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.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordQuestionsRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordQuestionsEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataSyncService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年10月12日 上午9:48:47
+ * @company 	QMTH
+ * @description ExamRecordQuestionsController.java
+ */
+@Api(tags = "考试结束后-作答记录接口")
+@RestController
+@RequestMapping("${$rmp.ctr.oe}/examRecordQuestions")
+public class ExamRecordQuestionsController extends ControllerSupport{
+
+	@Autowired
+	private ExamRecordQuestionsRepo examRecordQuestionsRepo;
+
+	@Autowired
+	private ExamRecordQuestionsService examRecordQuestionsService;
+
+    @Autowired
+    private ExamRecordDataSyncService examRecordDataSyncService;
+	
+	@ApiOperation(value = "获取交卷之后的所有试题作答信息")
+	@GetMapping("/getExamRecordQuestions")
+	public ExamRecordQuestionsEntity getExamRecordQuestions(@RequestParam Long examRecordDataId,@RequestParam(required = false) String fromCache){
+		Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+		if(fromCache!=null) {
+            examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
+            if(examRecordDataId==null) {
+                throw new StatusException("1001", "未找到数据");
+            }
+        }
+
+		return examRecordQuestionsService.getExamRecordQuestionsAndFixExamRecordDataIfNecessary(examRecordDataId);
+	}
+	
+}

+ 192 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamScoreController.java

@@ -0,0 +1,192 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:28:03.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.JsonMapper;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExportUtils;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ObjectiveScoreInfo;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.support.examing.ExamBoss;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+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 cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 考试分数相关接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/27
+ */
+@RestController
+@Api(tags = "考试分数相关接口")
+@RequestMapping("${$rmp.ctr.oe}/exam/score")
+public class ExamScoreController extends ControllerSupport {
+    @Autowired
+    private ExamScoreService examScoreService;
+
+    @Autowired
+    private RedisClient redisClient;
+
+    @Autowired
+    private ExamScoreRepo examScoreRepo;
+
+    @PostMapping("/statistic/list")
+    @ApiOperation(value = "查询“成绩统计”列表(分页)")
+    public Page<ExamScoreInfo> getExamAuditList(@RequestBody ExamScoreQuery query) {
+        Page<ExamScoreInfo> examScoreList = examScoreService.getExamScoreList(query);
+
+        examScoreList.getContent().forEach(p->{
+            p.setIdentityNumber(IdentityNumberHelper.conceal(getRootOrgId(),p.getIdentityNumber()));
+        });
+        return examScoreList;
+    }
+
+    @Naked
+    @GetMapping("/statistic/list/export")
+    @ApiOperation(value = "导出“成绩统计”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
+    public void exportExamRecordDetailList(@RequestParam String query, HttpServletResponse response) throws Exception {
+        ExamScoreQuery newQuery = new JsonMapper().fromJson(query, ExamScoreQuery.class);
+        Check.isNull(newQuery, "请求参数不能为空!");
+        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
+        List<ExamScoreInfo> examScoreList = examScoreService.exportExamScoreList(newQuery);
+        ExportUtils.exportEXCEL("成绩统计列表", ExamScoreInfo.class, examScoreList, response);
+    }
+
+    @ApiOperation(value = "根据examStudentId获取客观分信息")
+    @GetMapping("/queryObjectiveScoreList")
+    public List<ObjectiveScoreInfo> queryObjectiveScoreList(@RequestParam Long examStudentId) {
+        Check.isNull(examStudentId, "examStudentId 不能为空");
+
+        List<ObjectiveScoreInfo> resultList = examScoreService.queryObjectiveScoreList(examStudentId);
+
+
+        //如果有未处理完成的考试记录,需要将未处理的考试记录数据添加到列表中
+        String examBossKey = RedisKeyHelper.getBuilder().examBossKey(examStudentId);
+        ExamBoss examBoss = redisClient.get(examBossKey, ExamBoss.class);
+        if (null != examBoss) {
+            //未完全同步的考试记录id
+            List<Long> unSyncedExamRecordDataIds = examBoss.getExamRecordDataIds();
+            if (null != unSyncedExamRecordDataIds && !unSyncedExamRecordDataIds.isEmpty()) {
+                //正序排列
+                unSyncedExamRecordDataIds.sort(Long::compareTo);
+
+                //已考次数
+                Integer examUsedNum = 0;
+
+                for (Long examRecordDataId : unSyncedExamRecordDataIds) {
+
+                    if (!resultList.isEmpty()) {
+                        examUsedNum = resultList.get(resultList.size() - 1).getExamOrder();
+                    }
+
+                    ExamRecordData examRecordData =
+                            redisClient.get(RedisKeyHelper.getBuilder().examRecordDataKey(examRecordDataId), ExamRecordData.class);
+                    if (null == examRecordData) {
+                        throw new StatusException("100001", "考试记录的缓存数据有误");
+                    }
+
+                    ObjectiveScoreInfo cachedObjectiveScoreInfo = getCachedObjectiveScoreInfo(examRecordData);
+                    cachedObjectiveScoreInfo.setExamOrder(
+                            getExamOrder(examRecordData.getExamId(), examRecordData.getStudentId(), examUsedNum));
+
+                    resultList.add(cachedObjectiveScoreInfo);
+                }
+            }
+        }
+
+        return resultList;
+    }
+
+    private ObjectiveScoreInfo getCachedObjectiveScoreInfo(final ExamRecordData examRecordData) {
+
+
+        ObjectiveScoreInfo objectiveScoreInfo = new ObjectiveScoreInfo();
+        objectiveScoreInfo.setExamRecordDataId(examRecordData.getId());
+        objectiveScoreInfo.setStartTime(examRecordData.getStartTime());
+        objectiveScoreInfo.setEndTime(examRecordData.getEndTime());
+
+        //如果考试没有结束,则只能返回部分数据
+        if (!isExamRecordEnded(examRecordData)) {
+            objectiveScoreInfo.setIsExamEnded(false);
+            return objectiveScoreInfo;
+        } else {
+            objectiveScoreInfo.setIsExamEnded(true);
+        }
+
+        if (examRecordData.getIsIllegality() == null || !examRecordData.getIsIllegality()) {
+            if ((null != examRecordData.getIsWarn() && !examRecordData.getIsWarn())
+                    || (null != examRecordData.getIsWarn() && examRecordData.getIsWarn()
+                    && null != examRecordData.getIsAudit() && examRecordData.getIsAudit())) {
+                objectiveScoreInfo.setIsAuditing(true);
+
+                //缓存中的分数是存储在临时考试记录表中的,所以需要从考试记录缓存中取
+                objectiveScoreInfo.setObjectiveScore(examRecordData.getObjectiveScore());
+            } else {
+                objectiveScoreInfo.setIsAuditing(false);
+            }
+
+            objectiveScoreInfo.setIsIllegality(false);
+        } else {
+            objectiveScoreInfo.setIsIllegality(true);
+        }
+
+        return objectiveScoreInfo;
+    }
+
+    private boolean isExamRecordEnded(ExamRecordData examRecordData) {
+        //如果考试记录状态为已处理,则直接返回true.
+        if (examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_END ||
+                examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_OVERDUE ||
+                examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_INVALID) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 计算考试次数
+     *
+     * @param examId      考试id
+     * @param studentId   学生id
+     * @param usedExamNum 已考次数
+     * @return
+     */
+    private Integer getExamOrder(Long examId, Long studentId, Integer usedExamNum) {
+        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        Integer canExamTimes = cachedExam.getExamTimes() == null ? 0 : cachedExam.getExamTimes().intValue();//可考次数
+
+        //超过可考次数,始终为可考次数+1
+        if (usedExamNum > canExamTimes) {
+            return canExamTimes;
+        }
+
+        return usedExamNum;
+
+    }
+
+}

+ 245 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamStudentController.java

@@ -0,0 +1,245 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-16 17:40:53.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.JsonMapper;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExportUtils;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 考生信息接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/16
+ */
+@RestController
+@Api(tags = "考生信息相关接口")
+@RequestMapping("${$rmp.ctr.oe}/exam/student")
+public class ExamStudentController extends ControllerSupport {
+    @Autowired
+    private ExamStudentService examStudentService;
+    @Autowired
+    private GainBaseDataService gainBaseDataService;
+
+    /**
+     * 查询课程本地缓存
+     */
+    private static Map<Long, CourseBean> courseBeanLocalCache;
+
+    private static Timer timer;
+
+    static {
+        courseBeanLocalCache = new ConcurrentHashMap<Long, CourseBean>();
+        timer = new Timer();
+    }
+
+    class CleanCourseBeanCacheTask extends TimerTask {
+        @Override
+        public void run() {
+            courseBeanLocalCache.clear();
+        }
+    }
+
+    public ExamStudentController() {
+        long delay = 10 * 60 * 1000;//10分钟后开始
+        long period = 30 * 60 * 1000;//30分钟清理一次
+        timer.schedule(new CleanCourseBeanCacheTask(), delay, period);
+    }
+
+    @PostMapping("/examScheduling/list")
+    @ApiOperation(value = "查询“考试进度详情”列表(分页)")
+    public Page<ExamStudentInfo> getExamScheduling(@RequestBody ExamStudentQuery query) {
+        Check.isNull(query, "查询参数不能为空!");
+        Check.isNull(query.getExamId(), "考试批次不能为空!");
+        Page<ExamStudentInfo> examStudentListPage = examStudentService.getExamStudentListPage(query);
+
+        examStudentListPage.getContent().forEach(p->{
+            p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(),p.getIdentityNumber()));
+        });
+
+        return examStudentListPage;
+    }
+
+    @GetMapping("/examScheduling/list/export")
+    @ApiOperation(value = "导出“考试进度详情”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
+    public void exportExamStudentList(@RequestParam String query, HttpServletResponse response) throws Exception {
+        ExamStudentQuery newQuery = new JsonMapper().fromJson(query, ExamStudentQuery.class);
+        Check.isNull(newQuery, "请求参数不能为空!");
+        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
+        List<ExamStudentInfo> examStudentInfoList = examStudentService.getExamStudentInfoList(newQuery);
+        List<ExamStudentExcel> list = ExamStudentEntityConvert.ofExcel(examStudentInfoList);
+        ExportUtils.exportEXCEL("考试详情列表", ExamStudentExcel.class, list, response);
+    }
+
+    @PostMapping("/unfinished/list")
+    @ApiOperation(value = "查询“缺考登记”列表(分页)")
+    public Page<ExamStudentInfo> getExamStudentUnFinishedList(@RequestBody ExamStudentQuery query) {
+        Check.isNull(query, "查询参数不能为空!");
+        Check.isNull(query.getExamId(), "考试批次不能为空!");
+        query.setFinished(0);
+        Page<ExamStudentInfo> examStudentListPage = examStudentService.getExamStudentListPage(query);
+        examStudentListPage.getContent().forEach(p->{
+            p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(),p.getIdentityNumber()));
+        });
+        return examStudentListPage;
+    }
+
+    @Naked
+    @GetMapping("/unfinished/list/export")
+    @ApiOperation(value = "导出“缺考登记”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
+    public void exportExamStudentUnFinishedList(@RequestParam String query, HttpServletResponse response) throws Exception {
+        ExamStudentQuery newQuery = new JsonMapper().fromJson(query, ExamStudentQuery.class);
+        Check.isNull(newQuery, "请求参数不能为空!");
+        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
+        newQuery.setFinished(0);//未完成
+        List<ExamStudentInfo> examStudentInfos = examStudentService.getExamStudentInfoList(newQuery);
+        List<ExamStudentUnFinishedExcel> list = ExamStudentEntityConvert.ofUnFinishedExcel(examStudentInfos);
+        ExportUtils.exportEXCEL("缺考登记列表", ExamStudentUnFinishedExcel.class, list, response);
+    }
+
+    @PostMapping("/reexamine/list")
+    @ApiOperation(value = "查询重考考生列表(分页)")
+    public Page<ExamStudentInfo> getReExamineStudentList(@RequestBody ExamStudentQuery query) {
+        Check.isNull(query, "查询参数不能为空!");
+        Check.isNull(query.getExamId(), "考试批次不能为空!");
+        Page<ExamStudentInfo> reExamineStudentList = examStudentService.getReExamineStudentList(query);
+        reExamineStudentList.getContent().forEach(p->{
+            p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(),p.getIdentityNumber()));
+        });
+        return reExamineStudentList;
+    }
+
+    @PostMapping("/reexamine")
+    @ApiOperation(value = "设置重考")
+    public void setReexamine(@RequestParam Long examStudentId) {
+        if (examStudentId == null) {
+            return;
+        }
+        examStudentService.setReexamine(examStudentId);
+    }
+
+    @PostMapping("/statistic/by/finished")
+    @ApiOperation(value = "考试概况-按考试完成进度统计")
+    public ExamStudentFinishedStatistic getExamStudentStatisticByFinished(@RequestParam Long examId) {
+        if (examId == null) {
+            return null;
+        }
+        return examStudentService.getExamStudentStatisticByFinished(examId);
+    }
+
+    @PostMapping("/statistic/by/org")
+    @ApiOperation(value = "考试概况-按学习中心统计")
+    public List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(@RequestParam Long examId, @RequestParam(required = false) Long orgId) {
+        if (examId == null) {
+            return null;
+        }
+        return examStudentService.getExamStudentStatisticByOrg(examId, orgId);
+    }
+
+    @GetMapping("/statistic/by/org/export")
+    @ApiOperation(value = "导出“考试概况-按学习中心统计”列表(Excel)")
+    public void exportExamStudentStatisticByOrg(@RequestParam Long examId,
+                                                @RequestParam(required = false) Long orgId,
+                                          HttpServletResponse response) throws Exception {
+        Check.isNull(examId, "请选择考试批次!");
+        List<ExamStudentOrgStatistic> examStudentStatisticByOrgList = examStudentService.getExamStudentStatisticByOrg(examId, orgId);
+        List<OrgCompleteProgressExcel> resultList =
+                ExamStudentEntityConvert.copyFromExamStudentOrgStatistic(examStudentStatisticByOrgList);
+        ExportUtils.exportEXCEL("学习中心完成进度", OrgCompleteProgressExcel.class, resultList, response);
+    }
+
+    /**
+     * 查询课程
+     *
+     * @param examId
+     * @param orgId
+     * @return
+     */
+    @GetMapping("/findCoursesByExamIdAndOrgId")
+    @ApiOperation(value = "查询课程")
+    public List<CourseBean> findCoursesByExamIdAndOrgId(@RequestParam Long examId, @RequestParam(required = false) Long orgId) {
+        if (examId == null) {
+            return null;
+        }
+        User user = getAccessUser();
+        List<Long> courseIdList = examStudentService.findCoursesFromExamStudent(examId, orgId);
+        List<CourseBean> courseBeanList = new ArrayList<CourseBean>();
+        for (Long courseId : courseIdList) {
+            CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
+            courseBeanList.add(courseBean);
+        }
+        return courseBeanList;
+    }
+
+    /**
+     * 课程完成进度
+     *
+     * @param examId
+     * @param courseId
+     * @return
+     */
+    @GetMapping("/courseProgress/list")
+    @ApiOperation(value = "课程完成进度")
+    public List<CourseProgressInfo> queryCourseProgressInfos(@RequestParam Long examId,
+                                                             @RequestParam(required = false) Long courseId,
+                                                             @RequestParam(required = false) String orderColumn) {
+        if (examId == null) {
+            return null;
+        }
+        List<CourseProgressInfo> courseProgressInfoList = examStudentService.queryCourseProgressInfos(examId, courseId, orderColumn);
+        if (courseProgressInfoList != null && courseProgressInfoList.size() > 0) {
+            for (CourseProgressInfo courseProgressInfo : courseProgressInfoList) {
+                long key = courseProgressInfo.getCourseId();
+                CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(key);
+                courseProgressInfo.setCourseCode(courseBean.getCode());
+                courseProgressInfo.setCourseName(courseBean.getName());
+            }
+        }
+        return courseProgressInfoList;
+    }
+
+    @GetMapping("/courseProgress/list/export")
+    @ApiOperation(value = "导出“课程完成进度”列表(Excel)")
+    public void exportCourseProgressInfos(@RequestParam Long examId,
+                                          @RequestParam(required = false) Long courseId,
+                                          @RequestParam(required = false) String orderColumn,
+                                          HttpServletResponse response) throws Exception {
+        Check.isNull(examId, "请选择考试批次!");
+        List<CourseProgressInfo> courseProgressInfoList = examStudentService.queryCourseProgressInfos(examId, courseId, orderColumn);
+        if (courseProgressInfoList != null && courseProgressInfoList.size() > 0) {
+            for (CourseProgressInfo courseProgressInfo : courseProgressInfoList) {
+                long key = courseProgressInfo.getCourseId();
+                CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(key);
+                courseProgressInfo.setCourseCode(courseBean.getCode());
+                courseProgressInfo.setCourseName(courseBean.getName());
+            }
+        }
+        List<CourseCompleteProgressExcel> resultList =
+                ExamStudentEntityConvert.copyFromCourseProgressInfo(courseProgressInfoList);
+        ExportUtils.exportEXCEL("课程完成进度", CourseCompleteProgressExcel.class, resultList, response);
+    }
+
+}

+ 360 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/IllegallyTypeController.java

@@ -0,0 +1,360 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.helpers.poi.ExcelWriter;
+import cn.com.qmth.examcloud.commons.util.PathUtil;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.IllegallyTypeDomain;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamAuditRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.IllegallyTypeRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamAuditEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.IllegallyTypeEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.IllegallyTypeService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.IllegallyTypeInfo;
+import cn.com.qmth.examcloud.support.enums.DataCategory;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.fileupload.disk.DiskFileItem;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
+
+import javax.persistence.criteria.Predicate;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @Description 违纪类型
+ * @Author lideyin
+ * @Date 2020/2/20 18:56
+ * @Version 1.0
+ */
+@RestController
+@RequestMapping("${$rmp.ctr.oe}/illegallyType")
+public class IllegallyTypeController extends ControllerSupport {
+
+    @Autowired
+    IllegallyTypeService illegallyTypeService;
+
+    @Autowired
+    SystemProperties systemConfig;
+
+    @Autowired
+    IllegallyTypeRepo illegallyTypeRepo;
+
+    @Autowired
+    ExamAuditRepo examAuditRepo;
+
+    private static final String[] EXCEL_HEADER = new String[]{"违纪类型名称", "违纪类型代码", "排序号"};
+
+    /**
+     * 方法注释
+     *
+     * @param curPage
+     * @param pageSize
+     * @param name
+     * @param code
+     * @param enable
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "分页查询违纪类型")
+    @GetMapping("illegallyTypePage/{curPage}/{pageSize}")
+    public PageInfo<IllegallyTypeDomain> illegallyTypePage(@PathVariable Integer curPage,
+                                                           @PathVariable Integer pageSize,
+                                                           @RequestParam(required = false) String name,
+                                                           @RequestParam(required = false) String code,
+                                                           @RequestParam(required = false) Boolean enable) {
+        User accessUser = getAccessUser();
+
+        Specification<IllegallyTypeEntity> specification = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+
+            List<Long> rootOrgIdList = new ArrayList<>();
+            rootOrgIdList.add(accessUser.getRootOrgId());
+            rootOrgIdList.add(-1L);//系统级别的数据默认rootOrgId为-1
+
+            predicates.add(root.get("rootOrgId").in(rootOrgIdList));
+
+            if (StringUtils.isNotBlank(name)) {
+                predicates.add(cb.like(root.get("name"), toSqlSearchPattern(name)));
+            }
+            if (StringUtils.isNotBlank(code)) {
+                predicates.add(cb.like(root.get("code"), toSqlSearchPattern(code)));
+            }
+            if (null != enable) {
+                predicates.add(cb.equal(root.get("enable"), enable));
+            }
+
+
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        PageRequest pageRequest = PageRequest.of(curPage, pageSize,
+                new Sort(Direction.DESC, "id"));
+
+
+        Page<IllegallyTypeEntity> page = illegallyTypeRepo.findAll(specification, pageRequest);
+
+        List<IllegallyTypeEntity> entityList = page.getContent();
+
+        List<IllegallyTypeDomain> domainList = new ArrayList<>();
+
+        for (IllegallyTypeEntity entity : entityList) {
+            IllegallyTypeDomain domain = new IllegallyTypeDomain();
+            domain.setRootOrgId(entity.getRootOrgId());
+            domain.setCode(entity.getCode());
+            domain.setEnable(entity.getEnable());
+            domain.setId(entity.getId());
+            domain.setName(entity.getName());
+            domain.setEnable(entity.getEnable());
+            domain.setSortNo(entity.getSortNo());
+            domain.setDataCategory(entity.getDataCategory() == null ? DataCategory.CUSTOM.name() : entity.getDataCategory().name());
+            domain.setUpdateTime(entity.getUpdateTime());
+            domainList.add(domain);
+        }
+
+        PageInfo<IllegallyTypeDomain> pageInfo = new PageInfo<>();
+        pageInfo.setList(domainList);
+        pageInfo.setTotal(page.getTotalElements());
+
+        return pageInfo;
+    }
+
+    @ApiOperation(value = "查询违纪类型")
+    @GetMapping("queryByNameLike")
+    public List<IllegallyTypeEntity> query(@RequestParam(required = false) String name,
+                                           @RequestParam(required = false) String code,
+                                           @RequestParam(required = false) Boolean enable) {
+
+        User accessUser = getAccessUser();
+        Long rootOrgId = accessUser.getRootOrgId();
+
+        Specification<IllegallyTypeEntity> specification = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+
+            List<Long> rootOrgIdList = new ArrayList<>();
+            rootOrgIdList.add(rootOrgId);
+            rootOrgIdList.add(-1L);//系统级别的数据默认rootOrgId为-1
+
+            predicates.add(root.get("rootOrgId").in(rootOrgIdList));
+
+            if (StringUtils.isNotBlank(name)) {
+                predicates.add(cb.like(root.get("name"), toSqlSearchPattern(name)));
+            }
+            if (StringUtils.isNotBlank(code)) {
+                predicates.add(cb.like(root.get("code"), toSqlSearchPattern(code)));
+            }
+            if (null != enable) {
+                predicates.add(cb.equal(root.get("enable"), enable));
+            }
+
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        // 过载保护
+        long total = illegallyTypeRepo.count(specification);
+        if (total > 1000) {
+            List<IllegallyTypeEntity> list = Lists.newArrayList();
+            return list;
+        }
+
+        List<Sort.Order> orderList=new ArrayList<>();
+        orderList.add(new Sort.Order(Direction.DESC,"dataCategory"));
+        orderList.add(new Sort.Order(Direction.ASC,"id"));
+
+        List<IllegallyTypeEntity> list = illegallyTypeRepo.findAll(specification,
+                new Sort(orderList));
+        return list;
+    }
+
+    /**
+     * 修正
+     *
+     * @param domain
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "新增违纪类型", notes = "新增")
+    @PostMapping
+    @Transactional
+    public Long addIllegallyType(@RequestBody IllegallyTypeDomain domain) {
+        trim(domain, true);
+
+        User accessUser = getAccessUser();
+        Long rootOrgId = accessUser.getRootOrgId();
+
+        String code = domain.getCode();
+        if (StringUtils.isBlank(code)) {
+            throw new StatusException("620001", "code is blank");
+        }
+        IllegallyTypeEntity course = illegallyTypeRepo.findByRootOrgIdAndCode(rootOrgId, code);
+        if (null != course) {
+            throw new StatusException("620002", "违纪类型编码已被占用");
+        }
+
+        IllegallyTypeInfo info = new IllegallyTypeInfo();
+        info.setRootOrgId(rootOrgId);
+        info.setCode(domain.getCode());
+        info.setEnable(true);
+        info.setName(domain.getName());
+        info.setSortNo(domain.getSortNo());
+        info.setDataCategory(info.getDataCategory() == null ? DataCategory.CUSTOM.name() : info.getDataCategory());
+
+        IllegallyTypeEntity saved = illegallyTypeService.saveIllegallyType(info);
+        return saved.getId();
+    }
+
+    /**
+     * 修正
+     *
+     * @param domain
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "修改违纪类型", notes = "修改")
+    @PutMapping
+    @Transactional
+    public Long updateIllegallyType(@RequestBody IllegallyTypeDomain domain) {
+        trim(domain, true);
+
+        User accessUser = getAccessUser();
+        Long rootOrgId = accessUser.getRootOrgId();
+
+        IllegallyTypeInfo info = new IllegallyTypeInfo();
+        info.setId(info.getId());
+        info.setRootOrgId(rootOrgId);
+        info.setCode(domain.getCode());
+        info.setId(domain.getId());
+        info.setName(domain.getName());
+        info.setSortNo(domain.getSortNo());
+        info.setDataCategory(info.getDataCategory() == null ? DataCategory.CUSTOM.name() : info.getDataCategory());
+
+        IllegallyTypeEntity saved = illegallyTypeService.saveIllegallyType(info);
+        return saved.getId();
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param ids
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "删除违纪类型")
+    @DeleteMapping("{ids}")
+    @Transactional
+    public void delete(@PathVariable String ids) {
+        List<Long> typeIds = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+                .collect(Collectors.toList());
+        for (Long typeId : typeIds) {
+            IllegallyTypeEntity one = GlobalHelper.getEntity(illegallyTypeRepo, typeId,
+                    IllegallyTypeEntity.class);
+            if (null == one) {
+                continue;
+            }
+
+            //判断审核表中是否存在当前类型的违纪类型
+            ExamAuditEntity examAudit =
+                    examAuditRepo.findFirstByDisciplineTypeAndCreationTimeGreaterThan(one.getCode(), one.getCreationTime());
+            boolean isIllegallyTypeInUse = (null != examAudit);
+            if (isIllegallyTypeInUse) {
+                throw new StatusException("100001","违纪类型已使用不允许删除");
+            }
+
+            validateRootOrgIsolation(one.getRootOrgId());
+            illegallyTypeRepo.delete(one);
+
+        }
+    }
+
+    @ApiOperation(value = "下载导入模板", notes = "下载导入模板")
+    @GetMapping("importTemplate")
+    public void getDownloadTemplate(HttpServletResponse response) {
+        String resoucePath = PathUtil.getResoucePath("templates/illegallyTypeImportTemplate.xlsx");
+        exportFile("违纪类型导入模板.xlsx", new File(resoucePath));
+    }
+
+    @ApiOperation(value = "导入", notes = "导入")
+    @PostMapping("import")
+    @Transactional
+    public Map<String, Object> importIllegallyType(@RequestParam CommonsMultipartFile file) {
+        DiskFileItem item = (DiskFileItem) file.getFileItem();
+        File storeLocation = item.getStoreLocation();
+        List<Map<String, Object>> failRecords = illegallyTypeService.importIllegallyType(getRootOrgId(),
+                storeLocation);
+        Map<String, Object> map = Maps.newHashMap();
+        map.put("hasError", CollectionUtils.isNotEmpty(failRecords));
+        map.put("failRecords", failRecords);
+        return map;
+    }
+
+    @ApiOperation(value = "导出违纪类型")
+    @GetMapping("export")
+    public void export(@RequestParam(required = false) String name,
+                       @RequestParam(required = false) String code,
+                       @RequestParam(required = false) Boolean enable) {
+        User accessUser = getAccessUser();
+
+        Specification<IllegallyTypeEntity> specification = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+
+            predicates.add(cb.equal(root.get("rootOrgId"), accessUser.getRootOrgId()));
+
+            if (StringUtils.isNotBlank(name)) {
+                predicates.add(cb.like(root.get("name"), toSqlSearchPattern(name)));
+            }
+            if (StringUtils.isNotBlank(code)) {
+                predicates.add(cb.like(root.get("code"), toSqlSearchPattern(code)));
+            }
+            if (null != enable) {
+                predicates.add(cb.equal(root.get("enable"), enable));
+            }
+
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        long count = illegallyTypeRepo.count(specification);
+        if (100000 < count) {
+            throw new StatusException("620200", "数据量过大,无法导出");
+        }
+
+        List<IllegallyTypeEntity> list = illegallyTypeRepo.findAll(specification);
+
+        List<Object[]> datas = Lists.newArrayList();
+
+        for (IllegallyTypeEntity cur : list) {
+            datas.add(new Object[]{cur.getName(), cur.getCode(), cur.getSortNo()});
+        }
+
+        String filePath = systemConfig.getTempDataDir() + File.separator
+                + System.currentTimeMillis() + ".xlsx";
+        File file = new File(filePath);
+
+        ExcelWriter.write(EXCEL_HEADER, new Class[]{String.class, String.class}, datas,
+                new File(filePath));
+
+        exportFile("违纪类型列表-" + getRootOrgId() + ".xlsx", file);
+
+        FileUtils.deleteQuietly(file);
+    }
+}

+ 144 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/OfflineExamController.java

@@ -0,0 +1,144 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.controller;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+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.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.OfflineExamService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.OfflineExamCourseInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+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 离线考试Controller
+ */
+@Api(tags = "离线考试控制")
+@RestController
+@RequestMapping("${$rmp.ctr.oe}/offlineExam")
+public class OfflineExamController extends ControllerSupport{
+
+	@Autowired
+	private OfflineExamService offlineExamService;
+
+	@Autowired
+	private ExamRecordDataRepo examRecordDataRepo;
+	/**
+	 * 答案文件最大限制
+	 * 单位:M
+	 */
+	private static int answerMaxsize = 30;
+
+
+	public static final String TEMP_FILE_EXP = "offlineExam/";
+
+
+	/**
+	 * 获取离线考试列表
+	 * @return
+	 */
+	@ApiOperation(value = "获取离线考试列表")
+	@GetMapping("/getOfflineCourse")
+	public List<OfflineExamCourseInfo> getOfflineCourse(){
+		User user = getAccessUser();
+		List<OfflineExamCourseInfo> offlineExamCourseInfos = offlineExamService.getOfflineCourse(user.getUserId());
+		return offlineExamCourseInfos;
+	}
+
+	/**
+	 * 开始考试
+	 * @param examStudentId
+	 */
+	@ApiOperation(value = "离线考试:开始考试")
+	@GetMapping("/startOfflineExam")
+	public void startOfflineExam(@RequestParam long examStudentId){
+		Check.isNull(examStudentId, "examStudentId不能为空");
+		offlineExamService.startOfflineExam(examStudentId);
+	}
+
+	/**
+	 * 交卷
+	 */
+	@ApiOperation(value = "离线考试:交卷")
+	@PostMapping("/submitPaper")
+	public void submitPaper(@RequestParam(value = "file") MultipartFile file,
+							@RequestParam long examRecordDataId)  throws Exception{
+		Check.isNull(file, "file不能为空");
+		Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+		String fileName = file.getOriginalFilename();
+		int index = fileName.lastIndexOf(".");
+		String fileSuffix = fileName.substring(index+1, fileName.length()).toUpperCase();
+		if(!"PDF".equals(fileSuffix) && !"ZIP".equals(fileSuffix)){
+			throw new StatusException("OfflineExamController-submitPaper-001","文件格式不正确");
+		}
+
+		ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo,examRecordDataId,ExamRecordDataEntity.class);
+		if(examRecordData.getExamType() != ExamType.OFFLINE){
+			throw new StatusException("OfflineExamController-submitPaper-002","非离线考试");
+		}
+		String offlineUploadFileType = ExamCacheTransferHelper.getCachedExamProperty(examRecordData.getExamId(),
+				examRecordData.getStudentId(),
+				ExamProperties.OFFLINE_UPLOAD_FILE_TYPE.name()).getValue();
+		if(StringUtils.isBlank(offlineUploadFileType) || "[]".equals(offlineUploadFileType)){
+			throw new StatusException("OfflineExamController-submitPaper-003","当前考试设置不允许上传附件");
+		}
+		if(offlineUploadFileType.indexOf(fileSuffix)<0){
+			throw new StatusException("OfflineExamController-submitPaper-004","当前考试允许上传文件格式为:" + offlineUploadFileType);
+		}
+		//判断文件大小
+		long fileSize = file.getSize();
+		if(fileSize > answerMaxsize * 1048576){
+			throw new StatusException("OfflineExamController-submitPaper-005","文件大小不能超过"+answerMaxsize+"M");
+		}
+		offlineExamService.submitPaper(examRecordDataId,getUploadFile(file));
+	}
+
+	private File getUploadFile(MultipartFile file){
+        //建临时文件夹
+		File dirFile = new File(TEMP_FILE_EXP);
+		if(!dirFile.exists()){
+			dirFile.mkdirs();
+		}
+        String fileName = file.getOriginalFilename();
+        File tempFile = new File(TEMP_FILE_EXP + fileName);
+        OutputStream os = null;
+        try {
+			os = new FileOutputStream(tempFile);
+			IOUtils.copyLarge(file.getInputStream(), os);
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}finally{
+			IOUtils.closeQuietly(os);
+		}
+        return tempFile;
+    }
+}

+ 83 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/PracticeController.java

@@ -0,0 +1,83 @@
+package cn.com.qmth.examcloud.core.oe.admin.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.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.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataSyncService;
+import cn.com.qmth.examcloud.core.oe.admin.service.PracticeService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.PracticeCourseInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.PracticeDetailInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.PracticeRecordInfo;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 
+ * @author      chenken
+ * @date        2018年9月7日 上午11:12:28
+ * @company     QMTH
+ * @description PracticeController.java
+ */
+@Api(tags = "练习相关接口")
+@RestController
+@RequestMapping("${$rmp.ctr.oe}/practice")
+public class PracticeController extends ControllerSupport{
+
+    @Autowired
+    private PracticeService practiceService;
+    
+    @Autowired
+    private ExamRecordDataSyncService examRecordDataSyncService;
+    /**
+     * 练习课程列表
+     * @param examId
+     * @param studentId
+     * @return
+     */
+    @ApiOperation(value = "练习课程列表")
+    @GetMapping("/queryPracticeCourseList")
+    public List<PracticeCourseInfo> queryPracticeCourseList(@RequestParam Long examId){
+        Check.isNull(examId, "examId不能为空");
+        User user = getAccessUser();
+        return practiceService.queryPracticeCourseList(examId, user.getUserId());
+    }
+    
+    /**
+     * 课程练习记录详情
+     * @param examStudentId
+     * @return
+     */
+    @ApiOperation(value = "课程练习记录详情")
+    @GetMapping("/queryPracticeRecordList")
+    public List<PracticeRecordInfo> queryPracticeRecordList(@RequestParam Long examStudentId){
+        Check.isNull(examStudentId, "examStudentId不能为空");
+        return practiceService.queryPracticeRecordList(examStudentId);
+    }
+    
+    /**
+     * 单次练习答题情况统计
+     * @return
+     */
+    @ApiOperation(value = "单次练习答题情况统计")
+    @GetMapping("/getPracticeDetailInfo")
+    public PracticeDetailInfo getPracticeDetailInfo(@RequestParam Long examRecordDataId,@RequestParam(required = false) String fromCache){
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+        if(fromCache!=null) {
+            examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
+            if(examRecordDataId==null) {
+                throw new StatusException("1001", "未找到数据");
+            }
+        }
+        return practiceService.getPracticeDetailInfo(examRecordDataId);
+    }
+    
+}

+ 75 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamCourseCloudServiceProvider.java

@@ -0,0 +1,75 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import cn.com.qmth.examcloud.core.basic.api.CourseCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetCoursesByIdListReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetCoursesByIdListResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamCourseCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamCourseBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetExamCourseReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetExamCourseResp;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcTemplate;
+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}/exam/course")
+public class ExamCourseCloudServiceProvider extends ControllerSupport implements ExamCourseCloudService {
+
+    private static final long serialVersionUID = 4538819077863159166L;
+
+    @Autowired
+    private CourseCloudService courseCloudService;
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    @Override
+    @ApiOperation(value = "获取考试相关的课程列表")
+    @PostMapping("/getExamCourses")
+    public GetExamCourseResp getExamCourses(@RequestBody GetExamCourseReq req) {
+        List<Long> courseIds = this.queryExamCourseIds(req.getExamId());
+
+        GetExamCourseResp resp = new GetExamCourseResp();
+        List<ExamCourseBean> courses = new ArrayList<>();
+        resp.setList(courses);
+        if (CollectionUtils.isEmpty(courseIds)) {
+            return resp;
+        }
+
+        try {
+            GetCoursesByIdListReq courseReq = new GetCoursesByIdListReq();
+            courseReq.setCourseIdList(courseIds);
+            GetCoursesByIdListResp courseResp = courseCloudService.getCoursesByIdList(courseReq);
+
+            for (CourseBean bean : courseResp.getCourseList()) {
+                ExamCourseBean course = new ExamCourseBean();
+                course.setId(bean.getId());
+                course.setCode(bean.getCode());
+                course.setName(bean.getName());
+                courses.add(course);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return resp;
+    }
+
+    private List<Long> queryExamCourseIds(Long examId) {
+        final String querySql = String.format("select distinct course_id from ec_oe_exam_student where exam_id = %s and enable = 1", examId);
+        return jdbcTemplate.queryForList(querySql, Long.class);
+    }
+
+}

+ 711 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordCloudServiceProvider.java

@@ -0,0 +1,711 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.*;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.*;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.*;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.*;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordForMarkingService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
+import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
+import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestionStructure;
+import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestionUnit;
+import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.QuestionAnswerCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.QuestionCacheBean;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import com.mysql.cj.util.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+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;
+import java.util.stream.Collectors;
+
+/**
+ * @author chenken
+ * @date 2018年9月19日 下午2:44:12
+ * @company QMTH
+ * @description ExamRecordCloudServiceProvider.java
+ */
+@Api(tags = "考试记录相关接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examRecord")
+public class ExamRecordCloudServiceProvider extends ControllerSupport implements ExamRecordCloudService {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 7678785677176906341L;
+
+    @Autowired
+    private ExamScoreRepo examScoreRepo;
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamCaptureRepo examCaptureRepo;
+
+    @Autowired
+    private ExamStudentRepo examStudentRepo;
+
+    @Autowired
+    private ExamRecordForMarkingService examRecordForMarkingService;
+
+    @Autowired
+    private ExamRecordQuestionsRepo examRecordQuestionsRepo;
+
+    @Autowired
+    private ExamRecordQuestionsService examRecordQuestionsService;
+
+    @Override
+    @ApiOperation(value = "查询是否已经开考")
+    @PostMapping("/checkExamIsStarted")
+    public CheckExamIsStartedResp checkExamIsStarted(@RequestBody CheckExamIsStartedReq examRecordReq) {
+        Check.isNull(examRecordReq.getExamId(), "examId不能为空");
+        ExamRecordDataEntity examRecordDataEntity = new ExamRecordDataEntity();
+        examRecordDataEntity.setExamId(examRecordReq.getExamId());
+        examRecordDataEntity.setCourseId(examRecordReq.getCourseId());
+        examRecordDataEntity.setStudentId(examRecordReq.getStudentId());
+        long examRecordNum = examRecordDataRepo.count(Example.of(examRecordDataEntity));
+        CheckExamIsStartedResp checkExamIsStartedResp = new CheckExamIsStartedResp();
+        checkExamIsStartedResp.setIsStarted(examRecordNum > 0);
+        return checkExamIsStartedResp;
+    }
+
+    @Override
+    @ApiOperation(value = "查询考试记录-供exchange调用")
+    @PostMapping("/queryExamRecordForSelectScoreByScoreId")
+    public QueryExamRecordForSelectScoreResp queryExamRecordForSelectScoreByScoreId(@RequestBody QueryExamRecordForSelectScoreReq req) {
+        QueryExamRecordForSelectScoreResp resp = new QueryExamRecordForSelectScoreResp();
+        if (req.getExamScoreId() != null) {
+            ExamScoreEntity examScore = GlobalHelper.getEntity(examScoreRepo, req.getExamScoreId(), ExamScoreEntity.class);
+            ExamRecordDataEntity examRecordData =
+                    GlobalHelper.getEntity(examRecordDataRepo, examScore.getExamRecordDataId(), ExamRecordDataEntity.class);
+            List<ExamRecordForSelectScore> examRecordForSelectScoreList = new ArrayList<ExamRecordForSelectScore>();
+            examRecordForSelectScoreList.add(buildExamRecordForSelectScore(examRecordData));
+            resp.setExamRecordForSelectScoreList(examRecordForSelectScoreList);
+        }
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "查询考试记录-供exchange调用")
+    @PostMapping("/queryExamRecordForSelectScoreByExamStudentId")
+    public QueryExamRecordForSelectScoreResp queryExamRecordForSelectScoreByExamStudentId(@RequestBody QueryExamRecordForSelectScoreReq req) {
+        QueryExamRecordForSelectScoreResp resp = new QueryExamRecordForSelectScoreResp();
+        if (req.getExamStudentId() != null) {
+            List<ExamRecordDataEntity> examRecordList = examRecordDataRepo.findByExamStudentId(req.getExamStudentId());
+            List<ExamRecordForSelectScore> examRecordForSelectScoreList = new ArrayList<ExamRecordForSelectScore>();
+            for (ExamRecordDataEntity examRecordData : examRecordList) {
+                examRecordForSelectScoreList.add(buildExamRecordForSelectScore(examRecordData));
+            }
+            resp.setExamRecordForSelectScoreList(examRecordForSelectScoreList);
+        }
+        return resp;
+    }
+
+    public ExamRecordForSelectScore buildExamRecordForSelectScore(ExamRecordDataEntity examRecordData) {
+        ExamRecordForSelectScore examRecordForSelectScore = new ExamRecordForSelectScore();
+        examRecordForSelectScore.setExamStudentId(examRecordData.getExamStudentId());
+        examRecordForSelectScore.setRootOrgId(examRecordData.getRootOrgId());
+        examRecordForSelectScore.setExamRecordDataId(examRecordData.getId());
+        examRecordForSelectScore.setStartTime(examRecordData.getStartTime());
+        examRecordForSelectScore.setEndTime(examRecordData.getEndTime());
+        examRecordForSelectScore.setCleanTime(examRecordData.getCleanTime());
+        examRecordForSelectScore.setIsWarn(examRecordData.getIsWarn());
+        examRecordForSelectScore.setIsAudit(examRecordData.getIsAudit());
+        examRecordForSelectScore.setIsIllegality(examRecordData.getIsIllegality());
+        examRecordForSelectScore.setStatus(examRecordData.getExamRecordStatus().name());
+
+        return examRecordForSelectScore;
+    }
+
+    @Override
+    @ApiOperation(value = "查询照片数据-供exchange调用")
+    @PostMapping("/getExamPhotoVerifyData")
+    public GetExamPhotoVerifyDataResp getExamPhotoVerifyData(@RequestBody GetExamPhotoVerifyDataReq req) {
+        Long scoreId = req.getScoreId();
+        ExamScoreEntity examScoreEntity = GlobalHelper.getEntity(examScoreRepo, scoreId, ExamScoreEntity.class);
+        if (examScoreEntity == null) {
+            return null;
+        }
+        Long examRecordDataId = examScoreEntity.getExamRecordDataId();
+        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+        if (examRecordData == null) {
+            return null;
+        }
+        List<ExamCaptureEntity> examCaptureList = examCaptureRepo.findByExamRecordDataId(examRecordDataId);
+
+        GetExamPhotoVerifyDataResp resp = new GetExamPhotoVerifyDataResp();
+        resp.setScoreId(scoreId);
+        resp.setSuccessRate(examRecordData.getFaceSuccessPercent());
+        int strangerCount = 0;
+        List<String> photoUrls = new ArrayList<String>();
+        for (ExamCaptureEntity examCaptureEntity : examCaptureList) {
+            if (examCaptureEntity.getIsStranger()) {
+                strangerCount++;
+            }
+            photoUrls.add(examCaptureEntity.getFileUrl());
+        }
+        resp.setStrangerCount(strangerCount);
+        resp.setPhotoUrls(photoUrls);
+        return resp;
+    }
+
+    /**
+     * 获取待阅卷的考试记录
+     *
+     * @param req
+     * @return
+     */
+    @Override
+    @ApiOperation(value = "获取待阅卷的考试记录-供bridge调用")
+    @PostMapping("/getToBeMarkExamRecord")
+    public GetToBeMarkExamRecordResp getToBeMarkExamRecord(@RequestBody GetToBeMarkExamRecordReq req) {
+        if (null == req.getExamStudentIdList() || req.getExamStudentIdList().isEmpty()) {
+            throw new StatusException("100101", "考生id不允许为空");
+        }
+
+        GetToBeMarkExamRecordResp resp = new GetToBeMarkExamRecordResp();
+        //待阅卷的考试记录集合
+        List<ToBeMarkExamRecordBean> toBeMarkExamRecordBeanList = new ArrayList<>();
+        for (Long examStudentId : req.getExamStudentIdList()) {
+            ExamStudentEntity examStudent = examStudentRepo.findByExamStudentId(examStudentId);
+
+            if (null == examStudent.getStudentId()) {
+                throw new StatusException("100101", "考生id:" + examStudentId + "不正确");
+            }
+
+            Long examId = examStudent.getExamId();
+            //待阅的考试列表
+            List<ExamRecordForMarkingEntity> examRecordForMarkingList =
+                    examRecordForMarkingService.queryValidExamRecordList(examId, examStudent.getCourseId());
+
+            //未参加考试或未违纪的考试数据略过
+            if (null == examRecordForMarkingList || examRecordForMarkingList.isEmpty()) {
+                continue;
+            }
+
+            for (ExamRecordForMarkingEntity markingRecord : examRecordForMarkingList) {
+                Long examRecordDataId = markingRecord.getExamRecordDataId();
+                ExamRecordDataEntity examRecordData =
+                        GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+
+                ToBeMarkExamRecordBean toBeMarkExamRecordBean = new ToBeMarkExamRecordBean();
+                toBeMarkExamRecordBean.setExamId(markingRecord.getExamId());
+                toBeMarkExamRecordBean.setStudentName(examRecordData.getStudentName());
+                toBeMarkExamRecordBean.setStudentCode(examRecordData.getStudentCode());
+
+                CourseCacheBean course = CacheHelper.getCourse(examRecordData.getCourseId());
+                toBeMarkExamRecordBean.setCourseCode(course.getCode());
+                toBeMarkExamRecordBean.setCourseName(course.getName());
+
+                toBeMarkExamRecordBean.setPaperType(markingRecord.getPaperType());
+                toBeMarkExamRecordBean.setExamStudentId(markingRecord.getExamStudentId());
+                toBeMarkExamRecordBean.setExamRecordDataId(markingRecord.getExamRecordDataId());
+                //待评阅的主观题集合
+                toBeMarkExamRecordBean.setSubjectiveAnswerList(
+                        getToBeMarkSubjectiveAnswerList(examRecordData.getExamRecordQuestionsId(),
+                                examRecordDataId, examId, course.getCode(), examRecordData.getPaperType()));
+
+                toBeMarkExamRecordBeanList.add(toBeMarkExamRecordBean);
+            }
+
+        }
+        resp.setToBeMarkExamRecordBeanList(toBeMarkExamRecordBeanList);
+        return resp;
+    }
+
+    /**
+     * 获取部分考试记录数据
+     *
+     * @param req
+     * @return
+     */
+    @Override
+    @ApiOperation(value = "获取部分考试记录数据")
+    @PostMapping("/getPartialExamRecordData")
+    public GetPartialExamRecordDataResp getPartialExamRecordData(@RequestBody GetPartialExamRecordDataReq req) {
+        ExamRecordDataEntity examRecordData =
+                GlobalHelper.getEntity(examRecordDataRepo, req.getExamRecordDataId(), ExamRecordDataEntity.class);
+
+        if (null == examRecordData) {
+            return new GetPartialExamRecordDataResp();
+        }
+
+        GetPartialExamRecordDataResp resp = new GetPartialExamRecordDataResp();
+        resp.setExamStudentId(examRecordData.getExamStudentId());
+
+        return resp;
+    }
+
+    /**
+     * 获取待阅卷的考试记录
+     *
+     * @param req
+     * @return
+     */
+    @Override
+    @ApiOperation(value = "分页获取待阅卷的考试记录")
+    @PostMapping("/getPagedToBeMarkExamRecord")
+    public GetPagedToBeMarkExamRecordResp getPagedToBeMarkExamRecord(@RequestBody GetPagedToBeMarkExamRecordReq req) {
+        Long st = System.currentTimeMillis();
+
+        Long examId = req.getExamId();
+        String courseCode = req.getSubjectCode();
+        Long startId = req.getStartId();
+        Integer size = req.getSize();
+
+        validateToBeMarkData(examId, courseCode, startId, size);
+
+        Long startTime = System.currentTimeMillis();
+
+        List<ExamStudentEntity> limitedExamStuList =
+                examStudentRepo.getLimitExamStudentList(examId, courseCode, startId, size);
+
+        if (log.isDebugEnabled()) {
+            log.debug("1.[GET_PAGED_TO_BE_MARK_EXAM_RECORD-" + examId + "-" + courseCode + "-" + startId + "]" +
+                    "获取考生耗时:" + (System.currentTimeMillis() - startTime) + " ms");
+        }
+
+        startTime = System.currentTimeMillis();
+
+        GetPagedToBeMarkExamRecordResp resp = new GetPagedToBeMarkExamRecordResp();
+        Long nextId = startId;
+
+        if (null == limitedExamStuList || limitedExamStuList.isEmpty()) {
+            resp.setNextId(nextId);
+            resp.setToBeMarkExamRecordBeanList(null);
+
+            if (log.isDebugEnabled()) {
+                log.debug("999.[GET_PAGED_TO_BE_MARK_EXAM_RECORD-" + examId + "-" + courseCode + "-" + startId + "]end..." +
+                        "未找到对应的考生数据,合计耗时:" + (System.currentTimeMillis() - st) + " ms");
+            }
+
+            return resp;
+        }
+
+        List<PagedToBeMarkExamRecordBean> pagedToBeMarkList = new ArrayList<>();
+
+        CourseCacheBean course = CacheHelper.getCourse(limitedExamStuList.get(0).getCourseId());
+
+        int si = 0;//考生索引
+        for (ExamStudentEntity examStu : limitedExamStuList) {
+            si++;
+
+            Long st1 = System.currentTimeMillis();
+
+            //当前考生待阅卷的考试记录
+            List<ExamRecordForMarkingEntity> examRecordForMarkingList =
+                    examRecordForMarkingService.queryValidExamRecordList(examStu.getExamStudentId());
+
+            if (log.isDebugEnabled()) {
+                log.debug("1-2." + si + "[GET_PAGED_TO_BE_MARK_EXAM_RECORD-" + examId + "-" + courseCode + "-" + examStu.getExamStudentId() + "]" +
+                        "获取考生待阅卷的考试记录耗时:" + (System.currentTimeMillis() - st1) + " ms");
+            }
+
+            //如果考生找不到待阅卷的数据,直接跳过
+            if (null == examRecordForMarkingList || examRecordForMarkingList.isEmpty()) {
+                continue;
+            }
+
+            st1=System.currentTimeMillis();
+
+            for (ExamRecordForMarkingEntity record : examRecordForMarkingList) {
+                PagedToBeMarkExamRecordBean pagedBean = new PagedToBeMarkExamRecordBean();
+                pagedBean.setExamId(examId);
+                pagedBean.setStudentName(examStu.getStudentName());
+                pagedBean.setStudentCode(examStu.getStudentCode());
+                pagedBean.setIdentityNumber(examStu.getIdentityNumber());
+                pagedBean.setCourseCode(courseCode);
+                pagedBean.setCourseName(course.getName());
+                pagedBean.setPaperType(examStu.getPaperType());
+                pagedBean.setExamStudentId(examStu.getExamStudentId());
+                pagedBean.setExamRecordDataId(record.getExamRecordDataId());
+                pagedBean.setGrade(examStu.getGrade());
+
+                pagedBean.setSubjectiveAnswerList(
+                        getSubjectiveAnswerList(record.getExamRecordDataId(), examId, courseCode, examStu.getPaperType()));
+
+                pagedToBeMarkList.add(pagedBean);
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("1-2." + si + "[GET_PAGED_TO_BE_MARK_EXAM_RECORD-" + examId + "-" + courseCode + "-" + examStu.getExamStudentId() + "]" +
+                        "构建带作答记录的待阅卷的考试记录耗时:" + (System.currentTimeMillis() - st1) + " ms");
+            }
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("2.[GET_PAGED_TO_BE_MARK_EXAM_RECORD-" + examId + "-" + courseCode + "-" + startId + "]" +
+                    "获取" + size + "条考生的待阅卷记录共耗时:" + (System.currentTimeMillis() - startTime) + " ms");
+        }
+
+        nextId = limitedExamStuList.get(limitedExamStuList.size() - 1).getExamStudentId() + 1;
+        resp.setNextId(nextId);
+        resp.setToBeMarkExamRecordBeanList(pagedToBeMarkList);
+
+        if (log.isDebugEnabled()) {
+            log.debug("999.[GET_PAGED_TO_BE_MARK_EXAM_RECORD-" + examId + "-" + courseCode + "-" + startId + "]end..." +
+                    "合计耗时:" + (System.currentTimeMillis() - st) + " ms");
+        }
+        return resp;
+    }
+
+    /**
+     * 获取主观题集合
+     *
+     * @param examRecordDataId
+     * @param examId
+     * @return
+     */
+    private List<PagedToBeMarkSubjectiveAnswerBean> getSubjectiveAnswerList(Long examRecordDataId,
+                                                                            Long examId, String courseCode, String paperType) {
+        //全部作答集合
+        ExamRecordQuestionsEntity allEqList =
+                examRecordQuestionsService.getExamRecordQuestionsAndFixExamRecordDataIfNecessary(examRecordDataId);
+        //主观题作答集合
+        List<ExamQuestionEntity> eqList = examRecordQuestionsService.querySubjectiveAnswerList(examRecordDataId);
+
+        List<PagedToBeMarkSubjectiveAnswerBean> resultList = new ArrayList<>();
+        for (ExamQuestionEntity eq : eqList) {
+            PagedToBeMarkSubjectiveAnswerBean bean = new PagedToBeMarkSubjectiveAnswerBean();
+            bean.setMainNumber(eq.getMainNumber());
+            bean.setOrder(eq.getOrder());
+            bean.setQuestionId(eq.getQuestionId());
+            bean.setStudentAnswer(eq.getStudentAnswer());
+            bean.setAnswerType(eq.getAnswerType());
+            bean.setQuestionType(eq.getQuestionType());
+
+            //获取指定小题的题干相关信息
+            QuestionCacheBean cachedQues = CacheHelper.getQuestion(examId, courseCode, paperType, eq.getQuestionId());
+
+            bean.setAnswer(getCorrectAnswer(eq.getOrder(), eq.getQuestionId(), cachedQues, allEqList.getExamQuestionEntities()));
+
+            bean.setParentBody(getParentBody(cachedQues));
+            bean.setBody(getBody(eq.getOrder(), eq.getQuestionId(), cachedQues, eqList));
+
+            resultList.add(bean);
+        }
+
+        return resultList;
+    }
+
+    /**
+     * 获取套题的主题干
+     *
+     * @param cachedQues
+     * @return
+     */
+    private String getParentBody(QuestionCacheBean cachedQues) {
+
+        DefaultQuestionStructure questionStructure = cachedQues.getDefaultQuestion().getMasterVersion();
+
+        //如果主题干不为空,则认为是套题
+        return questionStructure.getBody();
+    }
+
+    /**
+     * 获取当前小题的题干
+     *
+     * @param curSubNumber       当前小题号
+     * @param questionId         原小题id
+     * @param cachedQues         带题干的试卷结构
+     * @param subjectiveQuesList 主观题集合
+     * @return
+     */
+    private String getBody(Integer curSubNumber, String questionId,
+                           QuestionCacheBean cachedQues, List<ExamQuestionEntity> subjectiveQuesList) {
+        DefaultQuestionStructure questionStructure = cachedQues.getDefaultQuestion().getMasterVersion();
+
+        //body为空,则说明当前小题为非套题(即questionUnitList集合大小为1),可直接返回小题题干
+        if (StringUtils.isNullOrEmpty(questionStructure.getBody())) {
+            return questionStructure.getQuestionUnitList().get(0).getBody();
+        }
+
+        //同一questionId的主观题集合(不带题干)
+        List<ExamQuestionEntity> noBodySubjectiveQuesList = subjectiveQuesList.stream().
+                filter(p -> p.getQuestionId().equals(questionId)).collect(Collectors.toList());
+
+        //同一questionId的主观题集合(带题干)
+        List<DefaultQuestionUnit> haveBodySubjectiveQuesList = questionStructure.getQuestionUnitList().stream()
+                .filter(p -> QuestionType.FILL_UP == p.getQuestionType() || QuestionType.ESSAY == p.getQuestionType())
+                .collect(Collectors.toList());
+
+        for (int i = 0; i < noBodySubjectiveQuesList.size(); i++) {
+            //如果小题号相同,则根据相同索引从带题干的集合中取出对应的小题题干
+            if (noBodySubjectiveQuesList.get(i).getOrder().intValue() == curSubNumber.intValue()) {
+                return haveBodySubjectiveQuesList.get(i).getBody();
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * 获取当前小题的标准答案
+     *
+     * @param curSubNumber       当前小题号
+     * @param questionId         原小题id
+     * @param cachedQues         带题干的试卷结构
+     * @param allEqList 所有作答集合
+     * @return
+     */
+    private String getCorrectAnswer(Integer curSubNumber, String questionId,
+                                    QuestionCacheBean cachedQues, List<ExamQuestionEntity> allEqList) {
+        QuestionAnswerCacheBean questionAnswerCache = CacheHelper.getQuestionAnswer(questionId);
+        List<String> rightAnswerList = questionAnswerCache.getRightAnswers();
+        DefaultQuestionStructure questionStructure = cachedQues.getDefaultQuestion().getMasterVersion();
+
+
+        //body为空,则说明当前小题为非套题(rightAnswerList集合大小为1),可直接返回
+        if (StringUtils.isNullOrEmpty(questionStructure.getBody())) {
+            return rightAnswerList.get(0);
+        }
+
+
+        //同一questionId的主观题集合(不带题干,有小题号)
+        List<ExamQuestionEntity> noBodySubjectiveQuesList = allEqList.stream().
+                filter(p -> p.getQuestionId().equals(questionId)).collect(Collectors.toList());
+
+        for (int i = 0; i < noBodySubjectiveQuesList.size(); i++) {
+            //如果小题号相同,则根据相同索引从带题干的集合中取出对应的小题题干
+            if (noBodySubjectiveQuesList.get(i).getOrder().intValue() == curSubNumber.intValue()) {
+                return rightAnswerList.get(i);
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * 校验待阅卷数据
+     *
+     * @param examId
+     * @param courseCode
+     * @param startId
+     * @param size
+     */
+    private void validateToBeMarkData(Long examId, String courseCode, Long startId, Integer size) {
+        if (null == examId) {
+            throw new StatusException("101001", "考生id不允许为空");
+        }
+
+        if (StringUtils.isNullOrEmpty(courseCode)) {
+            throw new StatusException("101002", "科目代码不允许为空");
+        }
+
+        if (null == startId) {
+            throw new StatusException("101003", "考试记录id不允许为空");
+        }
+
+        if (null == size) {
+            throw new StatusException("101004", "数据量大小不允许为空");
+        }
+
+        if (size.intValue() > 500) {
+            throw new StatusException("101005", "数据量最大不得超过500");
+        }
+    }
+
+    /**
+     * 获取待评阅的主观题集合
+     *
+     * @param recordQuestionsId
+     * @param examRecordDataId
+     * @param examId
+     * @param courseCode
+     * @param paperType
+     * @return
+     */
+    private List<ToBeMarkSubjectiveAnswerBean> getToBeMarkSubjectiveAnswerList(String recordQuestionsId,
+                                                                               Long examRecordDataId, Long examId, String courseCode, String paperType) {
+        ExamRecordQuestionsEntity questionsEntity;
+        if (null != recordQuestionsId) {
+            questionsEntity = GlobalHelper.getEntity(examRecordQuestionsRepo, recordQuestionsId, ExamRecordQuestionsEntity.class);
+        } else {
+            questionsEntity = examRecordQuestionsService.getExamRecordQuestionsAndFixExamRecordDataIfNecessary(examRecordDataId);
+        }
+
+        List<ExamQuestionEntity> examQuestionList = questionsEntity.getExamQuestionEntities();
+        //获取所有的主观题
+        List<ExamQuestionEntity> subjectiveQuestionList = examQuestionList.stream().filter(p -> p.getQuestionType() == QuestionType.ESSAY ||
+                p.getQuestionType() == QuestionType.FILL_UP).collect(Collectors.toList());
+
+        List<ToBeMarkSubjectiveAnswerBean> subjectiveAnswerBeanList = new ArrayList<>();
+
+        for (int i = 0; i < subjectiveQuestionList.size(); i++) {
+            ExamQuestionEntity sq = subjectiveQuestionList.get(i);
+
+            ToBeMarkSubjectiveAnswerBean subjectiveAnswerBean;
+
+            //集合中是否已存在当前作答结果(因为套题会提前插入部分数据)
+            String questionId = sq.getQuestionId();
+            Integer order = sq.getOrder();
+            List<ToBeMarkSubjectiveAnswerBean> existSubjectiveAnswerBeanList = subjectiveAnswerBeanList.stream().filter(p -> questionId.equals(p.getQuestionId())
+                    && order.equals(p.getOrder())).collect(Collectors.toList());
+
+            //如果已经存在,则直接修改,因为套题一次性设置多个小题的题干,所以如果已经设置过就不再设置题干部分
+            if (null != existSubjectiveAnswerBeanList && !existSubjectiveAnswerBeanList.isEmpty()) {
+                subjectiveAnswerBean = existSubjectiveAnswerBeanList.get(0);
+
+                subjectiveAnswerBean.setMainNumber(sq.getMainNumber());
+                subjectiveAnswerBean.setOrder(sq.getOrder());
+                subjectiveAnswerBean.setQuestionId(sq.getQuestionId());
+                subjectiveAnswerBean.setStudentAnswer(transformedStudentAnswer(sq.getStudentAnswer()));//格式化过的答案
+                subjectiveAnswerBean.setAnswerType(sq.getAnswerType());
+                subjectiveAnswerBean.setRealAnswerType(transformedAnswerType(sq.getAnswerType(), sq.getQuestionType(), examId));//实际的作答类型
+
+                subjectiveAnswerBean.setAnswer(sq.getCorrectAnswer());
+            }
+
+            //不存在则重新实例化,并添加
+            else {
+                subjectiveAnswerBean = new ToBeMarkSubjectiveAnswerBean();
+
+                subjectiveAnswerBean.setMainNumber(sq.getMainNumber());
+                subjectiveAnswerBean.setOrder(sq.getOrder());
+                subjectiveAnswerBean.setQuestionId(sq.getQuestionId());
+                subjectiveAnswerBean.setStudentAnswer(transformedStudentAnswer(sq.getStudentAnswer()));//格式化过的答案
+                subjectiveAnswerBean.setAnswerType(sq.getAnswerType());
+                subjectiveAnswerBean.setRealAnswerType(transformedAnswerType(sq.getAnswerType(), sq.getQuestionType(), examId));//实际的作答类型
+
+                subjectiveAnswerBean.setAnswer(sq.getCorrectAnswer());
+
+                /**题干部分相关处理逻辑--start**/
+                //TODO 这是由于历史原因(套题下的小题共享同一个questionId),导致这个恶心的变通实现方式,如果以后优化了套题的结构,可修改
+                QuestionCacheBean getQuestionResp = CacheHelper.getQuestion(examId,
+                        courseCode, paperType, questionId);//获取题干相关信息
+                DefaultQuestionStructure questionStructure = getQuestionResp.getDefaultQuestion().getMasterVersion();
+
+                //所有的小题单元
+                List<DefaultQuestionUnit> questionUnits = questionStructure.getQuestionUnitList();
+
+                //如果主题干不为空,则认为是套题(套题需要将大题题干和小题题干拼在一起)
+                String mainBody = questionStructure.getBody();//主题干
+                if (!StringUtils.isNullOrEmpty(mainBody)) {
+
+                    //当前order对应的小题在此套题中的索引
+                    int currentIndex = 0;
+                    for (int j = 0; j < questionUnits.size(); j++) {
+
+                        //判断当前套题中的小题是否为主观题,只有主观题才需要添加进来
+                        if (!isSubjectiveQuestion(questionUnits.get(j).getQuestionType())) {
+                            continue;
+                        }
+
+                        //拼装第一条套题下的小题,(本条数据是完整的),相当于全部添加,无需更新
+                        if (StringUtils.isNullOrEmpty(subjectiveAnswerBean.getBody())) {
+                            currentIndex = j;
+                            //第1个子主观题的题干(不一定是套题的第一题)
+                            String subBody0 = questionUnits.get(j).getBody();
+
+                            subjectiveAnswerBean.setBody(mainBody + subBody0);//构建题干
+                            subjectiveAnswerBeanList.add(subjectiveAnswerBean);
+                        }
+                        //拼装套题的其它几个小题的题干(只初始化题干和唯一标识),并提前添加到结果集中
+                        else {
+                            ToBeMarkSubjectiveAnswerBean nextSubjectiveAnswerBean = new ToBeMarkSubjectiveAnswerBean();
+                            nextSubjectiveAnswerBean.setQuestionId(questionId);
+                            nextSubjectiveAnswerBean.setOrder(order + (j - currentIndex));
+                            nextSubjectiveAnswerBean.setBody(mainBody + questionUnits.get(j).getBody());//构建题干
+                            subjectiveAnswerBeanList.add(nextSubjectiveAnswerBean);
+                        }
+
+                    }
+                }
+                //不是套题,则默认取第一条记录的题干
+                else {
+                    subjectiveAnswerBean.setBody(questionUnits.get(0).getBody());//构建题干
+                    subjectiveAnswerBeanList.add(subjectiveAnswerBean);
+                }
+                /**题干部分相关处理逻辑--end**/
+
+            }
+        }
+
+        return subjectiveAnswerBeanList;
+    }
+
+    /**
+     * 当前题型是否为主观题
+     *
+     * @param questionType
+     * @return
+     */
+    private boolean isSubjectiveQuestion(QuestionType questionType) {
+        return (QuestionType.ESSAY == questionType || QuestionType.FILL_UP == questionType);
+    }
+
+    /**
+     * 根据原始作答类型和学生实际作答推断出真实的作答类型
+     *
+     * @param answerType   原始作答类型
+     * @param questionType 题型
+     * @param examId       考试id
+     * @return
+     */
+    private String transformedAnswerType(AnswerType answerType, QuestionType questionType, Long examId) {
+        //如果题型为问答题,且作答类型不为音频作答,且开启微信作答,则此题为图片作答题
+        if (questionType == QuestionType.ESSAY &&
+                (null == answerType || (null != answerType && answerType != AnswerType.SINGLE_AUDIO))) {
+            if (ExamCacheTransferHelper.weixinAnswerEnabled(examId)) {
+                return "image";
+            }
+        }
+
+        if (null != answerType && answerType == AnswerType.SINGLE_AUDIO) {
+            return "audio";
+        }
+
+        return "text";
+    }
+
+    /**
+     * 转化过的作答
+     *
+     * @param studentAnswer
+     * @return
+     */
+    private String transformedStudentAnswer(String studentAnswer) {
+        if (StringUtils.isNullOrEmpty(studentAnswer)) {
+            return studentAnswer;
+        }
+
+        //图片题特殊处理(因为图片作答题中有html标签)
+        Document doc = Jsoup.parse(studentAnswer);
+        Elements imgElements = doc.select("img[src]");
+        String imgStudentAnswer = "";
+        for (Element el : imgElements) {
+            String src = el.attr("src");
+            if (!StringUtils.isNullOrEmpty(src)) {
+                imgStudentAnswer += src + "|";
+            }
+        }
+        if (!StringUtils.isNullOrEmpty(imgStudentAnswer)) {
+            return imgStudentAnswer.substring(0, imgStudentAnswer.length() - 1);
+        }
+
+        return studentAnswer;
+    }
+
+}

+ 302 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordForMarkingCloudServiceProvider.java

@@ -0,0 +1,302 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordForMarkingCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordForMarkingBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.*;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.FindExamRecordForMarkingInfoResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetSingleExamRecordDataResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.QueryValidExamRecordInfoPageResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.QueryValidExamRecordInfoResp;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordForMarkingRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordForMarkingEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordForMarkingService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
+import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import com.google.common.collect.Lists;
+import com.mysql.cj.util.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+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.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 javax.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import static cn.com.qmth.examcloud.core.oe.admin.base.Constants.OE_CODE_400;
+
+/**
+ * @author chenken
+ * @date 2018年9月27日 上午11:01:23
+ * @company QMTH
+ * @description 阅卷相关接口
+ */
+@Api(tags = "阅卷获取信息相关接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examRecordForMarking")
+public class ExamRecordForMarkingCloudServiceProvider extends ControllerSupport implements ExamRecordForMarkingCloudService {
+
+    private static final long serialVersionUID = -8343697288418890873L;
+
+    @Autowired
+    private ExamRecordForMarkingService examRecordForMarkingService;
+    @Autowired
+    private ExamStudentRepo examStudentRepo;
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    @Autowired
+    private ExamRecordForMarkingRepo examRecordForMarkingRepo;
+
+    @Autowired
+    private ExamRecordQuestionsService examRecordQuestionsService;
+    
+    @Override
+    @ApiOperation(value = "根据条件查询阅卷需要的信息")
+    @PostMapping("/findExamRecordForMarkingInfo")
+    public FindExamRecordForMarkingInfoResp findExamRecordForMarkingInfo(@RequestBody FindExamRecordForMarkingInfoReq req) {
+        Long id = req.getId();
+        Long examId = req.getExamId();
+        FindExamRecordForMarkingInfoResp resp = new FindExamRecordForMarkingInfoResp();
+        if (id == null && examId == null) {
+            return resp;
+        }
+        List<ExamRecordForMarkingEntity> examRecordForMarkingList =
+                examRecordForMarkingService.findExamRecordForMarkingInfo(id, examId, req.getCourseId(), req.getBatchNum());
+        List<ExamRecordForMarkingBean> examRecordForMarkingBeanList = new ArrayList<ExamRecordForMarkingBean>();
+        for (ExamRecordForMarkingEntity entity : examRecordForMarkingList) {
+            ExamRecordForMarkingBean examRecordForMarkingBean = new ExamRecordForMarkingBean();
+            examRecordForMarkingBean.setId(entity.getId());
+            examRecordForMarkingBean.setExamId(entity.getExamId());
+            examRecordForMarkingBean.setBasePaperId(entity.getBasePaperId());
+            examRecordForMarkingBean.setPaperType(entity.getPaperType());
+            examRecordForMarkingBean.setCourseId(entity.getCourseId());
+            examRecordForMarkingBean.setOfflineFileName(entity.getOfflineFileName());
+            examRecordForMarkingBean.setOfflineFileUrl(FileStorageUtil.realPath(entity.getOfflineFileUrl()));
+            examRecordForMarkingBean.setBatchNum(entity.getBatchNum());
+            examRecordForMarkingBeanList.add(examRecordForMarkingBean);
+        }
+        resp.setExamRecordForMarkingBeanList(examRecordForMarkingBeanList);
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "查询有效成绩")
+    @PostMapping("/queryValidExamRecordInfo")
+    public QueryValidExamRecordInfoResp queryValidExamRecordInfo(@RequestBody QueryValidExamRecordInfoReq req) {
+        Check.isNull(req.getExamId(), "examId不能为空");
+        Check.isNull(req.getCourseId(), "courseId不能为空");
+        List<ExamRecordForMarkingEntity> examRecordForMarkingList = examRecordForMarkingService.queryValidExamRecordList(req.getExamId(), req.getCourseId());
+
+        QueryValidExamRecordInfoResp resp = new QueryValidExamRecordInfoResp();
+        List<ExamRecordForMarkingBean> examRecordForMarkingBeanList = new ArrayList<ExamRecordForMarkingBean>();
+        for (ExamRecordForMarkingEntity entity : examRecordForMarkingList) {
+            ExamRecordForMarkingBean examRecordForMarkingBean = new ExamRecordForMarkingBean();
+            examRecordForMarkingBean.setId(entity.getId());
+            examRecordForMarkingBean.setExamId(entity.getExamId());
+            examRecordForMarkingBean.setExamRecordDataId(entity.getExamRecordDataId());
+            examRecordForMarkingBean.setExamStudentId(entity.getExamStudentId());
+            examRecordForMarkingBean.setBasePaperId(entity.getBasePaperId());
+            examRecordForMarkingBean.setPaperType(entity.getPaperType());
+            examRecordForMarkingBean.setCourseId(entity.getCourseId());
+            examRecordForMarkingBean.setObjectiveScore(entity.getObjectiveScore());
+            examRecordForMarkingBeanList.add(examRecordForMarkingBean);
+        }
+        resp.setExamRecordForMarkingBeanList(examRecordForMarkingBeanList);
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "分页查询有效成绩")
+    @PostMapping("/queryValidExamRecordInfoPage")
+    public QueryValidExamRecordInfoPageResp queryValidExamRecordInfoPage(@RequestBody QueryValidExamRecordInfoPageReq req) {
+        Check.isNull(req.getExamId(), "examId不能为空");
+        Check.isNull(req.getCourseId(), "courseId不能为空");
+        Check.isNull(req.getStart(), "start不能为空");
+        Check.isNull(req.getSize(), "size不能为空");
+        if (req.getStart().longValue() <= 0) {
+            throw new StatusException(OE_CODE_400, "start必须大于0");
+        }
+        if (req.getSize().longValue() <= 0) {
+            throw new StatusException(OE_CODE_400, "size必须大于0");
+        }
+        Long courseId = req.getCourseId();
+        Long examId = req.getExamId();
+        Long size = req.getSize();
+        Long start = req.getStart();
+        //分页获取考生id
+        Pageable pageable = PageRequest.of(0, size.intValue(), Sort.Direction.ASC, "id");
+
+        Specification<ExamStudentEntity> specification = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(cb.equal(root.get("examId"), examId));
+            predicates.add(cb.equal(root.get("courseId"), courseId));
+            predicates.add(cb.greaterThanOrEqualTo(root.get("id"), start));
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        Page<ExamStudentEntity> page = examStudentRepo.findAll(specification,
+                pageable);
+
+        Iterator<ExamStudentEntity> iterator = page.iterator();
+
+        List<Long> stuIdList = Lists.newArrayList();
+        Long next = start;
+        while (iterator.hasNext()) {
+            ExamStudentEntity e = iterator.next();
+            next = e.getId();
+            stuIdList.add(e.getExamStudentId());
+        }
+
+        QueryValidExamRecordInfoPageResp resp = new QueryValidExamRecordInfoPageResp();
+        if (!next.equals(start)) {
+            next++;
+        }
+        resp.setNext(next);
+        //根据分页获取的考生id获取需要阅卷的试卷
+        List<ExamRecordForMarkingBean> examRecordForMarkingBeanList = new ArrayList<ExamRecordForMarkingBean>();
+        if (stuIdList.size() > 0) {
+            List<ExamRecordForMarkingEntity> examRecordForMarkingList =
+                    examRecordForMarkingService.queryValidExamRecordInfoByStuIds(examId, courseId, stuIdList, req.getBatchNum());
+
+            if (null == examRecordForMarkingList) {
+                resp.setExamRecordForMarkingBeanList(null);
+                return resp;
+            }
+
+            for (ExamRecordForMarkingEntity entity : examRecordForMarkingList) {
+                ExamRecordForMarkingBean examRecordForMarkingBean = new ExamRecordForMarkingBean();
+                examRecordForMarkingBean.setId(entity.getId());
+                examRecordForMarkingBean.setExamId(entity.getExamId());
+                examRecordForMarkingBean.setExamRecordDataId(entity.getExamRecordDataId());
+                examRecordForMarkingBean.setExamStudentId(entity.getExamStudentId());
+                examRecordForMarkingBean.setBasePaperId(entity.getBasePaperId());
+                examRecordForMarkingBean.setPaperType(entity.getPaperType());
+                examRecordForMarkingBean.setCourseId(entity.getCourseId());
+                examRecordForMarkingBean.setObjectiveScore(entity.getObjectiveScore());
+                examRecordForMarkingBean.setBatchNum(entity.getBatchNum());
+                examRecordForMarkingBeanList.add(examRecordForMarkingBean);
+            }
+        }
+        resp.setExamRecordForMarkingBeanList(examRecordForMarkingBeanList);
+        return resp;
+    }
+
+    /**
+     * 获取单个考试记录信息
+     *
+     * @param req
+     * @return
+     */
+    @Override
+    @PostMapping("/getSingleExamRecordData")
+    public GetSingleExamRecordDataResp getSingleExamRecordData(@RequestBody GetSingleExamRecordDataReq req) {
+        Check.isNull(req.getId(), "id不能为空");
+        ExamRecordDataEntity entity = GlobalHelper.getEntity(examRecordDataRepo, req.getId(), ExamRecordDataEntity.class);
+        if (entity == null) {
+            return new GetSingleExamRecordDataResp();
+        }
+
+        return copyGetSingleExamRecordDataRespFrom(entity);
+    }
+
+    private GetSingleExamRecordDataResp copyGetSingleExamRecordDataRespFrom(ExamRecordDataEntity entity) {
+        GetSingleExamRecordDataResp resp = new GetSingleExamRecordDataResp();
+
+        ExamRecordDataBean data = new ExamRecordDataBean();
+        data.setId(entity.getId());
+        data.setExamId(entity.getExamId());
+        data.setExamType(entity.getExamType() == null ? null : entity.getExamType().toString());
+        data.setExamStudentId(entity.getExamStudentId());
+        data.setStudentId(entity.getStudentId());
+        data.setStudentCode(entity.getStudentCode());
+        data.setStudentName(entity.getStudentName());
+        data.setIdentityNumber(entity.getIdentityNumber());
+        data.setCourseId(entity.getCourseId());
+        data.setCourseLevel(entity.getCourseLevel());
+        data.setOrgId(entity.getOrgId());
+        data.setRootOrgId(entity.getRootOrgId());
+        data.setBasePaperId(entity.getBasePaperId());
+        data.setPaperType(entity.getPaperType());
+        data.setPaperStructId(entity.getPaperStructId());
+        data.setInfoCollector(entity.getInfoCollector());
+        data.setExamRecordQuestionsId(entity.getExamRecordQuestionsId());
+        data.setExamRecordStatus(entity.getExamRecordStatus() == null ? null : entity.getExamRecordStatus().toString());
+        data.setStartTime(entity.getStartTime());
+        data.setEndTime(entity.getEndTime());
+        data.setCleanTime(entity.getCleanTime());
+        data.setWarn(entity.getIsWarn());
+        data.setAudit(entity.getIsAudit());
+        data.setIllegality(entity.getIsIllegality());
+        data.setUsedExamTime(entity.getUsedExamTime());
+        data.setExamOrder(entity.getExamOrder());
+        data.setReexamine(entity.getIsReexamine());
+        data.setContinued(entity.getIsContinued());
+        data.setAllObjectivePaper(entity.getIsAllObjectivePaper());
+        data.setContinuedCount(entity.getContinuedCount());
+        data.setExceed(entity.getIsExceed());
+        data.setFaceSuccessCount(entity.getFaceSuccessCount());
+        data.setFaceFailedCount(entity.getFaceFailedCount());
+        data.setFaceStrangerCount(entity.getFaceStrangerCount());
+        data.setFaceTotalCount(entity.getFaceTotalCount());
+        data.setFaceSuccessPercent(entity.getFaceSuccessPercent());
+        data.setFaceVerifyResult(entity.getFaceVerifyResult() == null ? null : entity.getFaceVerifyResult().toString());
+        data.setBaiduFaceLivenessSuccessPercent(entity.getBaiduFaceLivenessSuccessPercent());
+        data.setFaceLandmarkVal(entity.getFaceLandmarkVal());
+
+        resp.setData(data);
+        return resp;
+    }
+    @Override
+    @PostMapping("/updateExamRecordForMarkingBatchNum")
+    public void updateExamRecordForMarkingBatchNum(@RequestBody UpdateExamRecordForMarkingBatchNumReq req) {
+        if (req.getIdList()==null || req.getIdList().isEmpty()){
+            throw new StatusException("222001","阅卷原始数据表id不允许为空");
+        }
+        if (req.getIdList().size()>100){
+            throw new StatusException("222002","阅卷原始数据表id集合最大不得超过100条");
+        }
+        if (StringUtils.isNullOrEmpty(req.getBatchNum())){
+            throw new StatusException("222003","批次号不允许为空");
+        }
+        examRecordForMarkingRepo.updateBatchNum(req.getIdList(),req.getBatchNum());
+    }
+
+    @Override
+    @PostMapping("/saveExamRecordForMarking")
+    public void saveExamRecordForMarking(@RequestBody SaveExamRecordForMarkingReq req) {
+        ExamRecordForMarkingEntity examRecordForMarkingExists = examRecordForMarkingRepo.findByExamRecordDataId(req.getExamRecordDataId());
+        if (examRecordForMarkingExists != null) {
+            return;
+        }
+        ExamRecordForMarkingEntity examRecordForMarking = new ExamRecordForMarkingEntity();
+        examRecordForMarking.setExamId(req.getExamId());
+        examRecordForMarking.setExamRecordDataId(req.getExamRecordDataId());
+        examRecordForMarking.setExamStudentId(req.getExamStudentId());
+        examRecordForMarking.setBasePaperId(req.getBasePaperId());
+        examRecordForMarking.setPaperType(req.getPaperType());
+        examRecordForMarking.setCourseId(req.getCourseId());
+        examRecordForMarking.setObjectiveScore(req.getObjectiveScore());
+        int subjectiveAnswerLength = examRecordQuestionsService.calculationSubjectiveAnswerLength(req.getExamRecordDataId());
+        examRecordForMarking.setSubjectiveAnswerLength(subjectiveAnswerLength);
+        examRecordForMarkingRepo.save(examRecordForMarking);
+    }
+
+}

+ 44 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordPaperStructProvider.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamPaperStructCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetExamRecordPaperStructReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetExamRecordPaperStructResp;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordPaperStructEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordPaperStructService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@Api(tags = "考试记录-试卷结构")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examRecordPaperStruct")
+public class ExamRecordPaperStructProvider extends ControllerSupport implements ExamPaperStructCloudService {
+
+	/**
+     * 
+     */
+    private static final long serialVersionUID = 9057798814755020949L;
+    @Autowired
+	private ExamRecordPaperStructService examRecordPaperStructService;
+	
+	@ApiOperation(value = "获取考试记录试卷结构")
+	@PostMapping("/getExamRecordPaperStruct")
+	@Override
+	public GetExamRecordPaperStructResp getExamRecordPaperStruct(@RequestBody GetExamRecordPaperStructReq req) {
+		Check.isNull(req.getExamRecordDataId(), "考试记录id不能为空");
+		ExamRecordPaperStructEntity examRecordPaperStructEntity = examRecordPaperStructService.getExamRecordPaperStruct(req.getExamRecordDataId());
+		GetExamRecordPaperStructResp resp = new GetExamRecordPaperStructResp();
+		if (null == examRecordPaperStructEntity){
+			return null;
+		}
+		resp.setDefaultPaper(examRecordPaperStructEntity.getDefaultPaper());
+		return resp;
+	}
+}

+ 290 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordQuestionsCloudServiceProvider.java

@@ -0,0 +1,290 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordQuestionsCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.QuerySubjectiveAnswerBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.SaveSubjectiveQuestionScoreBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.SubjectiveAnswerBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetSubjectiveAnswerReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.QuerySubjectiveAnswerListReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.SaveSubjectiveQuestionScoreReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetSubjectiveAnswerResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.QuerySubjectiveAnswerListResp;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordQuestionsRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamQuestionEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordQuestionsEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.SubjectiveQuestionScoreInfo;
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
+import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestionStructure;
+import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestionUnit;
+import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.QuestionCacheBean;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import com.mysql.cj.util.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author chenken
+ * @date 2018年9月27日 上午11:02:55
+ * @company QMTH
+ * @description 学生作答答案相关接口
+ */
+@Api(tags = "学生作答答案相关接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examRecordQuestions")
+public class ExamRecordQuestionsCloudServiceProvider extends ControllerSupport implements ExamRecordQuestionsCloudService {
+
+    private static final long serialVersionUID = -1058894529829651231L;
+
+    @Autowired
+    private ExamRecordQuestionsService examRecordQuestionsService;
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamRecordQuestionsRepo examRecordQuestionsRepo;
+
+    /**
+     * 阅卷获取考试记录主观题答案信息
+     */
+    @Override
+    @ApiOperation(value = "阅卷获取考试记录主观题答案信息")
+    @PostMapping("/querySubjectiveAnswerList")
+    public QuerySubjectiveAnswerListResp querySubjectiveAnswerList(@RequestBody QuerySubjectiveAnswerListReq req) {
+        List<ExamQuestionEntity> examQuestionList = examRecordQuestionsService.querySubjectiveAnswerList(req.getExamRecordDataId());
+        QuerySubjectiveAnswerListResp resp = new QuerySubjectiveAnswerListResp();
+        List<QuerySubjectiveAnswerBean> querySubjectiveAnswerBeanList = new ArrayList<QuerySubjectiveAnswerBean>();
+        for (ExamQuestionEntity examQuestionEntity : examQuestionList) {
+            QuerySubjectiveAnswerBean querySubjectiveAnswerBean = new QuerySubjectiveAnswerBean();
+            querySubjectiveAnswerBean.setExamRecordDataId(examQuestionEntity.getExamRecordDataId());
+            querySubjectiveAnswerBean.setMainNumber(examQuestionEntity.getMainNumber());
+            querySubjectiveAnswerBean.setOrder(examQuestionEntity.getOrder());
+            querySubjectiveAnswerBean.setQuestionId(examQuestionEntity.getQuestionId());
+            querySubjectiveAnswerBean.setStudentAnswer(examQuestionEntity.getStudentAnswer());
+            querySubjectiveAnswerBean.setAnswerType(examQuestionEntity.getAnswerType());
+            querySubjectiveAnswerBeanList.add(querySubjectiveAnswerBean);
+        }
+        resp.setQuerySubjectiveAnswerBeanList(querySubjectiveAnswerBeanList);
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "阅卷回传主观题得分")
+    @PostMapping("/saveSubjectiveQuestionScore")
+    public void saveSubjectiveQuestionScore(@RequestBody SaveSubjectiveQuestionScoreReq req) {
+        Check.isNull(req.getExamRecordDataId(), "examRecordDataId 不能为空");
+        List<SaveSubjectiveQuestionScoreBean> saveSubjectiveQuestionScoreList = req.getSaveSubjectiveQuestionScoreList();
+        if (saveSubjectiveQuestionScoreList == null || saveSubjectiveQuestionScoreList.size() == 0) {
+            return;
+        }
+        long examRecordDataId = req.getExamRecordDataId();
+        List<SubjectiveQuestionScoreInfo> subjectiveQuestionScoreInfoList = new ArrayList<SubjectiveQuestionScoreInfo>();
+        for (SaveSubjectiveQuestionScoreBean bean : saveSubjectiveQuestionScoreList) {
+            SubjectiveQuestionScoreInfo info = new SubjectiveQuestionScoreInfo();
+            info.setExamRecordDataId(bean.getExamRecordDataId());
+            info.setOrder(bean.getOrder());
+            info.setQuestionId(bean.getQuestionId());
+            info.setScore(bean.getScore());
+            subjectiveQuestionScoreInfoList.add(info);
+        }
+        examRecordQuestionsService.saveSubjectiveQuestionScore(examRecordDataId, subjectiveQuestionScoreInfoList);
+
+    }
+
+    /**
+     * 阅卷获取考试记录主观题答案信息
+     */
+    @Override
+    @ApiOperation(value = "阅卷获取考试记录主观题答案信息")
+    @PostMapping("/getSubjectiveAnswerList")
+    public GetSubjectiveAnswerResp getSubjectiveAnswerList(@RequestBody GetSubjectiveAnswerReq req) {
+        int rowCount = 100;
+        List<ExamRecordDataEntity> examRecordDataList =
+                examRecordDataRepo.findLimitedDataByExamIdAndIdMoreThan(req.getExamId(), req.getStartExamRecordId(), rowCount);
+
+        if (null == examRecordDataList || examRecordDataList.isEmpty()) {
+            return new GetSubjectiveAnswerResp(req.getStartExamRecordId(), null);
+        }
+
+        List<SubjectiveAnswerBean> subjectiveAnswerBeanList = new ArrayList<>();
+
+        for (ExamRecordDataEntity record : examRecordDataList) {
+            String recordQuestionsId = record.getExamRecordQuestionsId();
+            CourseCacheBean course = CacheHelper.getCourse(record.getCourseId());
+            ExamRecordQuestionsEntity questionsEntity;
+            if (null != recordQuestionsId) {
+                questionsEntity = GlobalHelper.getEntity(examRecordQuestionsRepo, recordQuestionsId, ExamRecordQuestionsEntity.class);
+            } else {
+                questionsEntity = examRecordQuestionsService.getExamRecordQuestionsAndFixExamRecordDataIfNecessary(record.getId());
+            }
+
+            List<ExamQuestionEntity> examQuestionList = questionsEntity.getExamQuestionEntities();
+            //获取所有的主观题
+            List<ExamQuestionEntity> subjectiveQuestionList = examQuestionList.stream().filter(p -> p.getQuestionType() == QuestionType.ESSAY ||
+                    p.getQuestionType() == QuestionType.FILL_UP).collect(Collectors.toList());
+
+            for (int i = 0; i < subjectiveQuestionList.size(); i++) {
+                ExamQuestionEntity sq = subjectiveQuestionList.get(i);
+
+                SubjectiveAnswerBean subjectiveAnswerBean;
+
+                //集合中是否已存在当前作答结果(因为套题会提前插入部分数据)
+                String questionId = sq.getQuestionId();
+                Integer order = sq.getOrder();
+                List<SubjectiveAnswerBean> existSubjectiveAnswerBeanList = subjectiveAnswerBeanList.stream().filter(p -> questionId.equals(p.getQuestionId())
+                        && order.equals(p.getOrder())).collect(Collectors.toList());
+
+                //如果已经存在,则直接修改,因为套题一次性设置多个小题的题干,所以如果已经设置过就不再设置题干部分
+                if (null != existSubjectiveAnswerBeanList && !existSubjectiveAnswerBeanList.isEmpty()) {
+                    subjectiveAnswerBean = existSubjectiveAnswerBeanList.get(0);
+
+                    subjectiveAnswerBean.setAnswerType(sq.getAnswerType());
+                    subjectiveAnswerBean.setRealAnswerType(transformedAnswerType(sq.getAnswerType(), sq.getQuestionType(), record.getExamId()));//实际的作答类型
+                    subjectiveAnswerBean.setCourseCode(course.getCode());
+                    subjectiveAnswerBean.setExamId(req.getExamId());
+                    subjectiveAnswerBean.setExamRecordDataId(record.getId());
+                    subjectiveAnswerBean.setExamStudentId(record.getExamStudentId());
+                    subjectiveAnswerBean.setMainNumber(sq.getMainNumber());
+                    subjectiveAnswerBean.setOrder(order);
+                    subjectiveAnswerBean.setQuestionId(questionId);
+                    subjectiveAnswerBean.setStudentAnswer(transformedStudentAnswer(sq.getStudentAnswer()));
+                    subjectiveAnswerBean.setAnswer(sq.getCorrectAnswer());
+                }
+
+                //不存在则重新实例化,并添加
+                else {
+                    subjectiveAnswerBean = new SubjectiveAnswerBean();
+
+                    subjectiveAnswerBean.setAnswerType(sq.getAnswerType());
+                    subjectiveAnswerBean.setRealAnswerType(transformedAnswerType(sq.getAnswerType(), sq.getQuestionType(), record.getExamId()));//实际的作答类型
+                    subjectiveAnswerBean.setCourseCode(course.getCode());
+                    subjectiveAnswerBean.setExamId(req.getExamId());
+                    subjectiveAnswerBean.setExamRecordDataId(record.getId());
+                    subjectiveAnswerBean.setExamStudentId(record.getExamStudentId());
+                    subjectiveAnswerBean.setMainNumber(sq.getMainNumber());
+                    subjectiveAnswerBean.setOrder(order);
+                    subjectiveAnswerBean.setQuestionId(questionId);
+                    subjectiveAnswerBean.setStudentAnswer(transformedStudentAnswer(sq.getStudentAnswer()));
+                    subjectiveAnswerBean.setAnswer(sq.getCorrectAnswer());
+
+                    /**题干部分相关处理逻辑**/
+                    //TODO 这是由于历史原因(套题下的小题共享同一个questionId),导致这个恶心的变通实现方式,如果以后优化了套题的结构,可修改
+                    QuestionCacheBean getQuestionResp = CacheHelper.getQuestion(record.getExamId(),
+                            course.getCode(), record.getPaperType(), questionId);
+                    DefaultQuestionStructure questionStructure = getQuestionResp.getDefaultQuestion().getMasterVersion();
+
+                    //所有的小题单元
+                    List<DefaultQuestionUnit> questionUnits = questionStructure.getQuestionUnitList();
+
+                    //如果主题干不为空,则认为是套题(套题需要将大题题干和小题题干拼在一起)
+                    String mainBody = questionStructure.getBody();//主题干
+                    if (!StringUtils.isNullOrEmpty(mainBody)) {
+                        //第1小题的子题干
+                        String subBody0 = questionUnits.get(0).getBody();
+
+                        //首先将本条数据添加(本条数据是完整的)
+                        subjectiveAnswerBean.setBody(mainBody + subBody0);
+                        subjectiveAnswerBeanList.add(subjectiveAnswerBean);
+
+                        //其次,拼装套题的其它几个小题的题干(只初始化题干和唯一标识),并提前添加到结果集中
+                        for (int j = 1; j < questionUnits.size(); j++) {
+                            SubjectiveAnswerBean nextSubjectiveAnswerBean = new SubjectiveAnswerBean();
+                            nextSubjectiveAnswerBean.setQuestionId(questionId);
+                            nextSubjectiveAnswerBean.setOrder(order + j);
+                            nextSubjectiveAnswerBean.setBody(questionUnits.get(j).getBody());
+                            subjectiveAnswerBeanList.add(nextSubjectiveAnswerBean);
+                        }
+                    }
+                    //不是套题,则默认取第一条记录的题干
+                    else {
+                        subjectiveAnswerBean.setBody(questionUnits.get(0).getBody());
+                        subjectiveAnswerBeanList.add(subjectiveAnswerBean);
+                    }
+                }
+            }
+        }
+        ExamRecordDataEntity lastExamRecordData = examRecordDataList.get(examRecordDataList.size() - 1);
+        Long nextId = lastExamRecordData.getId() + 1;
+        return new GetSubjectiveAnswerResp(nextId, subjectiveAnswerBeanList);
+    }
+
+    /**
+     * 根据原始作答类型和学生实际作答推断出真实的作答类型
+     *
+     * @param answerType   原始作答类型
+     * @param questionType 题型
+     * @param examId       考试id
+     * @return
+     */
+    private String transformedAnswerType(AnswerType answerType, QuestionType questionType, Long examId) {
+        //需要图片作答的考试id(以英文逗号分隔)
+        Object oExamId = CacheHelper.getSysProperty("oe.imgAnswer.examId").getValue();
+        if (null != oExamId) {
+            String strExamId = String.valueOf(oExamId);
+            List<String> examIdList = Arrays.asList(strExamId.split(","));
+            //如果题型为问答题,且作答类型不为音频作答,且考试为需要图片作答,则此题为图片作答题
+            if (questionType == QuestionType.ESSAY &&
+                    (null == answerType || (null != answerType && answerType != AnswerType.SINGLE_AUDIO))) {
+                if (examIdList.stream().anyMatch(eid -> eid.equals(String.valueOf(examId)))) {
+                    return "image";
+                }
+            }
+        }
+
+        if (null != answerType && answerType == AnswerType.SINGLE_AUDIO) {
+            return "audio";
+        }
+
+        return "text";
+    }
+
+    /**
+     * 转化过的作答
+     *
+     * @param studentAnswer
+     * @return
+     */
+    private String transformedStudentAnswer(String studentAnswer) {
+        if (StringUtils.isNullOrEmpty(studentAnswer)) {
+            return studentAnswer;
+        }
+
+        //图片题特殊处理(因为图片作答题中有html标签)
+        Document doc = Jsoup.parse(studentAnswer);
+        Elements imgElements = doc.select("img[src]");
+        String imgStudentAnswer = "";
+        for (Element el : imgElements) {
+            String src = el.attr("src");
+            if (!StringUtils.isNullOrEmpty(src)) {
+                imgStudentAnswer += src + "|";
+            }
+        }
+        if (!StringUtils.isNullOrEmpty(imgStudentAnswer)) {
+            return imgStudentAnswer.substring(0, imgStudentAnswer.length() - 1);
+        }
+
+        return studentAnswer;
+    }
+}

+ 65 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreCloudServiceProvider.java

@@ -0,0 +1,65 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamScoreCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamScoreBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.QueryExamScoreReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.QueryExamScoreResp;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+
+@Api(tags = "考试分数信息相关接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examScore")
+public class ExamScoreCloudServiceProvider extends ControllerSupport implements ExamScoreCloudService{
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -1957765271029136836L;
+	
+	@Autowired
+	private ExamScoreRepo examScoreRepo;
+	
+	@Override
+	@ApiOperation(value = "分数查询")
+	@PostMapping("/queryExamScore")
+	public QueryExamScoreResp queryExamScore(@RequestBody QueryExamScoreReq req) {
+		List<Long> examRecordDataIds = req.getExamRecordDataIds();
+		
+		if(examRecordDataIds == null || examRecordDataIds.size() == 0){
+			return null;
+		}
+		
+		List<ExamScoreEntity> examScoreList = examScoreRepo.findByExamRecordDataIdIn(req.getExamRecordDataIds());
+		
+		List<ExamScoreBean> examScoreBeans = new ArrayList<ExamScoreBean>();
+		for(ExamScoreEntity examScore:examScoreList){
+			ExamScoreBean examScoreBean = new ExamScoreBean();
+			examScoreBean.setId(examScore.getId());
+			examScoreBean.setExamRecordDataId(examScore.getExamRecordDataId());
+			examScoreBean.setObjectiveAccuracy(examScore.getObjectiveAccuracy());
+			examScoreBean.setObjectiveScore(examScore.getObjectiveScore());
+			examScoreBean.setSubjectiveScore(examScore.getSubjectiveScore());
+			examScoreBean.setSuccPercent(examScore.getSuccPercent());
+			examScoreBean.setTotalScore(examScore.getTotalScore());
+			examScoreBeans.add(examScoreBean);
+		}
+		QueryExamScoreResp queryExamScoreResp = new QueryExamScoreResp();
+		queryExamScoreResp.setExamScoreBeans(examScoreBeans);
+		return queryExamScoreResp;
+	}
+
+}

+ 474 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreDataCloudServiceProvider.java

@@ -0,0 +1,474 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import org.apache.commons.lang3.StringUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+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 cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.basic.api.CourseCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetCourseReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetCourseResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamScoreDataCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamScoreDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.QueryCapturePhotoBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ScoreDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.FindExamScoreDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetAuditDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetFinalScoreDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetScoreDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.QueryCapturePhotoReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.QueryScoreDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.FindExamScoreDataResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetAuditDataResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetFinalScoreDataResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetScoreDataResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.QueryCapturePhotoResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.QueryScoreDataResp;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamAuditRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamCaptureRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamAuditEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamCaptureEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentFinalScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.DisciplineType;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentFinalScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
+import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamReq;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamResp;
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@Api(tags = "考试分数信息相关接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examScoreData")
+public class ExamScoreDataCloudServiceProvider extends ControllerSupport implements ExamScoreDataCloudService {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 8280332746440316604L;
+
+    @Autowired
+    private ExamScoreRepo examScoreRepo;
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamStudentRepo examStudentRepo;
+
+    @Autowired
+    private ExamAuditRepo examAuditRepo;
+
+    @Autowired
+    private ExamCaptureRepo examCaptureRepo;
+
+    @Autowired
+    private GainBaseDataService gainBaseDataService;
+
+    @Autowired
+    private ExamCloudService examCloudService;
+
+    @Autowired
+    private CourseCloudService courseCloudService;
+
+    @Autowired
+    private ExamScoreService examScoreService;
+
+    @Autowired
+    private ExamStudentFinalScoreService examStudentFinalScoreService;
+
+    @Override
+    @ApiOperation(value = "根据分数id查询考试记录,学生信息,分数信息等数据")
+    @PostMapping("/findExamScoreDataByScoreId")
+    public FindExamScoreDataResp findExamScoreDataByScoreId(@RequestBody FindExamScoreDataReq findExamScoreDataReq) {
+        long scoreId = findExamScoreDataReq.getScoreId();
+        ExamScoreEntity examScore = GlobalHelper.getEntity(examScoreRepo, scoreId, ExamScoreEntity.class);
+        ExamRecordDataEntity examRecordDataEntity = GlobalHelper.getEntity(examRecordDataRepo, examScore.getExamRecordDataId(), ExamRecordDataEntity.class);
+        ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(examRecordDataEntity.getExamStudentId());
+
+        FindExamScoreDataResp findExamScoreDataResp = new FindExamScoreDataResp();
+        List<ExamScoreDataBean> examScoreDatas = new ArrayList<ExamScoreDataBean>();
+        examScoreDatas.add(buildExamScoreDataBean(examScore, examRecordDataEntity, examStudentEntity));
+        findExamScoreDataResp.setExamScoreDatas(examScoreDatas);
+
+        return findExamScoreDataResp;
+    }
+
+    @Override
+    @ApiOperation(value = "根据examIdAndStudentCode查询考试记录,学生信息,分数信息等数据")
+    @PostMapping("/findExamScoreDataByExamIdAndStudentCode")
+    public FindExamScoreDataResp findExamScoreDataByExamIdAndStudentCode(@RequestBody FindExamScoreDataReq findExamScoreDataReq) {
+        Long examId = findExamScoreDataReq.getExamId();
+        String studentCode = findExamScoreDataReq.getStudentCode();
+        List<ExamRecordDataEntity> examRecordDataList = examRecordDataRepo.findByExamIdAndStudentCode(examId, studentCode);
+
+        List<ExamScoreDataBean> examScoreDatas = new ArrayList<ExamScoreDataBean>();
+        for (ExamRecordDataEntity examRecordData : examRecordDataList) {
+            ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(examRecordData.getId());
+            ExamStudentEntity examStudent = examStudentRepo.findByExamStudentId(examRecordData.getExamStudentId());
+            examScoreDatas.add(buildExamScoreDataBean(examScore, examRecordData, examStudent));
+        }
+
+        FindExamScoreDataResp findExamScoreDataResp = new FindExamScoreDataResp();
+        findExamScoreDataResp.setExamScoreDatas(examScoreDatas);
+        return findExamScoreDataResp;
+    }
+
+    private ExamScoreDataBean buildExamScoreDataBean(
+            ExamScoreEntity examScore, ExamRecordDataEntity examRecordDataEntity, ExamStudentEntity examStudentEntity) {
+        ExamScoreDataBean examScoreDataBean = new ExamScoreDataBean();
+        examScoreDataBean.setExamId(examStudentEntity.getExamId());
+        examScoreDataBean.setStudentName(examStudentEntity.getStudentName());
+        examScoreDataBean.setStudentCode(examStudentEntity.getStudentCode());
+        examScoreDataBean.setIdentityNumber(examStudentEntity.getIdentityNumber());
+
+        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
+        examScoreDataBean.setCourseName(courseBean.getName());
+        examScoreDataBean.setCourseCode(examStudentEntity.getCourseCode());
+        examScoreDataBean.setStartTime(examRecordDataEntity.getStartTime());
+        examScoreDataBean.setEndTime(examRecordDataEntity.getEndTime());
+        examScoreDataBean.setSuccPercent(examRecordDataEntity.getFaceSuccessPercent());
+        examScoreDataBean.setIsIllegality(examRecordDataEntity.getIsIllegality());
+        examScoreDataBean.setScoreId(examScore.getId());
+        examScoreDataBean.setTotalScore(examScore.getTotalScore());
+        examScoreDataBean.setObjectiveScore(examScore.getObjectiveScore());
+        examScoreDataBean.setSubjectiveScore(examScore.getSubjectiveScore());
+        return examScoreDataBean;
+    }
+
+    @Override
+    @ApiOperation(value = "根据examRecordDataId查询成绩数据")
+    @PostMapping("/getScoreData")
+    public GetScoreDataResp getScoreData(@RequestBody GetScoreDataReq req) {
+        Long examRecordDataId = req.getExamRecordDataId();
+        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+        if (null == examRecordData) {
+            throw new StatusException("getScoreData-exception", "请求参数不正确");
+        }
+        ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(examRecordDataId);
+
+        GetScoreDataResp resp = new GetScoreDataResp();
+        resp.setExamRecordDataId(examRecordDataId);
+        resp.setStudentCode(examRecordData.getStudentCode());
+        resp.setStudentName(examRecordData.getStudentName());
+        resp.setIdentityNumber(examRecordData.getIdentityNumber());
+
+        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examRecordData.getCourseId());
+        resp.setCourseCode(courseBean.getCode());
+        resp.setCourseName(courseBean.getName());
+        resp.setStartTime(examRecordData.getStartTime());
+        resp.setEndTime(examRecordData.getEndTime());
+        resp.setTotalScore(examScore.getTotalScore());
+
+        resp.setIsWarn(examRecordData.getIsWarn());
+        resp.setIsAudit(examRecordData.getIsAudit());
+        resp.setIsIllegality(examRecordData.getIsIllegality());
+
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "根据examRecordDataId查询成绩审核数据")
+    @PostMapping("/getAuditData")
+    public GetAuditDataResp getAuditData(@RequestBody GetAuditDataReq req) {
+        Check.isEmpty(req.getExamRecordDataId(), "考试记录ID不能为空");
+        ExamAuditEntity examAuditEntity = examAuditRepo.findByExamRecordDataId(req.getExamRecordDataId());
+        //考试记录
+        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, req.getExamRecordDataId(), ExamRecordDataEntity.class);
+        if (null == examRecordData) {
+            throw new StatusException("100001", "找不到考试记录id为" + req.getExamRecordDataId() + "数据");
+        }
+        GetAuditDataResp resp = new GetAuditDataResp();
+        if (examAuditEntity != null) {
+            resp.setExamRecordDataId(examAuditEntity.getExamRecordDataId());
+            DisciplineType disciplineType = examAuditEntity.getDisciplineType();
+            if (disciplineType != null) {
+                resp.setDisciplineType(disciplineType.getName());
+            }
+            resp.setDisciplineDetail(examAuditEntity.getDisciplineDetail());
+            resp.setCreationTime(examAuditEntity.getCreationTime());
+            resp.setAuditUserName(examAuditEntity.getAuditUserName());
+            //是否违纪
+            resp.setIsDiscipline(examRecordData.getIsIllegality() ? 1 : 0);
+        }
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "根据examRecordDataId查询抓拍照片数据")
+    @PostMapping("/queryCapturePhoto")
+    public QueryCapturePhotoResp queryCapturePhoto(@RequestBody QueryCapturePhotoReq req) {
+        Check.isEmpty(req.getExamRecordDataId(), "考试记录ID不能为空");
+        List<ExamCaptureEntity> examCaptureList = examCaptureRepo.findByExamRecordDataId(req.getExamRecordDataId());
+        QueryCapturePhotoResp queryCapturePhotoResp = new QueryCapturePhotoResp();
+        queryCapturePhotoResp.setExamRecordDataId(req.getExamRecordDataId());
+        List<QueryCapturePhotoBean> capturePhotoList = new ArrayList<QueryCapturePhotoBean>();
+        if (examCaptureList.size() > 0) {
+            for (ExamCaptureEntity examCaptureEntity : examCaptureList) {
+                QueryCapturePhotoBean queryCapturePhotoBean = new QueryCapturePhotoBean();
+                queryCapturePhotoBean.setFileUrl(examCaptureEntity.getFileUrl());
+                queryCapturePhotoBean.setIsPass(examCaptureEntity.getIsPass());
+                queryCapturePhotoBean.setIsStranger(examCaptureEntity.getIsStranger());
+                String faceLivenessResult = examCaptureEntity.getFacelivenessResult();
+                queryCapturePhotoBean.setIsLivenessPass(isLivenessPass(faceLivenessResult));
+                capturePhotoList.add(queryCapturePhotoBean);
+            }
+        }
+        queryCapturePhotoResp.setCapturePhotoList(capturePhotoList);
+        return queryCapturePhotoResp;
+    }
+
+    /**
+     * 人脸真实性是否通过
+     *
+     * @param faceLivenessResult
+     * @return
+     */
+    private Boolean isLivenessPass(String faceLivenessResult) {
+        if (StringUtils.isNotBlank(faceLivenessResult)) {
+            JSONObject jsonObject;
+            try {
+                jsonObject = new JSONObject(faceLivenessResult);
+                if (jsonObject.has("error_code") && jsonObject.getInt("error_code") == 0 && jsonObject.has("result")) {
+                    JSONObject resultJson = jsonObject.getJSONObject("result");
+                    if (resultJson.has("face_liveness")) {
+                        double faceLivenessVal = resultJson.getDouble("face_liveness");
+
+                        Double baiduFacelivenessThreshold;
+                        SysPropertyCacheBean baiduFacelivenessThresholdProperty = CacheHelper.getSysProperty("$baidu.faceliveness.threshold");
+                        if (!baiduFacelivenessThresholdProperty.getHasValue()) {
+                            baiduFacelivenessThreshold = Constants.DEFAULT_BAIDU_FACELIVENESS_THRESHOLD;
+                        } else {
+                            baiduFacelivenessThreshold = Double.valueOf(baiduFacelivenessThresholdProperty.getValue().toString());
+                        }
+
+                        if (faceLivenessVal > baiduFacelivenessThreshold) {
+                            return true;
+                        }
+                    }
+                }
+            } catch (JSONException e) {
+                e.printStackTrace();
+                return false;
+            }
+
+        }
+        return false;
+    }
+
+    @Override
+    @ApiOperation(value = "按考试名称,rootOrgId,courseCode,身份证号或学号查询成绩信息")
+    @PostMapping("/queryScoreData")
+    public QueryScoreDataResp queryScoreData(@RequestBody QueryScoreDataReq req) {
+        if (StringUtils.isBlank(req.getExamName())) {
+            throw new StatusException("OE-ADMIN-1000005", "考试名称不能为空");
+        }
+        if (StringUtils.isBlank(req.getCourseCode())) {
+            throw new StatusException("OE-ADMIN-1000005", "课程code不能为空");
+        }
+        if (req.getRootOrgId() == null) {
+            throw new StatusException("OE-ADMIN-1000005", "rootOrgId不能为空");
+        }
+        if (StringUtils.isBlank(req.getIdentityNumber()) && StringUtils.isBlank(req.getStudentCode())) {
+            throw new StatusException("OE-ADMIN-1000005", "身份证号和学号不能都为空");
+        }
+        GetExamReq getExamReq = new GetExamReq();
+        getExamReq.setRootOrgId(req.getRootOrgId());
+        //如果考试代码为空,则将考试名称作为考试代码
+        if (StringUtils.isEmpty(req.getExamCode())) {
+            getExamReq.setCode(req.getExamName());
+        } else {
+            getExamReq.setCode(req.getExamCode());
+        }
+
+        GetExamResp getExamResp = examCloudService.getExam(getExamReq);
+        ExamBean examBean = getExamResp.getExamBean();
+
+        GetCourseReq getCourseReq = new GetCourseReq();
+        getCourseReq.setRootOrgId(req.getRootOrgId());
+        getCourseReq.setCode(req.getCourseCode());
+        GetCourseResp getCourseResp = courseCloudService.getCourse(getCourseReq);
+        CourseBean courseBean = getCourseResp.getCourseBean();
+
+        ExamRecordDataEntity selectCondition = new ExamRecordDataEntity();
+        selectCondition.setExamId(examBean.getId());
+        selectCondition.setCourseId(courseBean.getId());
+        if (StringUtils.isNotBlank(req.getIdentityNumber())) {
+            selectCondition.setIdentityNumber(req.getIdentityNumber());
+        }
+        if (StringUtils.isNotBlank(req.getStudentCode())) {
+            selectCondition.setStudentCode(req.getStudentCode());
+        }
+        //查询考试记录
+        List<ScoreDataBean> scoreDataBeanList = new ArrayList<ScoreDataBean>();
+        List<ExamRecordDataEntity> examRecordList = examRecordDataRepo.findAll(Example.of(selectCondition));
+        for (ExamRecordDataEntity examRecordData : examRecordList) {
+            ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(examRecordData.getId());
+            if (examScore == null) {
+                throw new StatusException("100001", "该考生未交卷");
+            }
+
+            ScoreDataBean scoreDataBean = new ScoreDataBean();
+            scoreDataBean.setExamRecordDataId(examRecordData.getId());
+            scoreDataBean.setStudentCode(examRecordData.getStudentCode());
+            scoreDataBean.setStudentName(examRecordData.getStudentName());
+            scoreDataBean.setIdentityNumber(examRecordData.getIdentityNumber());
+
+            scoreDataBean.setCourseCode(courseBean.getCode());
+            scoreDataBean.setCourseName(courseBean.getName());
+            scoreDataBean.setStartTime(examRecordData.getStartTime());
+            scoreDataBean.setEndTime(examRecordData.getEndTime());
+            scoreDataBean.setTotalScore(examScore.getTotalScore());
+
+            scoreDataBean.setIsWarn(examRecordData.getIsWarn());
+            scoreDataBean.setIsAudit(examRecordData.getIsAudit());
+            scoreDataBean.setIsIllegality(examRecordData.getIsIllegality());
+            scoreDataBeanList.add(scoreDataBean);
+        }
+        QueryScoreDataResp queryScoreDataResp = new QueryScoreDataResp();
+        queryScoreDataResp.setScoreDataBeanList(scoreDataBeanList);
+        return queryScoreDataResp;
+    }
+
+    @ApiOperation(value = "按考试信息,rootOrgId,courseCode,身份证号或学号查询最终成绩信息")
+    @PostMapping("/getFinalScoreData")
+    @Override
+    public GetFinalScoreDataResp getFinalScoreData(@RequestBody GetFinalScoreDataReq req) {
+        if (req.getRootOrgId() == null) {
+            throw new StatusException("100002", "学校id不允许为空");
+        }
+        if (req.getExamId() == null) {
+            throw new StatusException("100003", "考试id不允许为空");
+        }
+        //只支持离线和在线考试
+        ExamBean examSettings = ExamCacheTransferHelper.getDefaultCachedExam(req.getExamId());
+        if (!(ExamType.ONLINE.toString().equals(examSettings.getExamType()) ||
+                ExamType.OFFLINE.toString().equals(examSettings.getExamType()) ||
+                ExamType.ONLINE_HOMEWORK.toString().equals(examSettings.getExamType()))) {
+            throw new StatusException("100009", "不支持的考试类型");
+        }
+
+        if (StringUtils.isEmpty(req.getIdentityNumber()) && StringUtils.isEmpty(req.getStudentCode())) {
+            throw new StatusException("100004", "学号和身份证号不能同时为空");
+        }
+        if (StringUtils.isEmpty(req.getCourseCode())) {
+            throw new StatusException("100005", "课程代码不允许为空");
+        }
+        String identityNumber = req.getIdentityNumber();
+        if (StringUtils.isEmpty(identityNumber)) {
+            identityNumber = gainBaseDataService.getStudentBean(req.getStudentCode(),
+                    req.getRootOrgId()).getIdentityNumber();
+            if (StringUtils.isEmpty(identityNumber)) {
+                throw new StatusException("100006", "找不到学号对应的学生身份证信息");
+            }
+        }
+
+        CourseBean courseBean = gainBaseDataService.getCourseBean(req.getCourseCode(), req.getRootOrgId());
+        Long courseId = courseBean.getId();
+        if (courseId == null) {
+            throw new StatusException("100007", "课程代码不正确");
+        }
+
+        GetFinalScoreDataResp resp = new GetFinalScoreDataResp();
+        //如果该学生缺考,直接返回
+        ExamStudentEntity examStudent = examStudentRepo.findByIdentityNumberAndExamIdAndCourseId(
+                identityNumber, req.getExamId(), courseId);
+        if (examStudent == null) {
+            throw new StatusException("100008", "考生不存在");
+        }
+        Boolean isAbsent = examStudent.getFinished() == null || !examStudent.getFinished();
+        if (isAbsent) {
+            resp.setAbsent(true);
+            return resp;
+        }
+
+        //最终生效的考试分数对象
+        ExamStudentFinalScoreEntity finalExamScore =
+                examStudentFinalScoreService.getFinalEffectiveExamScore(examStudent.getExamStudentId());
+
+        //如果查到最终有分数数据,则直接返回
+        if (null != finalExamScore) {
+            ScoreDataBean scoreDataBean = buildScoreDataBean(identityNumber, courseBean,
+                    finalExamScore.getTotalScore(), finalExamScore.getExamRecordDataId());
+            resp.setScoreDataBean(scoreDataBean);
+            resp.setAbsent(false);
+            resp.setIllegality(false);
+            resp.setAudit(scoreDataBean.getIsAudit());
+            return resp;
+        }
+
+        //如果查不到有效的分数集合,则将所有的考试分数集合均返回
+        resp.setAbsent(false);
+        List<ExamScoreEntity> allExamScoreList =
+                examScoreService.getAllExamScoreList(req.getExamId(), identityNumber, courseId);
+        if (allExamScoreList == null) {
+            return resp;
+        }
+
+        List<ScoreDataBean> scoreDataBeanList = new ArrayList<>();
+        for (ExamScoreEntity scoreEntity : allExamScoreList) {
+            scoreDataBeanList.add(buildScoreDataBean(identityNumber,
+                    courseBean, scoreEntity.getTotalScore(), scoreEntity.getExamRecordDataId()));
+        }
+        resp.setAllScoreDataBeanList(scoreDataBeanList);
+        resp.setAudit(scoreDataBeanList.stream().anyMatch(p -> p.getIsAudit() == null || !p.getIsAudit()));
+        resp.setIllegality(scoreDataBeanList.stream().anyMatch(p -> p.getIsIllegality() != null && p.getIsIllegality()));
+        return resp;
+
+    }
+
+    private ScoreDataBean buildScoreDataBean(String identityNumber, final CourseBean courseBean,
+                                             Double totalScore, Long examRecordDataId) {
+        ScoreDataBean scoreDataBean = new ScoreDataBean();
+        scoreDataBean.setIdentityNumber(identityNumber);
+        scoreDataBean.setCourseCode(courseBean.getCode());
+        scoreDataBean.setCourseName(courseBean.getName());
+
+        scoreDataBean.setTotalScore(totalScore);
+        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo,
+                examRecordDataId, ExamRecordDataEntity.class);
+
+        scoreDataBean.setEndTime(examRecordData.getEndTime());
+        scoreDataBean.setExamId(examRecordData.getExamId());
+        scoreDataBean.setExamRecordDataId(examRecordData.getId());
+        scoreDataBean.setIsAudit(examRecordData.getIsAudit());
+        scoreDataBean.setIsIllegality(examRecordData.getIsIllegality());
+        scoreDataBean.setIsWarn(examRecordData.getIsWarn());
+        scoreDataBean.setStartTime(examRecordData.getStartTime());
+        scoreDataBean.setStudentCode(examRecordData.getStudentCode());
+        scoreDataBean.setStudentName(examRecordData.getStudentName());
+
+        ExamBean examSettings = ExamCacheTransferHelper.getDefaultCachedExam(examRecordData.getExamId());
+        scoreDataBean.setExamCode(examSettings.getCode());
+        scoreDataBean.setExamName(examSettings.getName());
+        return scoreDataBean;
+    }
+
+}

+ 82 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreNoticeQueueCloudServiceProvider.java

@@ -0,0 +1,82 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Date;
+import java.util.List;
+
+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.RestController;
+
+import cn.com.qmth.examcloud.commons.util.OKHttpUtil;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamScoreNoticeQueueCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreNoticeQueueRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreNoticeQueueEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreObtainQueueService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.NotifyUrlInfo;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@Api(tags = "定时发送获取分数通知接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examScoreNoticeQueue")
+public class ExamScoreNoticeQueueCloudServiceProvider extends ControllerSupport
+		implements ExamScoreNoticeQueueCloudService {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -2622841462905285211L;
+
+	@Autowired
+	private ExamScoreNoticeQueueRepo examScoreNoticeQueueRepo;
+	@Autowired
+	private ExamScoreObtainQueueService examScoreObtainQueueService;
+
+	/**
+	 * 定时发送分数获取通知 定时任务每5分钟发送一次
+	 */
+	@ApiOperation(value = "定时发送获取分数通知接口")
+	@PostMapping("/sendObtainScoreNotice")
+	@Override
+	public void sendObtainScoreNotice() {
+		log.info("开始执行sendObtainScoreNotice");
+		// 获取所有的通知队列
+		List<ExamScoreNoticeQueueEntity> examScoreNoticeQueueList = examScoreNoticeQueueRepo.findAll();
+		if (examScoreNoticeQueueList == null || examScoreNoticeQueueList.size() == 0) {
+			return;
+		}
+		for (ExamScoreNoticeQueueEntity noticeEntity : examScoreNoticeQueueList) {
+			// 获取当前组织机构的通知对象
+			NotifyUrlInfo notifyUrlInfo = examScoreObtainQueueService.getNotifyUrlInfo(noticeEntity.getRootOrgId());
+			// 只有配置了通知接口的才发送通知
+			if (StringUtils.isNotBlank(notifyUrlInfo.getNotifyUrl())) {
+				try {
+					OKHttpUtil.call(notifyUrlInfo.getHttpMethod(), notifyUrlInfo.getNotifyUrl());
+					// 发送通知没有问题,则清除通知队列中数据
+					examScoreNoticeQueueRepo.deleteById(noticeEntity.getRootOrgId());
+				} catch (Exception e) {
+					if (e instanceof UnknownHostException || e instanceof SocketException) {
+						try {
+							// 如果是由于连接超时,或者读取数据超时导致异常,需要对发送通知失败次数进行累加
+							long failTimes = noticeEntity.getFailTimes() == null ? 0
+									: noticeEntity.getFailTimes().longValue();
+							noticeEntity.setFailTimes(failTimes + 1);
+							noticeEntity.setUpdateTime(new Date());
+							examScoreNoticeQueueRepo.save(noticeEntity);
+						} catch (Exception e1) {
+							log.error("examScoreNoticeQueueRepo.save exception:" + e1.getMessage(), e1);
+						}
+					}
+					log.error("OeExamScoreNoticeQueueCloudServiceProvider-sendObtainScoreNotice:" + e.getMessage(), e);
+				}
+			}
+		}
+		log.info("结束执行sendObtainScoreNotice");
+	}
+
+}

+ 108 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreObtainQueueCloudServiceProvider.java

@@ -0,0 +1,108 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamScoreObtainQueueCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.DeleteExamScoreQueueReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetTopExamScoreQueueReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetTopExamScoreQueueResp;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.DateUtils;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreNoticeQueueRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreObtainQueueRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreNoticeQueueEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreObtainQueueEntity;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@Api(tags = "考试分数队列接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examScoreObtainQueue")
+public class ExamScoreObtainQueueCloudServiceProvider extends ControllerSupport
+        implements ExamScoreObtainQueueCloudService {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 9204539038969246524L;
+
+    @Autowired
+    private ExamScoreObtainQueueRepo examScoreObtainQueueRepo;
+
+    @Autowired
+    private ExamScoreNoticeQueueRepo examScoreNoticeQueueRepo;
+
+    @Override
+    @ApiOperation(value = "获取当前机构分数队列顶层记录")
+    @PostMapping("/getTopExamScoreQueue")
+    public GetTopExamScoreQueueResp getTopExamScoreQueue(@RequestBody GetTopExamScoreQueueReq req) {
+        Check.isNull(req.getRootOrgId(), "顶级机构ID不能为空");
+        ExamScoreObtainQueueEntity examScoreObtainQueue = examScoreObtainQueueRepo
+                .findTopByRootOrgIdAndIsValidOrderByIdAsc(req.getRootOrgId(), true);
+        GetTopExamScoreQueueResp getTopExamScoreQueueResp = new GetTopExamScoreQueueResp();
+        getTopExamScoreQueueResp.setIsEmpty(true);
+        if (examScoreObtainQueue != null) {
+            getTopExamScoreQueueResp.setExamId(examScoreObtainQueue.getExamId());
+            getTopExamScoreQueueResp.setQueueId(examScoreObtainQueue.getId());
+            getTopExamScoreQueueResp.setExamRecordDataId(examScoreObtainQueue.getExamRecordDataId());
+            getTopExamScoreQueueResp.setIsEmpty(false);
+        }
+        return getTopExamScoreQueueResp;
+    }
+
+    @Override
+    @ApiOperation(value = "删除分数队列记录")
+    @PostMapping("/deleteExamScoreQueue")
+    public void deleteExamScoreQueue(@RequestBody DeleteExamScoreQueueReq req) {
+        Check.isNull(req.getQueueId(), "队列ID不能为空");
+        Check.isNull(req.getRootOrgId(), "顶级机构ID不能为空");
+        ExamScoreObtainQueueEntity examScoreObtainQueue = GlobalHelper.getEntity(examScoreObtainQueueRepo,
+                req.getQueueId(), ExamScoreObtainQueueEntity.class);
+        if (examScoreObtainQueue != null && examScoreObtainQueue.getRootOrgId().equals(req.getRootOrgId())) {
+            examScoreObtainQueue.setIsValid(false);
+            examScoreObtainQueueRepo.save(examScoreObtainQueue);
+        }
+    }
+
+    /**
+     * 定时更新获取分数通知队列
+     * 
+     */
+    @ApiOperation(value = "定时更新获取分数通知队列")
+    @PostMapping("/updateObtainScoreNodifyQueue")
+    @Override
+    public void updateObtainScoreNodifyQueue() {
+        Date now = new Date();
+        Date twoHoursBefore = DateUtils.addHours(now, -2);
+        // 1.查找超过创建超过2小时,且通知队列中不存在的待获取的数据
+        List<ExamScoreObtainQueueEntity> toObtainQueueList = examScoreObtainQueueRepo
+                .findByCreationTimeLessThanEquals(twoHoursBefore);
+        // 1.1如果不存在,不作任何处理
+        if (toObtainQueueList == null || toObtainQueueList.isEmpty()) {
+            return;
+        }
+        // 1.2如果存在,则创建相应组织机构的通知队列
+        List<Long> rootOrgList = toObtainQueueList.stream().map(ExamScoreObtainQueueEntity::getRootOrgId).distinct()
+                .collect(Collectors.<Long> toList());
+        List<ExamScoreNoticeQueueEntity> noticeQueueEntityList = new ArrayList<ExamScoreNoticeQueueEntity>();
+        rootOrgList.forEach(ro -> {
+            ExamScoreNoticeQueueEntity noticeQueueEntity = new ExamScoreNoticeQueueEntity();
+            noticeQueueEntity.setRootOrgId(ro);
+            noticeQueueEntity.setCreationTime(now);
+            noticeQueueEntityList.add(noticeQueueEntity);
+        });
+        // 批量插入通知队列
+        examScoreNoticeQueueRepo.saveAll(noticeQueueEntityList);
+    }
+}

+ 212 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamStudentCloudServiceProvider.java

@@ -0,0 +1,212 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-15 14:15:05.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.api.OeExamStudentCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamStudentCountBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.FindStudentType;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.OeExamStudentBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ToBeMarkExamStudentBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.*;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.*;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordForMarkingRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordForMarkingEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import com.mysql.cj.util.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 考生信息接口
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/15
+ */
+@Api(tags = "考生信息相关接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/exam/student")
+public class ExamStudentCloudServiceProvider extends ControllerSupport implements OeExamStudentCloudService {
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1638873981075522570L;
+
+    @Autowired
+    private ExamStudentRepo examStudentRepo;
+
+    @Autowired
+    private ExamRecordForMarkingRepo examRecordForMarkingRepo;
+
+    @Deprecated
+    @ApiOperation(value = "同步考生(全部)数据")
+    @PostMapping("/sync/all/data")
+    public ExamStudentSyncAllDataResp syncExamStudentAllData(@RequestBody ExamStudentSyncAllDataReq req) {
+        /*List<ExamStudentInfo> examStudents = ExamStudentBeanConvert.of(req);
+        examStudentService.syncExamStudentAllData(examStudents);
+        return new ExamStudentSyncAllDataResp();*/
+        return null;
+    }
+
+    @Deprecated
+    @ApiOperation(value = "同步考生(部分)数据")
+    @PostMapping("/sync/part/data")
+    public ExamStudentSyncPartDataResp syncExamStudentPartData(@RequestBody ExamStudentSyncPartDataReq req) {
+        /*List<ExamStudentPartInfo> examStudents = ExamStudentBeanConvert.of(req);
+        examStudentService.syncExamStudentPartData(examStudents);
+        return new ExamStudentSyncPartDataResp();*/
+        return null;
+    }
+
+    @Override
+    @ApiOperation(value = "根据条件查询考生")
+    @PostMapping("/findExamStudentInfoBy")
+    public FindExamStudentInfoResp findExamStudentInfoBy(@RequestBody FindExamStudentInfoReq req) {
+        FindStudentType findStudentType = req.getFindStudentType();
+        if (findStudentType == null) {
+            throw new StatusException("ExamStudentCloudServiceProvider-findExamStudentInfoBy-exception", "查询方式不能为空");
+        }
+        FindExamStudentInfoResp resp = new FindExamStudentInfoResp();
+
+        if (findStudentType == FindStudentType.BY_EXAMID) {
+            Long examId = req.getFindStudentBean().getExamId();
+            if (examId != null) {
+                List<ExamStudentEntity> examStudents = examStudentRepo.findByExamId(examId);
+                resp.setExamStudents(buildExamStudentBeans(examStudents));
+            }
+        } else if (findStudentType == FindStudentType.BY_EXAMID_AND_IDENTITYNUMBERS) {
+            Long examId = req.getFindStudentBean().getExamId();
+            List<String> identityNumbes = req.getFindStudentBean().getIdentityNumbers();
+            if (examId != null && identityNumbes != null && identityNumbes.size() > 0) {
+                List<ExamStudentEntity> examStudents = examStudentRepo.findByExamIdAndIdentityNumberIn(examId, identityNumbes);
+                resp.setExamStudents(buildExamStudentBeans(examStudents));
+            }
+        } else if (findStudentType == FindStudentType.BY_IDENTITYNUMBERS_AND_ROOTORGID) {
+            Long rootOrgId = req.getFindStudentBean().getRootOrgId();
+            List<String> identityNumbes = req.getFindStudentBean().getIdentityNumbers();
+            if (rootOrgId != null && identityNumbes != null && identityNumbes.size() > 0) {
+                List<ExamStudentEntity> examStudents = examStudentRepo.findByRootOrgIdAndIdentityNumberIn(rootOrgId, identityNumbes);
+                resp.setExamStudents(buildExamStudentBeans(examStudents));
+            }
+        } else if (findStudentType == FindStudentType.BY_EXAMID_AND_STUDENTCODES) {
+            Long examId = req.getFindStudentBean().getExamId();
+            List<String> studentCodes = req.getFindStudentBean().getStudentCodes();
+            if (examId != null && studentCodes != null && studentCodes.size() > 0) {
+                List<ExamStudentEntity> examStudents = examStudentRepo.findByExamIdAndStudentCodeIn(examId, studentCodes);
+                resp.setExamStudents(buildExamStudentBeans(examStudents));
+            }
+        }
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "解绑考生学号")
+    @PostMapping("/unbindExamStudent")
+    public UnbindExamStudentResp unbindExamStudent(UnbindExamStudentReq req) {
+        UnbindExamStudentResp resp = new UnbindExamStudentResp();
+        if (StringUtils.isNullOrEmpty(req.getIdentityNumber())) {
+            resp.setSuccess(false);
+            resp.setMsg("身份证号不允许为空");
+            return resp;
+        }
+        if (null == req.getStudentCodeList() || req.getStudentCodeList().isEmpty()) {
+            resp.setSuccess(false);
+            resp.setMsg("学号不允许为空");
+            return resp;
+        }
+        examStudentRepo.updateExamStudentCode(req.getIdentityNumber(), req.getStudentCodeList());
+        resp.setSuccess(true);
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "根据考试批次等查询条件获取考生数量(一次最多返回100条数据)")
+    @PostMapping("/getExamStudentCount")
+    public GetExamStudentCountResp getExamStudentCount(@RequestBody GetExamStudentCountReq req) {
+        if (req.getExamIdList() == null || req.getExamIdList().size() == 0) {
+            throw new StatusException("100001", "考试id不允许为空");
+        }
+
+        if (req.getExamIdList().size() > 100) {
+            throw new StatusException("100002", "考试id个数不得超过100个");
+        }
+
+        Long count;
+        List<ExamStudentCountBean> countBeanList = new ArrayList<>();
+        for (Long examId : req.getExamIdList()) {
+            if (req.getFinishedExam() == null) {
+                count = examStudentRepo.countByExamId(examId);
+            } else {
+                count = examStudentRepo.countByExamIdAndFinished(examId, req.getFinishedExam());
+            }
+
+            ExamStudentCountBean countBean = new ExamStudentCountBean();
+            countBean.setExamId(examId);
+            countBean.setCount(count == null ? 0 : count);
+            countBeanList.add(countBean);
+        }
+
+        GetExamStudentCountResp resp = new GetExamStudentCountResp();
+        resp.setCountList(countBeanList);
+        return resp;
+    }
+
+    /**
+     * 根据查询条件获取所有待阅卷的考生
+     *
+     * @param req
+     * @return
+     */
+    @Override
+    @ApiOperation(value = "获取待阅卷的考生记录")
+    @PostMapping("/getToBeMarkExamStudent")
+    public GetToBeMarkExamStudentResp getToBeMarkExamStudent(@RequestBody GetToBeMarkExamStudentReq req) {
+        List<ExamRecordForMarkingEntity> examRecordForMarkingList = examRecordForMarkingRepo.findByExamId(req.getExamId());
+        GetToBeMarkExamStudentResp resp = new GetToBeMarkExamStudentResp();
+
+        List<Long> examStudentIdList = examRecordForMarkingList.stream().
+                map(p -> p.getExamStudentId()).distinct().collect(Collectors.toList());
+
+        List<ToBeMarkExamStudentBean> examStudentBeanList = new ArrayList<>();
+        for (Long examStudentId : examStudentIdList) {
+            ToBeMarkExamStudentBean examStudentBean = new ToBeMarkExamStudentBean();
+            examStudentBean.setExamStudentId(examStudentId);
+            examStudentBeanList.add(examStudentBean);
+        }
+
+        resp.setExamStudentList(examStudentBeanList);
+        return resp;
+    }
+
+    private List<OeExamStudentBean> buildExamStudentBeans(List<ExamStudentEntity> examStudents) {
+        List<OeExamStudentBean> examStudentBeans = new ArrayList<OeExamStudentBean>();
+        for (ExamStudentEntity examStudent : examStudents) {
+            OeExamStudentBean examStudentBean = new OeExamStudentBean();
+            examStudentBean.setExamStudentId(examStudent.getExamStudentId());
+            examStudentBean.setExamId(examStudent.getExamId());
+            examStudentBean.setStudentCode(examStudent.getStudentCode());
+            examStudentBean.setStudentName(examStudent.getStudentName());
+            examStudentBean.setIdentityNumber(examStudent.getIdentityNumber());
+            examStudentBean.setCourseCode(examStudent.getCourseCode());
+            examStudentBean.setFinished(examStudent.getFinished());
+            examStudentBeans.add(examStudentBean);
+        }
+        return examStudentBeans;
+    }
+}

+ 180 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamStudentDataCloudServiceProvider.java

@@ -0,0 +1,180 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import java.util.ArrayList;
+import java.util.DoubleSummaryStatistics;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamStudentDataCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamStudentDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamStudentScoreDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetExamStudentDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetExamStudentDataResp;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordQuestionsRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamQuestionEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordQuestionsEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.ExamStudentEffectiveScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * @Description 获取考生考试相关数据接口
+ * @Author lideyin
+ * @Date 2019/7/18 11:10
+ * @Version 1.0
+ */
+@Api(tags = "获取考生考试相关数据接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/examStudentData")
+public class ExamStudentDataCloudServiceProvider extends ControllerSupport implements ExamStudentDataCloudService {
+    /**
+     *
+     */
+    private static final long serialVersionUID = 4538819077863159166L;
+    @Autowired
+    ExamRecordQuestionsRepo examRecordQuestionsRepo;
+    @Autowired
+    private ExamStudentService examStudentService;
+    @Autowired
+    private GainBaseDataService gainBaseDataService;
+    @Autowired
+    private ExamRecordService examRecordService;
+
+    @Override
+    @ApiOperation(value = "获取考生分数相关数据")
+    @PostMapping("/getExamStudentData")
+    public GetExamStudentDataResp getExamStudentData(@RequestBody GetExamStudentDataReq req) {
+        validateRequestData(req);
+        Long nextId = req.getStartId();
+
+        GetExamStudentDataResp resp = new GetExamStudentDataResp();
+        List<ExamStudentInfo> examStudentList = examStudentService.getLimitExamStudentList(req.getExamId(), req.getStartId(), req.getSize());
+        if (examStudentList == null || examStudentList.isEmpty()) {
+            resp.setNextId(nextId);
+            return resp;
+        }
+
+        nextId = examStudentList.get(examStudentList.size() - 1).getId();
+        nextId++;
+        resp.setNextId(nextId);
+
+        List<Long> examStudentIdList = examStudentList.stream().map(p -> p.getExamStudentId()).collect(Collectors.toList());
+        //阅卷方式
+        String markingType = ExamCacheTransferHelper.
+                getDefaultCachedExamProperty(req.getExamId(), ExamProperties.MARKING_TYPE.name()).getValue();
+        List<ExamStudentEffectiveScoreInfo> effectiveScoreList = examRecordService.getExamStudentEffectiveScoreList(markingType, examStudentIdList);
+        List<ExamRecordQuestionsEntity> questionList = null;
+        if (effectiveScoreList != null && !effectiveScoreList.isEmpty()) {
+            List<Long> examRecordDataIdList = effectiveScoreList.stream().map(p -> p.getExamRecordDataId()).collect(Collectors.toList());
+            questionList = examRecordQuestionsRepo.findByExamRecordDataIdIn(examRecordDataIdList);
+            //错误数据的特殊处理
+            String strErrorExamRecordDataIds = "";
+            List<Long> errorExamRecordDataIdList;
+            if (questionList == null || questionList.isEmpty()) {
+                errorExamRecordDataIdList = effectiveScoreList.stream().map(p -> p.getExamRecordDataId()).collect(Collectors.toList());
+                for (Long eid : errorExamRecordDataIdList) {
+                    strErrorExamRecordDataIds += eid + ",";
+                }
+                log.error("[GETEXAMSTUDENTDATA],200105考试作答数据异常,错误的考试记录id:" + strErrorExamRecordDataIds);
+                throw new StatusException("200105", "考试作答数据异常,相关考试记录id为:" + strErrorExamRecordDataIds);
+            }
+            if (questionList.size() != effectiveScoreList.size()) {
+                List<Long> finalExamRecordDataIdList = questionList.stream().map(p -> p.getExamRecordDataId()).collect(Collectors.toList());
+                errorExamRecordDataIdList = effectiveScoreList.stream().filter(p -> !finalExamRecordDataIdList.contains(p.getExamRecordDataId())).map(p -> p.getExamRecordDataId()).collect(Collectors.toList());
+                for (Long eid : errorExamRecordDataIdList) {
+                    strErrorExamRecordDataIds += eid + ",";
+                }
+                log.error("[GETEXAMSTUDENTDATA],200106考试作答数据异常,错误的考试记录id:" + strErrorExamRecordDataIds);
+                throw new StatusException("200106", "考试作答数据异常,相关考试记录id为:" + strErrorExamRecordDataIds);
+            }
+        }
+        List<ExamStudentDataBean> examStudentDataBeanList = new ArrayList<>();
+        for (ExamStudentInfo es : examStudentList) {
+            ExamStudentDataBean esBean = new ExamStudentDataBean();
+            ExamStudentEffectiveScoreInfo score = null;
+
+            esBean.setExamStudentId(es.getExamStudentId());
+            esBean.setExamId(es.getExamId());
+            esBean.setCourseCode(es.getCourseCode());
+            esBean.setCourseId(es.getCourseId());
+            esBean.setAbsent(!es.getFinished());
+            esBean.setRootOrgId(es.getRootOrgId());
+            esBean.setOrgId(es.getOrgId());
+
+            if (effectiveScoreList != null && !effectiveScoreList.isEmpty()) {
+                List<ExamStudentEffectiveScoreInfo> scoreList = effectiveScoreList.stream().
+                        filter(p -> p.getExamStudentId().equals(es.getExamStudentId())).collect(Collectors.toList());
+                if (scoreList != null && !scoreList.isEmpty()) {
+                    score = scoreList.get(0);
+                }
+                if (score != null) {
+                    esBean.setBasePaperId(score.getBasePaperId());
+                    //考生总得分
+                    esBean.setScore(score.getScore());
+
+                    ExamStudentEffectiveScoreInfo finalScore = score;
+                    //试卷总分
+                    List<ExamRecordQuestionsEntity> curQuestionList = questionList.stream().
+                            filter(p -> p.getExamRecordDataId().equals(finalScore.getExamRecordDataId())).collect(Collectors.toList());
+//                    if (curQuestionList==null || curQuestionList.isEmpty()){
+//                        //正常的数据不会出现这种情况con,暂时先记日志
+//                        log.error("[GETEXAMSTUDENTDATA]200106考试作答记录数据异常,examRecordDataId:"+finalScore.getExamRecordDataId());
+////                        throw new StatusException("200106","考试作答记录数据异常");
+//                        continue;
+//                    }
+                    ExamRecordQuestionsEntity examRecordQuestionsEntity = curQuestionList.get(0);
+                    List<ExamQuestionEntity> examQuestionList = examRecordQuestionsEntity.getExamQuestionEntities();
+                    DoubleSummaryStatistics questionScoreStatistics = examQuestionList.stream().
+                            collect(Collectors.summarizingDouble(ExamQuestionEntity::getQuestionScore));
+
+                    esBean.setTotalScore(questionScoreStatistics.getSum());
+                    List<ExamStudentScoreDataBean> examStudentScoreDetailList = new ArrayList<>();
+                    for (ExamQuestionEntity eq : examQuestionList) {
+                        ExamStudentScoreDataBean essBean = new ExamStudentScoreDataBean();
+                        essBean.setQuestionId(eq.getQuestionId());
+                        essBean.setQuestionOrder(eq.getOrder());
+                        essBean.setScore(eq.getStudentScore());
+                        essBean.setTotalScore(eq.getQuestionScore());
+                        examStudentScoreDetailList.add(essBean);
+                    }
+                    esBean.setScoreDetails(examStudentScoreDetailList);
+                }
+            }
+            examStudentDataBeanList.add(esBean);
+        }
+        resp.setList(examStudentDataBeanList);
+
+        return resp;
+    }
+
+    private void validateRequestData(GetExamStudentDataReq req) {
+        if (req.getStartId() == null) {
+            throw new StatusException("200101", "起始考生id不允许为空");
+        }
+        if (req.getExamId() == null) {
+            throw new StatusException("200102", "考试id不允许为空");
+        }
+        if (req.getSize() == null) {
+            throw new StatusException("200103", "数据条数不允许为空");
+        }
+        Integer maxSize = PropertyHolder.getInt("exam.oe.getExamStudentData.maxSize", 200);
+        if (req.getSize() > maxSize) {
+            throw new StatusException("200104", "该接口一次最多只能返回" + maxSize + "条数据");
+        }
+    }
+}

+ 39 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/OeAdminScorePushCloudServiceProvider.java

@@ -0,0 +1,39 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+
+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.RestController;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.OeAdminScorePushCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScorePushQueueRepo;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreQueueService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+
+
+@Api(tags = "推送成绩接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/oeAdminScorePush")
+public class OeAdminScorePushCloudServiceProvider extends ControllerSupport implements OeAdminScorePushCloudService{
+
+	private static final long serialVersionUID = -113490665725775879L;
+
+	@Autowired
+	private ExamScoreQueueService examScoreQueueService;
+	
+	@Autowired
+	private ExamScorePushQueueRepo examScorePushQueueRepo;
+	
+	@Autowired
+	private ExamRecordDataRepo examRecordDataRepo;
+	
+	@Override
+	@PostMapping("/disposeScoreQueue")
+	public void disposeScoreQueue() {
+		examScoreQueueService.disposeScoreQueue();
+	}
+
+}

+ 154 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/SyncCloudServiceProvider.java

@@ -0,0 +1,154 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.support.Constants;
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamStudentBeanConvert;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.cache.ExamStudentCache;
+import cn.com.qmth.examcloud.global.api.HandleSyncCloudService;
+import cn.com.qmth.examcloud.global.api.request.SyncCourseReq;
+import cn.com.qmth.examcloud.global.api.request.SyncExamReq;
+import cn.com.qmth.examcloud.global.api.request.SyncExamStudentReq;
+import cn.com.qmth.examcloud.global.api.request.SyncOrgReq;
+import cn.com.qmth.examcloud.global.api.request.SyncSpecialtyReq;
+import cn.com.qmth.examcloud.global.api.request.SyncStudentReq;
+import cn.com.qmth.examcloud.global.api.request.SyncUserReq;
+import cn.com.qmth.examcloud.global.api.response.SyncCourseResp;
+import cn.com.qmth.examcloud.global.api.response.SyncExamResp;
+import cn.com.qmth.examcloud.global.api.response.SyncExamStudentResp;
+import cn.com.qmth.examcloud.global.api.response.SyncOrgResp;
+import cn.com.qmth.examcloud.global.api.response.SyncSpecialtyResp;
+import cn.com.qmth.examcloud.global.api.response.SyncStudentResp;
+import cn.com.qmth.examcloud.global.api.response.SyncUserResp;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年10月29日 下午4:39:24
+ * @company 	QMTH
+ * @description 数据同步相关接口
+ */
+@Api(tags = "数据同步相关接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/exam/dataSync")
+public class SyncCloudServiceProvider  extends ControllerSupport implements HandleSyncCloudService {
+	
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -4093980356473146740L;
+
+	@Autowired
+    private ExamStudentService examStudentService;
+	
+    @Autowired
+    private ExamStudentRepo examStudentRepo;
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    
+    @Autowired
+    private ExamStudentCache examStudentCache;
+
+	@ApiOperation(value = "同步考生")
+    @PostMapping("/syncExamStudent")
+    public SyncExamStudentResp syncExamStudent(@RequestBody SyncExamStudentReq req) {
+		//此锁是为了保证同步考生和交卷后更新考生信息不冲突
+		String sequenceLockKey = Constants.EXAM_CONTROL_LOCK_PREFIX + req.getStudentId();
+
+		try {
+			//添加考试控制全局锁
+			SequenceLockHelper.getLockSimple(sequenceLockKey);
+
+			if("delete".equals(req.getSyncType())){
+				ExamStudentEntity examStudent = examStudentRepo.findByExamStudentId(req.getId());
+				if(examStudent != null && (examStudent.getFinished() == null || !examStudent.getFinished())){
+					examStudentRepo.delete(examStudent);
+					examStudentCache.remove(examStudent.getId());
+				}
+			}else if("update".equals(req.getSyncType())){
+				ExamStudentInfo examStudent = ExamStudentBeanConvert.of(req);
+				List<Long> examStudentIdList = examStudentService.saveExamStudentList(Lists.newArrayList(examStudent));
+				for (Long cur : examStudentIdList) {
+					examStudentCache.refresh(cur);
+				}
+			}
+
+			SyncExamStudentResp resp = new SyncExamStudentResp();
+			return resp;
+		} finally {
+			SequenceLockHelper.releaseLockSimple(sequenceLockKey);
+		}
+	}
+
+    @ApiOperation(value = "同步课程")
+    @PostMapping("/syncCourse")
+    public SyncCourseResp syncCourse(@RequestBody SyncCourseReq req) {
+    	Long courseId = req.getId();
+    	String courseLevel = req.getLevel();
+    	examStudentRepo.updateCourse(courseId, courseLevel);
+		examRecordDataRepo.updateCourse(courseId, courseLevel);
+        return null;
+    }
+
+    @ApiOperation(value = "同步学生")
+    @PostMapping("/syncStudent")
+    public SyncStudentResp syncStudent(@RequestBody SyncStudentReq req) {
+    	//考生表和考试记录表
+    	long studentId = req.getId();
+    	String studentName = req.getName();
+
+//		examStudentRepo.updateStudentNameAndStudentCode(studentId, studentName, studentCode);
+//    	examRecordRepo.updateStudentNameAndStudentCode(studentId, studentName, studentCode);
+		examStudentRepo.updateStudentName(studentId,studentName);
+		examRecordDataRepo.updateStudentName(studentId, studentName);
+		//如果有需要解绑的学号信息,需要对学号进行解绑
+		if (null != req.getUnboundStudentCodeList() && !req.getUnboundStudentCodeList().isEmpty()) {
+			examStudentRepo.updateExamStudentCode(req.getIdentityNumber(), req.getUnboundStudentCodeList());
+		}
+    	return null;
+    }
+
+    @ApiOperation(value = "同步机构")
+    @PostMapping("/syncOrg")
+    public SyncOrgResp syncOrg(@RequestBody SyncOrgReq req) {
+        return null;
+    }
+    
+    @ApiOperation(value = "同步专业")
+    @PostMapping("syncSpecialty")
+    public SyncSpecialtyResp syncSpecialty(@RequestBody SyncSpecialtyReq req) {
+        return null;
+    }
+
+    @ApiOperation(value = "同步考试")
+    @PostMapping("syncExam")
+    public SyncExamResp syncExam(@RequestBody SyncExamReq req) {
+        return null;
+    }
+
+	@Override
+	public SyncUserResp syncUser(SyncUserReq req) {
+		// TODO Auto-generated method stub
+		return null;
+	}
+
+	
+	
+}

+ 673 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/SyncExamDataCloudServiceProvider.java

@@ -0,0 +1,673 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.provider;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.core.oe.admin.api.SyncExamDataCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.*;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.SyncExamDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.SyncExamDataResp;
+import cn.com.qmth.examcloud.core.oe.admin.dao.*;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.*;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.cache.ExamStudentCache;
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.transaction.annotation.Transactional;
+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;
+
+/**
+ * @Description 同步考试相关数据接口
+ * @Author lideyin
+ * @Date 2019/12/20 10:32
+ * @Version 1.0
+ */
+@Api(tags = "同步考试相关数据接口")
+@RestController
+@RequestMapping("${$rmp.cloud.oe}/exam/sync")
+public class SyncExamDataCloudServiceProvider extends ControllerSupport implements SyncExamDataCloudService {
+
+    private static final long serialVersionUID = -25466948667789119L;
+
+    private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(SyncExamDataCloudServiceProvider.class);
+
+    @Autowired
+    private ExamRecordForMarkingService examRecordForMarkingService;
+    @Autowired
+    private ExamScorePushQueueService examScorePushQueueService;
+    @Autowired
+    private ExamScoreObtainQueueService examScoreObtainQueueService;
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    @Autowired
+    private ExamStudentRepo examStudentRepo;
+    @Autowired
+    private ExamSyncCaptureRepo examSyncCaptureRepo;
+    @Autowired
+    private ExamCaptureRepo examCaptureRepo;
+    @Autowired
+    private ExamFaceLivenessVerifyRepo examFaceLivenessVerifyRepo;
+    @Autowired
+    private ExamRecordPaperStructRepo examRecordPaperStructRepo;
+    @Autowired
+    private ExamRecordQuestionsRepo examRecordQuestionsRepo;
+    @Autowired
+    private FaceBiopsyRepo faceBiopsyRepo;
+    @Autowired
+    private FaceBiopsyItemRepo faceBiopsyItemRepo;
+    @Autowired
+    private FaceBiopsyItemStepRepo faceBiopsyItemStepRepo;
+    @Autowired
+    private ExamAuditService examAuditService;
+    @Autowired
+    private ExamRecordDataSyncRepo examRecordDataSyncRepo;
+    @Autowired
+    private ExamScoreRepo examScoreRepo;
+    @Autowired
+    private ExamStudentCache examStudentCache;
+    @Autowired
+    private ExamStudentFinalScoreService examStudentFinalScoreService;
+
+
+    /**
+     * 同步考试记录数据
+     *
+     * @param req
+     * @return
+     */
+    @ApiOperation(value = "同步考试记录相关数据")
+    @PostMapping("/syncExamData")
+    @Transactional
+    @Override
+    public SyncExamDataResp syncExamData(@RequestBody SyncExamDataReq req) {
+
+        long st = System.currentTimeMillis();
+
+        long startTime = System.currentTimeMillis();
+
+        Long transitionExamRecordDataId = req.getExamRecordData().getId();
+
+        //校验考试记录是否已同步,如果已同步,则直接返回
+        if (hasSynced(transitionExamRecordDataId)) {
+            return new SyncExamDataResp();
+        }
+
+        startTime = this.debugCost("1 校验考试记录是否已同步", transitionExamRecordDataId, startTime);
+
+        //临时考试记录
+        ExamRecordDataBean transitionExamRecordData = req.getExamRecordData();
+
+        //必须先更新考生的考试次数,同步考试记录会用
+        Long examStudentId = transitionExamRecordData.getExamStudentId();
+        updateExamStudent(examStudentId);
+
+        startTime = this.debugCost("2 更新考生考试次数", transitionExamRecordDataId, startTime);
+
+        //同步考试记录,并返回真实的考试记录id
+        Long realExamRecordDataId = syncExamRecordData(transitionExamRecordData);
+
+        startTime = this.debugCost("3 同步考试记录表", transitionExamRecordDataId, startTime);
+
+        //添加同步记录
+        addExamRecordDataSync(transitionExamRecordData.getId(), realExamRecordDataId);
+
+        startTime = this.debugCost("4 添加同步关系表", transitionExamRecordDataId, startTime);
+
+        //同步考试分数表
+        Long examScoreId = syncExamScore(transitionExamRecordData, realExamRecordDataId);
+
+        startTime = this.debugCost("5 同步考试分数表", transitionExamRecordDataId, startTime);
+
+        //计算最终分数
+        String examType = ExamCacheTransferHelper.getDefaultCachedExam(transitionExamRecordData.getExamId()).getExamType();
+        if (ExamType.ONLINE.name().equals(examType) || ExamType.OFFLINE.name().equals(examType)) {
+            examStudentFinalScoreService.calcAndSaveFinalScore(examStudentId);
+        }
+
+        startTime = this.debugCost("6 计算最终考试分数", transitionExamRecordDataId, startTime);
+
+        //同步抓拍照片结果(同步抓拍的数据)
+        if (null != req.getExamSyncCapture()) {
+            syncExamSyncCapture(req.getExamSyncCapture(), realExamRecordDataId);
+        }
+
+        startTime = this.debugCost("7 同步抓拍照片结果(同步抓拍的数据)", transitionExamRecordDataId, startTime);
+
+        //同步抓拍照片结果(异步抓拍的数据)
+        if (null != req.getExamCaptures()) {
+
+            syncExamCapture(req.getExamCaptures(), realExamRecordDataId);
+        }
+
+        startTime = this.debugCost("8 同步抓拍照片结果(异步抓拍的数据)", transitionExamRecordDataId, startTime);
+
+        //同步face id活体检测数据
+        if (null != req.getExamFaceLivenessVerifies()) {
+
+            syncExamFaceLivenessVerify(req.getExamFaceLivenessVerifies(), realExamRecordDataId);
+        }
+
+        startTime = this.debugCost("9 同步face id活体检测数据", transitionExamRecordDataId, startTime);
+
+        //同步新活检
+        if (null != req.getFaceBiopsy()) {
+
+            syncFaceBiopsy(req.getFaceBiopsy(), realExamRecordDataId);
+        }
+
+        startTime = this.debugCost("10 同步face id活体检测数据", transitionExamRecordDataId, startTime);
+
+        //同步考试记录对应的试卷结构
+        syncExamRecordPaperStruct(req.getExamRecordPaperStruct(), realExamRecordDataId);
+
+        startTime = this.debugCost("11 同步考试记录对应的试卷结构", transitionExamRecordDataId, startTime);
+
+        //同步作答记录
+        syncExamRecordQuestions(req.getExamRecordQuestions(), realExamRecordDataId);
+
+        startTime = this.debugCost("12 同步作答记录", transitionExamRecordDataId, startTime);
+
+        //如果开启了活检
+        if (FaceBiopsyHelper.isFaceEnable(transitionExamRecordData.getRootOrgId(), transitionExamRecordData.getExamId(), transitionExamRecordData.getStudentId())) {
+            //计算违纪自动审核结果(无人脸或活检失败)
+            boolean isNoPhotoAndIllegality = (null == req.getExamCaptures() || req.getExamCaptures().isEmpty());//是否无照片
+            saveAutoAudit(transitionExamRecordData, isNoPhotoAndIllegality, realExamRecordDataId);
+        }
+
+        startTime = this.debugCost("13 如果开启了活检,计算违纪自动审核结果", transitionExamRecordDataId, startTime);
+
+        //同步后续处理
+        processAfterSyncExamData(realExamRecordDataId, examScoreId, transitionExamRecordData.getObjectiveScore(),
+                examStudentId, transitionExamRecordDataId);
+
+        this.debugCost("14 同步后续处理 - 共计", transitionExamRecordDataId, startTime);
+
+        this.debugCost("100 同步考试记录相关数据 - 共计", transitionExamRecordDataId, st);
+
+        return new SyncExamDataResp();
+    }
+
+    public Long syncExamScore(ExamRecordDataBean transitionExamRecordData, Long realExamRecordDataId) {
+        //如果考试成绩不存在,则新增,否则更新数据
+        ExamScoreEntity examScoreEntity = examScoreRepo.findByExamRecordDataId(realExamRecordDataId);
+
+        if (examScoreEntity == null) {
+            examScoreEntity = new ExamScoreEntity();
+            examScoreEntity.setExamRecordDataId(realExamRecordDataId);
+        }
+        examScoreEntity.setObjectiveScore(transitionExamRecordData.getObjectiveScore());
+        examScoreEntity.setObjectiveAccuracy(transitionExamRecordData.getObjectiveAccuracy());
+        examScoreEntity.setTotalScore(transitionExamRecordData.getTotalScore());//交卷时,总分=客观分得分
+        examScoreEntity.setSubjectiveScore(transitionExamRecordData.getSubjectiveScore());
+        examScoreEntity.setSuccPercent(transitionExamRecordData.getSuccPercent());
+
+        ExamScoreEntity result = examScoreRepo.saveAndFlush(examScoreEntity);
+
+        return result.getId();
+    }
+
+    /**
+     * 更新考生相关信息
+     * 增加考试次数,修改考试状态为已完成
+     *
+     * @param examStudentId
+     */
+    private void updateExamStudent(Long examStudentId) {
+        //更新数据库中的已考次数
+        ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(examStudentId);
+        if (null == examStudentEntity) {
+            throw new StatusException("100101", "考生id不正确");
+        }
+
+        Integer usedTimes = examStudentEntity.getUsedNum() == null
+                ? 0
+                : examStudentEntity.getUsedNum();
+
+        examStudentEntity.setUsedNum(usedTimes + 1);
+        examStudentEntity.setFinished(true);
+
+        examStudentRepo.saveAndFlush(examStudentEntity);
+    }
+
+    /**
+     * 添加考试记录的同步记录
+     *
+     * @param cacheId
+     * @param dbId
+     */
+    private void addExamRecordDataSync(Long cacheId, Long dbId) {
+        ExamRecordDataSyncEntity entity = new ExamRecordDataSyncEntity();
+        entity.setCacheId(cacheId);
+        entity.setDbId(dbId);
+
+        examRecordDataSyncRepo.saveAndFlush(entity);
+    }
+
+    /**
+     * 考试记录表是否已同步
+     *
+     * @param cacheExamRecordDataId
+     * @return
+     */
+    private boolean hasSynced(Long cacheExamRecordDataId) {
+        ExamRecordDataSyncEntity query = new ExamRecordDataSyncEntity();
+        query.setCacheId(cacheExamRecordDataId);
+        Example<ExamRecordDataSyncEntity> example = Example.of(query);
+        return examRecordDataSyncRepo.exists(example);
+    }
+
+    private Long syncExamRecordData(ExamRecordDataBean examRecordData) {
+
+
+        ExamRecordDataEntity examRecordDataEntity = copyExamRecordDataEntityFrom(examRecordData);
+
+        ExamRecordDataEntity result = examRecordDataRepo.saveAndFlush(examRecordDataEntity);
+
+        return result.getId();
+    }
+
+    private ExamRecordDataEntity copyExamRecordDataEntityFrom(ExamRecordDataBean examRecordData) {
+        ExamRecordDataEntity result = new ExamRecordDataEntity();
+        Long examId = examRecordData.getExamId();
+        result.setExamId(examId);
+        result.setExamType(ExamType.valueOf(examRecordData.getExamType()));
+        result.setExamStudentId(examRecordData.getExamStudentId());
+        Long studentId = examRecordData.getStudentId();
+        result.setStudentId(studentId);
+
+        //部分数据从考生表中获取
+        ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(examRecordData.getExamStudentId());
+        if (null == examStudentEntity) {
+            throw new StatusException("100101", "考生id不正确");
+        }
+        result.setStudentCode(examStudentEntity.getStudentCode());
+        result.setStudentName(examStudentEntity.getStudentName());
+        result.setIdentityNumber(examStudentEntity.getIdentityNumber());
+        result.setInfoCollector(examStudentEntity.getInfoCollector());
+        Integer usedNum = examStudentEntity.getUsedNum();
+        result.setExamOrder(getExamOrder(examId, studentId, usedNum));//考试次数
+        result.setIsReexamine(isReexamine(examId, studentId, usedNum));//是否重考
+
+        result.setCourseId(examRecordData.getCourseId());
+        result.setOrgId(examRecordData.getOrgId());
+        result.setRootOrgId(examRecordData.getRootOrgId());
+        result.setBasePaperId(examRecordData.getBasePaperId());
+
+//        result.setPaperStructId(examRecordData.getPaperStructId());
+        result.setPaperType(examRecordData.getPaperType());
+
+        CourseCacheBean course = CacheHelper.getCourse(examRecordData.getCourseId());
+        result.setCourseLevel(course.getLevel());
+
+
+        result.setStartTime(examRecordData.getStartTime());
+        result.setEndTime(examRecordData.getEndTime());
+        result.setCleanTime(examRecordData.getCleanTime());
+        result.setIsWarn(examRecordData.getWarn());
+        result.setIsAudit(examRecordData.getAudit());
+        result.setIsIllegality(examRecordData.getIllegality());
+        result.setExamRecordStatus(ExamRecordStatus.getByName(examRecordData.getExamRecordStatus()));
+        result.setUsedExamTime(examRecordData.getUsedExamTime());
+
+        result.setIsContinued(examRecordData.getContinued());
+        result.setContinuedCount(examRecordData.getContinuedCount());
+        result.setFaceSuccessCount(examRecordData.getFaceSuccessCount());
+        result.setFaceFailedCount(examRecordData.getFaceFailedCount());
+        result.setFaceStrangerCount(examRecordData.getFaceStrangerCount());
+        result.setFaceTotalCount(examRecordData.getFaceTotalCount());
+        result.setFaceSuccessPercent(examRecordData.getFaceSuccessPercent());
+
+        if (StringUtils.isNotBlank(examRecordData.getFaceVerifyResult())) {
+            result.setFaceVerifyResult(IsSuccess.valueOf(examRecordData.getFaceVerifyResult()));
+        }
+
+        result.setFaceLandmarkVal(examRecordData.getFaceLandmarkVal());
+        result.setIsAllObjectivePaper(examRecordData.getAllObjectivePaper());
+        result.setBaiduFaceLivenessSuccessPercent(examRecordData.getBaiduFaceLivenessSuccessPercent());
+        result.setIsExceed(examRecordData.getExceed());
+
+        return result;
+    }
+
+    /**
+     * 计算考试次数
+     *
+     * @param examId      考试id
+     * @param studentId   学生id
+     * @param usedExamNum 已考次数
+     * @return
+     */
+    private Integer getExamOrder(Long examId, Long studentId, Integer usedExamNum) {
+        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        Integer canExamTimes = cachedExam.getExamTimes() == null ? 0 : cachedExam.getExamTimes().intValue();//可考次数
+
+        //超过可考次数,始终为可考次数+1
+        if (usedExamNum > canExamTimes) {
+            return canExamTimes + 1;
+        }
+
+        return usedExamNum;
+
+    }
+
+    /**
+     * 计算是否为重考
+     *
+     * @param examId      考试id
+     * @param studentId   学生id
+     * @param usedExamNum 已考次数
+     * @return
+     */
+    private boolean isReexamine(Long examId, Long studentId, Integer usedExamNum) {
+        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        Integer canExamTimes = cachedExam.getExamTimes() == null ? 0 : cachedExam.getExamTimes().intValue();//可考次数
+
+        //超过可考次数,则认为有重考
+        if (usedExamNum > canExamTimes) {
+            return true;
+        }
+
+        return false;
+
+    }
+
+    private void saveAutoAudit(ExamRecordDataBean examRecordData, boolean isNoPhotoAndIllegality, Long realExamRecordDataId) {
+        boolean isAutoAudit = false;//是否已自动审核
+        //无照片违纪自动审核
+        if (isNoPhotoAndIllegality) {
+            examAuditService.saveExamAuditByNoPhoto(realExamRecordDataId);
+            isAutoAudit = true;
+        } else {
+            //活体检测失败违纪自动审核
+            if (null != examRecordData.getFaceVerifyResult()
+                    && IsSuccess.FAILED.name().equals(examRecordData.getFaceVerifyResult())
+                    && examRecordData.getIllegality()) {
+                examAuditService.saveExamAuditByFaceVerifyFailed(realExamRecordDataId, examRecordData.getRootOrgId());
+                isAutoAudit = true;
+            }
+        }
+
+        //如果进行了自动审核,更新考试记录表中的自动审核状态
+        if (isAutoAudit) {
+            //更新考试记录表中的作答记录id
+            ExamRecordDataEntity examRecordDataEntity =
+                    GlobalHelper.getEntity(examRecordDataRepo, realExamRecordDataId, ExamRecordDataEntity.class);
+            examRecordDataEntity.setIsAudit(true);
+            examRecordDataRepo.save(examRecordDataEntity);
+        }
+    }
+
+    private void syncExamRecordQuestions(ExamRecordQuestionsBean examRecordQuestions, Long realExamRecordDataId) {
+        ExamRecordQuestionsEntity entity = copyExamRecordQuestionsFrom(examRecordQuestions, realExamRecordDataId);
+        examRecordQuestionsRepo.save(entity);
+
+        //更新考试记录表中的作答记录id
+        ExamRecordDataEntity examRecordData =
+                GlobalHelper.getEntity(examRecordDataRepo, realExamRecordDataId, ExamRecordDataEntity.class);
+        examRecordData.setExamRecordQuestionsId(entity.getId());
+        examRecordDataRepo.save(examRecordData);
+    }
+
+    private ExamRecordQuestionsEntity copyExamRecordQuestionsFrom(ExamRecordQuestionsBean bean, Long realExamRecordDataId) {
+        ExamRecordQuestionsEntity entity = new ExamRecordQuestionsEntity();
+        entity.setExamRecordDataId(realExamRecordDataId);
+
+        List<ExamQuestionEntity> examQuestionEntityList = new ArrayList<>();
+
+        for (ExamQuestionBean questionBean : bean.getExamQuestionBeans()) {
+            ExamQuestionEntity questionEntity = new ExamQuestionEntity();
+
+            questionEntity.setExamRecordDataId(realExamRecordDataId);
+            questionEntity.setMainNumber(questionBean.getMainNumber());
+            questionEntity.setQuestionId(questionBean.getQuestionId());
+            questionEntity.setOrder(questionBean.getOrder());
+            questionEntity.setStudentAnswer(questionBean.getStudentAnswer());
+            questionEntity.setStudentScore(questionBean.getStudentScore());
+            questionEntity.setQuestionScore(questionBean.getQuestionScore());
+            questionEntity.setQuestionType(questionBean.getQuestionType());
+            questionEntity.setIsAnswer(questionBean.getAnswer());
+            questionEntity.setIsSign(questionBean.getSign());
+            questionEntity.setCorrectAnswer(questionBean.getCorrectAnswer());
+            questionEntity.setOptionPermutation(questionBean.getOptionPermutation());
+            questionEntity.setAudioPlayTimes(questionBean.getAudioPlayTimes());
+
+            if (null != questionBean.getAnswerType()) {
+
+                questionEntity.setAnswerType(AnswerType.valueOf(questionBean.getAnswerType()));
+            }
+
+            examQuestionEntityList.add(questionEntity);
+        }
+
+        entity.setExamQuestionEntities(examQuestionEntityList);
+
+        return entity;
+    }
+
+    private void syncExamRecordPaperStruct(ExamRecordPaperStructBean examRecordPaperStruct, Long examRecordDataId) {
+        ExamRecordPaperStructEntity entity = copyExamRecordPaperStructFrom(examRecordPaperStruct, examRecordDataId);
+        examRecordPaperStructRepo.save(entity);
+
+        //更新考试记录表中的试卷结构id
+        ExamRecordDataEntity examRecordData =
+                GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+        examRecordData.setPaperStructId(entity.getId());
+        examRecordDataRepo.save(examRecordData);
+    }
+
+    private ExamRecordPaperStructEntity copyExamRecordPaperStructFrom(ExamRecordPaperStructBean examRecordPaperStruct, Long examRecordDataId) {
+        ExamRecordPaperStructEntity entity = new ExamRecordPaperStructEntity();
+        entity.setId(examRecordPaperStruct.getId());
+        entity.setDefaultPaper(examRecordPaperStruct.getDefaultPaper());
+
+        return entity;
+    }
+
+    @Transactional
+    public void syncFaceBiopsy(FaceBiopsyBean faceBiopsy, Long examRecordDataId) {
+        FaceBiopsyEntity faceBiopsyEntity = copyFaceBiopsyFrom(faceBiopsy, examRecordDataId);
+        faceBiopsyRepo.saveAndFlush(faceBiopsyEntity);
+
+        for (FaceBiopsyItemBean itemBean : faceBiopsy.getFaceBiopsyItems()) {
+            FaceBiopsyItemEntity faceBiopsyItemEntity = copyFaceBiopsyItemFrom(
+                    itemBean, faceBiopsyEntity.getId(), examRecordDataId);
+            faceBiopsyItemRepo.saveAndFlush(faceBiopsyItemEntity);
+
+            List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList = copyFaceBiopsyItemStepFrom(
+                    itemBean.getFaceBiopsyItemSteps(), faceBiopsyItemEntity.getId(), examRecordDataId);
+            for (FaceBiopsyItemStepEntity fbis : faceBiopsyItemStepEntityList) {
+                faceBiopsyItemStepRepo.saveAndFlush(fbis);
+            }
+
+        }
+    }
+
+    private FaceBiopsyEntity copyFaceBiopsyFrom(FaceBiopsyBean bean, Long examRecordDataId) {
+        FaceBiopsyEntity entity = new FaceBiopsyEntity();
+        entity.setRootOrgId(bean.getRootOrgId());
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setResult(bean.getResult());
+        entity.setErrorMsg(bean.getErrorMsg());
+        entity.setVerifiedTimes(bean.getVerifiedTimes());
+
+        return entity;
+    }
+
+    private FaceBiopsyItemEntity copyFaceBiopsyItemFrom(FaceBiopsyItemBean bean, Long faceByiopsyId, Long examRecordDataId) {
+        FaceBiopsyItemEntity entity = new FaceBiopsyItemEntity();
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setFaceBiopsyId(faceByiopsyId);
+        entity.setFaceBiopsyType(FaceBiopsyType.valueOf(bean.getFaceBiopsyType()));
+        entity.setCompleted(bean.getCompleted());
+        entity.setResult(bean.getResult());
+        entity.setErrorMsg(bean.getErrorMsg());
+        entity.setInFreezeTime(bean.getInFreezeTime());
+
+        return entity;
+    }
+
+    private List<FaceBiopsyItemStepEntity> copyFaceBiopsyItemStepFrom(List<FaceBiopsyItemStepBean> faceBiopsyItemSteps,
+                                                                      Long faceBiopsyItemId, Long examRecordDataId) {
+        List<FaceBiopsyItemStepEntity> faceBiopsyItemStepEntityList = new ArrayList<>();
+        for (FaceBiopsyItemStepBean bean : faceBiopsyItemSteps) {
+            FaceBiopsyItemStepEntity entity = new FaceBiopsyItemStepEntity();
+            entity.setExamRecordDataId(examRecordDataId);
+            entity.setFaceBiopsyItemId(faceBiopsyItemId);
+            entity.setAction(FaceBiopsyAction.valueOf(bean.getAction()));
+            entity.setResourceRelativePath(bean.getResourceRelativePath());
+            entity.setResourceType(ResourceType.valueOf(bean.getResourceType()));
+            entity.setActionStay(bean.getActionStay());
+            entity.setResult(bean.getResult());
+            entity.setErrorMsg(bean.getErrorMsg());
+            entity.setExt1(bean.getExt1());
+            entity.setExt2(bean.getExt2());
+            entity.setExt3(bean.getExt3());
+            entity.setExt4(bean.getExt4());
+            entity.setExt5(bean.getExt5());
+
+            faceBiopsyItemStepEntityList.add(entity);
+        }
+
+        return faceBiopsyItemStepEntityList;
+    }
+
+    private void syncExamFaceLivenessVerify(List<ExamFaceLivenessVerifyBean> examFaceLivenessVerifies, Long examRecordDataId) {
+        for (ExamFaceLivenessVerifyBean bean : examFaceLivenessVerifies) {
+            ExamFaceLivenessVerifyEntity entity = copyExamFaceLivenessVerifyFrom(bean, examRecordDataId);
+
+            examFaceLivenessVerifyRepo.saveAndFlush(entity);
+        }
+    }
+
+    private ExamFaceLivenessVerifyEntity copyExamFaceLivenessVerifyFrom(ExamFaceLivenessVerifyBean bean,
+                                                                        Long examRecordDataId) {
+        ExamFaceLivenessVerifyEntity entity = new ExamFaceLivenessVerifyEntity();
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setStartTime(bean.getStartTime());
+        entity.setUsedTime(bean.getUsedTime());
+        entity.setResultJson(bean.getResultJson());
+
+        if (null != bean.getVerifyResult()) {
+            entity.setVerifyResult(FaceVerifyResult.valueOf(bean.getVerifyResult()));
+        }
+
+        entity.setBizId(bean.getBizId());
+        entity.setIsError(bean.getIsError());
+        entity.setErrorMsg(bean.getErrorMsg());
+        entity.setOperateNum(bean.getOperateNum());
+
+        return entity;
+    }
+
+    private void syncExamSyncCapture(ExamSyncCaptureBean examSyncCapture, Long examRecordDataId) {
+        ExamSyncCaptureEntity examSyncCaptureEntity = copyExamSyncCaptureFrom(examSyncCapture, examRecordDataId);
+
+        examSyncCaptureRepo.saveAndFlush(examSyncCaptureEntity);
+    }
+
+    private ExamSyncCaptureEntity copyExamSyncCaptureFrom(ExamSyncCaptureBean examSyncCapture, Long examRecordDataId) {
+        ExamSyncCaptureEntity entity = new ExamSyncCaptureEntity();
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setFileUrl(examSyncCapture.getFileUrl());
+        entity.setFileName(examSyncCapture.getFileName());
+        entity.setFaceCompareResult(examSyncCapture.getFaceCompareResult());
+        entity.setIsPass(examSyncCapture.getPass());
+        entity.setIsStranger(examSyncCapture.getStranger());
+        entity.setLandmark(examSyncCapture.getLandmark());
+        entity.setUsedTime(examSyncCapture.getUsedTime());
+        entity.setFacelivenessResult(examSyncCapture.getFacelivenessResult());
+        entity.setProcessTime(examSyncCapture.getProcessTime());
+        entity.setHasVirtualCamera(examSyncCapture.getHasVirtualCamera());
+        entity.setCameraInfos(examSyncCapture.getCameraInfos());
+        entity.setExtMsg(examSyncCapture.getExtMsg());
+
+        return entity;
+    }
+
+    private void syncExamCapture(List<ExamCaptureBean> examCaptures, Long examRecordDataId) {
+        for (ExamCaptureBean bean : examCaptures) {
+            ExamCaptureEntity examCaptureEntity = copyExamCaptureFrom(bean, examRecordDataId);
+            examCaptureRepo.saveAndFlush(examCaptureEntity);
+        }
+    }
+
+    private ExamCaptureEntity copyExamCaptureFrom(ExamCaptureBean examCapture, Long examRecordDataId) {
+        ExamCaptureEntity entity = new ExamCaptureEntity();
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setFileUrl(examCapture.getFileUrl());
+        entity.setFileName(examCapture.getFileName());
+        entity.setFaceCompareResult(examCapture.getFaceCompareResult());
+        entity.setIsPass(examCapture.getPass());
+        entity.setIsStranger(examCapture.getStranger());
+        entity.setLandmark(examCapture.getLandmark());
+        entity.setUsedTime(examCapture.getUsedTime());
+        entity.setFacelivenessResult(examCapture.getFacelivenessResult());
+        entity.setProcessTime(examCapture.getProcessTime());
+        entity.setHasVirtualCamera(examCapture.getHasVirtualCamera());
+        entity.setCameraInfos(examCapture.getCameraInfos());
+        entity.setExtMsg(examCapture.getExtMsg());
+
+        return entity;
+    }
+
+    /**
+     * 同步后的后续操作
+     *
+     * @param realExamRecordDataId
+     * @param examScoreId
+     */
+    private void processAfterSyncExamData(Long realExamRecordDataId, Long examScoreId, Double objectiveScore,
+                                          Long examStudentId, Long transitionExamRecordDataId) {
+        long startTime = System.currentTimeMillis();
+
+        // 保存阅卷相关数据
+        examRecordForMarkingService.saveExamRecordForMarking(realExamRecordDataId, objectiveScore);
+
+        startTime = this.debugCost("14.1 同步后续处理 - 保存阅卷相关数据", transitionExamRecordDataId, startTime);
+
+        // 保存考试分数数据到推分队列
+        examScorePushQueueService.saveScoreDataInfoToQueue(realExamRecordDataId, examScoreId);
+
+        startTime = this.debugCost("14.2 同步后续处理 - 保存考试分数数据到推分队列", transitionExamRecordDataId, startTime);
+
+        // 保存考试分数数据到分数获取队列
+        examScoreObtainQueueService.saveExamScoreObtainQueue(realExamRecordDataId);
+
+        startTime = this.debugCost("14.3 同步后续处理 - 保存考试分数数据到推分队列", transitionExamRecordDataId, startTime);
+
+        //刷新考生的缓存
+        examStudentCache.refresh(examStudentId);
+
+        this.debugCost("14.4 同步后续处理 - 刷新考生的缓存", transitionExamRecordDataId, startTime);
+    }
+
+    //记录耗时
+    private Long debugCost(String msg, Long examRecordDataId, Long startTime) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("[SYNC_EXAM_DATA_CLOUD_SERVICE_PROVIDER-" + examRecordDataId + "]:" + msg +
+                    " 耗时:" + (System.currentTimeMillis() - startTime) + " ms");
+        }
+
+        return System.currentTimeMillis();
+    }
+
+}

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

@@ -0,0 +1,134 @@
+<?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-admin</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-core-oe-admin-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-admin-api-client</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>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-marking-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-core-oe-student-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>

+ 182 - 0
examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/Constants.java

@@ -0,0 +1,182 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-24 11:34:17.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.base;
+
+/**
+ * 系统常量
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/17
+ */
+public interface Constants {
+
+    /**
+     * 系统错误
+     */
+    String OE_CODE_500 = "OE-000500";
+
+    /**
+     * 参数错误
+     */
+    String OE_CODE_400 = "OE-000400";
+
+    /**
+     * 权限错误
+     */
+    String OE_CODE_403 = "OE-000403";
+
+    /**
+     * 开始考试锁
+     */
+    String START_EXAM_LOCK_PREFIX = "oe_student:start_exam_lock_studentid_";
+    /**
+     * 结束考试锁前缀
+     */
+    String END_EXAM_LOCK_PREFIX = "oe_student:end_exam_lock_studentid_";
+    /**
+     * 考试交卷锁前缀
+     */
+    String HAND_IN_EXAM_LOCK_PREFIX = "oe_student:hand_in_exam_lock_";
+    /**
+     * 处理照片锁前缀
+     */
+    String EXAM_CAPTURE_PHOTO_LOCK_PREFIX = "oe_student:exam_capture_lock_";
+    /**
+     * 获取人脸活体检测基本信息前缀
+     */
+    String GET_FACE_BIOPSY_INFO_PREFIX = "oe_student:get_face_biopsy_info_lock_";
+    //
+    String ERROR_MSG = "error_message";
+
+
+    /**
+     * 学生考试session前缀
+     */
+    String OE_STUDENT_EXAM_SESSION_PREFIX = "oe_student:exam_session_";
+
+
+    String EXAM_CAPTURE_QUEUE_LOCK_PREFIX = "oe_student:exam_capture_queue_lock_";
+
+
+    /**
+     * 原始数据库表
+     */
+    String OE_EXAM_RECORD = "oe_exam_record";
+    String ECS_EXAM_STUDENT = "ecs_exam_student";
+    String OE_EXAM_QUESTION = "oe_exam_question";
+    String OE_EXAM_CAPTURE = "oe_exam_capture";
+    String OE_EXAM_SCORE = "oe_exam_score";
+    String OE_EXAM_AUDIT = "oe_exam_audit";
+    String OE_FACE_VERIFY = "oe_face_verify";
+
+    /**
+     * 新数据库表
+     */
+    String EC_OE_EXAM_RECORD_DATA = "ec_oe_exam_record_data";
+    String EC_OE_EXAM_STUDENT = "ec_oe_exam_student";
+    String EXAM_RECORD_QUESTIONS = "examRecordQuestions";
+    String EC_OE_EXAM_CAPTURE = "ec_oe_exam_capture";
+    String EC_OE_EXAM_SCORE = "ec_oe_exam_score";
+    String EC_OE_EXAM_AUDIT = "ec_oe_exam_audit";
+    String EC_OE_EXAM_FACE_VERIFY = "ec_oe_exam_face_liveness_verify";
+
+    /**
+     * 课程信息表
+     */
+    String ECS_CORE_COURSE = "ecs_core_course";
+
+    //缓存
+    String CACHE_EXAM_STUDENT_PREFIX = "cache_examStudent_";
+    String CACHE_PHONE_PREFIX = "cache_phone_";
+    String CACHE_STUDENT_PREFIX = "cache_student_";
+    String CACHE_ORG_PREFIX = "cache_org_";
+    String CACHE_COURSE_PREFIX = "cache_course_";
+
+    String isTrue = "true";
+
+    String isFalse = "false";
+    /**
+     * 考试成绩通知路径前缀
+     */
+    String OE_EXAM_SCORE_NOTIFY_URL_PREFIX="oe.examScoreNotify.url.";
+    /**
+     * 考试成绩通知路径传输方法前缀
+     */
+    String OE_EXAM_SCORE_NOTIFY_URL_HTTP_METHOD_PREFIX="oe.examScoreNotify.url.httpMethod.";
+
+    //face++ 人脸比对相关错误详情
+    /**
+     * face++ 人脸比对API并发次数超过上限
+     */
+    String FACE_COMPARE_CONCURRENCY_LIMIT_EXCEEDED="CONCURRENCY_LIMIT_EXCEEDED";
+    /**
+     * face++ api_key没有调用本API的权限,具体原因为:用户自己禁止该api_key调用、管理员禁止该api_key调用、由于账户余额不足禁止调用。
+     */
+    String FACE_COMPARE_AUTHORIZATION_ERROR="AUTHORIZATION_ERROR";
+    /**
+     * face++ 下载图片超时
+     */
+    String FACE_COMPARE_IMAGE_DOWNLOAD_TIMEOUT="IMAGE_DOWNLOAD_TIMEOUT";
+
+
+    //百度活检错误码 http://ai.baidu.com/docs#/Face-Java-SDK/514d7ea4
+    String BAIDU_ERROR_CODE = "error_code";
+    String BAIDU_ERROR_MSG="error_msg";
+    String BAIDU_SUCCESS_ERROR_CODE_VALUE = "0";
+
+    /**
+     * 连接超时或读取数据超时
+     */
+    String BAIDU_FACELIVENESS_CONNECTION_OR_READ_DATA_TIME_OUT_CODE ="SDK108";
+
+    /**
+     * 百度在线活体检测QPS超过上限的错误码
+     */
+    String BAIDU_FACELIVENESS_QPS_LIMIT_EXCEEDED_CODE ="18";
+
+
+    /**
+     * 抓拍照片又拍云签名前缀
+     */
+    String EXAM_CAPTURE_PHOTO_UPYUN_SIGN_PREFIX ="OE_EXAM_CAPTURE_PHOTO_UPYUN_SIGN_";
+
+    /**
+     * 处理照片高优先级
+     */
+    int PROCESS_CAPTURE_HIGH_PRIORITY=1;
+
+    /**
+     * 照片处理中状态码
+     */
+    String CAPTURE_PROCESSING_STATUS_CODE="101222";
+
+    /**
+     * 考试未结束状态码
+     */
+    String EXAM_RECORD_NOT_END_STATUS_CODE="101333";
+
+    /**
+     * 交卷处理中
+     */
+    String PROCESSING_EXAM_RECORD_CODE="S-101000";
+
+    /**
+     * 通用成功编码
+     */
+    String COMMON_SUCCESS_CODE = "000000";
+
+    /**
+     * 同步人脸比对结果前缀
+     */
+    String FACE_SYNC_COMPARE_RESULT_PREFIX = "FACE_SYNC_COMPARE_RESULT_";
+
+    /**
+     * 活体检测方案key
+     */
+    String IDENTIFICATION_OF_LIVING_BODY_SCHEME_KEY ="IDENTIFICATION_OF_LIVING_BODY_SCHEME";
+}

+ 91 - 0
examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.base.utils;
+
+import static cn.com.qmth.examcloud.core.oe.admin.base.Constants.OE_CODE_400;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+
+/**
+ * 参数校验类
+ *
+ * @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;
+        }
+    }
+
+}

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

@@ -0,0 +1,167 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils;
+
+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;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.alibaba.fastjson.JSONObject;
+
+
+/**
+ * @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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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();
+    }
+}

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

@@ -0,0 +1,259 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+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;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * @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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/HtmlUtil.java

@@ -0,0 +1,95 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/HttpPoolUtil.java

@@ -0,0 +1,91 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/PagingAndSortingDTO.java

@@ -0,0 +1,101 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/PagingAndSortingSpecification.java

@@ -0,0 +1,74 @@
+package cn.com.qmth.examcloud.core.oe.admin.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;
+    }
+
+}

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

@@ -0,0 +1,163 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.util.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 编码工具类
+ * 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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/QuerySpecification.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/QuestionTypeUtil.java

@@ -0,0 +1,40 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/RowIterator.java

@@ -0,0 +1,63 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/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.admin.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);
+    }
+
+}

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

@@ -0,0 +1,127 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils.baiduFaceVerify;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+
+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;
+
+/**
+ * 
+ * @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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/baiduFaceVerify/Base64Util.java

@@ -0,0 +1,65 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/baiduFaceVerify/GsonUtils.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package cn.com.qmth.examcloud.core.oe.admin.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);
+    }
+}

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

@@ -0,0 +1,77 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils.baiduFaceVerify;
+
+import java.io.BufferedReader;
+import java.io.DataOutputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ColumnSetting.java

@@ -0,0 +1,61 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelError.java

@@ -0,0 +1,48 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelErrorType.java

@@ -0,0 +1,98 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelProperty.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.core.oe.admin.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;
+}

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

@@ -0,0 +1,100 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils.excel;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+
+import com.esotericsoftware.reflectasm.ConstructorAccess;
+import com.esotericsoftware.reflectasm.FieldAccess;
+
+/**
+ * 
+ * @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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelReaderHandle.java

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

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

@@ -0,0 +1,77 @@
+package cn.com.qmth.examcloud.core.oe.admin.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-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelWriter.java

@@ -0,0 +1,93 @@
+package cn.com.qmth.examcloud.core.oe.admin.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.Color;
+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);
+    }
+    
+}

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

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils.excel;
+
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.util.Collection;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/*
+ * 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-admin-dao/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-admin</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-core-oe-admin-dao</artifactId>
+	
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-core-oe-admin-base</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+	</dependencies>
+</project>

+ 24 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamAuditRepo.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamAuditEntity;
+
+import java.util.Date;
+
+/**
+ * @author chenken
+ * @date 2018/8/15 10:05
+ * @company QMTH
+ * @description ExamAuditEntityRepo
+ */
+@Repository
+public interface ExamAuditRepo extends JpaRepository<ExamAuditEntity, Long>, JpaSpecificationExecutor<ExamAuditEntity> {
+
+	public ExamAuditEntity findByExamRecordDataId(Long examRecordDataId);
+
+	ExamAuditEntity findFirstByDisciplineTypeAndCreationTimeGreaterThan(String disciplineType, Date creationTime);
+
+}

+ 115 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureQueueRepo.java

@@ -0,0 +1,115 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+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.admin.dao.entity.ExamCaptureQueueEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamCaptureQueueStatus;
+
+import javax.transaction.Transactional;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author chenken
+ * @date 2018/8/17 14:42
+ * @company QMTH
+ * @description ExamCaptureQueueRepo
+ */
+@Repository
+public interface ExamCaptureQueueRepo extends JpaRepository<ExamCaptureQueueEntity, Long>, JpaSpecificationExecutor<ExamCaptureQueueEntity> {
+
+    /**
+     * @param limit           一次取值数量
+     * @param processBatchNum 批次号
+     * @return List<ExamCaptureQueueEntity>
+     * @description 根据处理批次号查询没有处理过的数据或者是处理失败的数据,按优先级倒序,创建时间升序
+     * @author lideyin
+     * @date 2019/7/31 16:40
+     */
+    @Query(value = "select * from ec_oe_exam_capture_queue "
+            + " where status IN ('PENDING','PROCESS_FACE_COMPARE_FAILED')  " +
+            " and (process_batch_num is null or (process_batch_num is not null and process_batch_num!=?2))"
+            + " order by priority desc,id asc limit ?1", nativeQuery = true)
+    List<ExamCaptureQueueEntity> findNeedFaceCompareExamCaptureQueuesLimitByProcessBatchNum(Integer limit, String processBatchNum);
+
+    /**
+     * 查询人脸比对处理完成,需要百度活体检测的数据,按优先级倒序,创建时间升序
+     *
+     * @param limit           数量
+     * @param processBatchNum 批次号
+     * @return List<ExamCaptureQueueEntity>
+     */
+    @Query(value = "select * from ec_oe_exam_capture_queue "
+            + " where status IN ('PROCESS_FACE_COMPARE_COMPLETE','PROCESS_FACELIVENESS_FAILED') and process_batch_num!=?2  "
+            + " order by priority desc,id asc limit ?1", nativeQuery = true)
+    List<ExamCaptureQueueEntity> findNeedFacelivenessDetectExamCaptureQueuesLimit(Integer limit, String processBatchNum);
+
+    List<ExamCaptureQueueEntity> findByStatusOrderByCreationTimeAsc(ExamCaptureQueueStatus status);
+
+    List<ExamCaptureQueueEntity> findByExamRecordDataId(Long examRecordDataId);
+
+    /**
+     * 是否存在未处理的图片抓拍队列(处理完成的队列数据都会清理掉)
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    @Query(value = "select * from ec_oe_exam_capture_queue where exam_record_data_id=?1 limit 1", nativeQuery = true)
+    ExamCaptureQueueEntity existsUnhandledByExamRecordDataId(Long examRecordDataId);
+
+    /**
+     * 修改数据状态为处理中
+     *
+     * @param id 主键ID
+     */
+    @Transactional
+    @Modifying
+    @Query(value = "update ec_oe_exam_capture_queue set status = 'PROCESSING' where id = ?1", nativeQuery = true)
+    void updateExamCaptureQueueStatusWithProcessing(Long id);
+
+
+    /**
+     * @param priority         优先级(值越大,优先级越高)
+     * @param examRecordDataId 考试记录id
+     * @description 更新考试抓拍队列优先级
+     * @date 2019/7/31 16:46
+     * @author lideyin
+     */
+    @Transactional
+    @Modifying
+    @Query(value = "update ec_oe_exam_capture_queue set priority=?1 where exam_record_data_id=?2", nativeQuery = true)
+    void updateExamCaptureQueuePriority(int priority, Long examRecordDataId);
+
+    /**
+     * @param captureQueueId
+     * @param errorMsg
+     * @param status
+     * @param batchNumber
+     * @param updateDate
+     */
+    @Transactional
+    @Modifying
+    @Query(value = "update ec_oe_exam_capture_queue set error_msg=?2,`status`=?3,process_batch_num=?4,error_num=error_num+1,update_time=?5 where id=?1", nativeQuery = true)
+    void saveExamCaptureQueueEntityByFailed(Long captureQueueId, String errorMsg, String status, String batchNumber,
+                                            Date updateDate);
+
+    @Transactional
+    @Modifying
+    @Query(value = "update ec_oe_exam_capture_queue set is_pass=?2,is_stranger=?3,`status`=?4,face_compare_result=?5,face_compare_start_time=?6,update_time=?7 where id=?1", nativeQuery = true)
+    void saveExamCaptureQueueEntityBySuccessful(Long captureQueueId, Boolean isPass, Boolean isStranger, String status,
+                                                String faceCompareResult,Long faceCompareStartTime,Date updateDate);
+
+    /**
+     * 更新批次号
+     * @param captureQueueId 队列id
+     * @param batchNum 批次号
+     */
+    @Transactional
+    @Modifying
+    @Query(value = "update ec_oe_exam_capture_queue set process_batch_num=?2 where id=?1", nativeQuery = true)
+    void updateProcessBatchNum(Long captureQueueId, String batchNum);
+}

+ 32 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureRepo.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamCaptureEntity;
+
+import java.util.List;
+
+/**
+ * @author chenken
+ * @date 2018/8/15 10:23
+ * @company QMTH
+ * @description ExamCaptureEntityRepo
+ */
+@Repository
+public interface ExamCaptureRepo extends JpaRepository<ExamCaptureEntity, Long>, JpaSpecificationExecutor<ExamCaptureEntity> {
+	
+	/**
+	 * propagation = Propagation.REQUIRES_NEW 
+	 * 原因:A事务运行中,B事务插入数据,A事务会出现不能读取最新数据的问题
+	 * @param examRecordDataId
+	 * @return
+	 */
+	@Transactional(propagation = Propagation.REQUIRES_NEW)
+    public List<ExamCaptureEntity> findByExamRecordDataId(Long examRecordDataId);
+    
+    public ExamCaptureEntity findByExamRecordDataIdAndFileName(Long examRecordDataId,String fileName);
+}

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

@@ -0,0 +1,43 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+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 cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamFaceLivenessVerifyEntity;
+
+import java.util.List;
+
+/**
+ * @author chenken
+ * @date 2018/8/15 10:08
+ * @company QMTH
+ * @description FaceVerifyEntityRepo
+ */
+@Repository
+public interface ExamFaceLivenessVerifyRepo extends JpaRepository<ExamFaceLivenessVerifyEntity, Long>, JpaSpecificationExecutor<ExamFaceLivenessVerifyEntity> {
+    /**
+     * 使用examRecordId查询
+     * @param examRecordDataId
+     * @return
+     */
+    @Query(nativeQuery = true, value = "select * from ec_oe_exam_face_liveness_verify t where t.exam_record_data_id = ?1 and t.is_error = 0 order by id asc")
+    public List<ExamFaceLivenessVerifyEntity> findByExamRecordDataIdOrderById(Long examRecordDataId);
+    
+    /**
+     * 测试是否存在
+     * @param examRecordDataId
+     * @return
+     */
+    @Query(nativeQuery = true, value = "select 1 from ec_oe_exam_face_liveness_verify t where t.exam_record_data_id = ?1 and t.is_error = 0 limit 1")
+    public Long countByExamRecordDataId(Long examRecordDataId);
+
+    /**
+     * 取出is_error=1的最新的一条
+     * @param examRecordDataId
+     * @return
+     */
+    @Query(nativeQuery = true, value = "select * from ec_oe_exam_face_liveness_verify t where t.exam_record_data_id = ?1 and t.is_error = 1 order by id desc limit 1")
+    public ExamFaceLivenessVerifyEntity findErrorFaceVerifyByExamRecordDataId(Long examRecordDataId);
+}

+ 44 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamFileAnswerTempRepo.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import java.util.Date;
+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 org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamFileAnswerTempEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.FileAnswerAcknowledgeStatus;
+
+@Repository
+public interface ExamFileAnswerTempRepo
+		extends JpaRepository<ExamFileAnswerTempEntity, Long>, JpaSpecificationExecutor<ExamFileAnswerTempEntity> {
+	public ExamFileAnswerTempEntity findByExamRecordDataIdAndQuestionOrderAndFilePath(Long examRecordDataId,
+																					  Integer questionOrder, String filePath);
+
+	/**
+	 * 根据考试记录id和状态获取最新的文件上传地址集合
+	 * 
+	 * @param examRecordDataId
+	 * @param status
+	 * @return
+	 */
+	@Query(value="select *" +
+			" from ec_oe_exam_file_answer_temp" +
+			" where exam_record_data_id= ?1 and status= 'CONFIRMED'",nativeQuery = true)
+	public List<ExamFileAnswerTempEntity> findByExamRecordDataIdAndStatus(Long examRecordDataId, FileAnswerAcknowledgeStatus status);
+	@Modifying
+	@Query(nativeQuery = true, value = "delete from ec_oe_exam_file_answer_temp where exam_record_data_id = ?1 and exam_student_id = ?2 and question_order = ?3 and file_path=?4")
+	public void delete(Long examRecordDataId, Long examStudentId, Integer questionOrder, String filePath);
+
+	@Query(value="select * from ec_oe_exam_file_answer_temp order by id limit 100 ",nativeQuery = true)
+	List<ExamFileAnswerTempEntity> findLimitTempFileAnswers(int limit);
+
+	@Transactional
+	@Modifying
+	@Query(nativeQuery = true, value = "delete from ec_oe_exam_file_answer_temp where creation_time <= ?1")
+	int deleteByCreationTime(Date creationTime);
+}

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

@@ -0,0 +1,98 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+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 org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.IsSuccess;
+
+import java.util.List;
+
+/**
+ * @author chenken
+ * @date 2018年8月13日 下午2:02:25
+ * @company QMTH
+ * @description ExamRecordDataRepo.java
+ */
+@Repository
+public interface ExamRecordDataRepo extends JpaRepository<ExamRecordDataEntity, Long>, JpaSpecificationExecutor<ExamRecordDataEntity> {
+
+    public List<ExamRecordDataEntity> findByExamStudentId(Long examStudentId);
+
+    @Query(value = "select erd.* " +
+            "from ec_oe_exam_record_data erd " +
+            "where erd.exam_record_status in('EXAM_END','EXAM_OVERDUE') " +
+            "and erd.is_illegality=0 and (is_warn=0 or is_warn=1  and erd.is_audit=1) and erd.exam_student_id in ?1", nativeQuery = true)
+    public List<ExamRecordDataEntity> findByExamStudentIdList(List<Long> examStudentIdList);
+
+    public List<ExamRecordDataEntity> findByExamIdAndStudentCode(Long examId, String studentCode);
+
+    List<ExamRecordDataEntity> findByExamIdAndIdentityNumberAndCourseId(Long examId, String identityNumber, Long courseId);
+
+    /**
+     * 同步更新考生信息
+     *
+     * @param studentName
+     * @param studentCode
+     * @param infoCollector
+     */
+    @javax.transaction.Transactional
+    @Modifying
+    @Query(nativeQuery = true, value = "update ec_oe_exam_record_data set student_name = ?2,student_code = ?3,info_collector=?4 where exam_student_id = ?1")
+    public void syncUpdateExamStudentInfo(Long examStudentId, String studentName, String studentCode, String infoCollector);
+
+    @javax.transaction.Transactional
+    @Modifying
+    @Query(nativeQuery = true, value = "update ec_oe_exam_record_data set student_name = ?2  where student_id = ?1")
+    public void updateStudentName(Long studentId, String studentName);
+
+    @javax.transaction.Transactional
+    @Modifying
+    @Query(nativeQuery = true, value = "update ec_oe_exam_record_data set course_level = ?2 where course_id = ?1")
+    public void updateCourse(Long courseId, String courseLevel);
+
+    /**
+     * 根据studentId查询状态为"考试中"的在线考试记录
+     *
+     * @param studentId
+     * @return
+     */
+    @Query(value = " select * from ec_oe_exam_record_data t1 " +
+            " where t1.student_id = ?1  " +
+            " and t1.exam_record_status = 'EXAM_ING' " +
+            " and t1.exam_type = 'ONLINE'", nativeQuery = true)
+    public ExamRecordDataEntity findOnlineExamingRecordByStudentId(Long studentId);
+
+    /**
+     * 更新考试记录中的考试作答记录id
+     *
+     * @param examRecordQuestionId 考试作答记录id
+     * @param id                   examRecordDataId
+     * @return
+     */
+    @Transactional
+    @Modifying
+    @Query(value = "update ec_oe_exam_record_data set exam_record_questions_id=?1 where id=?2", nativeQuery = true)
+    int updateExamRecordDataQuestionIdById(String examRecordQuestionId, Long id);
+
+    /**
+     * 更新活体检测结果
+     *
+     * @param faceVerifyResult
+     * @param id
+     * @return
+     */
+    @Transactional
+    @Modifying
+    @Query(value = "update ec_oe_exam_record_data set face_verify_result = ?1  where student_id = ?2", nativeQuery = true)
+    int updateFaceVerifyResult(String faceVerifyResult, Long id);
+
+    @Query(value = "select * from ec_oe_exam_record_data " +
+            "where exam_record_status in('EXAM_END','EXAM_OVERDUE') and is_illegality=0 and (is_warn=0 or is_warn=1  and is_audit=1) " +
+            "and exam_id=?1 and id>=?2 order by id asc limit ?3", nativeQuery = true)
+    List<ExamRecordDataEntity> findLimitedDataByExamIdAndIdMoreThan(Long examId, Long startExamRecordId, int rowCount);
+}

+ 18 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordDataSyncRepo.java

@@ -0,0 +1,18 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataSyncEntity;
+
+/**
+ * @Description 同步
+ * @Author lideyin
+ * @Date 2019/12/23 18:25
+ * @Version 1.0
+ */
+@Repository
+public interface ExamRecordDataSyncRepo extends JpaRepository<ExamRecordDataSyncEntity, Long>, JpaSpecificationExecutor<ExamRecordDataSyncEntity> {
+    public ExamRecordDataSyncEntity findByCacheId(Long cacheId);
+}

+ 47 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordForMarkingRepo.java

@@ -0,0 +1,47 @@
+package cn.com.qmth.examcloud.core.oe.admin.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.admin.dao.entity.ExamRecordForMarkingEntity;
+
+import javax.transaction.Transactional;
+
+@Repository
+public interface ExamRecordForMarkingRepo extends JpaRepository<ExamRecordForMarkingEntity, Long>, JpaSpecificationExecutor<ExamRecordForMarkingEntity> {
+	@Query(nativeQuery = true, value = "select t.* from ec_oe_exam_record_4_marking t " +
+			"where t.exam_id = ?1 and t.course_id =?2 and t.exam_student_id in (?3)")
+	public List<ExamRecordForMarkingEntity> findByExamIdAndCourseIdAndExamStudentIds(Long examId,Long courseId,List<Long> examStudentIds);
+	@Query(nativeQuery = true, value = "select t.* from ec_oe_exam_record_4_marking t " +
+			"where t.exam_id = ?1 and t.course_id =?2 and t.exam_student_id in (?3) and (t.batch_num is null or t.batch_num!=?4)")
+	public List<ExamRecordForMarkingEntity> findByExamIdAndCourseIdAndExamStudentIdsAndBatchNum(Long examId,Long courseId,List<Long> examStudentIds,String batchNum);
+	public List<ExamRecordForMarkingEntity> findByExamIdAndCourseId(Long examId,Long courseId);
+	
+	
+	public ExamRecordForMarkingEntity findByExamRecordDataId(Long examRecordDataId);
+	
+	/**
+	 * 根据考生ID查询
+     * 测试是否存在
+     * @param examStudentId
+     * @return
+     */
+    @Query(nativeQuery = true, value = "select 1 from ec_oe_exam_record_4_marking t where t.exam_student_id = ?1 limit 1")
+    public Long countByExamStudentId(Long examStudentId);
+	
+    public ExamRecordForMarkingEntity findTopByExamStudentId(Long examStudentId);
+
+	List<ExamRecordForMarkingEntity> findByExamId(Long examId);
+
+	@Transactional
+	@Modifying
+	@Query(value = "update ec_oe_exam_record_4_marking set batch_num=?2 where id in (?1)",nativeQuery = true)
+	void updateBatchNum(List<Long> idList, String batchNum);
+
+	List<ExamRecordForMarkingEntity> findByExamStudentId(Long examStudentId);
+}

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

@@ -0,0 +1,20 @@
+package cn.com.qmth.examcloud.core.oe.admin.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.admin.dao.entity.ExamRecordPaperStructEntity;
+
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年9月1日 上午10:28:46
+ * @company 	QMTH
+ * @description ExamRecordPaperStructRepo.java
+ */
+@Repository
+public interface ExamRecordPaperStructRepo  extends MongoRepository<ExamRecordPaperStructEntity, String>,QueryByExampleExecutor<ExamRecordPaperStructEntity> {
+
+}

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

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import java.util.List;
+
+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.admin.dao.entity.ExamRecordQuestionsEntity;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年9月3日 上午10:48:39
+ * @company 	QMTH
+ * @description ExamRecordQuestionsRepo.java
+ */
+@Repository
+public interface ExamRecordQuestionsRepo  extends MongoRepository<ExamRecordQuestionsEntity, String>,QueryByExampleExecutor<ExamRecordQuestionsEntity>{
+	
+	ExamRecordQuestionsEntity findByExamRecordDataId(Long examRecordDataId);
+	/**
+	 * 考试记录id集合获取相应的考试记录
+	 * @param examRecordDataId
+	 * @return
+	 */
+	List<ExamRecordQuestionsEntity> findByExamRecordDataIdIn(List<Long> examRecordDataIdList);
+
+}

+ 24 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamScoreNoticeQueueRepo.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreNoticeQueueEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreObtainQueueEntity;
+/**
+ * 
+ * @author lideyin
+ * @date 2019年3月11日 下午2:08:00
+ * @description
+ */
+@Repository
+public interface ExamScoreNoticeQueueRepo extends JpaRepository<ExamScoreNoticeQueueEntity, Long>, JpaSpecificationExecutor<ExamScoreNoticeQueueEntity>{
+
+//	/**
+//	 * 根据机构id获取通知队列
+//	 * @param rootOrgId 顶级机构 id
+//	 * @return
+//	 */
+//	public ExamScoreNoticeQueueEntity findByRootOrgId(Long rootOrgId);
+}

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

@@ -0,0 +1,36 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import java.util.Date;
+import java.util.List;
+
+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 cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreObtainQueueEntity;
+import feign.Param;
+
+@Repository
+public interface ExamScoreObtainQueueRepo extends JpaRepository<ExamScoreObtainQueueEntity, Long>, JpaSpecificationExecutor<ExamScoreObtainQueueEntity>{
+
+	/**
+	 * 取队列最顶层记录
+	 * @param rootOrgId	顶级机构id
+	 * @param isValid	是否有效
+	 * @return
+	 */
+	public ExamScoreObtainQueueEntity findTopByRootOrgIdAndIsValidOrderByIdAsc(Long rootOrgId,Boolean isValid);
+	
+	public ExamScoreObtainQueueEntity findByExamRecordDataId(Long examRecordDataId);
+	/**
+	 * 获取创建时间小于某个时间的获取分数队列数据
+	 * @param creationTime 日期阈值
+	 * @return
+	 */
+	@Query(nativeQuery = true, value="select soq.* from ec_oe_exam_score_obtain_queue soq "
+			+ "left join ec_oe_exam_score_notice_queue snq on soq.root_org_id=snq.root_org_id "
+			+ "where soq.is_valid=1 and soq.creation_time<=?1 and snq.root_org_id is null")
+	public List<ExamScoreObtainQueueEntity> findByCreationTimeLessThanEquals(@Param("creationTime") Date creationTime);
+	
+}

+ 25 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamScorePushQueueRepo.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.core.oe.admin.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.Query;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScorePushQueue;
+
+@Repository
+public interface ExamScorePushQueueRepo extends JpaRepository<ExamScorePushQueue, Long>,JpaSpecificationExecutor<ExamScorePushQueue> {
+
+	/**
+	 * 查询可处理的记录,每次查询200条
+	 * 失败次数不大于2
+	 * @return
+	 */
+	@Query(nativeQuery = true, value = "select * from ec_oe_exam_score_push_queue where `status` IN ('PENDING','PROCESS_FAILED') and failed_times <=2 order by id asc limit 200")
+	public List<ExamScorePushQueue> findExamScorePushQueue();
+	
+	public ExamScorePushQueue findByExamRecordDataId(Long examRecordDataId);
+	
+}

+ 30 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamScoreRepo.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+
+import java.util.List;
+
+/**
+ * @author chenken
+ * @date 2018/8/15 9:54
+ * @company QMTH
+ * @description ExamScoreEntityRepo
+ */
+@Repository
+public interface ExamScoreRepo extends JpaRepository<ExamScoreEntity, Long>, JpaSpecificationExecutor<ExamScoreEntity> {
+
+    ExamScoreEntity findFirstByExamRecordDataId(Long examRecordDataId);
+
+    int countByExamRecordDataId(Long examRecordDataId);
+
+    List<ExamScoreEntity> findByExamRecordDataIdIn(List<Long> examRecordDataIds);
+
+    ExamScoreEntity findByExamRecordDataId(Long examRecordDataId);
+    
+    
+
+}

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

@@ -0,0 +1,20 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentFinalScoreEntity;
+
+/**
+ * @Description 考生最终成绩
+ * @Author lideyin
+ * @Date 2019/11/7 11:29
+ * @Version 1.0
+ */
+@Repository
+public interface ExamStudentFinalScoreRepo extends
+        JpaRepository<ExamStudentFinalScoreEntity, Long>, JpaSpecificationExecutor<ExamStudentFinalScoreEntity> {
+
+    ExamStudentFinalScoreEntity findByExamStudentId(Long examStudentId);
+}

+ 101 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamStudentRepo.java

@@ -0,0 +1,101 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.transaction.Transactional;
+
+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.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
+
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年8月13日 上午11:19:06
+ * @company 	QMTH
+ * @description 考试信息Repo
+ */
+@Repository
+public interface ExamStudentRepo extends JpaRepository<ExamStudentEntity, Long>, JpaSpecificationExecutor<ExamStudentEntity> {
+	
+	public List<ExamStudentEntity> findByStudentIdAndExamIdIn(Long studentId,List<Long> examId);
+	
+	public List<ExamStudentEntity> findByStudentIdAndEnableAndExamIdIn(Long studentId,Boolean enable,List<Long> examId);
+
+	public List<ExamStudentEntity> findByStudentId(Long studentId);
+
+	public ExamStudentEntity findByExamStudentId(Long examStudentId);
+	ExamStudentEntity findByIdentityNumberAndExamIdAndCourseId(String identityNumber,Long examId,Long courseId);
+	
+	public List<ExamStudentEntity> findByExamIdAndIdentityNumberIn(Long examId,List<String> identityNumbes);
+	
+	/**
+	 * @param examId
+	 * @param studentCodes
+	 * @return
+	 */
+	public List<ExamStudentEntity> findByExamIdAndStudentCodeIn(Long examId,List<String> studentCodes);
+	
+	public List<ExamStudentEntity> findByRootOrgIdAndIdentityNumberIn(Long rootOrgId,List<String> identityNumbes);
+	
+	@Transactional
+	@Modifying
+	@Query(nativeQuery = true, value = "update ec_oe_exam_student set finished = 1 where exam_student_id = ?1")
+	public void updateExamStudentFinished(Long examStudentId);
+	
+	public List<ExamStudentEntity> findByExamIdAndStudentId(Long examId,Long studentId);
+	
+	public List<ExamStudentEntity> findByExamId(Long examId);
+
+	Long countByExamId(Long examId);
+
+	Long countByExamIdAndFinished(Long examId,Boolean finished);
+	
+	@Transactional
+	@Modifying
+	@Query(nativeQuery = true, value = "update ec_oe_exam_student set student_name = ?2,student_code = ?3 where student_id = ?1")
+	public void updateStudentNameAndStudentCode(Long studentId,String studentName,String studentCode);
+
+	@Transactional
+	@Modifying
+	@Query(nativeQuery = true, value = "update ec_oe_exam_student set student_name = ?2 where student_id = ?1")
+	public void updateStudentName(Long studentId,String studentName);
+
+	@Transactional
+	@Modifying
+	@Query(nativeQuery = true, value = "update ec_oe_exam_student set course_level = ?2 where course_id = ?1")
+	public void updateCourse(Long courseId,String courseLevel);
+
+	/**
+	 * 解绑学号
+	 * @param studentCodes 学号集合
+	 * @param identityNumber 身份证号
+	 */
+	@Transactional
+	@Modifying
+	@Query(nativeQuery = true, value = "update ec_oe_exam_student set student_code=null where identity_number= :identityNumber and student_code in :studentCodes")
+	public void updateExamStudentCode(@Param("identityNumber") String identityNumber,@Param("studentCodes") List<String> studentCodes);
+
+
+	@Transactional
+	@Modifying
+	@Query(nativeQuery = true,value = "delete from ec_oe_exam_student  where exam_id = ?1")
+	public void deleteByExamId(Long examId);
+
+
+	@Query(value = "select *  from ec_oe_exam_student where exam_id=?1 and id>=?2 order by id limit ?3",nativeQuery = true)
+	List<ExamStudentEntity> getLimitExamStudentList(Long examId, Long startId, Integer size);
+
+	ExamStudentEntity findByStudentIdAndExamIdAndCourseId(Long studentId,Long examId,Long courseId);
+
+	@Query(value = "select *  from ec_oe_exam_student where enable=1 and finished=1 and exam_id=?1 and course_code=?2 and exam_student_id>=?3 order by id limit ?4",nativeQuery = true)
+	List<ExamStudentEntity> getLimitExamStudentList(Long examId, String courseCode,Long startId, Integer size);
+
+}

+ 17 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamSyncCaptureRepo.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamSyncCaptureEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+/**
+ * @Description 同步抓拍照片
+ * @Author lideyin
+ * @Date 2019/12/10 14:05
+ * @Version 1.0
+ */
+@Repository
+public interface ExamSyncCaptureRepo extends JpaRepository<ExamSyncCaptureEntity, Long>, JpaSpecificationExecutor<ExamSyncCaptureEntity> {
+    ExamSyncCaptureEntity findByExamRecordDataId(Long examRecordId);
+}

+ 38 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamingRecordRepo.java

@@ -0,0 +1,38 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+
+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 cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamingRecordEntity;
+
+import java.util.List;
+
+/**
+ * @Description 正在进行中的考试
+ * @Author lideyin
+ * @Date 2019/8/26 10:36
+ * @Version 1.0
+ */
+@Repository
+public interface ExamingRecordRepo extends JpaRepository<ExamingRecordEntity, Long>, JpaSpecificationExecutor<ExamingRecordEntity> {
+	/**
+	 * 查找指定数量的考试中的记录
+	 * @param startId
+	 * @param limit
+	 * @return
+	 */
+	@Query(value = "select * from ec_oe_examing_record where id>= ?1 order by id asc limit ?2",nativeQuery = true)
+	List<ExamingRecordEntity> getLimitExamingRecords(Long startId, int limit);
+
+	/**
+	 * 根据studentId查询状态为"考试中"的在线考试记录
+	 * @param studentId
+	 * @return
+	 */
+	@Query(value= "select * from ec_oe_examing_record where student_id=?1 and exam_type<>'OFFLINE'", nativeQuery = true)
+	ExamingRecordEntity findOnlineExamingRecord(Long studentId);
+
+}

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

@@ -0,0 +1,67 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+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.admin.dao.entity.FaceBiopsyItemEntity;
+
+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_oe_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);
+}

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

@@ -0,0 +1,21 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.FaceBiopsyItemStepEntity;
+
+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);
+
+}

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

@@ -0,0 +1,30 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+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.admin.dao.entity.FaceBiopsyEntity;
+
+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_oe_exam_face_biopsy set result=?2,error_msg=?3 where exam_record_data_id=?1")
+    void updateFaceBiopsyResult(Long examRecordDataId, boolean result, String errorMsg);
+}

+ 26 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/HandInExamRecordRepo.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+
+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 cn.com.qmth.examcloud.core.oe.admin.dao.entity.HandInExamRecordEntity;
+
+import java.util.List;
+
+/**
+ * 已交卷的考试记录仓库
+ */
+@Repository
+public interface HandInExamRecordRepo extends JpaRepository<HandInExamRecordEntity, Long>, JpaSpecificationExecutor<HandInExamRecordEntity> {
+	/**
+	 * 查找指定数量已交卷的考试记录
+	 * @param startId
+	 * @param limit
+	 * @return
+	 */
+	@Query(value = "select * from ec_oe_hand_in_exam_record where id>= ?1 order by id asc limit ?2",nativeQuery = true)
+	List<HandInExamRecordEntity> getLimitHandInExamRecords(Long startId, int limit);
+}

+ 23 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/IllegallyTypeRepo.java

@@ -0,0 +1,23 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.IllegallyTypeEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+import java.util.List;
+
+public interface IllegallyTypeRepo
+		extends
+			JpaRepository<IllegallyTypeEntity, Long>,
+			QueryByExampleExecutor<IllegallyTypeEntity>,
+			JpaSpecificationExecutor<IllegallyTypeEntity> {
+
+	IllegallyTypeEntity findByRootOrgIdAndCode(Long rootOrgId, String code);
+
+	IllegallyTypeEntity findByRootOrgIdAndCodeAndDataCategory(Long rootOrgId, String code,String dataCategory);
+
+	List<IllegallyTypeEntity> findByRootOrgId(Long rootOrgId);
+
+	int countByRootOrgIdAndNameLike(Long rootOrgId, String name);
+}

+ 13 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/OfflineFileRepo.java

@@ -0,0 +1,13 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamAuditEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.OfflineFileEntity;
+
+@Repository
+public interface OfflineFileRepo extends JpaRepository<OfflineFileEntity, Long>, JpaSpecificationExecutor<OfflineFileEntity> {
+
+}

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

@@ -0,0 +1,20 @@
+package cn.com.qmth.examcloud.core.oe.admin.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.Query;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.OrgScoreHandleEntity;
+
+@Repository
+public interface OrgScoreHandleRepo extends JpaRepository<OrgScoreHandleEntity, Long>, JpaSpecificationExecutor<OrgScoreHandleEntity>{
+	
+	public OrgScoreHandleEntity findByRootOrgId(Long rootOrgId);
+	
+	@Query(nativeQuery = true, value = "select * from ec_oe_exam_org_score_handle where notify_url is not null")
+	public List<OrgScoreHandleEntity> findEnableNotifyItems();
+	
+}

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

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

+ 117 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamAuditEntity.java

@@ -0,0 +1,117 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
+
+import javax.persistence.*;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.AuditStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.DisciplineType;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+
+/**
+ * @author chenken
+ * @date 2018/8/15 10:01
+ * @company QMTH
+ * @description ExamAuditEntity
+ */
+@Entity
+@Table(name = "ec_oe_exam_audit",indexes = {
+        @Index(name = "IDX_E_O_E_A_001",columnList = "examRecordDataId",unique = true)
+})
+public class ExamAuditEntity extends JpaEntity {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = -6330756328920243183L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    /**
+     * ec_oe_exam_record_data  ID
+     */
+    private Long examRecordDataId;
+
+    /**
+     * 违纪类型
+     */
+    @Enumerated(EnumType.STRING)
+    private DisciplineType disciplineType;
+
+    /**
+     * 违纪详情
+     */
+    private String disciplineDetail;
+
+    /**
+     * 审核人ID
+     */
+    private String userId;
+    /**
+     * 审核人姓名
+     */
+    private String auditUserName;
+    /**
+     * 审核状态
+     */
+    @Enumerated(EnumType.STRING)
+    private AuditStatus status;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getDisciplineDetail() {
+        return disciplineDetail;
+    }
+
+    public void setDisciplineDetail(String disciplineDetail) {
+        this.disciplineDetail = disciplineDetail;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public AuditStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(AuditStatus status) {
+        this.status = status;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public DisciplineType getDisciplineType() {
+        return disciplineType;
+    }
+
+    public void setDisciplineType(DisciplineType disciplineType) {
+        this.disciplineType = disciplineType;
+    }
+
+	public String getAuditUserName() {
+		return auditUserName;
+	}
+
+	public void setAuditUserName(String auditUserName) {
+		this.auditUserName = auditUserName;
+	}
+    
+}

部分文件因文件數量過多而無法顯示