Ver Fonte

merge from release_v4.0.1

deason há 4 anos atrás
pai
commit
636bbce4af
82 ficheiros alterados com 3538 adições e 1366 exclusões
  1. 11 4
      .gitignore
  2. 21 22
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamAuditBeanConvert.java
  3. 3 1
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamStudentBeanConvert.java
  4. 4 3
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamAuditController.java
  5. 16 10
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamCaptureController.java
  6. 60 26
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordController.java
  7. 18 10
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamScoreController.java
  8. 36 27
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamStudentController.java
  9. 42 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExportTaskController.java
  10. 2 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/IllegallyTypeController.java
  11. 202 114
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/OfflineExamController.java
  12. 5 6
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/PracticeController.java
  13. 4 56
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordCloudServiceProvider.java
  14. 46 11
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordForMarkingCloudServiceProvider.java
  15. 7 5
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamScoreDataCloudServiceProvider.java
  16. 1 1
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamStudentDataCloudServiceProvider.java
  17. 57 4
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/SyncExamDataCloudServiceProvider.java
  18. 27 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamContinuedRecordRepo.java
  19. 21 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamProcessRecordRepo.java
  20. 20 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordFileAnswerRepo.java
  21. 34 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExportTaskRepo.java
  22. 76 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamContinuedRecordEntity.java
  23. 78 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamProcessRecordEntity.java
  24. 79 1
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamRecordDataEntity.java
  25. 124 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamRecordFileAnswerEntity.java
  26. 0 25
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamRecordForMarkingEntity.java
  27. 26 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamStudentEntity.java
  28. 119 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExportTaskEntity.java
  29. 0 1
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/FaceBiopsyItemEntity.java
  30. 29 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/ExportTaskStatus.java
  31. 27 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/ExportTaskType.java
  32. 17 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/AsyncExportService.java
  33. 23 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamProcessRecordService.java
  34. 4 3
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordDataService.java
  35. 5 3
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordForMarkingService.java
  36. 5 4
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamStudentService.java
  37. 32 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExportTaskService.java
  38. 14 6
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/OfflineExamService.java
  39. 78 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/ExamProcessRecordInfo.java
  40. 15 23
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/OfflineExamCourseInfo.java
  41. 44 5
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditInfo.java
  42. 5 2
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditMapper.java
  43. 72 3
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditQuery.java
  44. 13 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examcapture/ExamCaptureAuditInfo.java
  45. 9 8
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordEntityConvert.java
  46. 122 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordFileAnswerInfo.java
  47. 15 15
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordInfo.java
  48. 49 12
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordQuery.java
  49. 2 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examscore/ExamScoreEntityConvert.java
  50. 56 39
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examscore/ExamScoreQuery.java
  51. 27 2
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentBean.java
  52. 48 16
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentInfo.java
  53. 15 5
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentQuery.java
  54. 93 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTaskInfo.java
  55. 82 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTaskListReq.java
  56. 139 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTaskListResp.java
  57. 2 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/cache/ExamStudentCache.java
  58. 236 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/AsyncExportServiceImpl.java
  59. 39 5
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamAuditServiceImpl.java
  60. 8 5
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamCaptureServiceImpl.java
  61. 51 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamProcessRecordServiceImpl.java
  62. 8 9
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordDataServiceImpl.java
  63. 22 5
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordForMarkingServiceImpl.java
  64. 14 3
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordServiceImpl.java
  65. 1 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreObtainQueueServiceImpl.java
  66. 1 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScorePushQueueServiceImpl.java
  67. 18 28
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreServiceImpl.java
  68. 1 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamStudentFinalScoreServiceImpl.java
  69. 167 116
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamStudentServiceImpl.java
  70. 219 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExportTaskServiceImpl.java
  71. 158 40
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/OfflineExamServiceImpl.java
  72. 10 7
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/PracticeServiceImpl.java
  73. 0 254
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/others/ExamCacheTransferHelper.java
  74. 136 137
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/pushscore/cugr/CugrExamScorePushServiceImpl.java
  75. 24 25
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/pushscore/cup/CupExamScorePushServiceImpl.java
  76. 148 148
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/pushscore/swufe/SwufeExamScorePushServiceImpl.java
  77. 2 0
      examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/OEAdminApp.java
  78. 15 7
      examcloud-core-oe-admin-starter/src/main/resources/aliyun.xml
  79. 2 2
      examcloud-core-oe-admin-starter/src/main/resources/application.properties
  80. 77 62
      examcloud-core-oe-admin-starter/src/main/resources/log4j2.xml
  81. 0 18
      jenkins-dev.sh
  82. 0 18
      jenkins-test.sh

+ 11 - 4
.gitignore

@@ -1,12 +1,19 @@
+*.class
+
+# Proguard folder generated by ide
 .project
 .classpath
 .settings
 target/
 .idea/
 *.iml
-*test/
-# Package Files #
-*.jar
-logs/
 
+# Log Files
+*.log
+*.class
 
+
+# Package Files #
+*.jar
+*.war
+*.ear

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

@@ -8,7 +8,6 @@
 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;
 
 /**
  * 类信息转换
@@ -18,35 +17,35 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.enums.DisciplineType;
  */
 public class ExamAuditBeanConvert implements JsonSerializable {
 
-	private Long examRecordDataId;
+    private Long examRecordDataId;
 
-	private String disciplineDetail;
+    private String disciplineDetail;
 
-	private Boolean isPass;
+    private Boolean isPass;
 
 
-	public Long getExamRecordDataId() {
-		return examRecordDataId;
-	}
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
 
-	public void setExamRecordDataId(Long examRecordDataId) {
-		this.examRecordDataId = examRecordDataId;
-	}
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
 
-	public String getDisciplineDetail() {
-		return disciplineDetail;
-	}
+    public String getDisciplineDetail() {
+        return disciplineDetail;
+    }
 
-	public void setDisciplineDetail(String disciplineDetail) {
-		this.disciplineDetail = disciplineDetail;
-	}
+    public void setDisciplineDetail(String disciplineDetail) {
+        this.disciplineDetail = disciplineDetail;
+    }
 
-	public Boolean getIsPass() {
-		return isPass;
-	}
+    public Boolean getIsPass() {
+        return isPass;
+    }
 
-	public void setIsPass(Boolean isPass) {
-		this.isPass = isPass;
-	}
+    public void setIsPass(Boolean isPass) {
+        this.isPass = isPass;
+    }
 
 }

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

@@ -108,7 +108,9 @@ public class ExamStudentBeanConvert implements JsonSerializable {
         info.setExtraNum(0);
         info.setUsedNum(0);
         info.setEnable(req.getEnable());
+        info.setExamStageId(req.getExamStageId());
+        info.setExamStageOrder(req.getExamStageOrder());
         return info;
     }
 
-}
+}

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

@@ -20,8 +20,8 @@ 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.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
@@ -155,12 +155,13 @@ public class ExamAuditController extends ControllerSupport {
 
         //原需求:已经生成了评卷任务,不能重新审为不通过,但是可以审核为已通过
         //20191220,需求临时调整:已经生成了评卷任务,均不可重审
+        //20200923,需求调整,恢复至原需求:已经生成了评卷任务,不能重新审为不通过,但是可以审核为已通过
         String markingTaskBuilt =
                 ExamCacheTransferHelper.getDefaultCachedExamProperty(examId, ExamProperties.MARKING_TASK_BUILDED.name()).getValue();
         if (StringUtils.isNotBlank(markingTaskBuilt) && Constants.isTrue.equals(markingTaskBuilt)) {
-//            if (!redoAuditInfo.getIsPass()) {
+            if (!redoAuditInfo.getIsPass()) {
             throw new StatusException("redoAudit-005", "该考试评卷任务已生成,不允许重审");
-//            }
+            }
         }
         examAuditService.redoAudit(redoAuditInfo, user);
     }

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

@@ -7,21 +7,19 @@
 
 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.ExamProcessRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.ExamProcessRecordInfo;
 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.support.helper.IdentityNumberHelper;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
 
 /**
  * 考试抓拍检测相关接口
@@ -35,6 +33,8 @@ import io.swagger.annotations.ApiOperation;
 public class ExamCaptureController extends ControllerSupport {
     @Autowired
     private ExamCaptureService examCaptureService;
+    @Autowired
+    private ExamProcessRecordService examProcessRecordService;
 
     @PostMapping("/audit/detail")
     @ApiOperation(value = "抓拍详情-审核详细信息")
@@ -54,4 +54,10 @@ public class ExamCaptureController extends ControllerSupport {
         return examCaptureService.getExamCaptureList(examRecordDataId);
     }
 
-}
+    @GetMapping("/getExamProcessRecords")
+    @ApiOperation(value = "获取考试过程记录")
+    public List<ExamProcessRecordInfo> getExamProcessRecords(@RequestParam Long examRecordDataId) {
+        return examProcessRecordService.getExamProcessRecords(examRecordDataId);
+    }
+
+}

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

@@ -7,31 +7,19 @@
 
 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.ExamRecordFileAnswerRepo;
 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.dao.entity.ExamRecordFileAnswerEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
 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.ExamRecordFileAnswerInfo;
 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;
@@ -41,6 +29,15 @@ 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.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * 考试记录相关接口
@@ -52,17 +49,25 @@ import io.swagger.annotations.ApiOperation;
 @Api(tags = "考试记录相关接口")
 @RequestMapping("${$rmp.ctr.oe}/exam/record")
 public class ExamRecordController extends ControllerSupport {
+
     @Autowired
     private ExamRecordService examRecordService;
 
+    @Autowired
+    private AsyncExportService asyncExportService;
+
     @Autowired
     private ExamRecordDataRepo examRecordDataRepo;
+
     @Autowired
     private ExamCaptureService examCaptureService;
-    
+
     @Autowired
     private ExamRecordForMarkingRepo examRecordForMarkingRepo;
 
+    @Autowired
+    private ExamRecordFileAnswerRepo examRecordFileAnswerRepo;
+
 
     @PostMapping("/waiting/audit/list")
     @ApiOperation(value = "查询“监考待审”列表(分页)")
@@ -71,7 +76,7 @@ public class ExamRecordController extends ControllerSupport {
         examRecordWaitingAuditList.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
         });
-        List<ExamRecordInfo> examRecordInfoList=examRecordWaitingAuditList.getContent();
+        List<ExamRecordInfo> examRecordInfoList = examRecordWaitingAuditList.getContent();
         if (examRecordInfoList != null && examRecordInfoList.size() > 0) {
             String examType = examRecordInfoList.get(0).getExamType();
 
@@ -102,12 +107,12 @@ public class ExamRecordController extends ControllerSupport {
                 examRecordInfo.setIdentityNumber((IdentityNumberHelper.conceal(examRecordInfo.getRootOrgId(),
                         examRecordInfo.getIdentityNumber())));
 
-                if (ExamType.OFFLINE.name() == examType) {
+                if (ExamType.OFFLINE.name().equals(examType)) {
                     long examRecordDataId = examRecordInfo.getDataId();
-                    ExamRecordForMarkingEntity examRecordForMarkingEntity = examRecordForMarkingRepo.findByExamRecordDataId(examRecordDataId);
-                    if (examRecordForMarkingEntity != null) {
-                        examRecordInfo.setOfflineFileUrl(
-                                FileStorageUtil.realPath(examRecordForMarkingEntity.getOfflineFileUrl()));
+                    List<ExamRecordFileAnswerEntity> fileAnswerList =
+                            examRecordFileAnswerRepo.findByExamRecordDataId(examRecordDataId);
+                    if (fileAnswerList != null) {
+                        examRecordInfo.setOfflineFiles(getOfflineFilesFrom(fileAnswerList));
                     }
                 }
                 if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
@@ -118,6 +123,24 @@ public class ExamRecordController extends ControllerSupport {
         return examRecordInfoPage;
     }
 
+    private List<ExamRecordFileAnswerInfo> getOfflineFilesFrom(List<ExamRecordFileAnswerEntity> fileAnswerList) {
+        List<ExamRecordFileAnswerInfo> resultList = new ArrayList<>();
+        for (ExamRecordFileAnswerEntity entity : fileAnswerList) {
+            ExamRecordFileAnswerInfo info = new ExamRecordFileAnswerInfo();
+            info.setId(entity.getId());
+            info.setExamRecordDataId(entity.getExamRecordDataId());
+            info.setOfflineFileUrl(FileStorageUtil.realPath(entity.getFileUrl()));
+            info.setOfflineFileName(entity.getFileName());
+            info.setOriginalFileName(entity.getOriginalFileName());
+            info.setFileType(entity.getFileType());
+            info.setSuffix(entity.getSuffix());
+            info.setProperties(entity.getProperties());
+            resultList.add(info);
+        }
+
+        return resultList;
+    }
+
     @Naked
     @GetMapping("/detail/list/export")
     @ApiOperation(value = "导出“考试明细”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
@@ -125,6 +148,7 @@ public class ExamRecordController extends ControllerSupport {
         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();
@@ -135,9 +159,17 @@ public class ExamRecordController extends ControllerSupport {
                 }
             }
         }
+
         ExportUtils.exportEXCEL("考试明细列表", ExamRecordInfo.class, examRecordInfoList, response);
     }
 
+    @Naked
+    @GetMapping("/detail/list/export/async")
+    @ApiOperation(value = "导出“考试明细”列表(异步)", notes = "参数示例:query={\"rootOrgId\":0,\"examId\":1}")
+    public void exportExamRecordDetails(@RequestParam String query) {
+        asyncExportService.exportExamRecordDetails(query);
+    }
+
     @PostMapping(value = "/refresh/capture/statistic")
     @ApiOperation(value = "监考待审-重新统计", notes = "根据人脸识别阀值重新计算数据")
     public ResponseEntity<String> refreshCaptureStatistic(@RequestParam Long examId) {
@@ -162,10 +194,12 @@ public class ExamRecordController extends ControllerSupport {
 
     @GetMapping("/detail/examStudentQuestionScoreList")
     @ApiOperation(value = "导出考生作答分数明细列表(Excel)", notes = "参数examId和examId必传")
-    public void getExamStudentQuestionScoreList(@RequestParam Long examId, @RequestParam String courseCode, HttpServletResponse response) throws Exception {
+    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);
+        List<ExamStudentQuestionScoreInfo> examRecordInfoList =
+                examRecordService.getExamStudentQuestionScoreList(examId, courseCode);
         ExportUtils.exportEXCEL("考试明细列表", ExamStudentQuestionScoreInfo.class, examRecordInfoList, response);
     }
-}
+}

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

@@ -11,8 +11,7 @@ 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.AsyncExportService;
 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;
@@ -23,7 +22,6 @@ 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.RedisKeyBuilder;
 import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
@@ -47,22 +45,23 @@ import java.util.List;
 @Api(tags = "考试分数相关接口")
 @RequestMapping("${$rmp.ctr.oe}/exam/score")
 public class ExamScoreController extends ControllerSupport {
+
     @Autowired
-    private ExamScoreService examScoreService;
+    private AsyncExportService asyncExportService;
 
     @Autowired
-    private RedisClient redisClient;
+    private ExamScoreService examScoreService;
 
     @Autowired
-    private ExamScoreRepo examScoreRepo;
+    private RedisClient redisClient;
 
     @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()));
+        examScoreList.getContent().forEach(p -> {
+            p.setIdentityNumber(IdentityNumberHelper.conceal(getRootOrgId(), p.getIdentityNumber()));
         });
         return examScoreList;
     }
@@ -78,6 +77,14 @@ public class ExamScoreController extends ControllerSupport {
         ExportUtils.exportEXCEL("成绩统计列表", ExamScoreInfo.class, examScoreList, response);
     }
 
+    @Naked
+    @GetMapping("/statistic/list/export/async")
+    @ApiOperation(value = "导出“成绩统计”列表(异步)",
+            notes = "参数示例:query={\"rootOrgId\":0,\"examId\":1,\"startLimit\":1,\"endLimit\":1}")
+    public void exportExamScores(@RequestParam String query) {
+        asyncExportService.exportExamScoreStatistics(query);
+    }
+
     @ApiOperation(value = "根据examStudentId获取客观分信息")
     @GetMapping("/queryObjectiveScoreList")
     public List<ObjectiveScoreInfo> queryObjectiveScoreList(@RequestParam Long examStudentId) {
@@ -101,7 +108,8 @@ public class ExamScoreController extends ControllerSupport {
 
                 for (Long examRecordDataId : unSyncedExamRecordDataIds) {
                     ExamRecordData examRecordData =
-                            redisClient.get(RedisKeyHelper.getBuilder().examRecordDataKey(examRecordDataId), ExamRecordData.class);
+                            redisClient.get(RedisKeyHelper.getBuilder().examRecordDataKey(examRecordDataId),
+                                    ExamRecordData.class);
                     if (null == examRecordData) {
                         throw new StatusException("100001", "考试记录的缓存数据有误");
                     }
@@ -182,7 +190,7 @@ public class ExamScoreController extends ControllerSupport {
      * @return
      */
     private Integer getExamOrder(Long examId, Long studentId, Integer usedExamNum) {
-        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getDefaultCachedExam(examId);
         Integer canExamTimes = cachedExam.getExamTimes() == null ? 0 : cachedExam.getExamTimes().intValue();//可考次数
 
         //超过可考次数,始终为可考次数+1

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

@@ -13,9 +13,9 @@ 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.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.helper.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;
@@ -41,8 +41,6 @@ import java.util.concurrent.ConcurrentHashMap;
 public class ExamStudentController extends ControllerSupport {
     @Autowired
     private ExamStudentService examStudentService;
-    @Autowired
-    private GainBaseDataService gainBaseDataService;
 
     /**
      * 查询课程本地缓存
@@ -76,8 +74,8 @@ public class ExamStudentController extends ControllerSupport {
         Check.isNull(query.getExamId(), "考试批次不能为空!");
         Page<ExamStudentInfo> examStudentListPage = examStudentService.getExamStudentListPage(query);
 
-        examStudentListPage.getContent().forEach(p->{
-            p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(),p.getIdentityNumber()));
+        examStudentListPage.getContent().forEach(p -> {
+            p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
         });
 
         return examStudentListPage;
@@ -101,8 +99,8 @@ public class ExamStudentController extends ControllerSupport {
         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()));
+        examStudentListPage.getContent().forEach(p -> {
+            p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
         });
         return examStudentListPage;
     }
@@ -126,8 +124,8 @@ public class ExamStudentController extends ControllerSupport {
         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()));
+        reExamineStudentList.getContent().forEach(p -> {
+            p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
         });
         return reExamineStudentList;
     }
@@ -135,39 +133,44 @@ public class ExamStudentController extends ControllerSupport {
     @PostMapping("/reexamine")
     @ApiOperation(value = "设置重考")
     public void setReexamine(@RequestParam Long examStudentId,
-                             @RequestParam(required=false) String reexamineType,
-                             @RequestParam(required=false) String reexamineDetail) {
+                             @RequestParam(required = false) String reexamineType,
+                             @RequestParam(required = false) String reexamineDetail) {
         if (examStudentId == null) {
             return;
         }
-        examStudentService.setReexamine(examStudentId,reexamineType,reexamineDetail);
+        examStudentService.setReexamine(examStudentId, reexamineType, reexamineDetail);
     }
 
     @PostMapping("/statistic/by/finished")
     @ApiOperation(value = "考试概况-按考试完成进度统计")
-    public ExamStudentFinishedStatistic getExamStudentStatisticByFinished(@RequestParam Long examId) {
+    public ExamStudentFinishedStatistic getExamStudentStatisticByFinished(@RequestParam Long examId,
+                                                                          @RequestParam(required = false) Long examStageId) {
         if (examId == null) {
             return null;
         }
-        return examStudentService.getExamStudentStatisticByFinished(examId);
+        return examStudentService.getExamStudentStatisticByFinished(examId, examStageId);
     }
 
     @PostMapping("/statistic/by/org")
     @ApiOperation(value = "考试概况-按学习中心统计")
-    public List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(@RequestParam Long examId, @RequestParam(required = false) Long orgId) {
+    public List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(@RequestParam Long examId,
+                                                                      @RequestParam(required = false) Long examStageId,
+                                                                      @RequestParam(required = false) Long orgId) {
         if (examId == null) {
             return null;
         }
-        return examStudentService.getExamStudentStatisticByOrg(examId, orgId);
+        return examStudentService.getExamStudentStatisticByOrg(examId, examStageId, orgId);
     }
 
     @GetMapping("/statistic/by/org/export")
     @ApiOperation(value = "导出“考试概况-按学习中心统计”列表(Excel)")
     public void exportExamStudentStatisticByOrg(@RequestParam Long examId,
+                                                @RequestParam(required = false) Long examStageId,
                                                 @RequestParam(required = false) Long orgId,
-                                          HttpServletResponse response) throws Exception {
+                                                HttpServletResponse response) throws Exception {
         Check.isNull(examId, "请选择考试批次!");
-        List<ExamStudentOrgStatistic> examStudentStatisticByOrgList = examStudentService.getExamStudentStatisticByOrg(examId, orgId);
+        List<ExamStudentOrgStatistic> examStudentStatisticByOrgList =
+                examStudentService.getExamStudentStatisticByOrg(examId, examStageId, orgId);
         List<OrgCompleteProgressExcel> resultList =
                 ExamStudentEntityConvert.copyFromExamStudentOrgStatistic(examStudentStatisticByOrgList);
         ExportUtils.exportEXCEL("学习中心完成进度", OrgCompleteProgressExcel.class, resultList, response);
@@ -182,15 +185,17 @@ public class ExamStudentController extends ControllerSupport {
      */
     @GetMapping("/findCoursesByExamIdAndOrgId")
     @ApiOperation(value = "查询课程")
-    public List<CourseBean> findCoursesByExamIdAndOrgId(@RequestParam Long examId, @RequestParam(required = false) Long orgId) {
+    public List<CourseCacheBean> findCoursesByExamIdAndOrgId(@RequestParam Long examId,
+                                                        @RequestParam(required = false) Long examStageId,
+                                                        @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>();
+        List<Long> courseIdList = examStudentService.findCoursesFromExamStudent(examId, examStageId, orgId);
+        List<CourseCacheBean> courseBeanList = new ArrayList<CourseCacheBean>();
         for (Long courseId : courseIdList) {
-            CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
+            CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
             courseBeanList.add(courseBean);
         }
         return courseBeanList;
@@ -206,16 +211,18 @@ public class ExamStudentController extends ControllerSupport {
     @GetMapping("/courseProgress/list")
     @ApiOperation(value = "课程完成进度")
     public List<CourseProgressInfo> queryCourseProgressInfos(@RequestParam Long examId,
+                                                             @RequestParam(required = false) Long examStageId,
                                                              @RequestParam(required = false) Long courseId,
                                                              @RequestParam(required = false) String orderColumn) {
         if (examId == null) {
             return null;
         }
-        List<CourseProgressInfo> courseProgressInfoList = examStudentService.queryCourseProgressInfos(examId, courseId, orderColumn);
+        List<CourseProgressInfo> courseProgressInfoList =
+                examStudentService.queryCourseProgressInfos(examId, examStageId, courseId, orderColumn);
         if (courseProgressInfoList != null && courseProgressInfoList.size() > 0) {
             for (CourseProgressInfo courseProgressInfo : courseProgressInfoList) {
                 long key = courseProgressInfo.getCourseId();
-                CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(key);
+                CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(key);
                 courseProgressInfo.setCourseCode(courseBean.getCode());
                 courseProgressInfo.setCourseName(courseBean.getName());
             }
@@ -226,15 +233,17 @@ public class ExamStudentController extends ControllerSupport {
     @GetMapping("/courseProgress/list/export")
     @ApiOperation(value = "导出“课程完成进度”列表(Excel)")
     public void exportCourseProgressInfos(@RequestParam Long examId,
+                                          @RequestParam(required = false) Long examStageId,
                                           @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);
+        List<CourseProgressInfo> courseProgressInfoList =
+                examStudentService.queryCourseProgressInfos(examId, examStageId, courseId, orderColumn);
         if (courseProgressInfoList != null && courseProgressInfoList.size() > 0) {
             for (CourseProgressInfo courseProgressInfo : courseProgressInfoList) {
                 long key = courseProgressInfo.getCourseId();
-                CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(key);
+                CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(key);
                 courseProgressInfo.setCourseCode(courseBean.getCode());
                 courseProgressInfo.setCourseName(courseBean.getName());
             }

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

@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 16:19: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.core.oe.admin.service.ExportTaskService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskListReq;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskListResp;
+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.Page;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@Api(tags = "导出任务相关接口")
+@RequestMapping("${$rmp.ctr.oe}/export/task")
+public class ExportTaskController extends ControllerSupport {
+
+    @Autowired
+    private ExportTaskService exportTaskService;
+
+    @PostMapping("/list")
+    @ApiOperation(value = "查询导出任务列表(分页)")
+    public Page<ExportTaskListResp> list(@RequestBody ExportTaskListReq req) {
+        User user = getAccessUser();
+        req.setRootOrgId(user.getRootOrgId());
+
+        return exportTaskService.getExportTaskList(req);
+    }
+
+    @PostMapping("/delete")
+    @ApiOperation(value = "删除某个导出任务")
+    public void delete(@RequestParam Long taskId) {
+        exportTaskService.deleteExportTaskById(taskId);
+    }
+
+}

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

@@ -20,6 +20,7 @@ import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
+import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.fileupload.disk.DiskFileItem;
@@ -51,6 +52,7 @@ import java.util.stream.Stream;
  * @Version 1.0
  */
 @RestController
+@Api(tags = "违纪类型相关接口")
 @RequestMapping("${$rmp.ctr.oe}/illegallyType")
 public class IllegallyTypeController extends ControllerSupport {
 

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

@@ -1,144 +1,232 @@
 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.commons.util.MD5;
 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.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+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 io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+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.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
 
 
 /**
- *
- * @author  	chenken
- * @date    	2018年9月5日 下午3:33:26
- * @company 	QMTH
+ * @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){
+public class OfflineExamController extends ControllerSupport {
+
+    @Autowired
+    private OfflineExamService offlineExamService;
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+    @Autowired
+    SystemProperties systemConfig;
+    /**
+     * 答案文件最大限制
+     * 单位: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)
+                && !"JPG".equals(fileSuffix) && !"JPEG".equals(fileSuffix) && !"PNG".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");
+        }
+
+        File f = getUploadFile(file);
+
+        offlineExamService.submitPaper(examRecordDataId, f,fileSuffix);
+    }
+
+    /**
+     * 交卷
+     *
+     * @param fileArray            文件数组
+     * @param examRecordDataId 考试记录id
+     * @param fileMd5Array        文件摘要信息
+     * @throws Exception
+     */
+    @ApiOperation(value = "离线考试:多文件交卷")
+    @PostMapping("/batchSubmitPaper")
+    public void batchSubmitPaper(@ApiParam(value = "文件数组") @RequestParam MultipartFile[] fileArray,
+                                 @ApiParam(value = "考试记录id") @RequestParam long examRecordDataId,
+                                 @ApiParam(value = "文件类型:ZIP/PDF/IMAGE") @RequestParam String fileType,
+                                 @ApiParam(value = "md5加密的文件摘要") @RequestParam String[] fileMd5Array) throws Exception {
+        Check.isNull(fileArray, "files不能为空");
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+        if (fileArray.length != fileMd5Array.length) {
+            throw new StatusException("200001", "文件数量和文件摘要信息不匹配");
+        }
+
+        List<File> resultFiles=new ArrayList<>();
+        for (int i = 0; i < fileArray.length; i++) {
+            MultipartFile file = fileArray[i];
+            String fileName = file.getOriginalFilename();
+            int index = fileName.lastIndexOf(".");
+            String fileSuffix = fileName.substring(index + 1, fileName.length()).toUpperCase();
+
+            String upperFileType=fileType.toUpperCase();
+            if (!"PDF".equals(upperFileType) && !"ZIP".equals(upperFileType)
+                    && !"IMAGE".equals(upperFileType)) {
+                throw new StatusException("100001", "文件类型不正确");
+            }
+
+            if (!"PDF".equals(fileSuffix) && !"ZIP".equals(fileSuffix)
+                    && !"JPG".equals(fileSuffix) && !"JPEG".equals(fileSuffix) && !"PNG".equals(fileSuffix)) {
+                throw new StatusException("100002", "文件格式不正确");
+            }
+
+            ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+            if (examRecordData.getExamType() != ExamType.OFFLINE) {
+                throw new StatusException("100003", "非离线考试");
+            }
+            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 (fileSuffix.equals("JPG") || fileSuffix.equals("JPEG") || fileSuffix.equals("PNG")) {
+                if (offlineUploadFileType.indexOf("IMAGE")<0) {
+                    throw new StatusException("100004", "当前考试允许上传文件格式为:" + offlineUploadFileType);
+                }
+            }else{
+                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");
+            }
+
+            File f = getUploadFile(file);
+            String strMd5 = MD5.md5Hex(f);
+            String summary = fileMd5Array[i];
+            if (!strMd5.equals(summary)) {
+                throw new StatusException("OfflineExamController-submitPaper-006", "文件数据不完整,请重新上传");
+            }
+
+            resultFiles.add(f);
+        }
+
+        offlineExamService.batchSubmitPaper(examRecordDataId,resultFiles,fileType);
+
+        //最后删除临时文件
+        for (File tempFile : resultFiles) {
+            tempFile.delete();
+        }
+    }
+
+    private File getUploadFile(MultipartFile file) {
         //建临时文件夹
-		File dirFile = new File(TEMP_FILE_EXP);
-		if(!dirFile.exists()){
-			dirFile.mkdirs();
-		}
+        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);
-		}
+            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;
     }
 }

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

@@ -21,7 +21,7 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 
 /**
- * 
+ *
  * @author      chenken
  * @date        2018年9月7日 上午11:12:28
  * @company     QMTH
@@ -34,13 +34,12 @@ public class PracticeController extends ControllerSupport{
 
     @Autowired
     private PracticeService practiceService;
-    
+
     @Autowired
     private ExamRecordDataSyncService examRecordDataSyncService;
     /**
      * 练习课程列表
      * @param examId
-     * @param studentId
      * @return
      */
     @ApiOperation(value = "练习课程列表")
@@ -50,7 +49,7 @@ public class PracticeController extends ControllerSupport{
         User user = getAccessUser();
         return practiceService.queryPracticeCourseList(examId, user.getUserId());
     }
-    
+
     /**
      * 课程练习记录详情
      * @param examStudentId
@@ -62,7 +61,7 @@ public class PracticeController extends ControllerSupport{
         Check.isNull(examStudentId, "examStudentId不能为空");
         return practiceService.queryPracticeRecordList(examStudentId);
     }
-    
+
     /**
      * 单次练习答题情况统计
      * @return
@@ -79,5 +78,5 @@ public class PracticeController extends ControllerSupport{
         }
         return practiceService.getPracticeDetailInfo(examRecordDataId);
     }
-    
+
 }

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

@@ -1,7 +1,6 @@
 package cn.com.qmth.examcloud.core.oe.admin.api.provider;
 
 import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
 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.*;
@@ -11,15 +10,14 @@ 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.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import com.mysql.cj.util.StringUtils;
@@ -329,7 +327,7 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
                 continue;
             }
 
-            st1=System.currentTimeMillis();
+            st1 = System.currentTimeMillis();
 
             for (ExamRecordForMarkingEntity record : examRecordForMarkingList) {
                 PagedToBeMarkExamRecordBean pagedBean = new PagedToBeMarkExamRecordBean();
@@ -344,8 +342,7 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
                 pagedBean.setExamRecordDataId(record.getExamRecordDataId());
                 pagedBean.setGrade(examStu.getGrade());
 
-                pagedBean.setSubjectiveAnswerList(
-                        getSubjectiveAnswerList(record.getExamRecordDataId(), examId, courseCode, examStu.getPaperType()));
+                pagedBean.setSubjectiveAnswerList(getSubjectiveAnswerList(record.getExamRecordDataId()));
 
                 pagedToBeMarkList.add(pagedBean);
             }
@@ -376,14 +373,9 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
      * 获取主观题集合
      *
      * @param examRecordDataId
-     * @param examId
      * @return
      */
-    private List<PagedToBeMarkSubjectiveAnswerBean> getSubjectiveAnswerList(Long examRecordDataId,
-                                                                            Long examId, String courseCode, String paperType) {
-        //全部作答集合
-        ExamRecordQuestionsEntity allEqList =
-                examRecordQuestionsService.getExamRecordQuestionsAndFixExamRecordDataIfNecessary(examRecordDataId);
+    private List<PagedToBeMarkSubjectiveAnswerBean> getSubjectiveAnswerList(Long examRecordDataId) {
         //主观题作答集合
         List<ExamQuestionEntity> eqList = examRecordQuestionsService.querySubjectiveAnswerList(examRecordDataId);
 
@@ -397,14 +389,6 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
             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);
         }
 
@@ -462,42 +446,6 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
         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 "";
-    }
-
     /**
      * 校验待阅卷数据
      *

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

@@ -3,6 +3,7 @@ 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.ExamRecordFileAnswerBean;
 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;
@@ -11,9 +12,11 @@ import cn.com.qmth.examcloud.core.oe.admin.api.response.QueryValidExamRecordInfo
 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.ExamRecordFileAnswerRepo;
 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.ExamRecordFileAnswerEntity;
 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;
@@ -67,7 +70,9 @@ public class ExamRecordForMarkingCloudServiceProvider extends ControllerSupport
 
     @Autowired
     private ExamRecordQuestionsService examRecordQuestionsService;
-    
+    @Autowired
+    private ExamRecordFileAnswerRepo examRecordFileAnswerRepo;
+
     @Override
     @ApiOperation(value = "根据条件查询阅卷需要的信息")
     @PostMapping("/findExamRecordForMarkingInfo")
@@ -78,9 +83,10 @@ public class ExamRecordForMarkingCloudServiceProvider extends ControllerSupport
         if (id == null && examId == null) {
             return resp;
         }
+
         List<ExamRecordForMarkingEntity> examRecordForMarkingList =
                 examRecordForMarkingService.findExamRecordForMarkingInfo(id, examId, req.getCourseId(), req.getBatchNum());
-        List<ExamRecordForMarkingBean> examRecordForMarkingBeanList = new ArrayList<ExamRecordForMarkingBean>();
+        List<ExamRecordForMarkingBean> examRecordForMarkingBeanList = new ArrayList<>();
         for (ExamRecordForMarkingEntity entity : examRecordForMarkingList) {
             ExamRecordForMarkingBean examRecordForMarkingBean = new ExamRecordForMarkingBean();
             examRecordForMarkingBean.setId(entity.getId());
@@ -88,15 +94,43 @@ public class ExamRecordForMarkingCloudServiceProvider extends ControllerSupport
             examRecordForMarkingBean.setBasePaperId(entity.getBasePaperId());
             examRecordForMarkingBean.setPaperType(entity.getPaperType());
             examRecordForMarkingBean.setCourseId(entity.getCourseId());
-            examRecordForMarkingBean.setOfflineFileName(entity.getOfflineFileName());
-            examRecordForMarkingBean.setOfflineFileUrl(FileStorageUtil.realPath(entity.getOfflineFileUrl()));
+
+            if(entity.getExamRecordDataId() == null){
+                throw new StatusException("500101", "阅卷信息错误,考试记录ID存在空值!");
+            }
+
+            long examRecordDataId = entity.getExamRecordDataId();
+            List<ExamRecordFileAnswerEntity> fileAnswerList = examRecordFileAnswerRepo.findByExamRecordDataId(examRecordDataId);
+            if (fileAnswerList != null) {
+                examRecordForMarkingBean.setOfflineFiles(getOfflineFilesFrom(fileAnswerList));
+            }
+
             examRecordForMarkingBean.setBatchNum(entity.getBatchNum());
             examRecordForMarkingBeanList.add(examRecordForMarkingBean);
         }
+
         resp.setExamRecordForMarkingBeanList(examRecordForMarkingBeanList);
         return resp;
     }
 
+    private List<ExamRecordFileAnswerBean> getOfflineFilesFrom(List<ExamRecordFileAnswerEntity> fileAnswerList) {
+        List<ExamRecordFileAnswerBean> resultList = new ArrayList<>();
+        for (ExamRecordFileAnswerEntity entity : fileAnswerList) {
+            ExamRecordFileAnswerBean bean = new ExamRecordFileAnswerBean();
+            bean.setId(entity.getId());
+            bean.setExamRecordDataId(entity.getExamRecordDataId());
+            bean.setOfflineFileUrl(FileStorageUtil.realPath(entity.getFileUrl()));
+            bean.setOfflineFileName(entity.getFileName());
+            bean.setOriginalFileName(entity.getOriginalFileName());
+            bean.setFileType(entity.getFileType());
+            bean.setSuffix(entity.getSuffix());
+            bean.setProperties(entity.getProperties());
+            resultList.add(bean);
+        }
+
+        return resultList;
+    }
+
     @Override
     @ApiOperation(value = "查询有效成绩")
     @PostMapping("/queryValidExamRecordInfo")
@@ -264,19 +298,20 @@ public class ExamRecordForMarkingCloudServiceProvider extends ControllerSupport
         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() == null || req.getIdList().isEmpty()) {
+            throw new StatusException("222001", "阅卷原始数据表id不允许为空");
         }
-        if (req.getIdList().size()>100){
-            throw new StatusException("222002","阅卷原始数据表id集合最大不得超过100条");
+        if (req.getIdList().size() > 100) {
+            throw new StatusException("222002", "阅卷原始数据表id集合最大不得超过100条");
         }
-        if (StringUtils.isNullOrEmpty(req.getBatchNum())){
-            throw new StatusException("222003","批次号不允许为空");
+        if (StringUtils.isNullOrEmpty(req.getBatchNum())) {
+            throw new StatusException("222003", "批次号不允许为空");
         }
-        examRecordForMarkingRepo.updateBatchNum(req.getIdList(),req.getBatchNum());
+        examRecordForMarkingRepo.updateBatchNum(req.getIdList(), req.getBatchNum());
     }
 
     @Override

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

@@ -20,14 +20,16 @@ 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.IllegallyTypeService;
-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.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import io.swagger.annotations.Api;
@@ -137,7 +139,7 @@ public class ExamScoreDataCloudServiceProvider extends ControllerSupport impleme
         examScoreDataBean.setStudentCode(examStudentEntity.getStudentCode());
         examScoreDataBean.setIdentityNumber(examStudentEntity.getIdentityNumber());
 
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
         examScoreDataBean.setCourseName(courseBean.getName());
         examScoreDataBean.setCourseCode(examStudentEntity.getCourseCode());
         examScoreDataBean.setStartTime(examRecordDataEntity.getStartTime());
@@ -168,7 +170,7 @@ public class ExamScoreDataCloudServiceProvider extends ControllerSupport impleme
         resp.setStudentName(examRecordData.getStudentName());
         resp.setIdentityNumber(examRecordData.getIdentityNumber());
 
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examRecordData.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examRecordData.getCourseId());
         resp.setCourseCode(courseBean.getCode());
         resp.setCourseName(courseBean.getName());
         resp.setStartTime(examRecordData.getStartTime());
@@ -516,7 +518,7 @@ public class ExamScoreDataCloudServiceProvider extends ControllerSupport impleme
             throw new StatusException("100003", "考试id不允许为空");
         }
         //只支持离线和在线考试
-        ExamBean examSettings = ExamCacheTransferHelper.getDefaultCachedExam(req.getExamId());
+        ExamSettingsCacheBean 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()))) {
@@ -613,7 +615,7 @@ public class ExamScoreDataCloudServiceProvider extends ControllerSupport impleme
         scoreDataBean.setStudentCode(examRecordData.getStudentCode());
         scoreDataBean.setStudentName(examRecordData.getStudentName());
 
-        ExamBean examSettings = ExamCacheTransferHelper.getDefaultCachedExam(examRecordData.getExamId());
+        ExamSettingsCacheBean examSettings = ExamCacheTransferHelper.getDefaultCachedExam(examRecordData.getExamId());
         scoreDataBean.setExamCode(examSettings.getCode());
         scoreDataBean.setExamName(examSettings.getName());
         return scoreDataBean;

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

@@ -6,6 +6,7 @@ import java.util.List;
 import java.util.stream.Collectors;
 
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -26,7 +27,6 @@ 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;

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

@@ -86,7 +86,10 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
     private ExamStudentCache examStudentCache;
     @Autowired
     private ExamStudentFinalScoreService examStudentFinalScoreService;
-
+    @Autowired
+    private ExamContinuedRecordRepo examContinuedRecordRepo;
+    @Autowired
+    private ExamProcessRecordRepo examProcessRecordRepo;
 
     /**
      * 同步考试记录数据
@@ -146,6 +149,16 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
 
         startTime = this.debugCost("6 计算最终考试分数", transitionExamRecordDataId, startTime);
 
+        //同步断点续考记录表
+        if (null != req.getExamContinuedRecords() && !req.getExamContinuedRecords().isEmpty()) {
+            syncExamContinuedRecords(req.getExamContinuedRecords(), realExamRecordDataId);
+        }
+
+        //同步考试过程记录表
+        if (null != req.getExamProcessRecords() && !req.getExamProcessRecords().isEmpty()) {
+            syncExamProcessRecords(req.getExamProcessRecords(), realExamRecordDataId);
+        }
+
         //同步抓拍照片结果(同步抓拍的数据)
         if (null != req.getExamSyncCapture()) {
             syncExamSyncCapture(req.getExamSyncCapture(), realExamRecordDataId);
@@ -207,6 +220,20 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         return new SyncExamDataResp();
     }
 
+    private void syncExamContinuedRecords(List<ExamContinuedRecordBean> examContinuedRecords, Long realExamRecordDataId) {
+        for (ExamContinuedRecordBean bean : examContinuedRecords) {
+            ExamContinuedRecordEntity entity = copyExamContinuedRecordFrom(bean, realExamRecordDataId);
+            examContinuedRecordRepo.saveAndFlush(entity);
+        }
+    }
+
+    private void syncExamProcessRecords(List<ExamProcessRecordBean> examProcessRecords, Long realExamRecordDataId) {
+        for (ExamProcessRecordBean bean : examProcessRecords) {
+            ExamProcessRecordEntity entity = copyExamProcessRecordFrom(bean, realExamRecordDataId);
+            examProcessRecordRepo.saveAndFlush(entity);
+        }
+    }
+
     public Long syncExamScore(ExamRecordDataBean transitionExamRecordData, Long realExamRecordDataId) {
         //如果考试成绩不存在,则新增,否则更新数据
         ExamScoreEntity examScoreEntity = examScoreRepo.findByExamRecordDataId(realExamRecordDataId);
@@ -306,7 +333,7 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         result.setInfoCollector(examStudentEntity.getInfoCollector());
         Integer usedNum = examStudentEntity.getUsedNum();
         result.setExamOrder(getExamOrder(examId, studentId, usedNum));//考试次数
-        boolean isReexamine=isReexamine(examId, studentId, usedNum);
+        boolean isReexamine = isReexamine(examId, studentId, usedNum);
         result.setIsReexamine(isReexamine);//是否重考
         //如果有重考,则需要保存重考相关数据
         if (isReexamine) {
@@ -351,6 +378,12 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         result.setIsAllObjectivePaper(examRecordData.getAllObjectivePaper());
         result.setBaiduFaceLivenessSuccessPercent(examRecordData.getBaiduFaceLivenessSuccessPercent());
         result.setIsExceed(examRecordData.getExceed());
+        result.setExamStageId(examRecordData.getExamStageId());
+        result.setExamStageOrder(examRecordData.getExamStageOrder());
+        result.setLastActiveTime(examRecordData.getLastActiveTime());
+        result.setEnterExamTime(examRecordData.getEnterExamTime());
+        result.setContinuedTime(examRecordData.getContinuedTime());
+        result.setSwitchScreenCount(examRecordData.getSwitchScreenCount());
 
         return result;
     }
@@ -364,7 +397,7 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
      * @return
      */
     private Integer getExamOrder(Long examId, Long studentId, Integer usedExamNum) {
-        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getDefaultCachedExam(examId);
         Integer canExamTimes = cachedExam.getExamTimes() == null ? 0 : cachedExam.getExamTimes().intValue();//可考次数
 
         //超过可考次数,始终为可考次数+1
@@ -385,7 +418,7 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
      * @return
      */
     private boolean isReexamine(Long examId, Long studentId, Integer usedExamNum) {
-        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getDefaultCachedExam(examId);
         Integer canExamTimes = cachedExam.getExamTimes() == null ? 0 : cachedExam.getExamTimes().intValue();//可考次数
 
         //超过可考次数,则认为有重考
@@ -636,6 +669,26 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         return entity;
     }
 
+    private ExamContinuedRecordEntity copyExamContinuedRecordFrom(ExamContinuedRecordBean bean,Long examRecordDataId){
+        ExamContinuedRecordEntity entity = new ExamContinuedRecordEntity();
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setContinuedTime(bean.getContinuedTime());
+        entity.setStartTime(bean.getStartTime());
+        entity.setUsedExamTime(bean.getUsedExamTime());
+
+        return entity;
+    }
+
+    private ExamProcessRecordEntity copyExamProcessRecordFrom(ExamProcessRecordBean bean,Long examRecordDataId){
+        ExamProcessRecordEntity entity = new ExamProcessRecordEntity();
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setProcessName(bean.getProcessName());
+        entity.setRecordTime(bean.getRecordTime());
+        entity.setSourceIp(bean.getSourceIp());
+
+        return entity;
+    }
+
     /**
      * 同步后的后续操作
      *

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

@@ -0,0 +1,27 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamContinuedRecordEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @Description 断点续考记录
+ * @Author lideyin
+ * @Date 2020/8/13 10:16
+ * @Version 1.0
+ */
+@Repository
+public interface ExamContinuedRecordRepo
+        extends JpaRepository<ExamContinuedRecordEntity, Long>, JpaSpecificationExecutor<ExamContinuedRecordEntity> {
+    /**
+     * 获取考试记录下的所有断点续考记录
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    List<ExamContinuedRecordEntity> findByExamRecordDataId(Long examRecordDataId);
+
+}

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

@@ -0,0 +1,21 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamProcessRecordEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ * @Description 考试过程记录
+ * @Author lideyin
+ * @Date 2020/8/20 18:19
+ * @Version 1.0
+ */
+@Repository
+public interface ExamProcessRecordRepo
+        extends JpaRepository<ExamProcessRecordEntity, Long>, JpaSpecificationExecutor<ExamProcessRecordEntity> {
+
+    List<ExamProcessRecordEntity> findByExamRecordDataIdOrderByRecordTimeAsc(Long examRecordDataId);
+}

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

@@ -0,0 +1,20 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordFileAnswerEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+import java.util.List;
+
+/**
+ * @Description 考试记录文件作答
+ * @Author lideyin
+ * @Date 2020/7/13 16:19
+ * @Version 1.0
+ */
+@Repository
+public interface ExamRecordFileAnswerRepo extends JpaRepository<ExamRecordFileAnswerEntity, Long>, JpaSpecificationExecutor<ExamRecordFileAnswerEntity> {
+
+	List<ExamRecordFileAnswerEntity> findByExamRecordDataId(Long examRecordDataId);
+
+}

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

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 16:24:09
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExportTaskEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
+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 org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+
+@Repository
+public interface ExportTaskRepo extends JpaRepository<ExportTaskEntity, Long>,
+        JpaSpecificationExecutor<ExportTaskEntity> {
+
+    @Transactional
+    @Modifying
+    @Query("update ExportTaskEntity set status=:status, statusMsg=:statusMsg, filePath=:filePath, updateTime=:updateTime where id=:id")
+    int updateStatusAndStatusMsgById(@Param("id") Long id,
+                                     @Param("status") ExportTaskStatus status,
+                                     @Param("statusMsg") String statusMsg,
+                                     @Param("filePath") String filePath,
+                                     @Param("updateTime") Date updateTime);
+
+
+}

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

@@ -0,0 +1,76 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
+
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+import org.hibernate.annotations.DynamicInsert;
+
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import java.util.Date;
+
+/**
+ * @Description 断点续考记录表
+ * @Author lideyin
+ * @Date 2020/8/13 9:44
+ * @Version 1.0
+ */
+@Entity
+@Table(name = "ec_oe_exam_continued_record", indexes = {
+        @Index(name = "IDX_E_O_E_C_R_001", columnList = "examRecordDataId"),
+})
+@DynamicInsert
+public class ExamContinuedRecordEntity extends WithIdJpaEntity {
+    private static final long serialVersionUID = 1559727365523696406L;
+
+    /**
+     * 考试记录ID
+     */
+    private Long examRecordDataId;
+
+    /**
+     * 断点续考时间
+     */
+    private Date continuedTime;
+
+    /**
+     * 开始答题时间
+     */
+    private Date startTime;
+
+    /**
+     * 考试已用时长(毫秒)
+     */
+    private long usedExamTime;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public Date getContinuedTime() {
+        return continuedTime;
+    }
+
+    public void setContinuedTime(Date continuedTime) {
+        this.continuedTime = continuedTime;
+    }
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public long getUsedExamTime() {
+        return usedExamTime;
+    }
+
+    public void setUsedExamTime(long usedExamTime) {
+        this.usedExamTime = usedExamTime;
+    }
+}

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

@@ -0,0 +1,78 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
+
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+import org.hibernate.annotations.DynamicInsert;
+import org.hibernate.annotations.DynamicUpdate;
+
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import java.util.Date;
+
+/**
+ * @Description 考试过程记录表
+ * @Author lideyin
+ * @Date 2020/8/13 9:44
+ * @Version 1.0
+ */
+@Entity
+@Table(name = "ec_oe_exam_process_record", indexes = {
+        @Index(name = "IDX_E_O_E_P_R_001", columnList = "examRecordDataId"),
+})
+@DynamicInsert
+@DynamicUpdate
+public class ExamProcessRecordEntity extends WithIdJpaEntity {
+    private static final long serialVersionUID = 1559727365523696406L;
+
+    /**
+     * 考试记录ID
+     */
+    private Long examRecordDataId;
+
+    /**
+     * 过程名称
+     */
+    private String processName;
+
+    /**
+     * 过程记录时间
+     */
+    private Date recordTime;
+
+    /**
+     * 访问源ip
+     */
+    private String sourceIp;
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public String getProcessName() {
+        return processName;
+    }
+
+    public void setProcessName(String processName) {
+        this.processName = processName;
+    }
+
+    public Date getRecordTime() {
+        return recordTime;
+    }
+
+    public void setRecordTime(Date recordTime) {
+        this.recordTime = recordTime;
+    }
+
+    public String getSourceIp() {
+        return sourceIp;
+    }
+
+    public void setSourceIp(String sourceIp) {
+        this.sourceIp = sourceIp;
+    }
+}

+ 79 - 1
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamRecordDataEntity.java

@@ -112,7 +112,7 @@ public class ExamRecordDataEntity extends JpaEntity {
     @Enumerated(EnumType.STRING)
     private ExamRecordStatus examRecordStatus;
     /**
-     * 考试开始时间
+     * 考试开始时间(即开始答题时间)
      */
     private Date startTime;
     /**
@@ -216,6 +216,36 @@ public class ExamRecordDataEntity extends JpaEntity {
     @Column(length = 200)
     private String reexamineDetail;
 
+    /**
+     * 场次id
+     */
+    private Long examStageId;
+
+    /**
+     * 场次号
+     */
+    private Integer examStageOrder;
+
+    /**
+     * 学生最后活动时间
+     */
+    private Date lastActiveTime;
+
+    /**
+     * 断点续考时间
+     */
+    private Date continuedTime;
+
+    /**
+     * 进入考试时间
+     */
+    private Date enterExamTime;
+
+    /**
+     * 切屏次数
+     */
+    private Integer switchScreenCount;
+
     public Long getId() {
         return id;
     }
@@ -549,4 +579,52 @@ public class ExamRecordDataEntity extends JpaEntity {
     public void setReexamineDetail(String reexamineDetail) {
         this.reexamineDetail = reexamineDetail;
     }
+
+    public Long getExamStageId() {
+        return examStageId;
+    }
+
+    public void setExamStageId(Long examStageId) {
+        this.examStageId = examStageId;
+    }
+
+    public Integer getExamStageOrder() {
+        return examStageOrder;
+    }
+
+    public void setExamStageOrder(Integer examStageOrder) {
+        this.examStageOrder = examStageOrder;
+    }
+
+    public Date getLastActiveTime() {
+        return lastActiveTime;
+    }
+
+    public void setLastActiveTime(Date lastActiveTime) {
+        this.lastActiveTime = lastActiveTime;
+    }
+
+    public Date getContinuedTime() {
+        return continuedTime;
+    }
+
+    public void setContinuedTime(Date continuedTime) {
+        this.continuedTime = continuedTime;
+    }
+
+    public Date getEnterExamTime() {
+        return enterExamTime;
+    }
+
+    public void setEnterExamTime(Date enterExamTime) {
+        this.enterExamTime = enterExamTime;
+    }
+
+    public Integer getSwitchScreenCount() {
+        return switchScreenCount;
+    }
+
+    public void setSwitchScreenCount(Integer switchScreenCount) {
+        this.switchScreenCount = switchScreenCount;
+    }
 }

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

@@ -0,0 +1,124 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+import javax.persistence.*;
+
+/**
+ * @Description 考试记录作答文件
+ * @Author lideyin
+ * @Date 2020/7/13 16:10
+ * @Version 1.0
+ */
+@Entity
+@Table(name = "ec_oe_exam_record_file_answer", indexes = {
+        @Index(name = "IDX_E_O_E_R_F_A_001", columnList = "examRecordDataId")})
+public class ExamRecordFileAnswerEntity extends JpaEntity {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = -1507040421795100741L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    /**
+     * 考试记录ID
+     */
+    private Long examRecordDataId;
+
+    /**
+     * 文件路径:离线考试
+     */
+    private String fileUrl;
+    /**
+     * 文件名称:离线考试
+     */
+    private String fileName;
+
+    /**
+     * 原始文件名称
+     */
+    private String originalFileName;
+
+    /**
+     * 文件类型
+     */
+    private String fileType;
+
+    /**
+     * 文件后缀名
+     */
+    private String suffix;
+
+    /**
+     * 预留扩展属性
+     */
+    private String properties;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public String getFileUrl() {
+        return fileUrl;
+    }
+
+    public void setFileUrl(String offlineFileUrl) {
+        this.fileUrl = offlineFileUrl;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String offlineFileName) {
+        this.fileName = offlineFileName;
+    }
+
+    public String getProperties() {
+        return properties;
+    }
+
+    public void setProperties(String properties) {
+        this.properties = properties;
+    }
+
+    public String getFileType() {
+        return fileType;
+    }
+
+    public void setFileType(String fileType) {
+        this.fileType = fileType;
+    }
+
+    public String getSuffix() {
+        return suffix;
+    }
+
+    public void setSuffix(String suffix) {
+        this.suffix = suffix;
+    }
+
+    public String getOriginalFileName() {
+        return originalFileName;
+    }
+
+    public void setOriginalFileName(String originalFileName) {
+        this.originalFileName = originalFileName;
+    }
+}

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

@@ -61,15 +61,6 @@ public class ExamRecordForMarkingEntity extends JpaEntity {
      */
     private Integer subjectiveAnswerLength;
 
-    /**
-     * 文件路径:离线考试
-     */
-    private String offlineFileUrl;
-    /**
-     * 文件名称:离线考试
-     */
-    private String offlineFileName;
-
     /**
      * 批次号
      */
@@ -123,22 +114,6 @@ public class ExamRecordForMarkingEntity extends JpaEntity {
         this.subjectiveAnswerLength = subjectiveAnswerLength;
     }
 
-    public String getOfflineFileUrl() {
-        return offlineFileUrl;
-    }
-
-    public void setOfflineFileUrl(String offlineFileUrl) {
-        this.offlineFileUrl = offlineFileUrl;
-    }
-
-    public String getOfflineFileName() {
-        return offlineFileName;
-    }
-
-    public void setOfflineFileName(String offlineFileName) {
-        this.offlineFileName = offlineFileName;
-    }
-
     public String getBasePaperId() {
         return basePaperId;
     }

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

@@ -162,6 +162,16 @@ public class ExamStudentEntity extends JpaEntity {
 	@Column(length = 200)
 	private String reexamineDetail;
 
+	/**
+	 * 场次id
+	 */
+	private Long examStageId;
+
+	/**
+	 * 场次号
+	 */
+	private Integer examStageOrder;
+
 	public Long getId() {
 		return id;
 	}
@@ -345,4 +355,20 @@ public class ExamStudentEntity extends JpaEntity {
 	public void setReexamineDetail(String reexamineDetail) {
 		this.reexamineDetail = reexamineDetail;
 	}
+
+	public Long getExamStageId() {
+		return examStageId;
+	}
+
+	public void setExamStageId(Long examStageId) {
+		this.examStageId = examStageId;
+	}
+
+	public Integer getExamStageOrder() {
+		return examStageOrder;
+	}
+
+	public void setExamStageOrder(Integer examStageOrder) {
+		this.examStageOrder = examStageOrder;
+	}
 }

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

@@ -0,0 +1,119 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+import javax.persistence.*;
+
+/**
+ * 导出任务表
+ */
+@Entity
+@Table(name = "ec_oe_export_task", indexes = {
+        @Index(name = "IDX_OE_ET_001", columnList = "rootOrgId"),
+        @Index(name = "IDX_OE_ET_002", columnList = "examId"),
+        @Index(name = "IDX_OE_ET_003", columnList = "type"),
+        @Index(name = "IDX_OE_ET_004", columnList = "status")})
+public class ExportTaskEntity extends JpaEntity {
+
+    private static final long serialVersionUID = 4853280464208226631L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    /**
+     * 导出任务类型
+     */
+    @Enumerated(EnumType.STRING)
+    @Column(length = 50, nullable = false)
+    private ExportTaskType type;
+
+    /**
+     * 导出任务状态
+     */
+    @Enumerated(EnumType.STRING)
+    @Column(length = 50, nullable = false)
+    private ExportTaskStatus status;
+
+    /**
+     * 任务状态信息
+     */
+    @Column(length = 500)
+    private String statusMsg;
+
+    /**
+     * 顶级机构
+     */
+    @Column(nullable = false)
+    private Long rootOrgId;
+
+    /**
+     * 考试ID
+     */
+    @Column
+    private Long examId;
+
+    /**
+     * 文件路径
+     */
+    @Column
+    private String filePath;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public ExportTaskType getType() {
+        return type;
+    }
+
+    public void setType(ExportTaskType type) {
+        this.type = type;
+    }
+
+    public ExportTaskStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(ExportTaskStatus status) {
+        this.status = status;
+    }
+
+    public String getStatusMsg() {
+        return statusMsg;
+    }
+
+    public void setStatusMsg(String statusMsg) {
+        this.statusMsg = statusMsg;
+    }
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+}

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

@@ -2,7 +2,6 @@ package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
 
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.FaceBiopsyType;
 import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
-import com.sun.org.apache.xpath.internal.operations.Bool;
 
 import javax.persistence.*;
 

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

@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 16:15:54
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.dao.enums;
+
+/**
+ * 导出任务类型状态
+ */
+public enum ExportTaskStatus {
+
+    EXPORTING("导出中"),
+
+    SUCCESS("成功"),
+
+    ERROR("失败");
+
+    ExportTaskStatus(String desc) {
+        this.desc = desc;
+    }
+
+    private String desc;
+
+    public String getDesc() {
+        return desc;
+    }
+
+}

+ 27 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/ExportTaskType.java

@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 15:58:23
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.dao.enums;
+
+/**
+ * 导出任务类型
+ */
+public enum ExportTaskType {
+
+    EXAM_DETAIL("考试明细"),
+
+    SCORE_STATISTIC("成绩统计");
+
+    ExportTaskType(String desc) {
+        this.desc = desc;
+    }
+
+    private String desc;
+
+    public String getDesc() {
+        return desc;
+    }
+
+}

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

@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 15:31:51
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.service;
+
+/**
+ * 异步导出相关接口
+ */
+public interface AsyncExportService {
+
+    void exportExamRecordDetails(String jsonParams);
+
+    void exportExamScoreStatistics(String jsonParams);
+
+}

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

@@ -0,0 +1,23 @@
+package cn.com.qmth.examcloud.core.oe.admin.service;
+
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.ExamProcessRecordInfo;
+
+import java.util.List;
+
+/**
+ * @Description 考试过程记录服务
+ * @Author lideyin
+ * @Date 2020/8/20 17:39
+ * @Version 1.0
+ */
+public interface ExamProcessRecordService {
+
+    /**
+     * 获取考试过程记录
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    List<ExamProcessRecordInfo> getExamProcessRecords(Long examRecordDataId);
+
+}

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

@@ -4,6 +4,8 @@ import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentBean;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 
 /**
  * @author chenken
@@ -17,13 +19,12 @@ public interface ExamRecordDataService {
     /**
      * 创建离线考试记录
      *
-     * @param examRecord
      * @param fullyObjective
      * @return
      */
     ExamRecordDataEntity createOfflineExamRecordData(ExamStudentBean bean,
-                                                     ExamBean examBean,
-                                                     CourseBean courseBean,
+                                                     ExamSettingsCacheBean examBean,
+                                                     CourseCacheBean courseBean,
                                                      String basePaperId,
                                                      String paperStructId, Boolean fullyObjective);
 

+ 5 - 3
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordForMarkingService.java

@@ -7,7 +7,7 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordForMarkingEntity
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordDataBean;
 
 /**
- * 
+ *
  * @author  	chenken
  * @date    	2018年9月17日 下午3:57:58
  * @company 	QMTH
@@ -19,7 +19,7 @@ public interface ExamRecordForMarkingService {
 	 * 获取 有效考试记录
 	 */
 	List<ExamRecordForMarkingEntity> queryValidExamRecordList(Long examId, Long courseId);
-	
+
 	/**
 	 * 根据条件查询阅卷需要的信息
 	 * @param id
@@ -32,7 +32,7 @@ public interface ExamRecordForMarkingService {
 
 	List<ExamRecordForMarkingEntity> queryValidExamRecordInfoByStuIds(Long examId, Long courseId,
 			List<Long> examStudentIds,String batchNum);
-	
+
 	/**
      * 离线考试-保存阅卷相关数据-对内
      * @param examRecordData
@@ -41,6 +41,8 @@ public interface ExamRecordForMarkingService {
      */
 	void saveOffLineExamRecordForMarking(ExamRecordDataBean examRecordData, String fileName, String fileUrl);
 
+	void saveOffLineExamRecordForMarking(ExamRecordDataBean examRecordData);
+
 	/**
 	 * 在线考试-保存阅卷相关数据-对内
 	 * @param examRecordDataId

+ 5 - 4
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamStudentService.java

@@ -77,16 +77,17 @@ public interface ExamStudentService {
      * @param examId
      * @return
      */
-    ExamStudentFinishedStatistic getExamStudentStatisticByFinished(Long examId);
+    ExamStudentFinishedStatistic getExamStudentStatisticByFinished(Long examId,Long examStageId);
 
     /**
      * 考试概况-按学习中心统计
      *
      * @param examId
      * @param orgId
+     * @param examStageId
      * @return
      */
-    List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(Long examId, Long orgId);
+    List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(Long examId,Long examStageId, Long orgId);
 
     /**
      * 获取考生信息
@@ -103,7 +104,7 @@ public interface ExamStudentService {
      * @param orgId
      * @return
      */
-    List<Long> findCoursesFromExamStudent(Long examId, Long orgId);
+    List<Long> findCoursesFromExamStudent(Long examId,Long examStageId, Long orgId);
 
     /**
      * 设置重考
@@ -120,7 +121,7 @@ public interface ExamStudentService {
      * @param examId
      * @param courseId
      */
-    public List<CourseProgressInfo> queryCourseProgressInfos(Long examId, Long courseId, String orderColumn);
+    public List<CourseProgressInfo> queryCourseProgressInfos(Long examId,Long examStageId, Long courseId, String orderColumn);
 
     /**
      * @return java.util.List<cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo>

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

@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 15:31:51
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.service;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskListReq;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskListResp;
+import org.springframework.data.domain.Page;
+
+/**
+ * 导出任务相关接口
+ */
+public interface ExportTaskService {
+
+    Long addExportTask(ExportTaskInfo info);
+
+    void updateExportTaskStatus(Long taskId, ExportTaskStatus status, String statusMsg);
+
+    void updateExportTaskStatus(Long taskId, ExportTaskStatus status, String statusMsg, String filePath);
+
+    void deleteExportTaskById(Long taskId);
+
+    boolean existUnFinishTask(ExportTaskType type, Long rootOrgId, Long examId);
+
+    Page<ExportTaskListResp> getExportTaskList(ExportTaskListReq req);
+
+}

+ 14 - 6
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/OfflineExamService.java

@@ -7,20 +7,20 @@ import cn.com.qmth.examcloud.core.oe.admin.service.bean.OfflineExamCourseInfo;
 
 
 /**
- * 
+ *
  * @author  	chenken
  * @date    	2018年9月5日 上午10:35:18
  * @company 	QMTH
  * @description 离线考试服务接口
  */
 public interface OfflineExamService {
-	
+
 	/**
 	 * 获取待考课程列表
 	 * @param studentId
 	 * @return
 	 */
-	public List<OfflineExamCourseInfo> getOfflineCourse(Long studentId);
+	List<OfflineExamCourseInfo> getOfflineCourse(Long studentId);
 
 	/**
 	 * 开始离线考试
@@ -29,14 +29,22 @@ public interface OfflineExamService {
 	 * @param studentId
 	 * @return
 	 */
-	public void startOfflineExam(Long examStudentId);
-	
+	void startOfflineExam(Long examStudentId);
+
 	/**
 	 * 上传作答
 	 * @param examRecordDataId
 	 * @param tempFile
 	 * @param fileType
 	 */
-	public void submitPaper(Long examRecordDataId,File tempFile)  throws Exception;
+	void submitPaper(Long examRecordDataId, File tempFile,String fileType)  throws Exception;
+
+	/**
+	 * 上传作答
+	 * @param examRecordDataId 考试记录id
+	 * @param tempFiles 文件集合
+	 * @param fileType 文件类型
+	 */
+	void batchSubmitPaper(Long examRecordDataId,List<File> tempFiles,String fileType)  throws Exception;
 
 }

+ 78 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/ExamProcessRecordInfo.java

@@ -0,0 +1,78 @@
+package cn.com.qmth.examcloud.core.oe.admin.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * @Description 考试过程记录
+ * @Author lideyin
+ * @Date 2020/8/20 17:44
+ * @Version 1.0
+ */
+public class ExamProcessRecordInfo implements JsonSerializable {
+    private static final long serialVersionUID = 5667104330981650606L;
+
+    /**
+     * 主键id
+     */
+    private Long id;
+
+    /**
+     * 考试记录ID
+     */
+    private Long examRecordDataId;
+
+    /**
+     * 过程名称
+     */
+    private String processName;
+
+    /**
+     * 过程记录时间
+     */
+    private String recordTime;
+
+    /**
+     * 访问源ip
+     */
+    private String sourceIp;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public String getProcessName() {
+        return processName;
+    }
+
+    public void setProcessName(String processName) {
+        this.processName = processName;
+    }
+
+    public String getRecordTime() {
+        return recordTime;
+    }
+
+    public void setRecordTime(String recordTime) {
+        this.recordTime = recordTime;
+    }
+
+    public String getSourceIp() {
+        return sourceIp;
+    }
+
+    public void setSourceIp(String sourceIp) {
+        this.sourceIp = sourceIp;
+    }
+}

+ 15 - 23
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/OfflineExamCourseInfo.java

@@ -2,10 +2,10 @@ package cn.com.qmth.examcloud.core.oe.admin.service.bean;
 
 import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
 
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
-
-
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordFileAnswerInfo;
 
 
 /**
@@ -20,7 +20,7 @@ public class OfflineExamCourseInfo implements Serializable {
 	private Long examId;
 
 	private String examName;
-	
+
 	private String orgName;//学校名称
 
 	private Long examStudentId;
@@ -42,19 +42,20 @@ public class OfflineExamCourseInfo implements Serializable {
 	private String specialtyName;
 
 	private Long examRecordDataId;
-	
+
 	private ExamRecordStatus status;//用于判断是否上传
-	
+
 	private String paperId;
-	
+
 	/**
 	 * 是否有效
 	 */
 	private Boolean isvalid;
-	
-	private String offlineFileUrl;//答卷地址
-	
-	private String fileName;//答卷文件名
+
+	/**
+	 * 离线考试作答文件集合
+	 */
+	private List<ExamRecordFileAnswerInfo> offlineFiles;
 
 	public Long getExamId() {
 		return examId;
@@ -184,20 +185,11 @@ public class OfflineExamCourseInfo implements Serializable {
 		this.orgName = orgName;
 	}
 
-	public String getOfflineFileUrl() {
-		return offlineFileUrl;
-	}
-
-	public void setOfflineFileUrl(String offlineFileUrl) {
-		this.offlineFileUrl = offlineFileUrl;
+	public List<ExamRecordFileAnswerInfo> getOfflineFiles() {
+		return offlineFiles;
 	}
 
-	public String getFileName() {
-		return fileName;
+	public void setOfflineFiles(List<ExamRecordFileAnswerInfo> offlineFiles) {
+		this.offlineFiles = offlineFiles;
 	}
-
-	public void setFileName(String fileName) {
-		this.fileName = fileName;
-	}
-
 }

+ 44 - 5
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditInfo.java

@@ -17,7 +17,7 @@ import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
  */
 public class ExamAuditInfo implements JsonSerializable {
     /**
-	 * 
+	 *
 	 */
 	private static final long serialVersionUID = -7260035187571848686L;
 	/**
@@ -26,7 +26,7 @@ public class ExamAuditInfo implements JsonSerializable {
     private Long id;
 
     private Long examRecordDataId;
-    
+
     /**
      * 审核状态
      */
@@ -35,13 +35,13 @@ public class ExamAuditInfo implements JsonSerializable {
      * 违纪详情
      */
     private String disciplineDetail;
-    
+
     private String disciplineType;
     /**
      * 审核人姓名
      */
     private String auditUserName;
-    
+
     /**
      * 考试ID
      */
@@ -168,6 +168,21 @@ public class ExamAuditInfo implements JsonSerializable {
      */
     private String objectiveScore;
 
+    /**
+     * 开考时间
+     */
+    private String paperStartTime;
+
+    /**
+     * 交卷时间
+     */
+    private String paperSubmitTime;
+
+    /**
+     * 审核时间
+     */
+    private String paperAuditTime;
+
     public Long getId() {
         return id;
     }
@@ -463,4 +478,28 @@ public class ExamAuditInfo implements JsonSerializable {
     public void setObjectiveScore(String objectiveScore) {
         this.objectiveScore = objectiveScore;
     }
-}
+
+    public String getPaperStartTime() {
+        return paperStartTime;
+    }
+
+    public void setPaperStartTime(String paperStartTime) {
+        this.paperStartTime = paperStartTime;
+    }
+
+    public String getPaperSubmitTime() {
+        return paperSubmitTime;
+    }
+
+    public void setPaperSubmitTime(String paperSubmitTime) {
+        this.paperSubmitTime = paperSubmitTime;
+    }
+
+    public String getPaperAuditTime() {
+        return paperAuditTime;
+    }
+
+    public void setPaperAuditTime(String paperAuditTime) {
+        this.paperAuditTime = paperAuditTime;
+    }
+}

+ 5 - 2
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditMapper.java

@@ -24,6 +24,7 @@ public class ExamAuditMapper {
         columns.append("audit.discipline_detail as disciplineDetail,");
         columns.append("audit.discipline_type as disciplineType,");
         columns.append("audit.audit_user_name as auditUserName,");
+        columns.append("audit.update_time as paperAuditTime,");
         columns.append("record.exam_id as examId,");
         columns.append("record.exam_student_id as examStudentId,");
         columns.append("record.student_id as studentId,");
@@ -39,8 +40,10 @@ public class ExamAuditMapper {
         columns.append("record.face_failed_count as faceFailedCount,");
         columns.append("record.face_stranger_count as faceStrangerCount,");
         columns.append("record.face_success_percent as faceSuccessPercent,");
-        columns.append("record.face_verify_result as faceVerifyResult");
+        columns.append("record.face_verify_result as faceVerifyResult,");
+        columns.append("record.start_time as paperStartTime,");
+        columns.append("record.end_time as paperSubmitTime");
         return columns.toString();
     }
 
-}
+}

+ 72 - 3
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditQuery.java

@@ -30,6 +30,8 @@ public class ExamAuditQuery implements JsonSerializable {
     private Long rootOrgId;
     @ApiModelProperty("考试ID")
     private Long examId;
+    @ApiModelProperty("场次ID")
+    private Long examStageId;
     @ApiModelProperty("学号")
     private String studentCode;
     @ApiModelProperty("学生姓名")
@@ -48,7 +50,19 @@ public class ExamAuditQuery implements JsonSerializable {
     private String disciplineType;
     @ApiModelProperty("审核人")
     private String auditUserName;
-    
+    @ApiModelProperty("开考起始时间")
+    private String startTime;
+    @ApiModelProperty("开考截止时间")
+    private String endTime;
+    @ApiModelProperty("交卷起始时间")
+    private String submitStartTime;
+    @ApiModelProperty("交卷截止时间")
+    private String submitEndTime;
+    @ApiModelProperty("审核起始时间")
+    private String auditStartTime;
+    @ApiModelProperty("审核截止时间")
+    private String auditEndTime;
+
     /**
      * 查询或导出  select  export
      */
@@ -177,5 +191,60 @@ public class ExamAuditQuery implements JsonSerializable {
 	public void setAuditUserName(String auditUserName) {
 		this.auditUserName = auditUserName;
 	}
-    
-}
+
+    public Long getExamStageId() {
+        return examStageId;
+    }
+
+    public void setExamStageId(Long examStageId) {
+        this.examStageId = examStageId;
+    }
+
+    public String getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(String startTime) {
+        this.startTime = startTime;
+    }
+
+    public String getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(String endTime) {
+        this.endTime = endTime;
+    }
+
+    public String getSubmitStartTime() {
+        return submitStartTime;
+    }
+
+    public void setSubmitStartTime(String submitStartTime) {
+        this.submitStartTime = submitStartTime;
+    }
+
+    public String getSubmitEndTime() {
+        return submitEndTime;
+    }
+
+    public void setSubmitEndTime(String submitEndTime) {
+        this.submitEndTime = submitEndTime;
+    }
+
+    public String getAuditStartTime() {
+        return auditStartTime;
+    }
+
+    public void setAuditStartTime(String auditStartTime) {
+        this.auditStartTime = auditStartTime;
+    }
+
+    public String getAuditEndTime() {
+        return auditEndTime;
+    }
+
+    public void setAuditEndTime(String auditEndTime) {
+        this.auditEndTime = auditEndTime;
+    }
+}

+ 13 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examcapture/ExamCaptureAuditInfo.java

@@ -125,6 +125,11 @@ public class ExamCaptureAuditInfo implements JsonSerializable {
      */
     private String objectiveTotalScore;
 
+    /**
+     * 切屏次数
+     */
+    private Integer switchScreenCount;
+
     public String getStatus() {
         return status;
     }
@@ -341,4 +346,12 @@ public class ExamCaptureAuditInfo implements JsonSerializable {
     public void setObjectiveTotalScore(String objectiveTotalScore) {
         this.objectiveTotalScore = objectiveTotalScore;
     }
+
+    public Integer getSwitchScreenCount() {
+        return switchScreenCount;
+    }
+
+    public void setSwitchScreenCount(Integer switchScreenCount) {
+        this.switchScreenCount = switchScreenCount;
+    }
 }

+ 9 - 8
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordEntityConvert.java

@@ -20,8 +20,9 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.IsSuccess;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.TrueFalse;
 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.support.cache.bean.CourseCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import com.google.common.collect.Lists;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -44,7 +45,7 @@ import java.util.stream.Collectors;
  */
 @Component
 public class ExamRecordEntityConvert {
-	
+
 	@Autowired
 	private ExamRecordDataRepo examRecordDataRepo;
 
@@ -76,16 +77,16 @@ public class ExamRecordEntityConvert {
         info.setStudentName(record.getStudentName());
         info.setIdentityNumber(record.getIdentityNumber());
         info.setCourseId(record.getCourseId());
-        
+
         long courseId = record.getCourseId();
-    	CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
-        
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
+
         info.setCourseCode(courseBean.getCode());
         info.setCourseName(courseBean.getName());
         info.setCourseNameAndCode(courseBean.getName() + "(" + courseBean.getCode() + ")");
         info.setCourseLevel(CourseLevel.getCourseLevelTitle(courseBean.getLevel()));
         info.setOrgId(record.getOrgId());
-        
+
         long orgId = record.getOrgId();
         OrgCacheBean orgBean = gainBaseDataService.getOrgBean(orgId);
         info.setOrgName(orgBean.getName());
@@ -163,11 +164,11 @@ public class ExamRecordEntityConvert {
         return new PageImpl<ExamRecordInfo>(list, pageable, page.getTotalElements());
     }
 
-    
+
     public List<ExamRecordInfo> of(List<ExamRecordDataEntity> entities) {
     	//缓存
         Map<String,Object> cahcheMap = new HashMap<String, Object>();
         List<ExamRecordInfo> list = entities.stream().map(entity -> of(entity,cahcheMap)).collect(Collectors.toList());
         return list;
     }
-}
+}

+ 122 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordFileAnswerInfo.java

@@ -0,0 +1,122 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-24 11:22:55.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * @Description 考试记录文件作答
+ * @Author lideyin
+ * @Date 2020/7/13 17:31
+ * @Version 1.0
+ */
+public class ExamRecordFileAnswerInfo implements JsonSerializable {
+    /**
+	 *
+	 */
+	private static final long serialVersionUID = -735354536432038902L;
+    private Long id;
+
+    /**
+     * 考试记录ID
+     */
+    private Long examRecordDataId;
+
+    /**
+     * 文件路径:离线考试
+     */
+    private String offlineFileUrl;
+    /**
+     * 文件名称:离线考试
+     */
+    private String offlineFileName;
+
+    /**
+     * 原始文件名称
+     */
+    private String originalFileName;
+
+    /**
+     * 文件类型
+     */
+    private String fileType;
+
+    /**
+     * 文件后缀名
+     */
+    private String suffix;
+
+    /**
+     * 预留扩展属性
+     */
+    private String properties;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public String getOfflineFileUrl() {
+        return offlineFileUrl;
+    }
+
+    public void setOfflineFileUrl(String offlineFileUrl) {
+        this.offlineFileUrl = offlineFileUrl;
+    }
+
+    public String getOfflineFileName() {
+        return offlineFileName;
+    }
+
+    public void setOfflineFileName(String offlineFileName) {
+        this.offlineFileName = offlineFileName;
+    }
+
+    public String getFileType() {
+        return fileType;
+    }
+
+    public void setFileType(String fileType) {
+        this.fileType = fileType;
+    }
+
+    public String getSuffix() {
+        return suffix;
+    }
+
+    public void setSuffix(String suffix) {
+        this.suffix = suffix;
+    }
+
+    public String getProperties() {
+        return properties;
+    }
+
+    public void setProperties(String properties) {
+        this.properties = properties;
+    }
+
+    public String getOriginalFileName() {
+        return originalFileName;
+    }
+
+    public void setOriginalFileName(String originalFileName) {
+        this.originalFileName = originalFileName;
+    }
+}

+ 15 - 15
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordInfo.java

@@ -11,6 +11,7 @@ import java.util.Date;
 
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
+import java.util.List;
 
 /**
  * 考试记录
@@ -20,7 +21,7 @@ import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
  */
 public class ExamRecordInfo implements JsonSerializable {
     /**
-	 * 
+	 *
 	 */
 	private static final long serialVersionUID = -735354536432038902L;
 	/**
@@ -264,12 +265,12 @@ public class ExamRecordInfo implements JsonSerializable {
      */
     @ExcelProperty(name = "学生电话", width = 30, index = 30)
     private String phone;
-    
+
     /**
-     * 离线考试作答文件
+     * 离线考试作答文件集合
      */
-    private String offlineFileUrl;
-    
+    private List<ExamRecordFileAnswerInfo> offlineFiles;
+
     /**
      * 虚拟摄像头名称
      */
@@ -700,22 +701,21 @@ public class ExamRecordInfo implements JsonSerializable {
 		this.baiduFaceLivenessSuccessPercent = baiduFaceLivenessSuccessPercent;
 	}
 
-	public String getOfflineFileUrl() {
-		return offlineFileUrl;
-	}
+    public List<ExamRecordFileAnswerInfo> getOfflineFiles() {
+        return offlineFiles;
+    }
 
-	public void setOfflineFileUrl(String offlineFileUrl) {
-		this.offlineFileUrl = offlineFileUrl;
-	}
+    public void setOfflineFiles(List<ExamRecordFileAnswerInfo> offlineFiles) {
+        this.offlineFiles = offlineFiles;
+    }
 
-    
     public String getVirtualCameraNames() {
         return virtualCameraNames;
     }
 
-    
+
     public void setVirtualCameraNames(String virtualCameraNames) {
         this.virtualCameraNames = virtualCameraNames;
     }
-    
-}
+
+}

+ 49 - 12
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordQuery.java

@@ -25,12 +25,13 @@ import io.swagger.annotations.ApiModelProperty;
  */
 @ApiModel
 public class ExamRecordQuery implements JsonSerializable {
-	
-	/**
-	 * 
-	 */
-	private static final long serialVersionUID = 4227973258005786636L;
-	private Long examRecordDataId;
+
+    private static final long serialVersionUID = 4227973258005786636L;
+
+    private Long rootOrgId;
+
+    private Long examRecordDataId;
+
     @ApiModelProperty("当前页数")
     private Integer pageNo;
     @ApiModelProperty("每页条数")
@@ -39,6 +40,8 @@ public class ExamRecordQuery implements JsonSerializable {
     private Long orgId;
     @ApiModelProperty("考试ID")
     private Long examId;
+    @ApiModelProperty("场次ID")
+    private Long examStageId;
     @ApiModelProperty("学号")
     private String studentCode;
     @ApiModelProperty("学生姓名")
@@ -69,13 +72,17 @@ public class ExamRecordQuery implements JsonSerializable {
     private List<String> recordStatuses;
     @ApiModelProperty("采集人")
     private String infoCollector;
-    @ApiModelProperty("考试开始时间")
+    @ApiModelProperty("开考起始时间")
     private String startTime;
-    @ApiModelProperty("考试结束时间")
+    @ApiModelProperty("开考截止时间")
     private String endTime;
     @ApiModelProperty("是否有虚拟设备")
     private Boolean hasVirtual;
-    
+    @ApiModelProperty("交卷起始时间")
+    private String submitStartTime;
+    @ApiModelProperty("交卷截止时间")
+    private String submitEndTime;
+
     public ExamRecordQuery addRecordStatus(String recordStatus) {
         if (recordStatuses == null) {
             recordStatuses = new ArrayList<>();
@@ -99,6 +106,14 @@ public class ExamRecordQuery implements JsonSerializable {
         return list;
     }
 
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
     public List<String> getRecordStatuses() {
         return recordStatuses;
     }
@@ -275,15 +290,37 @@ public class ExamRecordQuery implements JsonSerializable {
 		this.isWarn = isWarn;
 	}
 
-    
+
     public Boolean getHasVirtual() {
         return hasVirtual;
     }
 
-    
+
     public void setHasVirtual(Boolean hasVirtual) {
         this.hasVirtual = hasVirtual;
     }
 
+    public Long getExamStageId() {
+        return examStageId;
+    }
+
+    public void setExamStageId(Long examStageId) {
+        this.examStageId = examStageId;
+    }
+
+    public String getSubmitStartTime() {
+        return submitStartTime;
+    }
 
-}
+    public void setSubmitStartTime(String submitStartTime) {
+        this.submitStartTime = submitStartTime;
+    }
+
+    public String getSubmitEndTime() {
+        return submitEndTime;
+    }
+
+    public void setSubmitEndTime(String submitEndTime) {
+        this.submitEndTime = submitEndTime;
+    }
+}

+ 2 - 1
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examscore/ExamScoreEntityConvert.java

@@ -23,6 +23,7 @@ public class ExamScoreEntityConvert {
         studentQuery.setPageSize(scoreQuery.getPageSize());
         studentQuery.setOrgId(scoreQuery.getOrgId());
         studentQuery.setExamId(scoreQuery.getExamId());
+        studentQuery.setExamStageId(scoreQuery.getExamStageId());
         studentQuery.setStudentCode(scoreQuery.getStudentCode());
         studentQuery.setStudentName(scoreQuery.getStudentName());
         studentQuery.setIdentityNumber(scoreQuery.getIdentityNumber());
@@ -39,4 +40,4 @@ public class ExamScoreEntityConvert {
         return studentQuery;
     }
 
-}
+}

+ 56 - 39
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examscore/ExamScoreQuery.java

@@ -1,10 +1,3 @@
-/*
- * *************************************************
- * Copyright (c) 2018 QMTH. All Rights Reserved.
- * Created by Deason on 2018-08-31 09:35:03.
- * *************************************************
- */
-
 package cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore;
 
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
@@ -13,42 +6,59 @@ import io.swagger.annotations.ApiModelProperty;
 
 /**
  * 考试分数信息查询条件类
- *
- * @author: fengdesheng
- * @since: 2018/8/31
  */
 @ApiModel
 public class ExamScoreQuery implements JsonSerializable {
-    /**
-	 * 
-	 */
-	private static final long serialVersionUID = 7583473311721120552L;
-	
-	@ApiModelProperty("当前页数")
+
+    private static final long serialVersionUID = 7583473311721120552L;
+
+    private Long rootOrgId;
+
+    @ApiModelProperty("当前页数")
     private Integer pageNo;
+
     @ApiModelProperty("每页条数")
     private Integer pageSize;
+
     @ApiModelProperty("学习中心ID")
     private Long orgId;
+
     @ApiModelProperty("考试ID")
     private Long examId;
+
+    @ApiModelProperty("场次ID")
+    private Long examStageId;
+
     @ApiModelProperty("学号")
     private String studentCode;
+
     @ApiModelProperty("学生姓名")
     private String studentName;
+
     @ApiModelProperty("身份证号")
     private String identityNumber;
+
     @ApiModelProperty("课程ID")
     private Long courseId;
+
     @ApiModelProperty("课程层次")
     private String courseLevel;
+
     @ApiModelProperty("是否完成考试")
     private Boolean finished;
-    
+
     private Integer startLimit;
-    
+
     private Integer endLimit;
-    
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
     public Integer getPageNo() {
         return pageNo;
     }
@@ -121,28 +131,35 @@ public class ExamScoreQuery implements JsonSerializable {
         this.courseLevel = courseLevel;
     }
 
-	public Boolean getFinished() {
-		return finished;
-	}
+    public Boolean getFinished() {
+        return finished;
+    }
+
+    public void setFinished(Boolean finished) {
+        this.finished = finished;
+    }
+
+    public Integer getStartLimit() {
+        return startLimit;
+    }
 
-	public void setFinished(Boolean finished) {
-		this.finished = finished;
-	}
+    public void setStartLimit(Integer startLimit) {
+        this.startLimit = startLimit;
+    }
 
-	public Integer getStartLimit() {
-		return startLimit;
-	}
+    public Integer getEndLimit() {
+        return endLimit;
+    }
 
-	public void setStartLimit(Integer startLimit) {
-		this.startLimit = startLimit;
-	}
+    public void setEndLimit(Integer endLimit) {
+        this.endLimit = endLimit;
+    }
 
-	public Integer getEndLimit() {
-		return endLimit;
-	}
+    public Long getExamStageId() {
+        return examStageId;
+    }
 
-	public void setEndLimit(Integer endLimit) {
-		this.endLimit = endLimit;
-	}
-	
-}
+    public void setExamStageId(Long examStageId) {
+        this.examStageId = examStageId;
+    }
+}

+ 27 - 2
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentBean.java

@@ -6,7 +6,7 @@ public class ExamStudentBean implements JsonSerializable {
 
 
 	/**
-     * 
+     *
      */
     private static final long serialVersionUID = -205239358290227432L;
 
@@ -115,6 +115,16 @@ public class ExamStudentBean implements JsonSerializable {
 	 */
 	private Boolean enable;
 
+	/**
+	 * 场次id
+	 */
+	private Long examStageId;
+
+	/**
+	 * 场次号
+	 */
+	private Integer examStageOrder;
+
 	public Long getId() {
 		return id;
 	}
@@ -283,4 +293,19 @@ public class ExamStudentBean implements JsonSerializable {
 		this.enable = enable;
 	}
 
-}
+	public Long getExamStageId() {
+		return examStageId;
+	}
+
+	public void setExamStageId(Long examStageId) {
+		this.examStageId = examStageId;
+	}
+
+	public Integer getExamStageOrder() {
+		return examStageOrder;
+	}
+
+	public void setExamStageOrder(Integer examStageOrder) {
+		this.examStageOrder = examStageOrder;
+	}
+}

+ 48 - 16
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentInfo.java

@@ -8,6 +8,9 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent;
 
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordFileAnswerInfo;
+
+import java.util.List;
 
 /**
  * 考生信息
@@ -17,7 +20,7 @@ import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
  */
 public class ExamStudentInfo implements JsonSerializable {
     /**
-	 * 
+	 *
 	 */
 	private static final long serialVersionUID = -3697944484049914650L;
 	/**
@@ -32,9 +35,9 @@ public class ExamStudentInfo implements JsonSerializable {
      * 考试ID
      */
     private Long examId;
-    
+
     private String examType;
-    
+
     /**
      * 学生ID
      */
@@ -103,15 +106,18 @@ public class ExamStudentInfo implements JsonSerializable {
      * 是否完成考试
      */
     private Boolean finished;
-    
+
     /**
      * 完成状态
      * 在线考试:已完成、未完成
      * 离线考试:已抽题、未抽题、已上传
      */
     private String finishedStatus;
-    
-    private String offlineFileUrl;
+
+    /**
+     * 离线考试作答文件集合
+     */
+    private List<ExamRecordFileAnswerInfo> offlineFiles;
     /**
      * 考点
      */
@@ -128,7 +134,7 @@ public class ExamStudentInfo implements JsonSerializable {
      * 是否允许上传附件
      */
     private Boolean canUploadAttachment;
-    
+
     /**
 	 * 是否可用
 	 */
@@ -144,6 +150,16 @@ public class ExamStudentInfo implements JsonSerializable {
      */
     private Integer extraNum;
 
+    /**
+     * 场次id
+     */
+    private Long examStageId;
+
+    /**
+     * 场次号
+     */
+    private Integer examStageOrder;
+
     public Long getId() {
         return id;
     }
@@ -352,14 +368,6 @@ public class ExamStudentInfo implements JsonSerializable {
 		this.finishedStatus = finishedStatus;
 	}
 
-	public String getOfflineFileUrl() {
-		return offlineFileUrl;
-	}
-
-	public void setOfflineFileUrl(String offlineFileUrl) {
-		this.offlineFileUrl = offlineFileUrl;
-	}
-
 	public Boolean getEnable() {
 		return enable;
 	}
@@ -383,4 +391,28 @@ public class ExamStudentInfo implements JsonSerializable {
     public void setExtraNum(Integer extraNum) {
         this.extraNum = extraNum;
     }
-}
+
+    public List<ExamRecordFileAnswerInfo> getOfflineFiles() {
+        return offlineFiles;
+    }
+
+    public void setOfflineFiles(List<ExamRecordFileAnswerInfo> offlineFiles) {
+        this.offlineFiles = offlineFiles;
+    }
+
+    public Long getExamStageId() {
+        return examStageId;
+    }
+
+    public void setExamStageId(Long examStageId) {
+        this.examStageId = examStageId;
+    }
+
+    public Integer getExamStageOrder() {
+        return examStageOrder;
+    }
+
+    public void setExamStageOrder(Integer examStageOrder) {
+        this.examStageOrder = examStageOrder;
+    }
+}

+ 15 - 5
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentQuery.java

@@ -21,10 +21,10 @@ import io.swagger.annotations.ApiModelProperty;
 @ApiModel
 public class ExamStudentQuery implements JsonSerializable {
     /**
-	 * 
+	 *
 	 */
 	private static final long serialVersionUID = 4853280464208226631L;
-	
+
 	@ApiModelProperty("当前页数")
     private Integer pageNo;
     @ApiModelProperty("每页条数")
@@ -33,6 +33,8 @@ public class ExamStudentQuery implements JsonSerializable {
     private Long orgId;
     @ApiModelProperty("考试ID")
     private Long examId;
+    @ApiModelProperty("场次ID")
+    private Long examStageId;
     @ApiModelProperty("学号")
     private String studentCode;
     @ApiModelProperty("学生姓名")
@@ -49,9 +51,9 @@ public class ExamStudentQuery implements JsonSerializable {
     private String infoCollector;
 
     private Integer startLimit;
-    
+
     private Integer endLimit;
-    
+
     /**
      * 查询或导出  select  export
      */
@@ -179,4 +181,12 @@ public class ExamStudentQuery implements JsonSerializable {
     public void setIgnoreUploadOfflineAnswer(Boolean ignoreUploadOfflineAnswer) {
         this.ignoreUploadOfflineAnswer = ignoreUploadOfflineAnswer;
     }
-}
+
+    public Long getExamStageId() {
+        return examStageId;
+    }
+
+    public void setExamStageId(Long examStageId) {
+        this.examStageId = examStageId;
+    }
+}

+ 93 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTaskInfo.java

@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 16:19:15
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
+import io.swagger.annotations.ApiModelProperty;
+
+public class ExportTaskInfo implements JsonSerializable {
+
+    private static final long serialVersionUID = 4853280464208226631L;
+
+    @ApiModelProperty("ID")
+    private Long id;
+
+    @ApiModelProperty("导出任务类型")
+    private ExportTaskType type;
+
+    @ApiModelProperty("导出任务状态")
+    private ExportTaskStatus status;
+
+    @ApiModelProperty("任务状态信息")
+    private String statusMsg;
+
+    @ApiModelProperty(value = "学校ID")
+    private Long rootOrgId;
+
+    @ApiModelProperty("考试ID")
+    private Long examId;
+
+    @ApiModelProperty("文件路径")
+    private String filePath;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public ExportTaskType getType() {
+        return type;
+    }
+
+    public void setType(ExportTaskType type) {
+        this.type = type;
+    }
+
+    public ExportTaskStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(ExportTaskStatus status) {
+        this.status = status;
+    }
+
+    public String getStatusMsg() {
+        return statusMsg;
+    }
+
+    public void setStatusMsg(String statusMsg) {
+        this.statusMsg = statusMsg;
+    }
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+}

+ 82 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTaskListReq.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 16:19:15
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
+import io.swagger.annotations.ApiModelProperty;
+
+public class ExportTaskListReq implements JsonSerializable {
+
+    private static final long serialVersionUID = 4853280464208226631L;
+
+    @ApiModelProperty("当前页数")
+    private Integer pageNo;
+
+    @ApiModelProperty("每页条数")
+    private Integer pageSize;
+
+    @ApiModelProperty("导出任务类型")
+    private ExportTaskType type;
+
+    @ApiModelProperty("导出任务状态")
+    private ExportTaskStatus status;
+
+    @ApiModelProperty(value = "学校ID", hidden = true)
+    private Long rootOrgId;
+
+    @ApiModelProperty("考试ID")
+    private Long examId;
+
+    public Integer getPageNo() {
+        return pageNo;
+    }
+
+    public void setPageNo(Integer pageNo) {
+        this.pageNo = pageNo;
+    }
+
+    public Integer getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(Integer pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public ExportTaskType getType() {
+        return type;
+    }
+
+    public void setType(ExportTaskType type) {
+        this.type = type;
+    }
+
+    public ExportTaskStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(ExportTaskStatus status) {
+        this.status = status;
+    }
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+}

+ 139 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTaskListResp.java

@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 16:19:21
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.Date;
+
+public class ExportTaskListResp implements JsonSerializable {
+
+    private static final long serialVersionUID = 4853280464208226631L;
+
+    @ApiModelProperty("ID")
+    private Long id;
+
+    @ApiModelProperty("导出任务类型")
+    private ExportTaskType type;
+
+    @ApiModelProperty("导出任务类型")
+    private String typeTitle;
+
+    @ApiModelProperty("导出任务状态")
+    private ExportTaskStatus status;
+
+    @ApiModelProperty("导出任务状态")
+    private String statusTitle;
+
+    @ApiModelProperty("任务状态信息")
+    private String statusMsg;
+
+    @ApiModelProperty("考试ID")
+    private Long examId;
+
+    @ApiModelProperty("考试名称")
+    private String examName;
+
+    @ApiModelProperty("文件路径")
+    private String filePath;
+
+    @ApiModelProperty("创建时间")
+    private Date creationTime;
+
+    @ApiModelProperty("更新时间")
+    private Date updateTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public ExportTaskType getType() {
+        return type;
+    }
+
+    public void setType(ExportTaskType type) {
+        this.type = type;
+    }
+
+    public String getTypeTitle() {
+        return typeTitle;
+    }
+
+    public void setTypeTitle(String typeTitle) {
+        this.typeTitle = typeTitle;
+    }
+
+    public ExportTaskStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(ExportTaskStatus status) {
+        this.status = status;
+    }
+
+    public String getStatusTitle() {
+        return statusTitle;
+    }
+
+    public void setStatusTitle(String statusTitle) {
+        this.statusTitle = statusTitle;
+    }
+
+    public String getStatusMsg() {
+        return statusMsg;
+    }
+
+    public void setStatusMsg(String statusMsg) {
+        this.statusMsg = statusMsg;
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamName() {
+        return examName;
+    }
+
+    public void setExamName(String examName) {
+        this.examName = examName;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public void setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+}

+ 2 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/cache/ExamStudentCache.java

@@ -32,6 +32,8 @@ public class ExamStudentCache extends RandomObjectRedisCache<ExamStudentCacheBea
         b.setUsedNum(e.getUsedNum());
         b.setPaperType(e.getPaperType());
         b.setGrade(e.getGrade());
+        b.setExamStageId(e.getExamStageId());
+        b.setExamStageOrder(e.getExamStageOrder());
 
         return b;
     }

+ 236 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/AsyncExportServiceImpl.java

@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 16:19:32
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.service.impl;
+
+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.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.ExcelWriter;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
+import cn.com.qmth.examcloud.core.oe.admin.service.*;
+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.examscore.ExamScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskInfo;
+import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import cn.com.qmth.examcloud.web.filestorage.FileStoragePathEnvInfo;
+import cn.com.qmth.examcloud.web.filestorage.YunPathInfo;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * 异步导出相关接口
+ */
+@Service
+public class AsyncExportServiceImpl implements AsyncExportService {
+
+    private static final ExamCloudLog log = ExamCloudLogFactory.getLog(AsyncExportServiceImpl.class);
+
+    private static final String TASK_EXPORT_DIR = "task_export";
+
+    @Autowired
+    private ExportTaskService exportTaskService;
+
+    @Autowired
+    private ExamRecordService examRecordService;
+
+    @Autowired
+    private ExamCaptureService examCaptureService;
+
+    @Autowired
+    private ExamScoreService examScoreService;
+
+    @Autowired
+    private SystemProperties systemConfig;
+
+    @Override
+    public void exportExamRecordDetails(String jsonParams) {
+        ExamRecordQuery req = new JsonMapper().fromJson(jsonParams, ExamRecordQuery.class);
+        Check.isNull(req, "请求参数不能为空!");
+        Check.isNull(req.getRootOrgId(), "学校ID不能为空!");
+        Check.isNull(req.getExamId(), "考试ID不能为空!");
+
+        boolean existTask = exportTaskService.existUnFinishTask(ExportTaskType.EXAM_DETAIL, req.getRootOrgId(), req.getExamId());
+        if (existTask) {
+            log.warn("exportExamRecordDetails has existed, please waiting...");
+            throw new StatusException("500S01", "当前考试尚有未完成的导出任务,请稍后再导!");
+        }
+
+        // 创建导出任务
+        ExportTaskInfo task = new ExportTaskInfo();
+        task.setRootOrgId(req.getRootOrgId());
+        task.setExamId(req.getExamId());
+        task.setType(ExportTaskType.EXAM_DETAIL);
+        task.setStatus(ExportTaskStatus.EXPORTING);
+        Long taskId = exportTaskService.addExportTask(task);
+
+        this.asyncExportExamRecordDetails(taskId, req);
+    }
+
+    @Async
+    public void asyncExportExamRecordDetails(Long taskId, ExamRecordQuery req) {
+        List<ExamRecordInfo> examRecords;
+        try {
+            examRecords = examRecordService.getExamRecordDetailList(req);
+
+            if (CollectionUtils.isEmpty(examRecords)) {
+                exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "当前条件暂无数据,任务终止");
+                return;
+            }
+
+            String examType = examRecords.get(0).getExamType();
+            for (ExamRecordInfo examRecordInfo : examRecords) {
+                if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
+                    examRecordInfo.setVirtualCameraNames(examCaptureService.getVirtualCameraNames(examRecordInfo.getDataId()));
+                }
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            if (e instanceof StatusException) {
+                exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, ((StatusException) e).getDesc());
+            } else {
+                exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "导出数据异常");
+            }
+            return;
+        }
+
+        // 导出文件的存储路径
+        final String filePath = String.format("/%s/%s/%s/e_record_detail_%s.xlsx", TASK_EXPORT_DIR, req.getRootOrgId(), dateDir(), randomUUID());
+        String tempFilePath = systemConfig.getTempDataDir() + File.separator + filePath;
+        File tempFile = new File(tempFilePath);
+        try {
+            tempFile.getParentFile().mkdirs();
+            tempFile.createNewFile();
+        } catch (IOException e) {
+            log.error(e.getMessage());
+            exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "生成导出文件异常");
+            return;
+        }
+
+        try (FileOutputStream out = new FileOutputStream(tempFile);) {
+            ExcelWriter excelWriter = new ExcelWriter(ExamRecordInfo.class, "考试明细列表");
+            excelWriter.write(examRecords, out);
+
+            // 上传至文件服务器
+            FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
+            env.setRootOrgId(String.valueOf(req.getRootOrgId()));
+            env.setRelativePath(filePath);
+            YunPathInfo oss = FileStorageUtil.saveFile(TASK_EXPORT_DIR, env, tempFile, null);
+
+            log.info("asyncExportExamRecordDetails finished... " + oss.getRelativePath());
+            exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.SUCCESS, null, oss.getRelativePath());
+        } catch (Exception e) {
+            log.info("asyncExportExamRecordDetails error... " + e.getMessage());
+            exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "上传至文件服务器异常");
+        } finally {
+            FileUtils.deleteQuietly(tempFile);
+        }
+    }
+
+    @Override
+    public void exportExamScoreStatistics(String jsonParams) {
+        ExamScoreQuery req = new JsonMapper().fromJson(jsonParams, ExamScoreQuery.class);
+        Check.isNull(req, "请求参数不能为空!");
+        Check.isNull(req.getRootOrgId(), "学校ID不能为空!");
+        Check.isNull(req.getExamId(), "考试ID不能为空!");
+
+        boolean existTask = exportTaskService.existUnFinishTask(ExportTaskType.SCORE_STATISTIC, req.getRootOrgId(), req.getExamId());
+        if (existTask) {
+            log.warn("exportExamScoreStatistics has existed, please waiting...");
+            throw new StatusException("500S02", "当前考试尚有未完成的导出任务,请稍后再导!");
+        }
+
+        // 创建导出任务
+        ExportTaskInfo task = new ExportTaskInfo();
+        task.setRootOrgId(req.getRootOrgId());
+        task.setExamId(req.getExamId());
+        task.setType(ExportTaskType.SCORE_STATISTIC);
+        task.setStatus(ExportTaskStatus.EXPORTING);
+        Long taskId = exportTaskService.addExportTask(task);
+
+        this.asyncExportExamScoreStatistics(taskId, req);
+    }
+
+    @Async
+    public void asyncExportExamScoreStatistics(Long taskId, ExamScoreQuery req) {
+        List<ExamScoreInfo> examScores;
+        try {
+            examScores = examScoreService.exportExamScoreList(req);
+
+            if (CollectionUtils.isEmpty(examScores)) {
+                exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "当前条件暂无数据,任务终止");
+                return;
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            if (e instanceof StatusException) {
+                exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, ((StatusException) e).getDesc());
+            } else {
+                exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "导出数据异常");
+            }
+            return;
+        }
+
+        // 导出文件的存储路径
+        final String filePath = String.format("/%s/%s/%s/e_score_statistic_%s.xlsx", TASK_EXPORT_DIR, req.getRootOrgId(), dateDir(), randomUUID());
+        String tempFilePath = systemConfig.getTempDataDir() + File.separator + filePath;
+        File tempFile = new File(tempFilePath);
+        try {
+            tempFile.getParentFile().mkdirs();
+            tempFile.createNewFile();
+        } catch (IOException e) {
+            log.error(e.getMessage());
+            exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "生成导出文件异常");
+            return;
+        }
+
+        try (FileOutputStream out = new FileOutputStream(tempFile);) {
+            ExcelWriter excelWriter = new ExcelWriter(ExamScoreInfo.class, "成绩统计列表");
+            excelWriter.write(examScores, out);
+
+            // 上传至文件服务器
+            FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
+            env.setRootOrgId(String.valueOf(req.getRootOrgId()));
+            env.setRelativePath(filePath);
+            YunPathInfo oss = FileStorageUtil.saveFile(TASK_EXPORT_DIR, env, tempFile, null);
+
+            log.info("asyncExportExamScoreStatistics finished... " + oss.getRelativePath());
+            exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.SUCCESS, null, oss.getRelativePath());
+        } catch (Exception e) {
+            log.info("asyncExportExamScoreStatistics error... " + e.getMessage());
+            exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "上传至文件服务器异常");
+        } finally {
+            FileUtils.deleteQuietly(tempFile);
+        }
+    }
+
+    private String randomUUID() {
+        return UUID.randomUUID().toString().replace("-", "");
+    }
+
+    private String dateDir() {
+        final String pattern = "yyyyMM";
+        return new SimpleDateFormat(pattern).format(new Date());
+    }
+
+}

+ 39 - 5
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamAuditServiceImpl.java

@@ -20,12 +20,14 @@ 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.bean.examaudit.*;
-import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
 import cn.com.qmth.examcloud.marking.api.MarkWorkCloudService;
 import cn.com.qmth.examcloud.marking.api.request.AppendMarkWorkPaperReq;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
 import cn.com.qmth.examcloud.support.enums.FaceBiopsyScheme;
+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 com.google.common.collect.Lists;
@@ -91,6 +93,9 @@ public class ExamAuditServiceImpl implements ExamAuditService {
 
     @Autowired
     IllegallyTypeRepo illegallyTypeRepo;
+
+    @Autowired
+    ExamStudentFinalScoreRepo examStudentFinalScoreRepo;
     /**
      * 活体检测失败自动审核
      */
@@ -123,6 +128,9 @@ public class ExamAuditServiceImpl implements ExamAuditService {
         if (query.getExamId() != null) {
             wrapper.and().eq("record.exam_id", query.getExamId());
         }
+        if (query.getExamStageId() != null) {
+            wrapper.and().eq("record.exam_stage_id", query.getExamStageId());
+        }
         if (StringUtils.isNotBlank(query.getStudentCode())) {
             wrapper.and().like("record.student_code", query.getStudentCode());
         }
@@ -150,6 +158,24 @@ public class ExamAuditServiceImpl implements ExamAuditService {
         if (StringUtils.isNoneBlank(query.getAuditUserName())) {
             wrapper.and().like("audit.audit_user_name", query.getAuditUserName());
         }
+
+        if (StringUtils.isNotBlank(query.getStartTime()) && StringUtils.isNotBlank(query.getEndTime())) {
+            wrapper.and().gte("record.start_time", query.getStartTime());
+            wrapper.and().lte("record.start_time", query.getEndTime());
+        }
+
+        if (StringUtils.isNotBlank(query.getSubmitStartTime()) && StringUtils.isNotBlank(query.getSubmitEndTime())) {
+            wrapper.and().isNotNull("record.end_time");
+            wrapper.and().gte("record.end_time", query.getSubmitStartTime());
+            wrapper.and().lte("record.end_time", query.getSubmitEndTime());
+        }
+
+        if (StringUtils.isNotBlank(query.getAuditStartTime()) && StringUtils.isNotBlank(query.getAuditEndTime())) {
+            wrapper.and().isNotNull("audit.update_time");
+            wrapper.and().gte("audit.update_time", query.getAuditStartTime());
+            wrapper.and().lte("audit.update_time", query.getAuditEndTime());
+        }
+
         long totalSize = 0;
         //查询总记录数
         if (query.getSelectType() == null || query.getSelectType() != SelectType.EXPORT) {
@@ -164,7 +190,7 @@ public class ExamAuditServiceImpl implements ExamAuditService {
         }
 
         //查询分页记录
-        wrapper.orderBy("record.id", true);
+        wrapper.orderBy("record.update_time", true);//需求调整20200816:按审核时间排序
         Query dataQuery = entityManager.createNativeQuery(wrapper.build());
 
 //        dataQuery.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(HashMap.class));
@@ -188,13 +214,13 @@ public class ExamAuditServiceImpl implements ExamAuditService {
             }
 
 
-            CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examAuditInfo.getCourseId());
+            CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examAuditInfo.getCourseId());
             examAuditInfo.setCourseName(courseBean.getName());
             examAuditInfo.setCourseCode(courseBean.getCode());
             examAuditInfo.setCourseLevel(CourseLevel.getCourseLevel(courseBean.getLevel()).getTitle());
 
             //考试名称
-            ExamBean examBean = ExamCacheTransferHelper.getCachedExam(query.getExamId(), examAuditInfo.getStudentId());
+            ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
             examAuditInfo.setExamName(examBean.getName());
 
             //客观分
@@ -225,7 +251,7 @@ public class ExamAuditServiceImpl implements ExamAuditService {
         //系统默认违纪类型
         List<IllegallyTypeEntity> sysIllegallyTypeList = illegallyTypeService.getSystemIllegallyTypes();
         if (null == sysIllegallyTypeList || sysIllegallyTypeList.isEmpty()) {
-            throw new StatusException("100001","默认违纪类型未配置");
+            throw new StatusException("100001", "默认违纪类型未配置");
         }
         for (Long examRecordDataId : examRecordDataIds) {
             String disciplineType = isPass ? DisciplineType.BATCH_PASS.name() : DisciplineType.BATCH_NOTPASS.name();
@@ -495,6 +521,14 @@ public class ExamAuditServiceImpl implements ExamAuditService {
             //删除阅卷需要数据,防止违纪数据进入阅卷
             examRecordForMarkingRepo.delete(examRecordForMarking);
         }
+
+        //删除考生的最终分数,后续会重新计算
+        ExamStudentFinalScoreEntity finalExamScore =
+                examStudentFinalScoreRepo.findByExamStudentId(examRecordData.getExamStudentId());
+        if (finalExamScore != null) {
+            examStudentFinalScoreRepo.delete(finalExamScore);
+        }
+
         //保存获取分数队列
         examScoreObtainQueueService.saveExamScoreObtainQueue(examRecordData.getId());
         //发送通知

+ 8 - 5
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamCaptureServiceImpl.java

@@ -22,12 +22,13 @@ import cn.com.qmth.examcloud.core.oe.admin.service.ExamCaptureService;
 import cn.com.qmth.examcloud.core.oe.admin.service.IllegallyTypeService;
 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.core.oe.admin.service.others.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.Constants;
 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.ExamStudentCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
@@ -97,7 +98,7 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
         detail.setStudentName(recordData.getStudentName());
         detail.setIdentityNumber(recordData.getIdentityNumber());
 
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(recordData.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(recordData.getCourseId());
 
         detail.setCourseCode(courseBean.getCode());
         detail.setCourseName(courseBean.getName());
@@ -137,6 +138,8 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
             detail.setObjectiveTotalScore(String.valueOf(examScore.getObjectiveScore()));
         }
 
+        detail.setSwitchScreenCount(null == recordData.getSwitchScreenCount() ? 0 : recordData.getSwitchScreenCount());
+
         return detail;
     }
 
@@ -180,7 +183,7 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
                             }
                         }
                     } catch (JSONException e) {
-                        LOG.error("获取虚拟摄像头名称失败",e);
+                        LOG.error("获取虚拟摄像头名称失败", e);
                         throw new StatusException("ExamCaptureService-001", "获取虚拟摄像头名称失败");
                     }
 
@@ -245,7 +248,7 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
                     }
                 }
             } catch (JSONException e) {
-                LOG.error("获取虚拟摄像头名称失败",e);
+                LOG.error("获取虚拟摄像头名称失败", e);
                 throw new StatusException("ExamCaptureService-002", "获取虚拟摄像头名称失败");
             }
 
@@ -280,7 +283,7 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
                     }
                 }
             } catch (JSONException e) {
-                LOG.error("json格式转换失败",e);
+                LOG.error("json格式转换失败", e);
                 return false;
             }
 

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

@@ -0,0 +1,51 @@
+package cn.com.qmth.examcloud.core.oe.admin.service.impl;
+
+import cn.com.qmth.examcloud.commons.util.DateUtil;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamProcessRecordRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamProcessRecordEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamProcessRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.ExamProcessRecordInfo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Description 考试过程记录
+ * @Author lideyin
+ * @Date 2020/8/20 17:49
+ * @Version 1.0
+ */
+@Service("examProcessRecordService")
+public class ExamProcessRecordServiceImpl implements ExamProcessRecordService {
+    @Autowired
+    private ExamProcessRecordRepo examProcessRecordRepo;
+
+    /**
+     * 获取考试过程记录
+     *
+     * @param examRecordDataId
+     * @return
+     */
+    @Override
+    public List<ExamProcessRecordInfo> getExamProcessRecords(Long examRecordDataId) {
+        List<ExamProcessRecordEntity> recordList =
+                examProcessRecordRepo.findByExamRecordDataIdOrderByRecordTimeAsc(examRecordDataId);
+        List<ExamProcessRecordInfo> resultList = new ArrayList<>();
+        if (null != recordList && !recordList.isEmpty()) {
+            for (ExamProcessRecordEntity record : recordList) {
+                ExamProcessRecordInfo info = new ExamProcessRecordInfo();
+                info.setId(record.getId());
+                info.setExamRecordDataId(record.getExamRecordDataId());
+                info.setProcessName(record.getProcessName());
+                info.setRecordTime(DateUtil.format(record.getRecordTime(), DateUtil.DatePatterns.CHINA_DEFAULT));
+                info.setSourceIp(record.getSourceIp());
+
+                resultList.add(info);
+            }
+        }
+
+        return resultList;
+    }
+}

+ 8 - 9
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordDataServiceImpl.java

@@ -1,18 +1,17 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
-import java.util.Date;
-
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 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.ExamRecordStatus;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataService;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentBean;
-import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
 
 /**
  * @author chenken
@@ -28,8 +27,8 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
 
     @Override
     public ExamRecordDataEntity createOfflineExamRecordData(ExamStudentBean examStudent,
-                                                            ExamBean examBean,
-                                                            CourseBean courseBean,
+                                                            ExamSettingsCacheBean examBean,
+                                                            CourseCacheBean courseBean,
                                                             String basePaperId,
                                                             String paperStructId, Boolean fullyObjective) {
         ExamRecordDataEntity examRecordDataEntity = new ExamRecordDataEntity();

+ 22 - 5
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordForMarkingServiceImpl.java

@@ -225,7 +225,7 @@ public class ExamRecordForMarkingServiceImpl implements ExamRecordForMarkingServ
             return null;
         }
         StringBuffer sql = new StringBuffer();
-        sql.append("select id,exam_id,base_paper_id,paper_type,course_id,offline_file_name,offline_file_url,batch_num " +
+        sql.append("select id,exam_id,exam_record_data_id,base_paper_id,paper_type,course_id,batch_num " +
                 " from ec_oe_exam_record_4_marking where 1=1 ");
         if (id != null) {
             sql.append(" and id = " + id);
@@ -249,9 +249,8 @@ public class ExamRecordForMarkingServiceImpl implements ExamRecordForMarkingServ
                 entity.setBasePaperId(rs.getString("base_paper_id"));
                 entity.setPaperType(rs.getString("paper_type"));
                 entity.setCourseId(rs.getLong("course_id"));
-                entity.setOfflineFileName(rs.getString("offline_file_name"));
-                entity.setOfflineFileUrl(rs.getString("offline_file_url"));
                 entity.setBatchNum(rs.getString("batch_num"));
+                entity.setExamRecordDataId(rs.getLong("exam_record_data_id"));
                 return entity;
             }
         });
@@ -272,8 +271,26 @@ public class ExamRecordForMarkingServiceImpl implements ExamRecordForMarkingServ
         examRecordForMarking.setExamRecordDataId(examRecordData.getId());
         examRecordForMarking.setExamStudentId(examRecordData.getExamStudentId());
         examRecordForMarking.setCourseId(examRecordData.getCourseId());
-        examRecordForMarking.setOfflineFileUrl(fileUrl);
-        examRecordForMarking.setOfflineFileName(offlineFileName);
+        examRecordForMarking.setObjectiveScore(0D);
+        examRecordForMarking.setSubjectiveAnswerLength(0);
+        examRecordForMarking.setBasePaperId(examRecordData.getBasePaperId());
+        examRecordForMarking.setPaperType(examRecordData.getPaperType());
+        examRecordForMarking.setUpdateTime(new Date());
+        examRecordForMarkingRepo.save(examRecordForMarking);
+    }
+
+    @Override
+    public void saveOffLineExamRecordForMarking(ExamRecordDataBean examRecordData) {
+        ExamRecordForMarkingEntity examRecordForMarking = examRecordForMarkingRepo.findByExamRecordDataId(examRecordData.getId());
+        if (examRecordForMarking == null) {
+            examRecordForMarking = new ExamRecordForMarkingEntity();
+            examRecordForMarking.setCreationTime(new Date());
+        }
+
+        examRecordForMarking.setExamId(examRecordData.getExamId());
+        examRecordForMarking.setExamRecordDataId(examRecordData.getId());
+        examRecordForMarking.setExamStudentId(examRecordData.getExamStudentId());
+        examRecordForMarking.setCourseId(examRecordData.getCourseId());
         examRecordForMarking.setObjectiveScore(0D);
         examRecordForMarking.setSubjectiveAnswerLength(0);
         examRecordForMarking.setBasePaperId(examRecordData.getBasePaperId());

+ 14 - 3
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordServiceImpl.java

@@ -30,11 +30,12 @@ import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordInf
 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.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
-import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -234,6 +235,10 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         if (query.getExamId() != null) {
             sql.append(" and record_data.exam_id = " + query.getExamId());
         }
+        if (query.getExamStageId() != null) {
+            sql.append(" and record_data.exam_stage_id = " + query.getExamStageId());
+        }
+
         if (StringUtils.isNotBlank(query.getStudentCode())) {
             sql.append(" and record_data.student_code LIKE '%" + query.getStudentCode() + "%'");
         }
@@ -259,6 +264,12 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             sql.append(" and record_data.start_time >= str_to_date('" + query.getStartTime() + "','%Y/%m/%d %H:%i:%s')"
                     + " and record_data.start_time <= str_to_date('" + query.getEndTime() + "','%Y/%m/%d %H:%i:%s')");
         }
+
+        if (StringUtils.isNotBlank(query.getSubmitStartTime()) && StringUtils.isNotBlank(query.getSubmitEndTime())) {
+            sql.append(" and record_data.end_time is not null and record_data.end_time >= str_to_date('" + query.getSubmitStartTime() + "','%Y/%m/%d %H:%i:%s')"
+                    + " and record_data.end_time <= str_to_date('" + query.getSubmitEndTime() + "','%Y/%m/%d %H:%i:%s')");
+        }
+
         //考试记录状态
         List<ExamRecordStatus> statusList = query.getRecordStatusList();
         if (statusList != null && statusList.size() > 0) {
@@ -416,7 +427,7 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             }
 
             //获取考试名称
-            ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examId, e.getStudentId());
+            ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
             e.setExamName(examBean.getName());
             Map<String, String> data = this.getPaperScore(e.getDataId());
             //试卷总分
@@ -494,7 +505,7 @@ public class ExamRecordServiceImpl implements ExamRecordService {
 
         list.forEach(examRecordInfo -> {
             //获取考试名称
-            ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examId, examRecordInfo.getStudentId());
+            ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
             examRecordInfo.setExamName(examBean.getName());
 
             ExamStudentInfo examStudent = (ExamStudentInfo) cahcheMap.get("examStudentinfo_" + examRecordInfo.getExamStudentId());

+ 1 - 1
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreObtainQueueServiceImpl.java

@@ -9,10 +9,10 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreObtainQueueEntity;
 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.core.oe.admin.service.others.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;

+ 1 - 1
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScorePushQueueServiceImpl.java

@@ -4,6 +4,7 @@ import java.util.Date;
 
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -17,7 +18,6 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScorePushQueue;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.OrgScoreHandleEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamScoreQueueStatus;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScorePushQueueService;
-import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.core.oe.admin.service.scorepushrule.ExamScorePushRuleService;
 import cn.com.qmth.examcloud.web.support.SpringContextHolder;
 

+ 18 - 28
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreServiceImpl.java

@@ -14,7 +14,10 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
 
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
@@ -26,7 +29,6 @@ import org.springframework.stereotype.Service;
 
 import com.google.common.collect.Lists;
 
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SpecUtils;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.CommonUtil;
@@ -48,10 +50,7 @@ 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.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
-import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
-import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
 import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
-import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 
 /**
  * 考试分数相关接口
@@ -98,7 +97,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         List<ExamScoreInfo> examScoreList = new ArrayList<>();
         examStudentList.forEach(examStudent -> {
             //获取考试信息
-            ExamBean examBean = ExamCacheTransferHelper.getCachedExam(query.getExamId(), examStudent.getStudentId());
+            ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
             examScoreList.add(of(examStudent, examBean, markingType));
         });
         return new PageImpl<ExamScoreInfo>(examScoreList, pageable, page.getTotalElements());
@@ -107,7 +106,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
     /**
      * 封装成绩统计结果
      */
-    private ExamScoreInfo of(ExamStudentInfo examStudent, ExamBean examBean, String markingType) {
+    private ExamScoreInfo of(ExamStudentInfo examStudent, ExamSettingsCacheBean examBean, String markingType) {
         ExamScoreInfo examScore = new ExamScoreInfo();
         examScore.setExamId(examStudent.getExamId());
         examScore.setExamName(examBean.getName());
@@ -132,7 +131,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         if (examTimes != null) {
             Integer extraExamNum = examStudent.getExtraNum() == null ? 0 : examStudent.getExtraNum();
             Integer usedNum = examStudent.getUsedNum() == null ? 0 : examStudent.getUsedNum();
-            Long leftExamTimes = (examTimes + extraExamNum - usedNum)<0?0:(examTimes + extraExamNum - usedNum);
+            Long leftExamTimes = (examTimes + extraExamNum - usedNum) < 0 ? 0 : (examTimes + extraExamNum - usedNum);
             examScore.setLeftExamTimes(leftExamTimes);
         }
         return setCommomScoreInfo(examScore, examStudent.getFinished(), examStudent.getExamStudentId(), examBean, markingType);
@@ -209,7 +208,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         //缓存
         for (ExamStudentEntity examStudent : examStudentList) {
             long courseId = examStudent.getCourseId();
-            CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
+            CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
 
             long orgId = examStudent.getOrgId();
             OrgCacheBean orgBean = gainBaseDataService.getOrgBean(orgId);
@@ -219,13 +218,13 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         return examScoreInfoList;
     }
 
-    private ExamScoreInfo convertToExamScoreInfo(ExamStudentEntity examStudent, CourseBean courseBean, OrgCacheBean orgBean, String markingType) {
+    private ExamScoreInfo convertToExamScoreInfo(ExamStudentEntity examStudent, CourseCacheBean courseBean, OrgCacheBean orgBean, String markingType) {
         if (examStudent == null) {
             return null;
         }
         //获取考试信息
-        ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examStudent.getExamId(),
-                examStudent.getStudentId());
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getCachedExam(examStudent.getExamId(),
+                examStudent.getStudentId(), examStudent.getExamStageId());
         ExamScoreInfo examScore = new ExamScoreInfo();
         examScore.setExamId(examStudent.getExamId());
         examScore.setExamName(examBean.getName());
@@ -253,14 +252,14 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         if (examTimes != null) {
             Integer extraExamNum = examStudent.getExtraNum() == null ? 0 : examStudent.getExtraNum();
             Integer usedNum = examStudent.getUsedNum() == null ? 0 : examStudent.getUsedNum();
-            Long leftExamTimes = (examTimes + extraExamNum - usedNum)<0?0:(examTimes + extraExamNum - usedNum);
+            Long leftExamTimes = (examTimes + extraExamNum - usedNum) < 0 ? 0 : (examTimes + extraExamNum - usedNum);
             examScore.setLeftExamTimes(leftExamTimes);
         }
 
         return setCommomScoreInfo(examScore, examStudent.getFinished(), examStudent.getExamStudentId(), examBean, markingType);
     }
 
-    private ExamScoreInfo setCommomScoreInfo(ExamScoreInfo examScoreInfo, Boolean finished, long examStudentId, ExamBean examBean, String markingType) {
+    private ExamScoreInfo setCommomScoreInfo(ExamScoreInfo examScoreInfo, Boolean finished, long examStudentId, ExamSettingsCacheBean examBean, String markingType) {
         //查询考试记录
         if (finished != null && finished) {
             //查询考试记录
@@ -276,8 +275,8 @@ public class ExamScoreServiceImpl implements ExamScoreService {
             ExamStudentFinalScoreEntity finalExamScore = examStudentFinalScoreService.getFinalEffectiveExamScore(examStudentId);
             setFinalExamScore(examScoreInfo, finalExamScore);
 
-            //考试开始时间
-            examScoreInfo.setStartTime(getExamRecordStartTime(finalExamScore));
+            //需求调整20200923:将开考时间,改为最终成绩的更新时间
+            examScoreInfo.setStartTime(getExamStudentFinalScoreUpdateTime(finalExamScore));
         } else {
             examScoreInfo.setIsFinished("否");//是否完成考试
             examScoreInfo.setIsAbsent("是");//是否缺考
@@ -315,21 +314,12 @@ public class ExamScoreServiceImpl implements ExamScoreService {
     /**
      * 考试开始时间
      */
-    private String getExamRecordStartTime(ExamStudentFinalScoreEntity examScore) {
-        if (examScore == null) {
+    private String getExamStudentFinalScoreUpdateTime(ExamStudentFinalScoreEntity examScore) {
+        if (examScore == null || examScore.getUpdateTime() == null) {
             return "";
         }
 
-        ExamRecordDataEntity examRecordData =
-                GlobalHelper.getEntity(examRecordDataRepo, examScore.getExamRecordDataId(), ExamRecordDataEntity.class);
-
-        if (examRecordData == null) {
-            return "";
-        }
-        if (examRecordData.getStartTime() == null) {
-            return "";
-        }
-        return CommonUtil.getDateStrWithSecond(examRecordData.getStartTime());
+        return CommonUtil.getDateStrWithSecond(examScore.getUpdateTime());
     }
 
     /**
@@ -434,7 +424,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
                 objectiveScoreInfo.setIsExamEnded(true);
             }
 
-            if (examRecordDataEntity.getIsIllegality()==null||!examRecordDataEntity.getIsIllegality()) {
+            if (examRecordDataEntity.getIsIllegality() == null || !examRecordDataEntity.getIsIllegality()) {
                 if (examRecordDataEntity.getIsWarn() && !examRecordDataEntity.getIsAudit()) {
                     objectiveScoreInfo.setIsAuditing(true);
                 } else if (!examRecordDataEntity.getIsWarn() || (examRecordDataEntity.getIsWarn() && examRecordDataEntity.getIsAudit())) {

+ 1 - 1
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamStudentFinalScoreServiceImpl.java

@@ -13,8 +13,8 @@ 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.MarkingType;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentFinalScoreService;
-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.ExamCacheTransferHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 

+ 167 - 116
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamStudentServiceImpl.java

@@ -7,37 +7,45 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
-import static cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentMapper.statisticFinishedColumns;
-import static cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentMapper.statisticOrgColumns;
-
-import java.math.BigDecimal;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import javax.persistence.EntityManager;
-import javax.persistence.Query;
-
+import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
-import cn.com.qmth.examcloud.commons.util.DateUtil;
-import cn.com.qmth.examcloud.commons.util.RegExpUtil;
+import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SpecUtils;
+import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SqlWrapper;
+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.ExamRecordFileAnswerRepo;
+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.ExamRecordFileAnswerEntity;
+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.dao.enums.CourseLevel;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.FinishStatus;
 import cn.com.qmth.examcloud.core.oe.admin.service.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.OnHandExamInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordFileAnswerInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.*;
 import cn.com.qmth.examcloud.core.oe.admin.service.cache.ExamStudentCache;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
 import cn.com.qmth.examcloud.examwork.api.ExamStudentCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamSpecialSettingsBean;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamStudentBean;
 import cn.com.qmth.examcloud.examwork.api.request.GetExamStudentReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetOngoingExamListReq;
 import cn.com.qmth.examcloud.examwork.api.response.GetExamStudentResp;
-import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import cn.com.qmth.examcloud.examwork.api.response.GetOngoingExamListResp;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.*;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.examing.ExamBoss;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
+import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
 import org.hibernate.query.NativeQuery;
 import org.hibernate.transform.Transformers;
@@ -53,38 +61,17 @@ import org.springframework.jdbc.core.RowMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import com.google.common.collect.Lists;
-
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
-import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SpecUtils;
-import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SqlWrapper;
-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.dao.enums.CourseLevel;
-import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
-import cn.com.qmth.examcloud.core.oe.admin.dao.enums.FinishStatus;
-import cn.com.qmth.examcloud.core.oe.admin.service.bean.OnHandExamInfo;
-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.bean.ExamSpecialSettingsBean;
-import cn.com.qmth.examcloud.examwork.api.request.GetOngoingExamListReq;
-import cn.com.qmth.examcloud.examwork.api.response.GetOngoingExamListResp;
-import cn.com.qmth.examcloud.support.cache.CacheHelper;
-import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
-import cn.com.qmth.examcloud.support.examing.ExamBoss;
-import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
-import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
-import cn.com.qmth.examcloud.web.redis.RedisClient;
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import java.math.BigDecimal;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DecimalFormat;
+import java.util.*;
+import java.util.stream.Collectors;
 
-import cn.com.qmth.examcloud.examwork.api.bean.ExamStudentBean;
+import static cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentMapper.statisticFinishedColumns;
+import static cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentMapper.statisticOrgColumns;
 
 /**
  * 考生信息接口
@@ -122,6 +109,8 @@ public class ExamStudentServiceImpl implements ExamStudentService {
 
     @Autowired
     ExamStudentCloudService examStudentCloudService;
+    @Autowired
+    private ExamRecordFileAnswerRepo examRecordFileAnswerRepo;
 
     @Transactional
     @Override
@@ -164,6 +153,8 @@ public class ExamStudentServiceImpl implements ExamStudentService {
             entity.setGrade(cur.getGrade());
             entity.setUpdateTime(new Date());
             entity.setEnable(cur.getEnable());
+            entity.setExamStageId(cur.getExamStageId());
+            entity.setExamStageOrder(cur.getExamStageOrder());
 
             //保存考生
             ExamStudentEntity examStudentEntity = examStudentRepo.save(entity);
@@ -217,7 +208,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     public Page<ExamStudentInfo> getExamStudentListPage(ExamStudentQuery query) {
         Check.isNull(query, "查询参数不能为空!");
 
-        ExamBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
 
 
         StringBuffer sql = new StringBuffer();
@@ -279,7 +270,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     @Override
     public List<ExamStudentInfo> getExamStudentInfoList(ExamStudentQuery query) {
         Check.isNull(query, "查询参数不能为空!");
-        ExamBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
 
         //查询条件
         StringBuffer sql = new StringBuffer();
@@ -334,6 +325,9 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         if (query.getExamId() != null) {
             sql.append(" and exam_id = " + query.getExamId());
         }
+        if (query.getExamStageId() != null) {
+            sql.append(" and exam_stage_id = " + query.getExamStageId());
+        }
         if (StringUtils.isNotBlank(query.getStudentCode())) {
             sql.append(" and student_code LIKE '%" + query.getStudentCode() + "%'");
         }
@@ -383,7 +377,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     private ExamStudentInfo buildExamStudentInfo(ExamStudentEntity examStudentEntity, Map<String, Object> cahcheMap, String examType) {
         ExamStudentInfo examStudentInfo = ExamStudentEntityConvert.of(examStudentEntity);
         examStudentInfo.setExamType(examType);
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentInfo.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentInfo.getCourseId());
         examStudentInfo.setCourseName(courseBean.getName());
         examStudentInfo.setCourseCode(courseBean.getCode());
         examStudentInfo.setCourseLevel(CourseLevel.getCourseLevel(courseBean.getLevel()).getTitle());
@@ -411,18 +405,39 @@ public class ExamStudentServiceImpl implements ExamStudentService {
             if (!examStudentEntity.getFinished()) {
                 examStudentInfo.setFinishedStatus(FinishStatus.未抽题.name());
             } else {
+                examStudentInfo.setFinishedStatus(FinishStatus.已抽题.name());
+
                 ExamRecordForMarkingEntity examRecordForMarking = examRecordForMarkingRepo.findTopByExamStudentId(examStudentEntity.getExamStudentId());
-                if (examRecordForMarking != null && StringUtils.isNotBlank(examRecordForMarking.getOfflineFileUrl())) {
-                    examStudentInfo.setFinishedStatus(FinishStatus.已上传.name());
-                    examStudentInfo.setOfflineFileUrl(FileStorageUtil.realPath(examRecordForMarking.getOfflineFileUrl()));
-                } else {
-                    examStudentInfo.setFinishedStatus(FinishStatus.已抽题.name());
+                if (examRecordForMarking != null) {
+                    List<ExamRecordFileAnswerEntity> fileAnswerList = examRecordFileAnswerRepo.findByExamRecordDataId(examRecordForMarking.getExamRecordDataId());
+                    if (null != fileAnswerList && !fileAnswerList.isEmpty()) {
+                        examStudentInfo.setFinishedStatus(FinishStatus.已上传.name());
+                        examStudentInfo.setOfflineFiles(getOfflineFilesFrom(fileAnswerList));
+                    }
                 }
             }
         }
         return examStudentInfo;
     }
 
+    private List<ExamRecordFileAnswerInfo> getOfflineFilesFrom(List<ExamRecordFileAnswerEntity> fileAnswerList) {
+        List<ExamRecordFileAnswerInfo> resultList = new ArrayList<>();
+        for (ExamRecordFileAnswerEntity entity : fileAnswerList) {
+            ExamRecordFileAnswerInfo info = new ExamRecordFileAnswerInfo();
+            info.setId(entity.getId());
+            info.setExamRecordDataId(entity.getExamRecordDataId());
+            info.setOfflineFileUrl(FileStorageUtil.realPath(entity.getFileUrl()));
+            info.setOfflineFileName(entity.getFileName());
+            info.setOriginalFileName(entity.getOriginalFileName());
+            info.setFileType(entity.getFileType());
+            info.setSuffix(entity.getSuffix());
+            info.setProperties(entity.getProperties());
+            resultList.add(info);
+        }
+
+        return resultList;
+    }
+
     private ExamStudentEntity getExamStudentEntityByResultSet(ResultSet rs) throws SQLException {
         ExamStudentEntity examStudentEntity = new ExamStudentEntity();
         examStudentEntity.setId(rs.getLong("id"));
@@ -451,7 +466,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     @Override
     public Page<ExamStudentInfo> getReExamineStudentList(ExamStudentQuery query) {
         //获取考试的默认次数
-        ExamBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
 
         //封装查询条件
         Pageable pageable = SpecUtils.buildPageable(query.getPageNo(), query.getPageSize());
@@ -489,6 +504,11 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         if (query.getOrgId() != null) {
             sql.append(" and t.org_id = " + query.getOrgId());
         }
+
+        if (query.getExamStageId() != null) {
+            sql.append(" and t.exam_stage_id = " + query.getExamStageId());
+        }
+
         if (StringUtils.isNotBlank(query.getStudentCode())) {
             sql.append(" and t.student_code LIKE '%" + query.getStudentCode() + "%'");
         }
@@ -539,14 +559,14 @@ public class ExamStudentServiceImpl implements ExamStudentService {
             OrgCacheBean orgBean = gainBaseDataService.getOrgBean(examStudentInfo.getOrgId());
             examStudentInfo.setOrgName(orgBean.getName());
             examStudentInfo.setOrgCode(orgBean.getCode());
-            CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentInfo.getCourseId());
+            CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentInfo.getCourseId());
             examStudentInfo.setCourseName(courseBean.getName());
             list.add(examStudentInfo);
         }
         return new PageImpl<>(list, pageable, totalSize);
     }
 
-    private Long countReExamine(ExamStudentQuery query, ExamBean examBean) {
+    private Long countReExamine(ExamStudentQuery query, ExamSettingsCacheBean examBean) {
         StringBuffer sql = new StringBuffer();
         sql.append("SELECT count(*)" +
                 " FROM " +
@@ -582,18 +602,25 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public ExamStudentFinishedStatistic getExamStudentStatisticByFinished(Long examId) {
-        ExamBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
-        if (ExamType.ONLINE.name().equals(examBean.getExamType()) || ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) {
+    public ExamStudentFinishedStatistic getExamStudentStatisticByFinished(Long examId, Long examStageId) {
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
+        if (ExamType.ONLINE.name().equals(examBean.getExamType()) ||
+                ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) {
             ExamStudentFinishedStatistic statistic = new ExamStudentFinishedStatistic();
             StringBuffer totalsql = new StringBuffer();
             totalsql.append("select count(t1.id) from ec_oe_exam_student t1 where 1=1 ");
             totalsql.append(" and exam_id = " + examId);
+            if (null != examStageId) {
+                totalsql.append(" and exam_stage_id = " + examStageId);
+            }
             Integer total = jdbcTemplate.queryForObject(totalsql.toString(), Integer.class);
 
             StringBuffer finishsql = new StringBuffer();
             finishsql.append("select count(t1.id) from ec_oe_exam_student t1 where 1=1 ");
             finishsql.append(" and exam_id = " + examId);
+            if (null != examStageId) {
+                finishsql.append(" and exam_stage_id = " + examStageId);
+            }
             finishsql.append(" AND ( finished = 1 OR t1.exam_student_id in ( SELECT t2.exam_student_id FROM ec_oes_exam_record_data t2 WHERE t2.exam_id=" + examId + "  )  )");
             Integer finish = jdbcTemplate.queryForObject(finishsql.toString(), Integer.class);
             statistic.setFinished(finish);
@@ -604,6 +631,10 @@ public class ExamStudentServiceImpl implements ExamStudentService {
                     .select(statisticFinishedColumns())
                     .from("ec_oe_exam_student").as("student")
                     .where().eq("student.exam_id", examId);
+            if (null != examStageId) {
+                wrapper.and().eq("student.exam_stage_id", examStageId);
+            }
+
             Query dataQuery = entityManager.createNativeQuery(wrapper.build());
             dataQuery.unwrap(NativeQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
             Map<String, BigDecimal> map = (HashMap) dataQuery.getSingleResult();
@@ -625,12 +656,16 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(Long examId, Long orgId) {
-        ExamBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
-        if (ExamType.ONLINE.name().equals(examBean.getExamType()) || ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) {
+    public List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(Long examId, Long examStageId, Long orgId) {
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
+        if (ExamType.ONLINE.name().equals(examBean.getExamType()) ||
+                ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) {
             StringBuffer totalsql = new StringBuffer();
             totalsql.append("select t1.org_id orgId,count(t1.id) totalCount from ec_oe_exam_student t1 where 1=1 ");
             totalsql.append(" and exam_id = " + examId);
+            if (null != examStageId) {
+                totalsql.append(" and exam_stage_id = " + examStageId);
+            }
             if (orgId != null) {
                 totalsql.append(" and org_id = " + orgId);
             }
@@ -640,6 +675,9 @@ public class ExamStudentServiceImpl implements ExamStudentService {
             StringBuffer finishsql = new StringBuffer();
             finishsql.append("select t1.org_id orgId,count(t1.id) finishedCount from ec_oe_exam_student t1 where 1=1 ");
             finishsql.append(" and exam_id = " + examId);
+            if (null != examStageId) {
+                finishsql.append(" and exam_stage_id = " + examStageId);
+            }
             if (orgId != null) {
                 finishsql.append(" and org_id = " + orgId);
             }
@@ -669,6 +707,10 @@ public class ExamStudentServiceImpl implements ExamStudentService {
                     .select(statisticOrgColumns())
                     .from("ec_oe_exam_student").as("student")
                     .where().eq("student.exam_id", examId);
+
+            if (null != examStageId) {
+                wrapper.and().eq("student.exam_stage_id", examStageId);
+            }
             if (orgId != null) {
                 wrapper.and().eq("student.org_id", orgId);
             }
@@ -693,8 +735,11 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public List<Long> findCoursesFromExamStudent(Long examId, Long orgId) {
+    public List<Long> findCoursesFromExamStudent(Long examId, Long examStageId, Long orgId) {
         String sql = "select course_id from ec_oe_exam_student where exam_id = " + examId;
+        if (null != examStageId) {
+            sql += " and exam_stage_id= " + examStageId;
+        }
         if (orgId != null) {
             sql += " and org_id = " + orgId;
         }
@@ -743,15 +788,21 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public List<CourseProgressInfo> queryCourseProgressInfos(Long examId, Long courseId, String orderColumn) {
+    public List<CourseProgressInfo> queryCourseProgressInfos(Long examId, Long examStageId,
+                                                             Long courseId, String orderColumn) {
         if (examId == null) {
             return null;
         }
-        ExamBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
-        if (ExamType.ONLINE.name().equals(examBean.getExamType()) || ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) {
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
+        if (ExamType.ONLINE.name().equals(examBean.getExamType()) ||
+                ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) {
             StringBuffer totalsql = new StringBuffer();
             totalsql.append("select t1.course_id courseId,count(t1.id) allNum from ec_oe_exam_student t1 where 1=1 ");
             totalsql.append(" and exam_id = " + examId);
+
+            if (null != examStageId) {
+                totalsql.append(" and exam_stage_id = " + examStageId);
+            }
             if (courseId != null) {
                 totalsql.append(" and course_id = " + courseId);
             }
@@ -761,6 +812,10 @@ public class ExamStudentServiceImpl implements ExamStudentService {
             StringBuffer finishsql = new StringBuffer();
             finishsql.append("select t1.course_id courseId,count(t1.id) completedNum from ec_oe_exam_student t1 where 1=1 ");
             finishsql.append(" and exam_id = " + examId);
+
+            if (null != examStageId) {
+                finishsql.append(" and exam_stage_id = " + examStageId);
+            }
             if (courseId != null) {
                 finishsql.append(" and course_id = " + courseId);
             }
@@ -796,6 +851,10 @@ public class ExamStudentServiceImpl implements ExamStudentService {
                     " count(course_id) all_num" +
                     " from ec_oe_exam_student " +
                     " where exam_id = " + examId;
+
+            if (null != examStageId) {
+                sql += " and exam_stage_id = " + examStageId;
+            }
             if (courseId != null) {
                 sql += " and course_id = " + courseId;
             }
@@ -864,7 +923,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         getOngoingExamListReq.setStudentId(studentId);
         GetOngoingExamListResp getOngoingExamListResp = examCloudService.getOngoingExamList(getOngoingExamListReq);
 
-        //获取学生所在组织机构的所有考试列表集合(虽然名字起的特殊考试设置,事实上取的就是实际的可考的考试列表,可能是考试中心特殊设置的考试时间,也可能不是)
+        //获取学生所在组织机构的所有考试列表集合
         List<ExamSpecialSettingsBean> examSpecialSettingsBeanList = getOngoingExamListResp.getExamSpecialSettingsList();
         if (examSpecialSettingsBeanList == null || examSpecialSettingsBeanList.size() == 0) {
             return null;
@@ -872,46 +931,43 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         List<Long> examIds = examSpecialSettingsBeanList.stream().map(ExamSpecialSettingsBean::getExamId).collect(Collectors.toList());
 
         //只查没有禁用的考生
-        List<ExamStudentEntity> examStudents = examStudentRepo.findByStudentIdAndEnableAndExamIdIn(studentId, true, examIds);
+        List<ExamStudentEntity> examStudents =
+                examStudentRepo.findByStudentIdAndEnableAndExamIdIn(studentId, true, examIds);
+
         List<OnHandExamInfo> examStudentDtoList = new ArrayList<OnHandExamInfo>();
+        Date now = new Date();
         for (ExamStudentEntity examStudent : examStudents) {
-            Stream<ExamSpecialSettingsBean> examSpecialSettingsBeanStream = examSpecialSettingsBeanList.stream().filter(examSpecialSettingsBean -> {
-                return examSpecialSettingsBean.getExamId().longValue() == examStudent.getExamId().longValue();
-            });
-            if (examSpecialSettingsBeanStream != null) {
-                ExamSpecialSettingsBean examSpecialSettingsBean = examSpecialSettingsBeanStream.findFirst().get();
-                examStudentDtoList.add(assemblingExamStudentDto(examStudent, examSpecialSettingsBean));
-            }
-        }
-
-        //针对指定机构进行学生特殊设置进行校验,FIXME 临时代码
-        SysPropertyCacheBean specialStudentProperty = CacheHelper.getSysProperty("studentSpecialExamTime.enabledRootOrgIds");
-        if (specialStudentProperty.getHasValue()) {
-            List<String> orgIdList = RegExpUtil.findAll(specialStudentProperty.getValue().toString(), "\\d+");
-            if (orgIdList.contains(String.valueOf(studentBean.getRootOrgId()))) {
-                for (OnHandExamInfo info : examStudentDtoList) {
-                    //如果考生特殊设置过开考时间,需要判断考生是否在考试时间内
-                    ExamStudentBean remoteExamStudent = getRemoteExamStudent(studentBean.getRootOrgId(), info.getExamStudentId());
-                    String strStartTime = remoteExamStudent.getExt4();
-                    String strEndTime = remoteExamStudent.getExt5();
-
-                    Date specialStartTime = StringUtils.isBlank(strStartTime) ? null :
-                            DateUtil.parse(strStartTime, DateUtil.DatePatterns.CHINA_DEFAULT);
-                    Date specialEndTime = StringUtils.isBlank(strEndTime) ? null :
-                            DateUtil.parse(strEndTime, DateUtil.DatePatterns.CHINA_DEFAULT);
-                    if (null != specialStartTime && null != specialEndTime) {
-                        info.setStartTime(specialStartTime);
-                        info.setEndTime(specialEndTime);
-                    }
-                }
-            }
+            assemblingExamStudentDto(examStudent, now, examStudentDtoList);
         }
 
         return examStudentDtoList;
     }
 
-    private OnHandExamInfo assemblingExamStudentDto(ExamStudentEntity examStudent, ExamSpecialSettingsBean examSpecialSettingsBean) {
+    private void assemblingExamStudentDto(ExamStudentEntity examStudent, Date now, final List<OnHandExamInfo> resultList) {
+        Long examId = examStudent.getExamId();
+        Long studentId = examStudent.getStudentId();
+        Long examStageId = examStudent.getExamStageId();
+
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId, examStageId);
+        if (examBean.getSpecialSettingsEnabled() && examStageId != null
+                && ExamSpecialSettingsType.STAGE_BASED == examBean.getSpecialSettingsType()) {
+            ExamStageCacheBean examStage = CacheHelper.getExamStage(examId, examStageId);
+
+            //场次如果禁用,该场次不允许考试
+            if (examStage.getHasValue() && !examStage.getEnable()) {
+                return;
+            }
+
+            //如果当前时间超过场次结束时间,不允许考试
+            if (now.after(examBean.getEndTime())) {
+                return;
+            }
+
+        }
+
         OnHandExamInfo examStudentInfo = new OnHandExamInfo();
+        resultList.add(examStudentInfo);
+
         examStudentInfo.setExamStudentId(examStudent.getExamStudentId());
         examStudentInfo.setStudentCode(examStudent.getStudentCode());
         examStudentInfo.setStudentName(examStudent.getStudentName());
@@ -919,7 +975,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         examStudentInfo.setRootOrgId(rootOrgId);
         examStudentInfo.setIdentityNumber(examStudent.getIdentityNumber());
 
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudent.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudent.getCourseId());
         examStudentInfo.setCourseName(courseBean.getName());
         examStudentInfo.setCourseCode(courseBean.getCode());
         examStudentInfo.setCourseLevel(CourseLevel.getCourseLevel(courseBean.getLevel()).getTitle());
@@ -929,16 +985,12 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         examStudentInfo.setOrgId(orgId);
 
         OrgCacheBean orgBean = gainBaseDataService.getOrgBean(orgId);
-        Long studentId = examStudent.getStudentId();
-        ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examStudent.getExamId(),
-                studentId);
 
         examStudentInfo.setOrgName(orgBean.getName());
-        Long examId = examBean.getId();
         examStudentInfo.setExamId(examId);
         examStudentInfo.setExamName(examBean.getName());
-        examStudentInfo.setStartTime(examSpecialSettingsBean.getBeginTime());//考试开始时间设置
-        examStudentInfo.setEndTime(examSpecialSettingsBean.getEndTime());//考试结束时间设置
+        examStudentInfo.setStartTime(examBean.getBeginTime());//考试开始时间设置
+        examStudentInfo.setEndTime(examBean.getEndTime());//考试结束时间设置
         examStudentInfo.setAllowExamCount(countExamTimes(examStudent, examBean));
         examStudentInfo.setPaperMins(examBean.getDuration());
         //是否启用人脸识别
@@ -964,10 +1016,9 @@ public class ExamStudentServiceImpl implements ExamStudentService {
             examStudentInfo.setAppExamEnabled(Boolean.valueOf(appExamEnabled));
         }
 
-        return examStudentInfo;
     }
 
-    private Integer countExamTimes(ExamStudentEntity examStudentInfo, ExamBean examBean) {
+    private Integer countExamTimes(ExamStudentEntity examStudentInfo, ExamSettingsCacheBean examBean) {
         if (ExamType.OFFLINE.name().equals(examBean.getExamType())) {
             return 1;
         }

+ 219 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExportTaskServiceImpl.java

@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2020 "https://github.com/deason" All Rights Reserved.
+ * Created by Deason on 2020-08-12 16:19:32
+ */
+
+package cn.com.qmth.examcloud.core.oe.admin.service.impl;
+
+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.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExportTaskRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExportTaskEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExportTaskService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskListReq;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskListResp;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamNamesReq;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamNamesResp;
+import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.*;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.criteria.Predicate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 导出任务相关接口
+ */
+@Service
+public class ExportTaskServiceImpl implements ExportTaskService {
+
+    private static final ExamCloudLog log = ExamCloudLogFactory.getLog(ExportTaskServiceImpl.class);
+
+    @Autowired
+    private ExportTaskRepo exportTaskRepo;
+
+    @Autowired
+    private ExamCloudService examCloudService;
+
+    @Override
+    @Transactional
+    public Long addExportTask(ExportTaskInfo info) {
+        Check.isNull(info.getRootOrgId(), "学校ID不能为空!");
+        Check.isNull(info.getType(), "导出任务类型不能为空!");
+        Check.isNull(info.getStatus(), "导出任务状态不能为空!");
+
+        ExportTaskEntity entity = new ExportTaskEntity();
+        entity.setType(info.getType());
+        entity.setStatus(info.getStatus());
+        entity.setStatusMsg(info.getStatusMsg());
+        entity.setRootOrgId(info.getRootOrgId());
+        entity.setExamId(info.getExamId());
+        entity.setFilePath(info.getFilePath());
+
+        exportTaskRepo.save(entity);
+        return entity.getId();
+    }
+
+    @Override
+    public void updateExportTaskStatus(Long taskId, ExportTaskStatus status, String statusMsg) {
+        this.updateExportTaskStatus(taskId, status, statusMsg, null);
+    }
+
+    @Override
+    @Transactional
+    public void updateExportTaskStatus(Long taskId, ExportTaskStatus status, String statusMsg, String filePath) {
+        Check.isNull(taskId, "导出任务ID不能为空!");
+        Check.isNull(status, "导出任务状态不能为空!");
+
+        exportTaskRepo.updateStatusAndStatusMsgById(taskId, status, statusMsg, filePath, new Date());
+    }
+
+    @Override
+    @Transactional
+    public void deleteExportTaskById(Long taskId) {
+        Optional<ExportTaskEntity> optional = exportTaskRepo.findById(taskId);
+        if (!optional.isPresent()) {
+            return;
+        }
+
+        if (ExportTaskStatus.EXPORTING == optional.get().getStatus()) {
+            throw new StatusException("500S05", "删除失败,当前导出任务尚未完成!");
+        }
+
+        exportTaskRepo.delete(optional.get());
+    }
+
+    @Override
+    public boolean existUnFinishTask(ExportTaskType type, Long rootOrgId, Long examId) {
+        Specification<ExportTaskEntity> spec = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            if (rootOrgId != null) {
+                predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+            }
+            if (examId != null) {
+                predicates.add(cb.equal(root.get("examId"), examId));
+            }
+            predicates.add(cb.equal(root.get("type"), type));
+            predicates.add(cb.equal(root.get("status"), ExportTaskStatus.EXPORTING));
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        List<ExportTaskEntity> tasks = exportTaskRepo.findAll(spec);
+        if (CollectionUtils.isEmpty(tasks)) {
+            return false;
+        }
+
+        boolean exist = false;
+
+        // 超时情况更新为“失败”状态
+        List<ExportTaskEntity> updateTasks = new ArrayList<>();
+        for (ExportTaskEntity task : tasks) {
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(task.getCreationTime());
+            calendar.add(Calendar.MINUTE, 10);
+
+            // 默认:10分钟
+            if (new Date().before(calendar.getTime())) {
+                exist = true;
+                continue;
+            }
+
+            task.setStatus(ExportTaskStatus.ERROR);
+            task.setStatusMsg("任务超时终止");
+            updateTasks.add(task);
+        }
+
+        if (CollectionUtils.isNotEmpty(updateTasks)) {
+            exportTaskRepo.saveAll(updateTasks);
+        }
+
+        return exist;
+    }
+
+    @Override
+    public Page<ExportTaskListResp> getExportTaskList(ExportTaskListReq req) {
+        Check.isNull(req.getRootOrgId(), "学校ID不能为空!");
+
+        if (req.getPageNo() == null || req.getPageNo() < 1) {
+            req.setPageNo(1);
+        }
+
+        if (req.getPageSize() == null || req.getPageSize() < 1) {
+            req.setPageSize(10);
+        }
+
+        Specification<ExportTaskEntity> spec = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(cb.equal(root.get("rootOrgId"), req.getRootOrgId()));
+
+            if (req.getExamId() != null) {
+                predicates.add(cb.equal(root.get("examId"), req.getExamId()));
+            }
+
+            if (req.getType() != null) {
+                predicates.add(cb.equal(root.get("type"), req.getType()));
+            }
+
+            if (req.getStatus() != null) {
+                predicates.add(cb.equal(root.get("status"), req.getStatus()));
+            }
+
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        Sort sort = Sort.by(Sort.Order.desc("id"));
+        Pageable pageable = PageRequest.of(req.getPageNo() - 1, req.getPageSize(), sort);
+        Page<ExportTaskEntity> page = exportTaskRepo.findAll(spec, pageable);
+
+        if (!page.hasContent()) {
+            return new PageImpl<>(new ArrayList<>(), pageable, 0);
+        }
+
+        Set<Long> examIds = page.getContent().stream().filter(e -> e.getExamId() != null).map(e -> e.getExamId()).collect(Collectors.toSet());
+        Map<Long, String> examNames;
+        if (CollectionUtils.isNotEmpty(examIds)) {
+            GetExamNamesReq namesReq = new GetExamNamesReq();
+            namesReq.setExamIds(examIds);
+            GetExamNamesResp namesResp = examCloudService.getExamNames(namesReq);
+            examNames = namesResp.getExamNames();
+        } else {
+            examNames = new HashMap<>();
+        }
+
+        List<ExportTaskListResp> list = page.getContent()
+                .stream().map(entity -> ofExportTask(entity, examNames)).collect(Collectors.toList());
+        return new PageImpl<>(list, pageable, page.getTotalElements());
+    }
+
+    private ExportTaskListResp ofExportTask(ExportTaskEntity entity, Map<Long, String> examNames) {
+        ExportTaskListResp info = new ExportTaskListResp();
+
+        info.setId(entity.getId());
+        info.setType(entity.getType());
+        info.setTypeTitle(entity.getType().getDesc());
+        info.setStatus(entity.getStatus());
+        info.setStatusTitle(entity.getStatus().getDesc());
+        info.setStatusMsg(entity.getStatusMsg());
+        info.setExamId(entity.getExamId());
+        if (entity.getExamId() != null) {
+            info.setExamName(examNames.get(entity.getExamId()));
+        }
+        info.setFilePath(FileStorageUtil.realPath(entity.getFilePath()));
+        info.setCreationTime(entity.getCreationTime());
+        info.setUpdateTime(entity.getUpdateTime());
+
+        return info;
+    }
+
+}

+ 158 - 40
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/OfflineExamServiceImpl.java

@@ -1,51 +1,43 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import javax.transaction.Transactional;
-
-import cn.com.qmth.examcloud.support.enums.ExamProperties;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
 import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 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.ExamRecordFileAnswerRepo;
 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.ExamRecordFileAnswerEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
-import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataService;
-import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordForMarkingService;
-import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreService;
-import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
-import cn.com.qmth.examcloud.core.oe.admin.service.OfflineExamService;
+import cn.com.qmth.examcloud.core.oe.admin.service.*;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.OfflineExamCourseInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordFileAnswerInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentBean;
-import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.core.questions.api.ExtractConfigCloudService;
 import cn.com.qmth.examcloud.core.questions.api.request.GetPaperReq;
 import cn.com.qmth.examcloud.core.questions.api.response.GetPaperResp;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
-import cn.com.qmth.examcloud.support.cache.bean.ExamOrgSettingsCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExamStudentSettingsCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.*;
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.filestorage.FileStoragePathEnvInfo;
 import cn.com.qmth.examcloud.web.filestorage.YunPathInfo;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import com.mysql.cj.util.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
 
 /**
  * @author chenken
@@ -78,7 +70,7 @@ public class OfflineExamServiceImpl implements OfflineExamService {
     private ExamRecordForMarkingService examRecordForMarkingService;
 
     @Autowired
-    private ExamRecordForMarkingRepo examRecordForMarkingRepo;
+    private ExamRecordFileAnswerRepo examRecordFileAnswerRepo;
 
     @Value("${app.upyun.uploadUrl}")
     private String upyunUploadUrl;
@@ -88,7 +80,8 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         List<ExamStudentEntity> examStudents = examStudentRepo.findByStudentId(studentId);
         List<OfflineExamCourseInfo> offlineExamCourseInfoList = new ArrayList<>();
         for (ExamStudentEntity examStudent : examStudents) {
-            ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examStudent.getExamId(), studentId);
+            ExamSettingsCacheBean examBean =
+                    ExamCacheTransferHelper.getCachedExam(examStudent.getExamId(), studentId,examStudent.getExamStageId());
             if (!ExamType.OFFLINE.name().equals(examBean.getExamType())) {
                 continue;
             }
@@ -103,10 +96,10 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         return offlineExamCourseInfoList;
     }
 
-    private OfflineExamCourseInfo toOfflineExamCourse(ExamStudentEntity examStudent, ExamBean examBean) {
+    private OfflineExamCourseInfo toOfflineExamCourse(ExamStudentEntity examStudent, ExamSettingsCacheBean examBean) {
         OfflineExamCourseInfo offlineExamCourseInfo = new OfflineExamCourseInfo();
 
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudent.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudent.getCourseId());
         offlineExamCourseInfo.setCourseCode(courseBean.getCode());
         offlineExamCourseInfo.setCourseLevel(courseBean.getLevel());
         offlineExamCourseInfo.setCourseName(courseBean.getName());
@@ -136,14 +129,31 @@ public class OfflineExamServiceImpl implements OfflineExamService {
             offlineExamCourseInfo.setStatus(examRecordDataEntity.getExamRecordStatus());
             offlineExamCourseInfo.setPaperId(examRecordDataEntity.getBasePaperId());
             if (examRecordDataEntity.getExamRecordStatus() == ExamRecordStatus.EXAM_END) {
-                ExamRecordForMarkingEntity examRecordForMarkingEntity = examRecordForMarkingRepo.findByExamRecordDataId(examRecordDataEntity.getId());
-                offlineExamCourseInfo.setOfflineFileUrl(FileStorageUtil.realPath(examRecordForMarkingEntity.getOfflineFileUrl()));
-                offlineExamCourseInfo.setFileName(examRecordForMarkingEntity.getOfflineFileName());
+                List<ExamRecordFileAnswerEntity> fileAnswerList = examRecordFileAnswerRepo.findByExamRecordDataId(examRecordDataEntity.getId());
+                offlineExamCourseInfo.setOfflineFiles(getOfflineFilesFrom(fileAnswerList));
             }
         }
         return offlineExamCourseInfo;
     }
 
+    private List<ExamRecordFileAnswerInfo> getOfflineFilesFrom(List<ExamRecordFileAnswerEntity> fileAnswerList) {
+        List<ExamRecordFileAnswerInfo> resultList = new ArrayList<>();
+        for (ExamRecordFileAnswerEntity entity : fileAnswerList) {
+            ExamRecordFileAnswerInfo info = new ExamRecordFileAnswerInfo();
+            info.setId(entity.getId());
+            info.setExamRecordDataId(entity.getExamRecordDataId());
+            info.setOfflineFileUrl(FileStorageUtil.realPath(entity.getFileUrl()));
+            info.setOfflineFileName(entity.getFileName());
+            info.setOriginalFileName(entity.getOriginalFileName());
+            info.setFileType(entity.getFileType());
+            info.setSuffix(entity.getSuffix());
+            info.setProperties(entity.getProperties());
+            resultList.add(info);
+        }
+
+        return resultList;
+    }
+
     @Override
     public void startOfflineExam(Long examStudentId) {
         SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
@@ -162,9 +172,9 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         ExamStudentEntity examStudentEntity = examStudentRepo.findByExamStudentId(examStudentId);
         ExamStudentBean bean = of(examStudentEntity);
         //检查并获取课程信息
-        CourseBean courseBean = checkCourse(bean);
+        CourseCacheBean courseBean = checkCourse(bean);
         //检查并获取考试信息
-        ExamBean examBean = checkExam(bean);
+        ExamSettingsCacheBean examBean = checkExam(bean);
         //获取题库试卷结构(由于存在随机抽卷,所以不能缓存 )
         GetPaperReq getPaperReq = new GetPaperReq();
         getPaperReq.setExamId(examStudentEntity.getExamId());
@@ -182,18 +192,18 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         examStudentRepo.updateExamStudentFinished(examStudentId);
     }
 
-    private CourseBean checkCourse(ExamStudentBean bean) {
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(bean.getCourseId());
+    private CourseCacheBean checkCourse(ExamStudentBean bean) {
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(bean.getCourseId());
         if (!courseBean.getEnable()) {
             throw new StatusException("2001", "该课程已被禁用");
         }
         return courseBean;
     }
 
-    private ExamBean checkExam(ExamStudentBean bean) {
+    private ExamSettingsCacheBean checkExam(ExamStudentBean bean) {
         Long examId = bean.getExamId();
         Long studentId = bean.getStudentId();
-        ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId);
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId,bean.getExamStageId());
 
         //如果启用了了特殊设置,并且无特殊设置时结束考试 配置 设置为true..且实际未设置特殊设置则不允许考试
         ExamPropertyCacheBean limitedIfNoSpecialSettings = ExamCacheTransferHelper.getDefaultCachedExamProperty(examId,
@@ -234,7 +244,7 @@ public class OfflineExamServiceImpl implements OfflineExamService {
 
     @Override
     @Transactional
-    public void submitPaper(Long examRecordDataId, File tempFile) throws Exception {
+    public void submitPaper(Long examRecordDataId, File tempFile, String fileType) throws Exception {
         SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
         Boolean stuClientLoginLimitBoolean = false;
         if (stuClientLoginLimit.getHasValue()) {
@@ -264,6 +274,10 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         tempFile.delete();
 
         examRecordForMarkingService.saveOffLineExamRecordForMarking(bean, fileNewName, pi.getRelativePath());
+        if (!StringUtils.isNullOrEmpty(fileType)) {
+            fileType=fileType.toLowerCase();
+        }
+        saveExamRecordFileAnswer(examRecordDataId, fileName,fileNewName, pi.getRelativePath(), fileType, fileSuffix);
 
         //更新考试记录状态
         examRecordDataEntity.setExamRecordStatus(ExamRecordStatus.EXAM_END);
@@ -271,6 +285,107 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         examRecordDataRepo.save(examRecordDataEntity);
     }
 
+    /**
+     * 上传作答
+     *
+     * @param examRecordDataId 考试记录id
+     * @param tempFiles        文件集合
+     * @param fileType         文件类型
+     */
+    @Override
+    @Transactional
+    public void batchSubmitPaper(Long examRecordDataId, List<File> tempFiles, String fileType) throws Exception {
+        {
+            SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
+            Boolean stuClientLoginLimitBoolean = false;
+            if (stuClientLoginLimit.getHasValue()) {
+                stuClientLoginLimitBoolean = Boolean.valueOf(stuClientLoginLimit.getValue().toString());
+            }
+            if (stuClientLoginLimitBoolean) {
+                throw new StatusException("4001", "系统维护中... ...");
+            }
+            ExamRecordDataEntity examRecordDataEntity = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+            if (examRecordDataEntity == null) {
+                return;
+            }
+            ExamRecordDataBean bean = of(examRecordDataEntity);
+            examRecordForMarkingService.saveOffLineExamRecordForMarking(bean);
+
+            //删除原文件
+            List<ExamRecordFileAnswerEntity> fileAnswerList = examRecordFileAnswerRepo.findByExamRecordDataId(examRecordDataId);
+            if (null != fileAnswerList && !fileAnswerList.isEmpty()) {
+                for (ExamRecordFileAnswerEntity erfa : fileAnswerList) {
+                    examRecordFileAnswerRepo.deleteById(erfa.getId());
+                    FileStorageUtil.deleteFile(erfa.getFileUrl());
+                }
+            }
+
+            //保存新上传的文件
+            for (File tempFile : tempFiles) {
+                String fileName = tempFile.getName();
+                String fileSuffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()).toLowerCase();
+                //上传文件至又拍云
+                ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+                String fileNewName = createOfflineFileName(bean) + "." + fileSuffix;
+                String relativePath = upyunUploadUrl + examRecordData.getExamId() + "/" + fileNewName;
+
+                //通用存储
+                FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
+                env.setRootOrgId(String.valueOf(bean.getRootOrgId()));
+                env.setRelativePath(relativePath);
+                YunPathInfo pi = FileStorageUtil.saveFile("offlineFile", env, tempFile, null);
+//                tempFile.delete();
+
+                //保存离线文件至数据库
+                ExamRecordFileAnswerEntity entity = new ExamRecordFileAnswerEntity();
+                entity.setExamRecordDataId(examRecordDataId);
+                entity.setOriginalFileName(fileName);
+                entity.setFileName(fileNewName);
+                entity.setFileUrl(pi.getRelativePath());
+                entity.setFileType(fileType.toLowerCase());
+                entity.setSuffix(fileSuffix);
+                examRecordFileAnswerRepo.save(entity);
+            }
+
+            //更新考试记录状态
+            examRecordDataEntity.setExamRecordStatus(ExamRecordStatus.EXAM_END);
+            examRecordDataEntity.setEndTime(new Date());//交卷(上传)时间
+            examRecordDataRepo.save(examRecordDataEntity);
+        }
+    }
+
+    /**
+     * 保存考试记录文件作答
+     *
+     * @param examRecordDataId
+     * @param originalFileName 原始文件名称
+     * @param fileName 新文件名称
+     * @param relativePath
+     * @param fileType
+     * @param fileSuffix
+     */
+    private void saveExamRecordFileAnswer(Long examRecordDataId,String originalFileName,
+                                          String fileName, String relativePath,
+                                          String fileType, String fileSuffix) {
+        //删除原文件
+        List<ExamRecordFileAnswerEntity> fileAnswerList = examRecordFileAnswerRepo.findByExamRecordDataId(examRecordDataId);
+        if (null != fileAnswerList && !fileAnswerList.isEmpty()) {
+            for (ExamRecordFileAnswerEntity erfa : fileAnswerList) {
+                examRecordFileAnswerRepo.deleteById(erfa.getId());
+                FileStorageUtil.deleteFile(erfa.getFileUrl());
+            }
+        }
+
+        ExamRecordFileAnswerEntity entity = new ExamRecordFileAnswerEntity();
+        entity.setExamRecordDataId(examRecordDataId);
+        entity.setOriginalFileName(originalFileName);
+        entity.setFileName(fileName);
+        entity.setFileUrl(relativePath);
+        entity.setFileType(fileType);
+        entity.setSuffix(fileSuffix);
+        examRecordFileAnswerRepo.save(entity);
+    }
+
 
     private String createOfflineFileName(ExamRecordDataBean examRecordData) {
         long currentTime = System.currentTimeMillis();
@@ -278,7 +393,7 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         long orgId = examRecordData.getOrgId();
         OrgCacheBean orgBean = gainBaseDataService.getOrgBean(orgId);
         long courseId = examRecordData.getCourseId();
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
         return orgBean.getCode() + "_" +
                 examRecordData.getStudentCode() + "_" +
                 examRecordData.getStudentName() + "_" +
@@ -355,6 +470,9 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         b.setExtraNum(et.getExtraNum());
         b.setGrade(et.getGrade());
         b.setEnable(et.getEnable());
+        b.setExamStageId(et.getExamStageId());
+        b.setExamStageOrder(et.getExamStageOrder());
+
         return b;
     }
 }

+ 10 - 7
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/PracticeServiceImpl.java

@@ -15,10 +15,12 @@ import cn.com.qmth.examcloud.core.oe.admin.service.bean.PaperStructInfo;
 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.core.oe.admin.service.others.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
 import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
 import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionGroup;
+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.web.helpers.GlobalHelper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -66,20 +68,21 @@ public class PracticeServiceImpl implements PracticeService {
             return null;
         }
         List<PracticeCourseInfo> practiceCourseInfos = new ArrayList<PracticeCourseInfo>();
-        ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examId, studentId);
         for (ExamStudentEntity examStudent : examStudentList) {
+            ExamSettingsCacheBean examBean =
+                    ExamCacheTransferHelper.getCachedExam(examId, studentId,examStudent.getExamStageId());
             practiceCourseInfos.add(buildPracticeCourseInfo(examStudent, examBean));
         }
         return practiceCourseInfos;
     }
 
 
-    private PracticeCourseInfo buildPracticeCourseInfo(ExamStudentEntity examStudent, ExamBean examBean) {
+    private PracticeCourseInfo buildPracticeCourseInfo(ExamStudentEntity examStudent, ExamSettingsCacheBean examBean) {
         PracticeCourseInfo practiceCourseInfo = new PracticeCourseInfo();
         practiceCourseInfo.setExamStudentId(examStudent.getExamStudentId());
         practiceCourseInfo.setCourseCode(examStudent.getCourseCode());
 
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudent.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudent.getCourseId());
         practiceCourseInfo.setCourseName(courseBean.getName());
         practiceCourseInfo.setStudentCode(examStudent.getStudentCode());
         practiceCourseInfo.setStudentName(examStudent.getStudentName());
@@ -138,7 +141,7 @@ public class PracticeServiceImpl implements PracticeService {
         List<ExamRecordDataEntity> examRecordDataList = examRecordDataRepo.findByExamStudentId(examStudentId);
         List<PracticeRecordInfo> practiceRecords = new ArrayList<PracticeRecordInfo>();
         ExamStudentEntity examStudent = examStudentRepo.findByExamStudentId(examStudentId);
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudent.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudent.getCourseId());
         for (ExamRecordDataEntity examRecordData : examRecordDataList) {
             if (examRecordData.getExamRecordStatus() != ExamRecordStatus.EXAM_ING) {
                 practiceRecords.add(buildPracticeRecord(examRecordData, courseBean));
@@ -147,7 +150,7 @@ public class PracticeServiceImpl implements PracticeService {
         return practiceRecords;
     }
 
-    private PracticeRecordInfo buildPracticeRecord(ExamRecordDataEntity examRecordData, CourseBean courseBean) {
+    private PracticeRecordInfo buildPracticeRecord(ExamRecordDataEntity examRecordData, CourseCacheBean courseBean) {
         PracticeRecordInfo practiceRecordInfo = new PracticeRecordInfo();
 
         practiceRecordInfo.setId(examRecordData.getId());
@@ -251,7 +254,7 @@ public class PracticeServiceImpl implements PracticeService {
         }
         practiceDetailInfo.setPaperStructInfos(paperStructInfos);
         ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examRecordData.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examRecordData.getCourseId());
         practiceDetailInfo.setCourseCode(courseBean.getCode());
         practiceDetailInfo.setCourseName(courseBean.getName());
         practiceDetailInfo.setId(examRecordDataId);

+ 0 - 254
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/others/ExamCacheTransferHelper.java

@@ -1,254 +0,0 @@
-/*
- * *************************************************
- * Copyright (c) 2018 QMTH. All Rights Reserved.
- * Created by Deason on 2018-08-29 10:44:05.
- * *************************************************
- */
-
-package cn.com.qmth.examcloud.core.oe.admin.service.others;
-
-import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
-import cn.com.qmth.examcloud.commons.util.StringUtil;
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
-import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
-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.ExamPropertyCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.OrgPropertyCacheBean;
-import cn.com.qmth.examcloud.support.enums.ExamProperties;
-import io.swagger.annotations.ApiOperation;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import sun.reflect.generics.reflectiveObjects.NotImplementedException;
-
-/**
- * @Description 网考缓存实体转换服务
- * @Author lideyin
- * @Date 2019/8/13 16:00
- * @Version 1.0
- */
-public class ExamCacheTransferHelper {
-
-    /**
-     * 获取缓存的考试信息
-     *
-     * @param examId    考试id
-     * @param studentId 学生id
-     * @return
-     */
-    public static ExamBean getCachedExam(Long examId, Long studentId) {
-        ExamSettingsCacheBean examCacheBean = CacheHelper.getExamSettings(examId);
-        //默认取考试中的通用设置
-        ExamBean examBean = copyExamBeanFrom(examCacheBean);
-
-        //是否开启特殊设置
-        Boolean specialSettingsEnabled = examCacheBean.getSpecialSettingsEnabled();
-        if (specialSettingsEnabled == true) {
-            ExamSpecialSettingsType specialSettingsType = examCacheBean.getSpecialSettingsType();
-
-            //开启特殊设置却未进行任何设置则直接返回通用设置
-            if (null == specialSettingsType) {
-                return examBean;
-            }
-
-            switch (specialSettingsType) {
-                case ORG_BASED:
-                    //需求调整,所有的组织机构取学生表所关联的orgId
-                    Long orgId = CacheHelper.getStudent(studentId).getOrgId();
-                    initOrgSpecialSettings(examId, orgId, examBean);
-                    break;
-                case STUDENT_BASED:
-                    //初始化学生的特殊化设置
-                    initStudentSpecialSettings(examId, studentId, examBean);
-                    break;
-                case COURSE_BASED:
-                    //暂无此需求
-                    throw new NotImplementedException();
-            }
-        }
-        return examBean;
-    }
-
-    /**
-     * 获取默认(即非个性化)的考试信息
-     * <b>注意:只有非个性化的信息才可调用此方法</b>
-     *
-     * @param examId 考试id
-     * @return
-     */
-    public static ExamBean getDefaultCachedExam(Long examId) {
-        return copyExamBeanFrom(CacheHelper.getExamSettings(examId));
-    }
-
-    /**
-     * 获取考试的属性
-     *
-     * @param examId
-     * @param studentId
-     * @param propKey
-     * @return
-     */
-    public static ExamPropertyCacheBean getCachedExamProperty(Long examId, Long studentId, String propKey) {
-        ExamSettingsCacheBean examCacheBean = CacheHelper.getExamSettings(examId);
-        //默认取考试中的通用设置
-        ExamPropertyCacheBean examPropertyCacheBean = CacheHelper.getExamProperty(examId, propKey);
-
-        //是否开启特殊设置
-        Boolean specialSettingsEnabled = examCacheBean.getSpecialSettingsEnabled();
-        if (specialSettingsEnabled == true) {
-            ExamSpecialSettingsType specialSettingsType = examCacheBean.getSpecialSettingsType();
-
-            //开启特殊设置却未进行任何设置则直接返回通用设置
-            if (null == specialSettingsType) {
-                return examPropertyCacheBean;
-            }
-
-            ExamPropertyCacheBean specialExamProperty = null;
-            switch (specialSettingsType) {
-                case ORG_BASED:
-                    //需求调整,所有的组织机构取学生表所关联的orgId
-                    Long orgId = CacheHelper.getStudent(studentId).getOrgId();
-                    specialExamProperty = CacheHelper.getExamOrgProperty(examId, orgId, propKey);
-                    break;
-                case STUDENT_BASED:
-                    specialExamProperty = CacheHelper.getExamStudentProperty(examId, studentId, propKey);
-                    break;
-                case COURSE_BASED:
-                    //暂无此需求
-                    throw new NotImplementedException();
-            }
-            if (specialExamProperty.getHasValue()) {
-                return specialExamProperty;
-            }
-        }
-
-        return examPropertyCacheBean;
-    }
-
-    /**
-     * 获取默认(即非个性化)的考试属性信息
-     *
-     * @param examId
-     * @param propKey
-     * @return
-     */
-    public static ExamPropertyCacheBean getDefaultCachedExamProperty(Long examId, String propKey) {
-        return CacheHelper.getExamProperty(examId, propKey);
-    }
-
-    /**
-     * 获取课程
-     *
-     * @param courseId 课程id
-     * @return CourseBean
-     */
-    public static CourseBean getCachedCourse(Long courseId) {
-        CourseCacheBean courseCacheBean = CacheHelper.getCourse(courseId);
-        return copyCourseBeanFrom(courseCacheBean);
-    }
-
-    @ApiOperation(value = "是否可以微信作答", notes = "")
-    @GetMapping("weixinAnswerEnabled/{examId}")
-    public static Boolean weixinAnswerEnabled(@PathVariable Long examId) {
-
-        ExamSettingsCacheBean examSettings = CacheHelper.getExamSettings(examId);
-
-        OrgPropertyCacheBean orgConf = CacheHelper.getOrgProperty(examSettings.getRootOrgId(),
-                ExamProperties.WEIXIN_ANSWER_ENABLED.name());
-        ExamPropertyCacheBean examConf = CacheHelper.getExamProperty(examId,
-                ExamProperties.WEIXIN_ANSWER_ENABLED.name());
-
-        String orgValue = orgConf.getValue();
-        String examValue = examConf.getValue();
-
-        if (!orgConf.getHasValue()) {
-            return false;
-        }
-        if (StringUtils.isBlank(orgValue)) {
-            return false;
-        }
-
-        if (StringUtils.isBlank(examValue)) {
-            return false;
-        }
-
-        if (StringUtil.isTrue(orgValue) && StringUtil.isTrue(examValue)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * 初始化学生的特殊化设置
-     *
-     * @param examId
-     * @param studentId
-     * @param examBean
-     */
-    private static void initStudentSpecialSettings(Long examId, Long studentId, ExamBean examBean) {
-        ExamSettingsCacheBean examSpecialSetting =
-                CacheHelper.getExamStudentSettings(examId, studentId);
-        if (null != examSpecialSetting.getBeginTime()) {
-            examBean.setBeginTime(examSpecialSetting.getBeginTime());
-        }
-        if (null != examSpecialSetting.getEndTime()) {
-            examBean.setEndTime(examSpecialSetting.getEndTime());
-        }
-    }
-
-    /**
-     * 初始化组织机构的特殊化设置
-     *
-     * @param examId
-     * @param orgId
-     * @param examBean
-     */
-    private static void initOrgSpecialSettings(Long examId, Long orgId, ExamBean examBean) {
-        ExamSettingsCacheBean examSpecialSetting =
-                CacheHelper.getExamOrgSettings(examId, orgId);
-        if (null != examSpecialSetting.getBeginTime()) {
-            examBean.setBeginTime(examSpecialSetting.getBeginTime());
-        }
-        if (null != examSpecialSetting.getEndTime()) {
-            examBean.setEndTime(examSpecialSetting.getEndTime());
-        }
-
-        if (null != examSpecialSetting.getExamLimit()) {
-            examBean.setExamLimit(examSpecialSetting.getExamLimit());
-        }
-    }
-
-    private static ExamBean copyExamBeanFrom(ExamSettingsCacheBean examCacheBean) {
-        ExamBean resultBean = new ExamBean();
-        resultBean.setId(examCacheBean.getId());
-        resultBean.setBeginTime(examCacheBean.getBeginTime());
-        resultBean.setCode(examCacheBean.getCode());
-        resultBean.setDuration(examCacheBean.getDuration());
-        resultBean.setEnable(examCacheBean.getEnable());
-        resultBean.setEndTime(examCacheBean.getEndTime());
-        resultBean.setExamLimit(examCacheBean.getExamLimit());
-        resultBean.setExamTimes(examCacheBean.getExamTimes());
-        resultBean.setExamType(examCacheBean.getExamType());
-        resultBean.setName(examCacheBean.getName());
-        resultBean.setRemark(examCacheBean.getRemark());
-        resultBean.setRootOrgId(examCacheBean.getRootOrgId());
-        resultBean.setSpecialSettingsEnabled(examCacheBean.getSpecialSettingsEnabled());
-        resultBean.setSpecialSettingsType(examCacheBean.getSpecialSettingsType());
-        return resultBean;
-    }
-
-    private static CourseBean copyCourseBeanFrom(CourseCacheBean courseCacheBean) {
-        CourseBean resultBean = new CourseBean();
-        resultBean.setCode(courseCacheBean.getCode());
-        resultBean.setEnable(courseCacheBean.getEnable());
-        resultBean.setId(courseCacheBean.getId());
-        resultBean.setLevel(courseCacheBean.getLevel());
-        resultBean.setName(courseCacheBean.getName());
-        resultBean.setRootOrgId(courseCacheBean.getRootOrgId());
-
-        return resultBean;
-    }
-}

+ 136 - 137
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/pushscore/cugr/CugrExamScorePushServiceImpl.java

@@ -1,15 +1,14 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.pushscore.cugr;
 
 import cn.com.qmth.examcloud.commons.util.DateUtil;
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScorePushQueueRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScorePushQueue;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamScoreQueueStatus;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScorePushService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreQueueService;
 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.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpEntity;
@@ -34,147 +33,147 @@ import java.sql.SQLException;
 import java.util.List;
 
 /**
- * 
- * @author  	chenken
- * @date    	2018年10月18日 下午4:08:51
- * @company 	QMTH
+ * @author chenken
+ * @date 2018年10月18日 下午4:08:51
+ * @company QMTH
  * @description 地质大学(武汉)入学  orgCode:cugr.ecs.qmth.com.cn
  */
 @Service("cugrExamScorePushServiceImpl")
-public class CugrExamScorePushServiceImpl implements ExamScorePushService{
-	
-	private static final Logger log = LoggerFactory.getLogger(CugrExamScorePushServiceImpl.class);
-
-	@Value("${$cug_wh_push_score_key}")
-	private String pushScoreKey;
-	
-	@Autowired
-	private JdbcTemplate jdbcTemplate;
-	
-	@Autowired
-	private ExamScorePushQueueRepo examScorePushQueueRepo;
-	
-	@Autowired
-	private ExamScoreQueueService examScoreQueueService;
-	
+public class CugrExamScorePushServiceImpl implements ExamScorePushService {
+
+    private static final Logger log = LoggerFactory.getLogger(CugrExamScorePushServiceImpl.class);
+
+    @Value("${$cug_wh_push_score_key}")
+    private String pushScoreKey;
+
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
+
+    @Autowired
+    private ExamScorePushQueueRepo examScorePushQueueRepo;
+
+    @Autowired
+    private ExamScoreQueueService examScoreQueueService;
+
     @Autowired
     private GainBaseDataService gainBaseDataService;
-	
-	private static final String CUGR_URL = "http://202.114.196.58/busInfcCenter/setexamscore.infc";
-	
-	@Override
-	public void scorePush(ExamScorePushQueue examScorePushQueue) {
-		CloseableHttpResponse httpResponse = null;
-		CloseableHttpClient httpClient = HttpClients.createDefault();
-		String result = "";
-		try{
-			String url = getUrl(examScorePushQueue);
-			log.info("cugr_push_score_url:"+url);
-			HttpPost httpPost = new HttpPost(url);
-			httpResponse = httpClient.execute(httpPost);
-			HttpEntity responseEntity = httpResponse.getEntity();
-			BufferedReader reader = new BufferedReader(new InputStreamReader(responseEntity.getContent()));
-	        StringBuffer buffer = new StringBuffer();
-	        String str = "";
-	        while(StringUtils.isNoneBlank((str = reader.readLine()))) {
-	            buffer.append(str);
-	        }
-	        result = buffer.toString();
-	        JSONObject jsonObject = new JSONObject(result);
-	        if(jsonObject.has("describe")&&"success".equals(jsonObject.getString("describe"))){
-	        	examScoreQueueService.saveExamScorePushQueue(result, examScorePushQueue, ExamScoreQueueStatus.PROCESS_SUCCESS);
-	        }else{
-	        	examScoreQueueService.saveExamScorePushQueue(result, examScorePushQueue, ExamScoreQueueStatus.PROCESS_FAILED);
-	        }
-		}catch(Exception e){
-			log.error(examScorePushQueue.getId() + "成绩推送失败", e);
-			examScoreQueueService.saveExamScorePushQueue(e.getMessage(), examScorePushQueue, ExamScoreQueueStatus.PROCESS_FAILED);
-		}finally{
-			try {
-				if(httpClient !=null){
-					httpClient.close();
-				}
-			} catch (Exception e) {
-				e.printStackTrace();
-			}
+
+    private static final String CUGR_URL = "http://202.114.196.58/busInfcCenter/setexamscore.infc";
+
+    @Override
+    public void scorePush(ExamScorePushQueue examScorePushQueue) {
+        CloseableHttpResponse httpResponse = null;
+        CloseableHttpClient httpClient = HttpClients.createDefault();
+        String result = "";
+        try {
+            String url = getUrl(examScorePushQueue);
+            log.info("cugr_push_score_url:" + url);
+            HttpPost httpPost = new HttpPost(url);
+            httpResponse = httpClient.execute(httpPost);
+            HttpEntity responseEntity = httpResponse.getEntity();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(responseEntity.getContent()));
+            StringBuffer buffer = new StringBuffer();
+            String str = "";
+            while (StringUtils.isNoneBlank((str = reader.readLine()))) {
+                buffer.append(str);
+            }
+            result = buffer.toString();
+            JSONObject jsonObject = new JSONObject(result);
+            if (jsonObject.has("describe") && "success".equals(jsonObject.getString("describe"))) {
+                examScoreQueueService.saveExamScorePushQueue(result, examScorePushQueue, ExamScoreQueueStatus.PROCESS_SUCCESS);
+            } else {
+                examScoreQueueService.saveExamScorePushQueue(result, examScorePushQueue, ExamScoreQueueStatus.PROCESS_FAILED);
+            }
+        } catch (Exception e) {
+            log.error(examScorePushQueue.getId() + "成绩推送失败", e);
+            examScoreQueueService.saveExamScorePushQueue(e.getMessage(), examScorePushQueue, ExamScoreQueueStatus.PROCESS_FAILED);
+        } finally {
             try {
-            	if(httpResponse!=null){
-            		 httpResponse.close();
-            	}
-			} catch (Exception e) {
-				e.printStackTrace();
-			}
-		}
-	}
-	private String getUrl(ExamScorePushQueue examScorePushQueue){
-		long examRecordDataId = examScorePushQueue.getExamRecordDataId();
-		long scoreId = examScorePushQueue.getExamScoreId();
-		
-		String examRecordsql = "select identity_number,course_id from ec_oe_exam_record_data where id="+examRecordDataId;
-		String scoreSql = "select total_score from  ec_oe_exam_score where id="+scoreId;
-		
-		List<TmpExamRecord> examRecordList = jdbcTemplate.query(examRecordsql,new RowMapper<TmpExamRecord>(){
-			@Override
-			public TmpExamRecord mapRow(ResultSet rs, int rowNum) throws SQLException {
-				TmpExamRecord tmpExamRecord = new TmpExamRecord();
-				tmpExamRecord.setIdentityNumber(rs.getString("identity_number"));
-				tmpExamRecord.setCourseId(rs.getLong("course_id"));
-				return tmpExamRecord;
-			}
-		});
-		
-		TmpExamRecord examRecord = examRecordList.get(0);
-		
-		CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examRecord.getCourseId());
-
-		double totalScore = jdbcTemplate.queryForObject(scoreSql, Double.class);
-		return buildUrl(totalScore,courseBean.getCode(),examRecord.getIdentityNumber());
-	}
-
-	private String buildUrl(Double totalScore, String courseCode, String identityNumber) {
-		String now = DateUtil.now("yyyy-MM-dd%20HH:mm:ss");
-
-		StringBuilder sb = new StringBuilder();
-		sb.append(courseCode).append(",").append(identityNumber).append(",").append("'cugexma'").append(pushScoreKey);
-		String sign = null;
-		try {
-			sign = DigestUtils.md5Hex(sb.toString().getBytes("utf-8"));
-		} catch (UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
-		StringBuilder params = new StringBuilder();
-		params.append("new_date").append("=").append(now);
-		params.append("&").append("total_score").append("=").append(totalScore);
-		params.append("&").append("rec_exam_course").append("=").append(courseCode);
-		params.append("&").append("card_no").append("=").append(identityNumber);
-		params.append("&").append("md5sign").append("=").append(sign);
-
-		return CUGR_URL + "?" + params.toString();
-	}
-	
+                if (httpClient != null) {
+                    httpClient.close();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            try {
+                if (httpResponse != null) {
+                    httpResponse.close();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private String getUrl(ExamScorePushQueue examScorePushQueue) {
+        long examRecordDataId = examScorePushQueue.getExamRecordDataId();
+        long scoreId = examScorePushQueue.getExamScoreId();
+
+        String examRecordsql = "select identity_number,course_id from ec_oe_exam_record_data where id=" + examRecordDataId;
+        String scoreSql = "select total_score from  ec_oe_exam_score where id=" + scoreId;
+
+        List<TmpExamRecord> examRecordList = jdbcTemplate.query(examRecordsql, new RowMapper<TmpExamRecord>() {
+            @Override
+            public TmpExamRecord mapRow(ResultSet rs, int rowNum) throws SQLException {
+                TmpExamRecord tmpExamRecord = new TmpExamRecord();
+                tmpExamRecord.setIdentityNumber(rs.getString("identity_number"));
+                tmpExamRecord.setCourseId(rs.getLong("course_id"));
+                return tmpExamRecord;
+            }
+        });
+
+        TmpExamRecord examRecord = examRecordList.get(0);
+
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examRecord.getCourseId());
+
+        double totalScore = jdbcTemplate.queryForObject(scoreSql, Double.class);
+        return buildUrl(totalScore, courseBean.getCode(), examRecord.getIdentityNumber());
+    }
+
+    private String buildUrl(Double totalScore, String courseCode, String identityNumber) {
+        String now = DateUtil.now("yyyy-MM-dd%20HH:mm:ss");
+
+        StringBuilder sb = new StringBuilder();
+        sb.append(courseCode).append(",").append(identityNumber).append(",").append("'cugexma'").append(pushScoreKey);
+        String sign = null;
+        try {
+            sign = DigestUtils.md5Hex(sb.toString().getBytes("utf-8"));
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+        StringBuilder params = new StringBuilder();
+        params.append("new_date").append("=").append(now);
+        params.append("&").append("total_score").append("=").append(totalScore);
+        params.append("&").append("rec_exam_course").append("=").append(courseCode);
+        params.append("&").append("card_no").append("=").append(identityNumber);
+        params.append("&").append("md5sign").append("=").append(sign);
+
+        return CUGR_URL + "?" + params.toString();
+    }
+
 }
 
-class TmpExamRecord{
-	
-	private String identityNumber;
-	
-	private Long courseId;
-	
-	public String getIdentityNumber() {
-		return identityNumber;
-	}
-
-	public void setIdentityNumber(String identityNumber) {
-		this.identityNumber = identityNumber;
-	}
-
-	public Long getCourseId() {
-		return courseId;
-	}
-
-	public void setCourseId(Long courseId) {
-		this.courseId = courseId;
-	}
-	
+class TmpExamRecord {
+
+    private String identityNumber;
+
+    private Long courseId;
+
+    public String getIdentityNumber() {
+        return identityNumber;
+    }
+
+    public void setIdentityNumber(String identityNumber) {
+        this.identityNumber = identityNumber;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
 }
 

+ 24 - 25
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/pushscore/cup/CupExamScorePushServiceImpl.java

@@ -1,26 +1,6 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.pushscore.cup;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URI;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.http.HttpEntity;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClientBuilder;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
 import cn.com.qmth.examcloud.commons.util.JsonUtil;
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 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.dao.ExamScoreRepo;
@@ -32,10 +12,29 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamScoreQueueStatus;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScorePushService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreQueueService;
-import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.core.oe.admin.service.pushscore.bean.ExamScorePushInfo;
-import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+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.web.helpers.GlobalHelper;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URI;
 
 /**
  * @author chenken
@@ -166,15 +165,15 @@ public class CupExamScorePushServiceImpl implements ExamScorePushService {
                                                      ExamStudentEntity examStudentEntity) {
         ExamScorePushInfo examScorePushInfo = new ExamScorePushInfo();
         examScorePushInfo.setExamId(examStudentEntity.getExamId());
-        ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examStudentEntity.getExamId(),
-                examStudentEntity.getStudentId());
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getCachedExam(examStudentEntity.getExamId(),
+                examStudentEntity.getStudentId(), examStudentEntity.getExamStageId());
 
         examScorePushInfo.setExamName(examBean.getName());
         examScorePushInfo.setStudentName(examStudentEntity.getStudentName());
         examScorePushInfo.setStudentCode(examStudentEntity.getStudentCode());
         examScorePushInfo.setIdentityNumber(examStudentEntity.getIdentityNumber());
 
-        CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
 
         examScorePushInfo.setCourseName(courseBean.getName());
         examScorePushInfo.setCourseCode(examStudentEntity.getCourseCode());

+ 148 - 148
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/pushscore/swufe/SwufeExamScorePushServiceImpl.java

@@ -1,12 +1,21 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.pushscore.swufe;
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.CommonUtil;
+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.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScorePushQueue;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamScoreQueueStatus;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScorePushService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreQueueService;
+import cn.com.qmth.examcloud.core.oe.admin.service.pushscore.bean.ExamScorePushInfo;
+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.web.helpers.GlobalHelper;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpEntity;
 import org.apache.http.NameValuePair;
@@ -22,126 +31,115 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.CommonUtil;
-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.ExamRecordDataEntity;
-import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
-import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScorePushQueue;
-import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
-import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamScoreQueueStatus;
-import cn.com.qmth.examcloud.core.oe.admin.service.ExamScorePushService;
-import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreQueueService;
-import cn.com.qmth.examcloud.core.oe.admin.service.others.ExamCacheTransferHelper;
-import cn.com.qmth.examcloud.core.oe.admin.service.pushscore.bean.ExamScorePushInfo;
-import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
-import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 
 /**
- * 
- * @author  	chenken
- * @date    	2018年10月18日 下午4:09:47
- * @company 	QMTH
+ * @author chenken
+ * @date 2018年10月18日 下午4:09:47
+ * @company QMTH
  * @description 西南财经大学 orgCode:swufe.ecs.qmth.com.cn
  */
 @Service("swufeExamScorePushServiceImpl")
-public class SwufeExamScorePushServiceImpl implements ExamScorePushService{
-
-	private static final Logger log = LoggerFactory.getLogger(SwufeExamScorePushServiceImpl.class);
-	
-	@Autowired
-	private ExamScoreRepo examScoreRepo;
-	
-	@Autowired
-	private ExamRecordDataRepo examRecordDataRepo;
-
-	@Autowired
-	private ExamStudentRepo examStudentRepo;
-
-	@Autowired
-	private ExamScoreQueueService examScoreQueueService;
-	
+public class SwufeExamScorePushServiceImpl implements ExamScorePushService {
+
+    private static final Logger log = LoggerFactory.getLogger(SwufeExamScorePushServiceImpl.class);
+
+    @Autowired
+    private ExamScoreRepo examScoreRepo;
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamStudentRepo examStudentRepo;
+
+    @Autowired
+    private ExamScoreQueueService examScoreQueueService;
+
     private static final String pushScoreUrl = "http://www.swufe-online.com/netedu/qmth.do";
-    
-	@Override
-	public void scorePush(ExamScorePushQueue examScorePushQueue) {
-		log.info("开始推送西南财大成绩:"+examScorePushQueue.getId());
-		Long scoreId = examScorePushQueue.getExamScoreId();
-		ExamScoreEntity examScore = GlobalHelper.getEntity(examScoreRepo,scoreId,ExamScoreEntity.class);
-		ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo,examScore.getExamRecordDataId(),ExamRecordDataEntity.class);
-
-		ExamStudentEntity examStudent = examStudentRepo.findByExamStudentId(examRecordData.getExamStudentId());
-		if(examScore == null || examRecordData == null || examStudent == null){
-			return;
-		}
-		ExamScorePushInfo examScorePushInfo = buildexamScorePushInfo(examScore,examRecordData,examStudent);
-		
-		if (examScorePushInfo != null) {
-			 CloseableHttpResponse httpResponse = null;
-			 CloseableHttpClient httpClient = null;
-			 try {
-	            httpClient = HttpClients.createDefault();
-	            HttpPost httpPost = new HttpPost(pushScoreUrl);
-	            List<NameValuePair> params = buildParamsData(examScorePushInfo);
-	            httpPost.setEntity(new UrlEncodedFormEntity(params,"UTF-8"));
-	            httpResponse = httpClient.execute(httpPost);
-	            String result = "";
-	            HttpEntity responseEntity = httpResponse.getEntity();
-	            int statusCode= httpResponse.getStatusLine().getStatusCode();
-	            if(statusCode == 200){
-	                BufferedReader reader = new BufferedReader(new InputStreamReader(responseEntity.getContent()));
-	                StringBuffer buffer = new StringBuffer();
-	                String str = "";
-	                while(StringUtils.isNoneBlank((str = reader.readLine()))) {
-	                    buffer.append(str);
-	                }
-	                result = buffer.toString();
-	            }
-	            log.info("推送西南财大成绩:"+examScorePushQueue.getId()+"结果:"+result);
-	            JSONObject jsonObject = new JSONObject(result);
-	            if(jsonObject.getBoolean("success")){
-	            	examScoreQueueService.saveExamScorePushQueue(result, examScorePushQueue, ExamScoreQueueStatus.PROCESS_SUCCESS);
-	            }else{
-	            	examScoreQueueService.saveExamScorePushQueue(result, examScorePushQueue, ExamScoreQueueStatus.PROCESS_FAILED);
-	            }
-			 }catch (Exception e) {
-				log.error(examScorePushQueue.getId() + "成绩推送失败", e);
-				examScoreQueueService.saveExamScorePushQueue(e.getMessage(), examScorePushQueue, ExamScoreQueueStatus.PROCESS_FAILED);
-	        } finally {
-	            if (httpResponse != null) {
-	                try {
-	                    httpResponse.close();
-	                } catch (IOException e) {
-	                    e.printStackTrace();
-	                } finally {
-	                    httpResponse = null;
-	                }
-	            }
-	            if (httpClient != null) {
-	                try {
-	                    httpClient.close();
-	                } catch (IOException e) {
-	                    e.printStackTrace();
-	                } finally {
-	                    httpClient = null;
-	                }
-	            }
-		    }
+
+    @Override
+    public void scorePush(ExamScorePushQueue examScorePushQueue) {
+        log.info("开始推送西南财大成绩:" + examScorePushQueue.getId());
+        Long scoreId = examScorePushQueue.getExamScoreId();
+        ExamScoreEntity examScore = GlobalHelper.getEntity(examScoreRepo, scoreId, ExamScoreEntity.class);
+        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examScore.getExamRecordDataId(), ExamRecordDataEntity.class);
+
+        ExamStudentEntity examStudent = examStudentRepo.findByExamStudentId(examRecordData.getExamStudentId());
+        if (examScore == null || examRecordData == null || examStudent == null) {
+            return;
         }
-	}
-
-	private List<NameValuePair> buildParamsData(ExamScorePushInfo examScorePushInfo){
-		Map<String,Object> map = CommonUtil.getKeyAndValue(examScorePushInfo);
-        map.put("op","pushScore");
-        map.put("x","xczx_white_list");
-        map.put("LoginName","qmth");
-        map.put("PassWord","qmth123!");
-        
+        ExamScorePushInfo examScorePushInfo = buildexamScorePushInfo(examScore, examRecordData, examStudent);
+
+        if (examScorePushInfo != null) {
+            CloseableHttpResponse httpResponse = null;
+            CloseableHttpClient httpClient = null;
+            try {
+                httpClient = HttpClients.createDefault();
+                HttpPost httpPost = new HttpPost(pushScoreUrl);
+                List<NameValuePair> params = buildParamsData(examScorePushInfo);
+                httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
+                httpResponse = httpClient.execute(httpPost);
+                String result = "";
+                HttpEntity responseEntity = httpResponse.getEntity();
+                int statusCode = httpResponse.getStatusLine().getStatusCode();
+                if (statusCode == 200) {
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(responseEntity.getContent()));
+                    StringBuffer buffer = new StringBuffer();
+                    String str = "";
+                    while (StringUtils.isNoneBlank((str = reader.readLine()))) {
+                        buffer.append(str);
+                    }
+                    result = buffer.toString();
+                }
+                log.info("推送西南财大成绩:" + examScorePushQueue.getId() + "结果:" + result);
+                JSONObject jsonObject = new JSONObject(result);
+                if (jsonObject.getBoolean("success")) {
+                    examScoreQueueService.saveExamScorePushQueue(result, examScorePushQueue, ExamScoreQueueStatus.PROCESS_SUCCESS);
+                } else {
+                    examScoreQueueService.saveExamScorePushQueue(result, examScorePushQueue, ExamScoreQueueStatus.PROCESS_FAILED);
+                }
+            } catch (Exception e) {
+                log.error(examScorePushQueue.getId() + "成绩推送失败", e);
+                examScoreQueueService.saveExamScorePushQueue(e.getMessage(), examScorePushQueue, ExamScoreQueueStatus.PROCESS_FAILED);
+            } finally {
+                if (httpResponse != null) {
+                    try {
+                        httpResponse.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    } finally {
+                        httpResponse = null;
+                    }
+                }
+                if (httpClient != null) {
+                    try {
+                        httpClient.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    } finally {
+                        httpClient = null;
+                    }
+                }
+            }
+        }
+    }
+
+    private List<NameValuePair> buildParamsData(ExamScorePushInfo examScorePushInfo) {
+        Map<String, Object> map = CommonUtil.getKeyAndValue(examScorePushInfo);
+        map.put("op", "pushScore");
+        map.put("x", "xczx_white_list");
+        map.put("LoginName", "qmth");
+        map.put("PassWord", "qmth123!");
+
         //是否违纪
         boolean isIllegality = (boolean) map.get("isIllegality");
-        map.put("isBreachThePrinciple", isIllegality?1:0);
+        map.put("isBreachThePrinciple", isIllegality ? 1 : 0);
         map.put("successRate", map.get("succPercent"));
         //以下属性西南财大接口不需要
         map.remove("succPercent");
@@ -150,38 +148,40 @@ public class SwufeExamScorePushServiceImpl implements ExamScorePushService{
         map.remove("studentRemark");
         map.remove("isMissExam");
         map.remove("serialVersionUID");
-        
+
         List<NameValuePair> params = new ArrayList<NameValuePair>();
-        for(String key:map.keySet()){
-        	params.add(new BasicNameValuePair(key,map.get(key)+""));
+        for (String key : map.keySet()) {
+            params.add(new BasicNameValuePair(key, map.get(key) + ""));
         }
-        
+
         return params;
-	}
-
-	private ExamScorePushInfo buildexamScorePushInfo(ExamScoreEntity examScore,ExamRecordDataEntity examRecordDataEntity, ExamStudentEntity examStudentEntity) {
-		ExamScorePushInfo examScorePushInfo = new ExamScorePushInfo();
-		examScorePushInfo.setExamId(examStudentEntity.getExamId());
-
-		ExamBean examBean = ExamCacheTransferHelper.getCachedExam(examStudentEntity.getExamId(),examStudentEntity.getStudentId());
-		examScorePushInfo.setExamName(examBean.getName());
-		
-		examScorePushInfo.setStudentName(examStudentEntity.getStudentName());
-		examScorePushInfo.setStudentCode(examStudentEntity.getStudentCode());
-		examScorePushInfo.setIdentityNumber(examStudentEntity.getIdentityNumber());
-
-		CourseBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
-		examScorePushInfo.setCourseName(courseBean.getName());
-		examScorePushInfo.setCourseCode(examStudentEntity.getCourseCode());
-		examScorePushInfo.setStartTime(examRecordDataEntity.getStartTime());
-		examScorePushInfo.setEndTime(examRecordDataEntity.getEndTime());
-		examScorePushInfo.setSuccPercent(examRecordDataEntity.getFaceSuccessPercent());
-		examScorePushInfo.setIsIllegality(examRecordDataEntity.getIsIllegality());
-		examScorePushInfo.setScoreId(examScore.getId());
-		examScorePushInfo.setTotalScore(examScore.getTotalScore());
-		examScorePushInfo.setObjectiveScore(examScore.getObjectiveScore());
-		examScorePushInfo.setSubjectiveScore(examScore.getSubjectiveScore());
-		return examScorePushInfo;
-	}
+    }
+
+    private ExamScorePushInfo buildexamScorePushInfo(ExamScoreEntity examScore, ExamRecordDataEntity examRecordDataEntity, ExamStudentEntity examStudentEntity) {
+        ExamScorePushInfo examScorePushInfo = new ExamScorePushInfo();
+        examScorePushInfo.setExamId(examStudentEntity.getExamId());
+
+        ExamSettingsCacheBean examBean =
+                ExamCacheTransferHelper.getCachedExam(examStudentEntity.getExamId(),
+                        examStudentEntity.getStudentId(), examRecordDataEntity.getExamStageId());
+        examScorePushInfo.setExamName(examBean.getName());
+
+        examScorePushInfo.setStudentName(examStudentEntity.getStudentName());
+        examScorePushInfo.setStudentCode(examStudentEntity.getStudentCode());
+        examScorePushInfo.setIdentityNumber(examStudentEntity.getIdentityNumber());
+
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentEntity.getCourseId());
+        examScorePushInfo.setCourseName(courseBean.getName());
+        examScorePushInfo.setCourseCode(examStudentEntity.getCourseCode());
+        examScorePushInfo.setStartTime(examRecordDataEntity.getStartTime());
+        examScorePushInfo.setEndTime(examRecordDataEntity.getEndTime());
+        examScorePushInfo.setSuccPercent(examRecordDataEntity.getFaceSuccessPercent());
+        examScorePushInfo.setIsIllegality(examRecordDataEntity.getIsIllegality());
+        examScorePushInfo.setScoreId(examScore.getId());
+        examScorePushInfo.setTotalScore(examScore.getTotalScore());
+        examScorePushInfo.setObjectiveScore(examScore.getObjectiveScore());
+        examScorePushInfo.setSubjectiveScore(examScore.getSubjectiveScore());
+        return examScorePushInfo;
+    }
 
 }

+ 2 - 0
examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/OEAdminApp.java

@@ -12,6 +12,7 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
 import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
 import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
+import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 import org.springframework.web.multipart.MultipartResolver;
 import org.springframework.web.multipart.commons.CommonsMultipartResolver;
@@ -22,6 +23,7 @@ import cn.com.qmth.examcloud.web.bootstrap.AppBootstrap;
 import cn.com.qmth.examcloud.web.jpa.DataIntegrityViolationTransverter;
 import cn.com.qmth.examcloud.web.support.SpringContextHolder;
 
+@EnableAsync
 @SpringBootApplication
 @Configuration
 @EnableJpaAuditing

+ 15 - 7
examcloud-core-oe-admin-starter/src/main/resources/aliyun.xml

@@ -1,11 +1,19 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <sites>
 
- 	<site>
-		<id>offlineFile</id>
-		<name>离线文件上传地址</name>
-		<aliyunId>1</aliyunId>
-		<maxSize>50M</maxSize>
-		<path>/${relativePath}</path>
-	</site>
+    <site>
+        <id>offlineFile</id>
+        <name>离线文件上传地址</name>
+        <aliyunId>1</aliyunId>
+        <maxSize>50M</maxSize>
+        <path>/${relativePath}</path>
+    </site>
+
+    <site>
+        <id>task_export</id>
+        <name>导出任务文件上传地址</name>
+        <aliyunId>1</aliyunId>
+        <maxSize>50M</maxSize>
+        <path>/${relativePath}</path>
+    </site>
 </sites>

+ 2 - 2
examcloud-core-oe-admin-starter/src/main/resources/application.properties

@@ -1,6 +1,6 @@
 spring.profiles.active=dev
+
 examcloud.startup.startupCode=2000
-#examcloud.startup.configCenterHost=192.168.10.125
-examcloud.startup.configCenterHost=localhost
+examcloud.startup.configCenterHost=192.168.10.39
 examcloud.startup.configCenterPort=9999
 examcloud.startup.appCode=OE

+ 77 - 62
examcloud-core-oe-admin-starter/src/main/resources/log4j2.xml

@@ -1,71 +1,86 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Configuration status="WARN" monitorInterval="30">
 
-	<Properties>
-		<Property name="commonLevel" value="${sys:log.commonLevel}" />
-	</Properties>
+    <Properties>
+        <Property name="commonLevel" value="${sys:log.commonLevel}"/>
+        <Property name="logPattern">
+            %d{yyyy-MM-dd HH:mm:ss.SSS} | %clr{%level} | %X{TRACE_ID} %X{CALLER} | %clr{%c{1.1}:%L}{cyan} | %m%n
+        </Property>
+    </Properties>
 
-	<Appenders>
-		<!-- 控制台 日志 -->
-		<Console name="Console" target="SYSTEM_OUT">
-			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m | %l%n" />
-		</Console>
-		<!-- debug 日志 -->
-		<RollingFile name="DEBUG_APPENDER" fileName="./logs/debug/debug.log"
-			filePattern="./logs/debug/debug-%d{yyyy.MM.dd.HH}-%i.log">
-			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m | %l%n" />
-			<Policies>
-				<TimeBasedTriggeringPolicy interval="1" />
-				<SizeBasedTriggeringPolicy size="100MB" />
-			</Policies>
-			<DefaultRolloverStrategy max="10000">
-				<Delete basePath="./logs/debug" maxDepth="1">
-					<IfFileName glob="debug-*.log">
-						<IfAccumulatedFileSize exceeds="2 GB" />
-					</IfFileName>
-				</Delete>
-			</DefaultRolloverStrategy>
-		</RollingFile>
-		<!-- 接口日志 -->
-		<RollingFile name="INTERFACE_APPENDER" fileName="./logs/interface/interface.log"
-			filePattern="./logs/interface/interface-%d{yyyy.MM.dd.HH}-%i.log">
-			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m%n" />
-			<Policies>
-				<TimeBasedTriggeringPolicy interval="1" />
-				<SizeBasedTriggeringPolicy size="100MB" />
-			</Policies>
-			<DefaultRolloverStrategy max="10000">
-				<Delete basePath="./logs/interface" maxDepth="1">
-					<IfFileName glob="interface-*.log">
-						<IfAccumulatedFileSize exceeds="10 GB" />
-					</IfFileName>
-				</Delete>
-			</DefaultRolloverStrategy>
-		</RollingFile>
-	</Appenders>
+    <Appenders>
+        <!-- 控制台 日志 -->
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout pattern="${logPattern}" charset="UTF-8"/>
+        </Console>
 
-	<Loggers>
-		<Logger name="cn.com.qmth" level="${commonLevel}" additivity="false">
-			<AppenderRef ref="DEBUG_APPENDER" />
-			<AppenderRef ref="Console" />
-		</Logger>
+        <!-- debug 日志 -->
+        <RollingFile name="DEBUG_APPENDER"
+                     fileName="./logs/debug/debug.log"
+                     filePattern="./logs/debug/debug-%d{yyyy.MM.dd.HH}-%i.log">
+            <PatternLayout pattern="${logPattern}" charset="UTF-8"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="false"/>
+                <SizeBasedTriggeringPolicy size="100 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="1000">
+                <Delete basePath="./logs/debug" maxDepth="1">
+                    <IfFileName glob="debug-*.log">
+                        <IfAccumulatedFileSize exceeds="2 GB"/>
+                    </IfFileName>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
 
-		<Logger name="INTERFACE_LOGGER" level="INFO" additivity="false">
-			<AppenderRef ref="INTERFACE_APPENDER" />
-			<AppenderRef ref="Console" />
-		</Logger>
+        <!-- 接口日志 -->
+        <RollingFile name="INTERFACE_APPENDER" fileName="./logs/interface/interface.log"
+                     filePattern="./logs/interface/interface-%d{yyyy.MM.dd.HH}-%i.log">
+            <PatternLayout pattern="${logPattern}" charset="UTF-8"/>
+            <Policies>
+                <TimeBasedTriggeringPolicy interval="1" modulate="false"/>
+                <SizeBasedTriggeringPolicy size="100 MB"/>
+            </Policies>
+            <DefaultRolloverStrategy max="1000">
+                <Delete basePath="./logs/interface" maxDepth="1">
+                    <IfFileName glob="interface-*.log">
+                        <IfAccumulatedFileSize exceeds="10 GB"/>
+                    </IfFileName>
+                </Delete>
+            </DefaultRolloverStrategy>
+        </RollingFile>
+    </Appenders>
 
-		<Logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="${commonLevel}" />
-		<Logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="${commonLevel}" />
-		<Logger name="org.hibernate.SQL" level="${commonLevel}" />
-		<Logger name="org.hibernate.type" level="${commonLevel}" />
-		<Logger name="org.hibernate.engine.QueryParameters" level="${commonLevel}" />
-		<Logger name="org.hibernate.engine.query.HQLQueryPlan" level="${commonLevel}" />
+    <Loggers>
+        <logger name="springfox.documentation" level="ERROR"/>
+        <logger name="org.springframework" level="ERROR"/>
+        <logger name="org.hibernate" level="ERROR"/>
+        <logger name="org.apache" level="ERROR"/>
+        <logger name="org.quartz" level="ERROR"/>
+        <logger name="org.docx4j" level="ERROR"/>
+        <logger name="cn.afterturn" level="ERROR"/>
+        <logger name="com.netflix" level="ERROR"/>
+        <logger name="com.aliyun" level="ERROR"/>
+        <logger name="io.lettuce" level="ERROR"/>
+        <logger name="io.netty" level="ERROR"/>
 
-		<Root level="INFO">
-			<AppenderRef ref="Console" />
-			<AppenderRef ref="DEBUG_APPENDER" />
-		</Root>
-	</Loggers>
+        <!--<logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG"/>-->
+        <!--<logger name="org.springframework.data.mongodb" level="DEBUG"/>-->
+        <!--<logger name="org.springframework.data.redis" level="DEBUG"/>-->
 
-</Configuration>
+        <Logger name="cn.com.qmth" level="${commonLevel}" additivity="false">
+            <AppenderRef ref="DEBUG_APPENDER"/>
+            <AppenderRef ref="Console"/>
+        </Logger>
+
+        <Logger name="INTERFACE_LOGGER" level="${commonLevel}" additivity="false">
+            <AppenderRef ref="INTERFACE_APPENDER"/>
+            <AppenderRef ref="Console"/>
+        </Logger>
+
+        <Root level="${commonLevel}">
+            <AppenderRef ref="Console"/>
+            <AppenderRef ref="DEBUG_APPENDER"/>
+        </Root>
+    </Loggers>
+
+</Configuration>

+ 0 - 18
jenkins-dev.sh

@@ -1,18 +0,0 @@
-#!/bin/bash
-pwd
-
-rm -rf ~/project/examcloud/examcloud-core-oe-admin-distribution.zip
-rm -rf ~/project/examcloud/examcloud-core-oe-admin/lib/
-
-cp examcloud-core-oe-admin-starter/target/examcloud-core-oe-admin-distribution.zip ~/project/examcloud/
-
-cd  ~/project/examcloud/
-unzip -o examcloud-core-oe-admin-distribution.zip
-
-cd examcloud-core-oe-admin
-echo "--spring.profiles.active=dev --examcloud.startup.configCenterHost=localhost" > start.args
-echo "-server -Xms512m -Xmx512m  -XX:-UseGCOverheadLimit" > start.vmoptions
-
-bash stop.sh
-BUILD_ID=DONTKILLME
-bash start.sh 100

+ 0 - 18
jenkins-test.sh

@@ -1,18 +0,0 @@
-#!/bin/bash
-pwd
-
-rm -rf ~/project/examcloud/examcloud-core-oe-admin-distribution.zip
-rm -rf ~/project/examcloud/examcloud-core-oe-admin/lib/
-
-cp examcloud-core-oe-admin-starter/target/examcloud-core-oe-admin-distribution.zip ~/project/examcloud/
-
-cd  ~/project/examcloud/
-unzip -o examcloud-core-oe-admin-distribution.zip
-
-cd examcloud-core-oe-admin
-echo "--spring.profiles.active=test --examcloud.startup.configCenterHost=localhost" > start.args
-echo "-server -Xms512m -Xmx512m  -XX:-UseGCOverheadLimit" > start.vmoptions
-
-bash stop.sh
-BUILD_ID=DONTKILLME
-bash start.sh 100