소스 검색

merge from release_v4.0.2

deason 4 년 전
부모
커밋
df923f1a5f
73개의 변경된 파일2263개의 추가작업 그리고 1048개의 파일을 삭제
  1. 69 15
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamAuditController.java
  2. 34 18
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordController.java
  3. 23 10
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamScoreController.java
  4. 66 14
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamStudentController.java
  5. 65 23
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/OfflineExamController.java
  6. 22 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordCloudServiceProvider.java
  7. 23 6
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/SyncExamDataCloudServiceProvider.java
  8. 6 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/jpa/SqlWrapper.java
  9. 28 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/processor/HttpMethodProcessorImpl.java
  10. 25 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/BatchSetDataUtil.java
  11. 75 4
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/FileDisposeUtil.java
  12. 0 172
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/JsonMapper.java
  13. 0 61
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ColumnSetting.java
  14. 0 48
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelError.java
  15. 0 98
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelErrorType.java
  16. 0 34
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelProperty.java
  17. 4 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelReader.java
  18. 2 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelReaderHandle.java
  19. 3 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelUtils.java
  20. 8 6
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelWriter.java
  21. 5 29
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExportUtils.java
  22. 2 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureRepo.java
  23. 5 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordDataRepo.java
  24. 24 13
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExportTaskRepo.java
  25. 1 1
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamCaptureQueueEntity.java
  26. 14 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamRecordDataEntity.java
  27. 58 2
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExportTaskEntity.java
  28. 2 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/ExportTaskStatus.java
  29. 3 1
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/ExportTaskType.java
  30. 15 2
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/AsyncExportService.java
  31. 0 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamAuditService.java
  32. 2 2
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordDataService.java
  33. 14 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordService.java
  34. 2 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamScoreService.java
  35. 9 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamStudentService.java
  36. 9 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExportTaskService.java
  37. 33 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/OnHandExamInfo.java
  38. 35 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/PracticeCourseInfo.java
  39. 1 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditEntityConvert.java
  40. 12 2
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditExcel.java
  41. 13 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditInfo.java
  42. 2 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditMapper.java
  43. 10 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditQuery.java
  44. 0 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examcapture/ExamCaptureAuditInfo.java
  45. 18 13
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordEntityConvert.java
  46. 62 2
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordInfo.java
  47. 23 2
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordQuery.java
  48. 1 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamStudentQuestionScoreInfo.java
  49. 2 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examscore/ExamScoreInfo.java
  50. 1 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/CourseCompleteProgressExcel.java
  51. 1 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentExcel.java
  52. 12 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentQuery.java
  53. 1 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentUnFinishedExcel.java
  54. 1 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/OrgCompleteProgressExcel.java
  55. 91 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTask.java
  56. 11 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTaskInfo.java
  57. 22 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/exporttask/ExportTaskListResp.java
  58. 277 209
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/AsyncExportServiceImpl.java
  59. 11 6
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamAuditServiceImpl.java
  60. 4 3
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamCaptureServiceImpl.java
  61. 10 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordDataServiceImpl.java
  62. 409 22
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordServiceImpl.java
  63. 84 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreServiceImpl.java
  64. 191 36
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamStudentServiceImpl.java
  65. 70 33
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExportTaskServiceImpl.java
  66. 2 6
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/LocalCacheServiceImpl.java
  67. 11 1
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/OfflineExamServiceImpl.java
  68. 34 10
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/PracticeServiceImpl.java
  69. 39 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/util/AsyncExportConcurrentUtil.java
  70. 1 1
      examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/OEAdminApp.java
  71. 121 106
      examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/config/ExamCloudResourceManager.java
  72. 28 27
      examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/config/ExamCloudWebMvcConfigurer.java
  73. 1 1
      examcloud-core-oe-admin-starter/src/main/resources/aliyun.xml

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

@@ -7,19 +7,42 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.JsonMapper;
 import cn.com.qmth.examcloud.core.oe.admin.base.Constants;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.JsonMapper;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExportUtils;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.IllegallyTypeRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.IllegallyTypeEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.AuditStatus;
 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.bean.examaudit.ExamAuditEntityConvert;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.RedoAuditInfo;
+import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
@@ -28,15 +51,6 @@ import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
 
 /**
  * 考试记录审核相关接口
@@ -54,9 +68,9 @@ public class ExamAuditController extends ControllerSupport {
 
     @Autowired
     private ExamRecordDataRepo examRecordDataRepo;
-
+    
     @Autowired
-    private GainBaseDataService gainBaseDataService;
+    private IllegallyTypeRepo illegallyTypeRepo;
 
     @PostMapping("/list")
     @ApiOperation(value = "查询“监考已审”列表(分页)")
@@ -89,7 +103,7 @@ public class ExamAuditController extends ControllerSupport {
     @GetMapping("/discipline/list/export")
     @ApiOperation(value = "导出“违纪名单”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
     public void exportExamAuditUnPassList(@RequestParam String query, HttpServletResponse response) throws Exception {
-        ExamAuditQuery newQuery = new JsonMapper().fromJson(query, ExamAuditQuery.class);
+        ExamAuditQuery newQuery = new JsonMapper().parseJson(query, ExamAuditQuery.class);
         Check.isNull(newQuery, "请求参数不能为空!");
         Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
 
@@ -115,6 +129,22 @@ public class ExamAuditController extends ControllerSupport {
         }
         User user = getAccessUser();
         examAuditService.singleAudit(examRecordDataId, isPass, disciplineDetail, disciplineType, user, illegallyTypeId);
+        StringBuilder sb=new StringBuilder();
+        sb.append("考试记录ID:"+examRecordDataId);
+        if(isPass) {
+        	sb.append(" 审核状态:通过");
+        }else {
+        	sb.append(" 审核状态:不通过");
+        }
+        if(illegallyTypeId!=null) {
+        	IllegallyTypeEntity illegallyType =
+        		GlobalHelper.getEntity(illegallyTypeRepo, illegallyTypeId, IllegallyTypeEntity.class);
+        	sb.append(" 违纪类型:"+illegallyType.getName());
+        }
+        if(StringUtils.isNotEmpty(disciplineDetail)) {
+        	sb.append(" 详情描述:"+disciplineDetail);
+        }
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "监考待审-审核",sb.toString()));
     }
 
     @PostMapping(value = "/batch/audit")
@@ -128,6 +158,14 @@ public class ExamAuditController extends ControllerSupport {
         }
         User user = getAccessUser();
         examAuditService.batchAudit(examRecordDataIds, isPass, user);
+        StringBuilder sb=new StringBuilder();
+        if(isPass) {
+        	sb.append("审核状态:通过");
+        }else {
+        	sb.append("审核状态:不通过");
+        }
+        sb.append(" 审核条数:"+examRecordDataIds.size());
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "监考待审-批量审核",sb.toString()));
     }
 
     @PostMapping(value = "/redoAudit")
@@ -164,6 +202,22 @@ public class ExamAuditController extends ControllerSupport {
             }
         }
         examAuditService.redoAudit(redoAuditInfo, user);
+        StringBuilder sb=new StringBuilder();
+        sb.append("考试记录ID:"+redoAuditInfo.getExamRecordDataIds().get(0));
+        if(redoAuditInfo.getIsPass()) {
+        	sb.append(" 审核状态:通过");
+        }else {
+        	sb.append(" 审核状态:不通过");
+        }
+        if(redoAuditInfo.getIllegallyTypeId()!=null) {
+        	IllegallyTypeEntity illegallyType =
+        		GlobalHelper.getEntity(illegallyTypeRepo, redoAuditInfo.getIllegallyTypeId(), IllegallyTypeEntity.class);
+        	sb.append(" 违纪类型:"+illegallyType.getName());
+        }
+        if(StringUtils.isNotEmpty(redoAuditInfo.getDisciplineDetail())) {
+        	sb.append(" 详情描述:"+redoAuditInfo.getDisciplineDetail());
+        }
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试明细-重审",sb.toString()));
     }
 
 }

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

@@ -7,13 +7,29 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
+import java.util.ArrayList;
+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.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.util.JsonMapper;
 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.ExamRecordFileAnswerEntity;
 import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
@@ -23,21 +39,15 @@ import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordFil
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamStudentQuestionScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.util.AsyncExportConcurrentUtil;
+import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-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;
 
 /**
  * 考试记录相关接口
@@ -62,9 +72,6 @@ public class ExamRecordController extends ControllerSupport {
     @Autowired
     private ExamCaptureService examCaptureService;
 
-    @Autowired
-    private ExamRecordForMarkingRepo examRecordForMarkingRepo;
-
     @Autowired
     private ExamRecordFileAnswerRepo examRecordFileAnswerRepo;
 
@@ -89,6 +96,13 @@ public class ExamRecordController extends ControllerSupport {
         return examRecordWaitingAuditList;
     }
 
+    @PostMapping("/waiting/audit/next")
+    @ApiOperation(value = "查询“监考待审”下一条记录")
+    public Long getExamRecordWaitingAuditNextId(@RequestBody ExamRecordQuery query,
+                                                @RequestParam Long examRecordDataId, String next) {
+        return examRecordService.getExamRecordWaitingAuditNextId(query, examRecordDataId, next);
+    }
+
     @PostMapping("/detail/check")
     @ApiOperation(value = "查询“考试明细” 是否存在待审数据")
     public Long existsWarnExamRecordDetail(@RequestBody ExamRecordQuery query) {
@@ -145,7 +159,7 @@ public class ExamRecordController extends ControllerSupport {
     @GetMapping("/detail/list/export")
     @ApiOperation(value = "导出“考试明细”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
     public void exportExamRecordDetailList(@RequestParam String query, HttpServletResponse response) throws Exception {
-        ExamRecordQuery newQuery = new JsonMapper().fromJson(query, ExamRecordQuery.class);
+        ExamRecordQuery newQuery = new JsonMapper().parseJson(query, ExamRecordQuery.class);
         Check.isNull(newQuery, "请求参数不能为空!");
         Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
 
@@ -163,17 +177,19 @@ 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);
+    	User user=getAccessUser();
+        asyncExportService.exportExamRecordDetails(query,user);
+        AsyncExportConcurrentUtil.checkOrStartJob();
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试明细-导出","导出条件:"+query));
     }
 
     @PostMapping(value = "/refresh/capture/statistic")
     @ApiOperation(value = "监考待审-重新统计", notes = "根据人脸识别阀值重新计算数据")
     public ResponseEntity<String> refreshCaptureStatistic(@RequestParam Long examId) {
-        //todo
+        examRecordService.refreshCaptureStatistic(examId);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 

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

@@ -7,15 +7,32 @@
 
 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.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.JsonMapper;
 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.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;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ObjectiveScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.util.AsyncExportConcurrentUtil;
+import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
 import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
 import cn.com.qmth.examcloud.support.examing.ExamBoss;
@@ -28,12 +45,6 @@ import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.List;
 
 /**
  * 考试分数相关接口
@@ -70,19 +81,21 @@ public class ExamScoreController extends ControllerSupport {
     @GetMapping("/statistic/list/export")
     @ApiOperation(value = "导出“成绩统计”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
     public void exportExamRecordDetailList(@RequestParam String query, HttpServletResponse response) throws Exception {
-        ExamScoreQuery newQuery = new JsonMapper().fromJson(query, ExamScoreQuery.class);
+        ExamScoreQuery newQuery = new JsonMapper().parseJson(query, ExamScoreQuery.class);
         Check.isNull(newQuery, "请求参数不能为空!");
         Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
         List<ExamScoreInfo> examScoreList = examScoreService.exportExamScoreList(newQuery);
         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);
+    	User user=getAccessUser();
+        asyncExportService.exportExamScoreStatistics(query,user);
+        AsyncExportConcurrentUtil.checkOrStartJob();
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "成绩统计-导出","导出条件:"+query));
     }
 
     @ApiOperation(value = "根据examStudentId获取客观分信息")

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

@@ -7,13 +7,47 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.util.JsonMapper;
 import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.JsonMapper;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExportUtils;
+import cn.com.qmth.examcloud.core.oe.admin.dao.IllegallyTypeRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.IllegallyTypeEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
-import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.CourseCompleteProgressExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.CourseProgressInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentEntityConvert;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentFinishedStatistic;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentOrgStatistic;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentUnFinishedExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.OrgCompleteProgressExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.util.AsyncExportConcurrentUtil;
+import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
 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;
@@ -21,13 +55,6 @@ import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * 考生信息接口
@@ -41,7 +68,11 @@ import java.util.concurrent.ConcurrentHashMap;
 public class ExamStudentController extends ControllerSupport {
     @Autowired
     private ExamStudentService examStudentService;
-
+    @Autowired
+    private AsyncExportService asyncExportService;
+    
+    @Autowired
+    private IllegallyTypeRepo illegallyTypeRepo;
     /**
      * 查询课程本地缓存
      */
@@ -77,19 +108,28 @@ public class ExamStudentController extends ControllerSupport {
         examStudentListPage.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
         });
-
         return examStudentListPage;
     }
 
     @GetMapping("/examScheduling/list/export")
     @ApiOperation(value = "导出“考试进度详情”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
     public void exportExamStudentList(@RequestParam String query, HttpServletResponse response) throws Exception {
-        ExamStudentQuery newQuery = new JsonMapper().fromJson(query, ExamStudentQuery.class);
+        ExamStudentQuery newQuery = new JsonMapper().parseJson(query, ExamStudentQuery.class);
         Check.isNull(newQuery, "请求参数不能为空!");
         Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
         List<ExamStudentInfo> examStudentInfoList = examStudentService.getExamStudentInfoList(newQuery);
         List<ExamStudentExcel> list = ExamStudentEntityConvert.ofExcel(examStudentInfoList);
         ExportUtils.exportEXCEL("考试详情列表", ExamStudentExcel.class, list, response);
+//        ExcelExportUtil.exportExcel("考试详情列表", ExamStudentExcel.class, list, response);
+    }
+    
+    @GetMapping("/examScheduling/list/export/async")
+    @ApiOperation(value = "导出“考试进度详情”列表(异步)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
+    public void exportExamSchedulingAsync(@RequestParam String query) {
+    	User user = getAccessUser();
+        asyncExportService.exportExamScheduling(query,user);
+        AsyncExportConcurrentUtil.checkOrStartJob();
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试进度详情-导出","导出条件:"+query));
     }
 
     @PostMapping("/unfinished/list")
@@ -109,7 +149,7 @@ public class ExamStudentController extends ControllerSupport {
     @GetMapping("/unfinished/list/export")
     @ApiOperation(value = "导出“缺考登记”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
     public void exportExamStudentUnFinishedList(@RequestParam String query, HttpServletResponse response) throws Exception {
-        ExamStudentQuery newQuery = new JsonMapper().fromJson(query, ExamStudentQuery.class);
+        ExamStudentQuery newQuery = new JsonMapper().parseJson(query, ExamStudentQuery.class);
         Check.isNull(newQuery, "请求参数不能为空!");
         Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
         newQuery.setFinished(0);//未完成
@@ -139,6 +179,19 @@ public class ExamStudentController extends ControllerSupport {
             return;
         }
         examStudentService.setReexamine(examStudentId, reexamineType, reexamineDetail);
+        User user = getAccessUser();
+        StringBuilder sb=new StringBuilder();
+        sb.append("考生ID:"+examStudentId);
+        if(StringUtils.isNotEmpty(reexamineType)) {
+        	IllegallyTypeEntity it=illegallyTypeRepo.findByRootOrgIdAndCode(-1L, reexamineType);
+        	if(it!=null) {
+        		sb.append(" 重考理由:"+it.getName());
+        	}
+        }
+        if(StringUtils.isNotEmpty(reexamineDetail)) {
+        	sb.append(" 详情描述:"+reexamineDetail);
+        }
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "设置重考-设置重考",sb.toString()));
     }
 
     @PostMapping("/statistic/by/finished")
@@ -191,7 +244,6 @@ public class ExamStudentController extends ControllerSupport {
         if (examId == null) {
             return null;
         }
-        User user = getAccessUser();
         List<Long> courseIdList = examStudentService.findCoursesFromExamStudent(examId, examStageId, orgId);
         List<CourseCacheBean> courseBeanList = new ArrayList<CourseCacheBean>();
         for (Long courseId : courseIdList) {

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

@@ -1,5 +1,24 @@
 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.ArrayList;
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
+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.enums.ExamType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
@@ -9,6 +28,8 @@ 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.reports.commons.bean.AdminOperateReport;
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.config.SystemProperties;
@@ -17,15 +38,6 @@ 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;
 
 
 /**
@@ -44,6 +56,10 @@ public class OfflineExamController extends ControllerSupport {
 
     @Autowired
     private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamStudentService examStudentService;
+
     @Autowired
     SystemProperties systemConfig;
     /**
@@ -77,6 +93,12 @@ public class OfflineExamController extends ControllerSupport {
     @GetMapping("/startOfflineExam")
     public void startOfflineExam(@RequestParam long examStudentId) {
         Check.isNull(examStudentId, "examStudentId不能为空");
+
+        boolean isEnable = examStudentService.isEnableExamStudent(examStudentId);
+        if(!isEnable){
+            throw new StatusException("000500", "当前考生已禁用!");
+        }
+
         offlineExamService.startOfflineExam(examStudentId);
     }
 
@@ -89,6 +111,17 @@ public class OfflineExamController extends ControllerSupport {
                             @RequestParam long examRecordDataId) throws Exception {
         Check.isNull(file, "file不能为空");
         Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+
+        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+        if (examRecordData.getExamType() != ExamType.OFFLINE) {
+            throw new StatusException("OfflineExamController-submitPaper-002", "非离线考试");
+        }
+
+        boolean isEnable = examStudentService.isEnableExamStudent(examRecordData.getExamStudentId());
+        if(!isEnable){
+            throw new StatusException("000500", "当前考生已禁用!");
+        }
+
         String fileName = file.getOriginalFilename();
         int index = fileName.lastIndexOf(".");
         String fileSuffix = fileName.substring(index + 1, fileName.length()).toUpperCase();
@@ -98,10 +131,6 @@ public class OfflineExamController extends ControllerSupport {
             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();
@@ -121,6 +150,9 @@ public class OfflineExamController extends ControllerSupport {
         File f = getUploadFile(file);
 
         offlineExamService.submitPaper(examRecordDataId, f,fileSuffix);
+
+        User user=getAccessUser();
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试进度详情-上传作答","考试记录ID:"+examRecordDataId+" 考生ID:"+examRecordData.getExamStudentId()));
     }
 
     /**
@@ -143,6 +175,23 @@ public class OfflineExamController extends ControllerSupport {
             throw new StatusException("200001", "文件数量和文件摘要信息不匹配");
         }
 
+        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+        if (examRecordData.getExamType() != ExamType.OFFLINE) {
+            throw new StatusException("100003", "非离线考试");
+        }
+
+        boolean isEnable = examStudentService.isEnableExamStudent(examRecordData.getExamStudentId());
+        if(!isEnable){
+            throw new StatusException("000500", "当前考生已禁用!");
+        }
+
+        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", "当前考试设置不允许上传附件");
+        }
+
         List<File> resultFiles=new ArrayList<>();
         for (int i = 0; i < fileArray.length; i++) {
             MultipartFile file = fileArray[i];
@@ -161,16 +210,7 @@ public class OfflineExamController extends ControllerSupport {
                 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")) {
@@ -206,6 +246,8 @@ public class OfflineExamController extends ControllerSupport {
         for (File tempFile : resultFiles) {
             tempFile.delete();
         }
+        User user=getAccessUser();
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试进度详情-上传作答","考试记录ID:"+examRecordDataId+" 考生ID:"+examRecordData.getExamStudentId()));
     }
 
     private File getUploadFile(MultipartFile file) {

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

@@ -8,6 +8,7 @@ import cn.com.qmth.examcloud.core.oe.admin.api.response.*;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.dao.*;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataService;
 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.question.commons.core.question.AnswerType;
@@ -74,6 +75,10 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
 
     @Autowired
     private ExamRecordQuestionsService examRecordQuestionsService;
+    
+    @Autowired
+    private ExamRecordDataService examRecordDataService;
+    
 
     @Override
     @ApiOperation(value = "查询是否已经开考")
@@ -657,4 +662,21 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
         return studentAnswer;
     }
 
+    
+	@Override
+    @ApiOperation(value = "获取考试监考待审记录数")
+    @PostMapping("/getGetAwaitingAuditCount")
+	public GetAwaitingAuditCountResp getGetAwaitingAuditCount(@RequestBody GetAwaitingAuditCountReq req) {
+		if(req.getRootOrgId()==null) {
+			throw new StatusException("500", "RootOrgId不允许为空");
+		}
+		if(req.getExamId()==null) {
+			throw new StatusException("500", "ExamId不允许为空");
+		}
+		Long count=examRecordDataService.getGetAwaitingAuditCount(req.getRootOrgId(),req.getExamId());
+		GetAwaitingAuditCountResp res=new GetAwaitingAuditCountResp();
+		res.setCount(count);
+		return res;
+	}
+
 }

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

@@ -17,9 +17,12 @@ import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
 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.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
@@ -34,6 +37,8 @@ import org.springframework.web.bind.annotation.RestController;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @Description 同步考试相关数据接口
@@ -168,7 +173,6 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
 
         //同步抓拍照片结果(异步抓拍的数据)
         if (null != req.getExamCaptures()) {
-
             syncExamCapture(req.getExamCaptures(), realExamRecordDataId);
         }
 
@@ -204,6 +208,7 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         if (FaceBiopsyHelper.isFaceEnable(transitionExamRecordData.getRootOrgId(), transitionExamRecordData.getExamId(), transitionExamRecordData.getStudentId())) {
             //计算违纪自动审核结果(无人脸或活检失败)
             boolean isNoPhotoAndIllegality = (null == req.getExamCaptures() || req.getExamCaptures().isEmpty());//是否无照片
+
             saveAutoAudit(transitionExamRecordData, isNoPhotoAndIllegality, realExamRecordDataId);
         }
 
@@ -431,7 +436,8 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
     }
 
     private void saveAutoAudit(ExamRecordDataBean examRecordData, boolean isNoPhotoAndIllegality, Long realExamRecordDataId) {
-        boolean isAutoAudit = false;//是否已自动审核
+        //是否已自动审核
+        boolean isAutoAudit = false;
         //无照片违纪自动审核
         if (isNoPhotoAndIllegality) {
             examAuditService.saveExamAuditByNoPhoto(realExamRecordDataId);
@@ -439,8 +445,7 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         } else {
             //活体检测失败违纪自动审核
             if (null != examRecordData.getFaceVerifyResult()
-                    && IsSuccess.FAILED.name().equals(examRecordData.getFaceVerifyResult())
-                    && examRecordData.getIllegality()) {
+                    && IsSuccess.FAILED.name().equals(examRecordData.getFaceVerifyResult())) {
                 examAuditService.saveExamAuditByFaceVerifyFailed(realExamRecordDataId, examRecordData.getRootOrgId());
                 isAutoAudit = true;
             }
@@ -637,7 +642,13 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         entity.setFacelivenessResult(examSyncCapture.getFacelivenessResult());
         entity.setProcessTime(examSyncCapture.getProcessTime());
         entity.setHasVirtualCamera(examSyncCapture.getHasVirtualCamera());
-        entity.setCameraInfos(examSyncCapture.getCameraInfos());
+
+        String cameraInfos = examSyncCapture.getCameraInfos();
+        if(StringUtils.isNotBlank(cameraInfos) && cameraInfos.length()>=800){
+            throw new StatusException("500","虚拟摄像头信息超长!");
+        }
+
+        entity.setCameraInfos(cameraInfos);
         entity.setExtMsg(examSyncCapture.getExtMsg());
 
         return entity;
@@ -663,7 +674,13 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         entity.setFacelivenessResult(examCapture.getFacelivenessResult());
         entity.setProcessTime(examCapture.getProcessTime());
         entity.setHasVirtualCamera(examCapture.getHasVirtualCamera());
-        entity.setCameraInfos(examCapture.getCameraInfos());
+
+        String cameraInfos = examCapture.getCameraInfos();
+        if(StringUtils.isNotBlank(cameraInfos) && cameraInfos.length()>=800){
+            throw new StatusException("500","虚拟摄像头信息超长!");
+        }
+
+        entity.setCameraInfos(cameraInfos);
         entity.setExtMsg(examCapture.getExtMsg());
 
         return entity;

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

@@ -42,6 +42,7 @@ public class SqlWrapper {
     private static final String ORDER_BY = " ORDER BY ";
     private static final String ASC = " ASC ";
     private static final String DESC = " DESC ";
+    private static final String HAVING = " HAVING ";
 
     private StringBuilder sql = new StringBuilder();
 
@@ -116,6 +117,11 @@ public class SqlWrapper {
         return this;
     }
 
+    public SqlWrapper having() {
+        sql.append(HAVING);
+        return this;
+    }
+
     public SqlWrapper or() {
         sql.append(OR);
         return this;

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

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

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

@@ -0,0 +1,25 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils;
+
+import java.util.List;
+
+public abstract class BatchSetDataUtil<P> {
+	public final void setDataForBatch(List<P> dataList, int batchSize) {
+		if (dataList == null || dataList.size() == 0) {
+			return;
+		}
+		if (dataList.size() <= batchSize) {
+			setData(dataList);
+		} else {
+			int size = dataList.size();
+			int len = batchSize;
+			int count = (size + len - 1) / len;
+
+			for (int i = 0; i < count; i++) {
+				List<P> subList = dataList.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+				setData(subList);
+			}
+		}
+	}
+
+	public abstract void setData(List<P> dataList);
+}

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

@@ -252,8 +252,79 @@ public class FileDisposeUtil {
         }
     }
 	
-	public static void main(String[] args) {
-		System.out.println(System.getProperty("sun.jnu.encoding"));
-		System.out.println(System.getProperty("file.encoding"));
-	}
+    public static File createZip(String sourceFilePath, String targetFilePath) throws IOException {
+        OutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            File zipfile = new File(targetFilePath);
+            zipfile.deleteOnExit();
+            fos = new FileOutputStream(zipfile);
+            zos = new ZipOutputStream(fos);
+            // zos.setEncoding("utf-8"); // Solve linxu's mess
+            writeZip(new File(sourceFilePath), null, zos);
+            return zipfile;
+        } finally {
+            if (zos != null) {
+                zos.close();
+            }
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+    
+    private static void writeZip(File file, String parentPath, ZipOutputStream zos) throws IOException {
+        if (file.exists()) {
+            ZipEntry ze = null;
+            if (file.isDirectory()) {// Processing folder
+                if (parentPath == null) {
+                    parentPath = "";
+                } else {
+                    parentPath += file.getName() + File.separator;
+                }
+                File[] files = file.listFiles();
+                if (files != null) {
+                    for (File f : files) {
+                        writeZip(f, parentPath, zos);
+                    }
+                } else { // An empty directory creates the current directory
+                    try {
+                        ze = new ZipEntry(parentPath);
+                        // ze.setUnixMode(755);// Solve Linux mess file Settings
+                        // 644 directory Settings 755
+                        zos.putNextEntry(ze);
+
+                        zos.flush();
+                    } finally {
+                        if (zos != null) {
+                            zos.closeEntry();
+                        }
+                    }
+                }
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+                    ze = new ZipEntry(parentPath + file.getName());
+                    // ze.setUnixMode(644);// Solve Linux mess file Settings 644
+                    // directory Settings 755
+                    zos.putNextEntry(ze);
+                    byte[] content = new byte[1024];
+                    int len;
+                    while ((len = fis.read(content)) != -1) {
+                        zos.write(content, 0, len);
+                        zos.flush();
+                    }
+
+                } finally {
+                    if (fis != null) {
+                        fis.close();
+                    }
+                    if (zos != null) {
+                        zos.closeEntry();
+                    }
+                }
+            }
+        }
+    }
 }

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

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

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

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

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

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

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

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

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

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

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

@@ -14,6 +14,10 @@ import org.apache.poi.ss.usermodel.WorkbookFactory;
 import com.esotericsoftware.reflectasm.ConstructorAccess;
 import com.esotericsoftware.reflectasm.FieldAccess;
 
+import cn.com.qmth.examcloud.support.excel.ColumnSetting;
+import cn.com.qmth.examcloud.support.excel.ExcelError;
+import cn.com.qmth.examcloud.support.excel.ExcelErrorType;
+
 /**
  * 
  * @Description: 读取excel

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

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

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

@@ -6,6 +6,9 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import cn.com.qmth.examcloud.support.excel.ColumnSetting;
+import cn.com.qmth.examcloud.support.excel.ExcelProperty;
+
 /**
  * Created by zhengmin on 2016/8/17.
  */

+ 8 - 6
examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/excel/ExcelWriter.java

@@ -1,5 +1,12 @@
 package cn.com.qmth.examcloud.core.oe.admin.base.utils.excel;
 
+import java.awt.Color;
+import java.io.OutputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.List;
+
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.FillPatternType;
 import org.apache.poi.ss.usermodel.Row;
@@ -9,12 +16,7 @@ import org.apache.poi.xssf.usermodel.XSSFColor;
 import org.apache.poi.xssf.usermodel.XSSFRichTextString;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 
-import java.awt.Color;
-import java.io.OutputStream;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.List;
+import cn.com.qmth.examcloud.support.excel.ColumnSetting;
 
 public class ExcelWriter extends ExcelUtils {
 	

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

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

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

@@ -29,4 +29,6 @@ public interface ExamCaptureRepo extends JpaRepository<ExamCaptureEntity, Long>,
     public List<ExamCaptureEntity> findByExamRecordDataId(Long examRecordDataId);
     
     public ExamCaptureEntity findByExamRecordDataIdAndFileName(Long examRecordDataId,String fileName);
+
+	public List<ExamCaptureEntity> findByExamRecordDataIdIn(List<Long> ids);
 }

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

@@ -99,4 +99,9 @@ public interface ExamRecordDataRepo extends JpaRepository<ExamRecordDataEntity,
             "where exam_record_status in('EXAM_END','EXAM_OVERDUE') and is_illegality=0 and (is_warn=0 or is_warn=1  and is_audit=1) " +
             "and exam_id=?1 and id>=?2 order by id asc limit ?3", nativeQuery = true)
     List<ExamRecordDataEntity> findLimitedDataByExamIdAndIdMoreThan(Long examId, Long startExamRecordId, int rowCount);
+
+    @Query(value = "select record_data.* from ec_oe_exam_record_data record_data " +
+            "where record_data.exam_record_status in('EXAM_END','EXAM_OVERDUE') and record_data.exam_id=?1 " +
+            "AND record_data.is_audit=0 ", nativeQuery = true)
+    List<ExamRecordDataEntity> findDataByExamId(Long examId);
 }

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

@@ -5,8 +5,8 @@
 
 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 java.util.Date;
+
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Modifying;
@@ -15,20 +15,31 @@ import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.Date;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExportTaskEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
 
 @Repository
-public interface ExportTaskRepo extends JpaRepository<ExportTaskEntity, Long>,
-        JpaSpecificationExecutor<ExportTaskEntity> {
+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);
 
-    @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);
+	@Query(nativeQuery = true, value = "select * from ec_oe_export_task t where t.status='WAITING' ORDER BY t.id limit 1")
+	public ExportTaskEntity findExportTaskToDispose();
 
+	@Transactional
+	@Modifying
+	@Query("update ExportTaskEntity set status=:status, startTime=:startTime where id=:id")
+	int startExportTask(@Param("id") Long id, @Param("status") ExportTaskStatus status,
+			@Param("startTime") Date startTime);
 
+	@Transactional
+	@Modifying
+	@Query("update ExportTaskEntity set endTime=:endTime where id=:id")
+	int endExportTask(@Param("id") Long id, @Param("endTime") Date endTime);
 }

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

@@ -73,7 +73,7 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
     /**
      * 摄像头信息  json字符串数组
      */
-    @Column(name="camera_infos",length=800)
+    @Column(name = "camera_infos", length = 800)
     private String cameraInfos;
     
     /**

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

@@ -246,6 +246,12 @@ public class ExamRecordDataEntity extends JpaEntity {
      */
     private Integer switchScreenCount;
 
+    /**
+     * ip(忽略该字段持久化)
+     */
+    @Transient
+    private String ip;
+
     public Long getId() {
         return id;
     }
@@ -627,4 +633,12 @@ public class ExamRecordDataEntity extends JpaEntity {
     public void setSwitchScreenCount(Integer switchScreenCount) {
         this.switchScreenCount = switchScreenCount;
     }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
 }

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

@@ -1,11 +1,23 @@
 package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
 
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
 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.*;
-
 /**
  * 导出任务表
  */
@@ -66,6 +78,26 @@ public class ExportTaskEntity extends JpaEntity {
      */
     @Column
     private Long creator;
+    
+    /**
+     * 导出的参数
+     */
+    @Column
+    private String exportParam;
+    
+    /**
+     * 开始处理时间
+     */
+    @Column
+	@Temporal(TemporalType.TIMESTAMP)
+	private Date startTime;
+    
+    /**
+     * 结束处理时间
+     */
+    @Column
+	@Temporal(TemporalType.TIMESTAMP)
+	private Date endTime;
 
     public Long getId() {
         return id;
@@ -130,6 +162,30 @@ public class ExportTaskEntity extends JpaEntity {
 	public void setCreator(Long creator) {
 		this.creator = creator;
 	}
+
+	public String getExportParam() {
+		return exportParam;
+	}
+
+	public void setExportParam(String exportParam) {
+		this.exportParam = exportParam;
+	}
+
+	public Date getStartTime() {
+		return startTime;
+	}
+
+	public void setStartTime(Date startTime) {
+		this.startTime = startTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
     
     
 }

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

@@ -9,6 +9,8 @@ package cn.com.qmth.examcloud.core.oe.admin.dao.enums;
  * 导出任务类型状态
  */
 public enum ExportTaskStatus {
+	
+	WAITING("等待处理"),
 
     EXPORTING("导出中"),
 

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

@@ -12,7 +12,9 @@ public enum ExportTaskType {
 
     EXAM_DETAIL("考试明细"),
 
-    SCORE_STATISTIC("成绩统计");
+    SCORE_STATISTIC("成绩统计"),
+    
+    EXAM_SCHEDULING("考试进度");
 
     ExportTaskType(String desc) {
         this.desc = desc;

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

@@ -5,13 +5,26 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service;
 
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentQuery;
+
 /**
  * 异步导出相关接口
  */
 public interface AsyncExportService {
 
-    void exportExamRecordDetails(String jsonParams);
+    void exportExamRecordDetails(String jsonParams,User user);
+
+    void exportExamScoreStatistics(String jsonParams,User user);
+    
+    void exportExamScheduling(String jsonParams,User user);
+
+	void asyncExportExamScheduling(Long taskId, ExamStudentQuery req);
+
+	void asyncExportExamRecordDetails(Long taskId, ExamRecordQuery req);
 
-    void exportExamScoreStatistics(String jsonParams);
+	void asyncExportExamScoreStatistics(Long taskId, ExamScoreQuery req);
 
 }

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

@@ -12,7 +12,6 @@ import java.util.List;
 import org.springframework.data.domain.Page;
 
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamAuditEntity;
-import cn.com.qmth.examcloud.core.oe.admin.dao.enums.DisciplineType;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.RedoAuditInfo;

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

@@ -1,9 +1,7 @@
 package cn.com.qmth.examcloud.core.oe.admin.service;
 
-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;
 
@@ -28,4 +26,6 @@ public interface ExamRecordDataService {
                                                      String basePaperId,
                                                      String paperStructId, Boolean fullyObjective);
 
+	Long getGetAwaitingAuditCount(Long rootOrgId, Long examId);
+
 }

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

@@ -49,6 +49,16 @@ public interface ExamRecordService {
      */
     Page<ExamRecordInfo> getExamRecordWaitingAuditList(ExamRecordQuery query);
 
+	/**
+	 * Description 查询“监考待审”下一条记录
+	 *
+	 * @param query
+	 * @param examRecordDataId
+	 * @param next
+	 * @return java.lang.Long
+	 */
+	Long getExamRecordWaitingAuditNextId(ExamRecordQuery query, Long examRecordDataId, String next);
+
     /**
      * 获取正在考试的考生ID列表
      *
@@ -94,4 +104,8 @@ public interface ExamRecordService {
      * @return
      */
     List<ExamStudentEffectiveScoreInfo> getExamStudentEffectiveScoreList(String markingType, List<Long> examStudentIdList);
+
+	List<ExamRecordInfo> getExamRecordDetailListForAsync(ExamRecordQuery query);
+
+	void refreshCaptureStatistic(Long examId);
 }

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

@@ -51,4 +51,6 @@ public interface ExamScoreService {
      * @return
      */
     public List<ObjectiveScoreInfo> queryObjectiveScoreList(Long examStudentId);
+
+	List<ExamScoreInfo> exportExamScoreListForAsync(ExamScoreQuery query);
 }

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

@@ -97,6 +97,13 @@ public interface ExamStudentService {
      */
     ExamStudentInfo getExamStudentInfo(Long examStudentId);
 
+    /**
+     * 判断考生是否启用
+     * @param examStudentId
+     * @return
+     */
+    boolean isEnableExamStudent(Long examStudentId);
+
     /**
      * 查询课程信息
      *
@@ -141,4 +148,6 @@ public interface ExamStudentService {
      */
     public List<OnHandExamInfo> queryOnlineExamList(Long studentId, ExamType examType);
 
+    List<ExamStudentInfo> getExamStudentInfoListForAsync(ExamStudentQuery query);
+
 }

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

@@ -5,6 +5,7 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service;
 
+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.bean.exporttask.ExportTaskInfo;
@@ -29,4 +30,12 @@ public interface ExportTaskService {
 
     Page<ExportTaskListResp> getExportTaskList(ExportTaskListReq req,Long userId);
 
+	ExportTaskEntity findExportTaskToDispose();
+
+	void updateExportTaskStatus(Long taskId, ExportTaskStatus status);
+
+	void startExportTask(Long taskId);
+
+	void endExportTask(Long taskId);
+
 }

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

@@ -2,6 +2,8 @@ package cn.com.qmth.examcloud.core.oe.admin.service.bean;
 
 import java.util.Date;
 
+import com.alibaba.fastjson.JSONArray;
+
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
 
 public class OnHandExamInfo implements JsonSerializable{
@@ -81,6 +83,11 @@ public class OnHandExamInfo implements JsonSerializable{
      */
     private Boolean appExamEnabled;
 
+    private Boolean examCycleEnabled;
+    
+    private JSONArray examCycleWeek;
+    
+    private JSONArray examCycleTimeRange ;
 
     public String getStudentName() {
         return studentName;
@@ -298,4 +305,30 @@ public class OnHandExamInfo implements JsonSerializable{
     public void setAppExamEnabled(Boolean appExamEnabled) {
         this.appExamEnabled = appExamEnabled;
     }
+
+	public Boolean getExamCycleEnabled() {
+		return examCycleEnabled;
+	}
+
+	public void setExamCycleEnabled(Boolean examCycleEnabled) {
+		this.examCycleEnabled = examCycleEnabled;
+	}
+
+	public JSONArray getExamCycleWeek() {
+		return examCycleWeek;
+	}
+
+	public void setExamCycleWeek(JSONArray examCycleWeek) {
+		this.examCycleWeek = examCycleWeek;
+	}
+
+	public JSONArray getExamCycleTimeRange() {
+		return examCycleTimeRange;
+	}
+
+	public void setExamCycleTimeRange(JSONArray examCycleTimeRange) {
+		this.examCycleTimeRange = examCycleTimeRange;
+	}
+    
+    
 }

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

@@ -2,6 +2,8 @@ package cn.com.qmth.examcloud.core.oe.admin.service.bean;
 
 import java.util.Date;
 
+import com.alibaba.fastjson.JSONArray;
+
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
 
 public class PracticeCourseInfo implements JsonSerializable{
@@ -48,6 +50,12 @@ public class PracticeCourseInfo implements JsonSerializable{
     private Date startTime;//考试时间范围开始时间
 
     private Date endTime; //考试时间范围结束时间
+    
+    private Boolean examCycleEnabled;
+    
+    private JSONArray examCycleWeek;
+    
+    private JSONArray examCycleTimeRange ;
 
 	public Long getExamId() {
 		return examId;
@@ -161,4 +169,31 @@ public class PracticeCourseInfo implements JsonSerializable{
 		this.examType = examType;
 	}
 
+	public Boolean getExamCycleEnabled() {
+		return examCycleEnabled;
+	}
+
+	public void setExamCycleEnabled(Boolean examCycleEnabled) {
+		this.examCycleEnabled = examCycleEnabled;
+	}
+
+	
+	public JSONArray getExamCycleWeek() {
+		return examCycleWeek;
+	}
+
+	public void setExamCycleWeek(JSONArray examCycleWeek) {
+		this.examCycleWeek = examCycleWeek;
+	}
+
+	public JSONArray getExamCycleTimeRange() {
+		return examCycleTimeRange;
+	}
+
+	public void setExamCycleTimeRange(JSONArray examCycleTimeRange) {
+		this.examCycleTimeRange = examCycleTimeRange;
+	}
+	
+	
+
 }

+ 1 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examaudit/ExamAuditEntityConvert.java

@@ -93,6 +93,7 @@ public class ExamAuditEntityConvert {
                 excel.setDisciplineDetail(e.getDisciplineDetail());
             }
             excel.setObjectiveScore(Double.valueOf(e.getObjectiveScore()));
+            excel.setIp(e.getIp());
             list.add(excel);
         });
         return list;

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

@@ -8,7 +8,7 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit;
 
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
+import cn.com.qmth.examcloud.support.excel.ExcelProperty;
 
 /**
  * 监考已审信息
@@ -51,7 +51,9 @@ public class ExamAuditExcel implements JsonSerializable {
     private Double objectiveScore;
     @ExcelProperty(name = "违纪描述", index = 14)
     private String disciplineDetail;
-    
+    @ExcelProperty(name = "Ip", index = 15)
+    private String ip;
+
     public Long getExamRecordDataId() {
 		return examRecordDataId;
 	}
@@ -171,4 +173,12 @@ public class ExamAuditExcel implements JsonSerializable {
     public void setObjectiveScore(Double objectiveScore) {
         this.objectiveScore = objectiveScore;
     }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
 }

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

@@ -183,6 +183,11 @@ public class ExamAuditInfo implements JsonSerializable {
      */
     private String paperAuditTime;
 
+    /**
+     * Ip
+     */
+    private String ip;
+
     public Long getId() {
         return id;
     }
@@ -502,4 +507,12 @@ public class ExamAuditInfo implements JsonSerializable {
     public void setPaperAuditTime(String paperAuditTime) {
         this.paperAuditTime = paperAuditTime;
     }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
 }

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

@@ -42,7 +42,8 @@ public class ExamAuditMapper {
         columns.append("record.face_success_percent as faceSuccessPercent,");
         columns.append("record.face_verify_result as faceVerifyResult,");
         columns.append("record.start_time as paperStartTime,");
-        columns.append("record.end_time as paperSubmitTime");
+        columns.append("record.end_time as paperSubmitTime,");
+        columns.append("GROUP_CONCAT(DISTINCT process.source_ip) ip");
         return columns.toString();
     }
 

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

@@ -62,6 +62,8 @@ public class ExamAuditQuery implements JsonSerializable {
     private String auditStartTime;
     @ApiModelProperty("审核截止时间")
     private String auditEndTime;
+    @ApiModelProperty("Ip")
+    private String ip;
 
     /**
      * 查询或导出  select  export
@@ -247,4 +249,12 @@ public class ExamAuditQuery implements JsonSerializable {
     public void setAuditEndTime(String auditEndTime) {
         this.auditEndTime = auditEndTime;
     }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
 }

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

@@ -8,7 +8,6 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.bean.examcapture;
 
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
 
 /**
  * 考试抓拍检测信息

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

@@ -7,8 +7,21 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Component;
+
+import com.google.common.collect.Lists;
+
 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.base.Constants;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.DateUtils;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
@@ -24,18 +37,6 @@ 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;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Pageable;
-import org.springframework.stereotype.Component;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 
 /**
  * 类信息转换
@@ -149,8 +150,12 @@ public class ExamRecordEntityConvert {
         ExamScoreEntity examScore = examScoreRepo.findByExamRecordDataId(recordData.getId());
         info.setObjectiveTotalScore(String.valueOf(examScore.getObjectiveScore()));
 
+        //ip
+        info.setIp(recordData.getIp());
+
         return info;
     }
+    
 
     public Page<ExamRecordInfo> of(Page<ExamRecordDataEntity> page) {
         Pageable pageable = new PageRequest(page.getNumber(), page.getSize());

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

@@ -8,10 +8,13 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord;
 
 import java.util.Date;
+import java.util.List;
 
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 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;
+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.support.excel.ExcelProperty;
 
 /**
  * 考试记录
@@ -41,6 +44,8 @@ public class ExamRecordInfo implements JsonSerializable {
      * 考试类型
      */
     private String examType;
+    
+    private ExamType examTypeValue;
     /**
      * 考试批次
      */
@@ -159,10 +164,14 @@ public class ExamRecordInfo implements JsonSerializable {
      */
     @ExcelProperty(name = "是否违纪(终审)", width = 30, index = 26)
     private String isIllegality;
+    
+    private Boolean isIllegalityValue;
     /**
      * 考试记录状态(考试中,考试结束,考试过期,考试作废)
      */
     private String examRecordStatus;
+    
+    private ExamRecordStatus examRecordStatusValue;
     /**
      * 采集人
      */
@@ -222,6 +231,8 @@ public class ExamRecordInfo implements JsonSerializable {
      * 活体检测结果
      */
     private String faceVerifyResult;
+    
+    private IsSuccess faceVerifyResultValue;
     /**
      * 人脸五官坐标比对值
      */
@@ -276,6 +287,14 @@ public class ExamRecordInfo implements JsonSerializable {
      */
     @ExcelProperty(name = "虚拟摄像头名称", width = 30, index = 31)
     private String virtualCameraNames;
+
+    /**
+     * Ip
+     */
+    @ExcelProperty(name = "Ip", width = 30, index = 32)
+    private String ip;
+
+
     public Long getId() {
         return id;
     }
@@ -718,4 +737,45 @@ public class ExamRecordInfo implements JsonSerializable {
         this.virtualCameraNames = virtualCameraNames;
     }
 
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
+
+	public ExamType getExamTypeValue() {
+		return examTypeValue;
+	}
+
+	public void setExamTypeValue(ExamType examTypeValue) {
+		this.examTypeValue = examTypeValue;
+	}
+
+	public Boolean getIsIllegalityValue() {
+		return isIllegalityValue;
+	}
+
+	public void setIsIllegalityValue(Boolean isIllegalityValue) {
+		this.isIllegalityValue = isIllegalityValue;
+	}
+
+	public ExamRecordStatus getExamRecordStatusValue() {
+		return examRecordStatusValue;
+	}
+
+	public void setExamRecordStatusValue(ExamRecordStatus examRecordStatusValue) {
+		this.examRecordStatusValue = examRecordStatusValue;
+	}
+
+	public IsSuccess getFaceVerifyResultValue() {
+		return faceVerifyResultValue;
+	}
+
+	public void setFaceVerifyResultValue(IsSuccess faceVerifyResultValue) {
+		this.faceVerifyResultValue = faceVerifyResultValue;
+	}
+    
+    
 }

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

@@ -10,6 +10,7 @@ package cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.alibaba.fastjson.JSON;
 import org.apache.commons.lang3.StringUtils;
 
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
@@ -83,9 +84,15 @@ public class ExamRecordQuery implements JsonSerializable {
     @ApiModelProperty("交卷截止时间")
     private String submitEndTime;
     
+    @ApiModelProperty("是否违纪")
+    private Boolean isIllegality;
+
     @ApiModelProperty("创建人ID")
     private Long creator;
 
+    @ApiModelProperty("Ip")
+    private String ip;
+
     public ExamRecordQuery addRecordStatus(String recordStatus) {
         if (recordStatuses == null) {
             recordStatuses = new ArrayList<>();
@@ -334,6 +341,20 @@ public class ExamRecordQuery implements JsonSerializable {
 	public void setCreator(Long creator) {
 		this.creator = creator;
 	}
-    
-    
+
+    public Boolean getIsIllegality() {
+        return isIllegality;
+    }
+
+    public void setIsIllegality(Boolean isIllegality) {
+        this.isIllegality = isIllegality;
+    }
+
+    public String getIp() {
+        return ip;
+    }
+
+    public void setIp(String ip) {
+        this.ip = ip;
+    }
 }

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

@@ -1,6 +1,6 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord;
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
+import cn.com.qmth.examcloud.support.excel.ExcelProperty;
 /**
  * 
  * @author lideyin

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

@@ -8,7 +8,7 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore;
 
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
+import cn.com.qmth.examcloud.support.excel.ExcelProperty;
 
 /**
  * 考试分数信息
@@ -135,6 +135,7 @@ public class ExamScoreInfo implements JsonSerializable {
     /**
      * 剩余考试次数
      */
+    @ExcelProperty(name = "剩余考试次数", width = 31, index = 19)
     private Long leftExamTimes;
     /**
      * 采集人

+ 1 - 1
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/CourseCompleteProgressExcel.java

@@ -10,7 +10,7 @@ package cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent;
 
 import java.io.Serializable;
 
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
+import cn.com.qmth.examcloud.support.excel.ExcelProperty;
 
 /**
  * @Description 课程完成进度

+ 1 - 1
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentExcel.java

@@ -10,7 +10,7 @@ package cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent;
 
 import java.io.Serializable;
 
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
+import cn.com.qmth.examcloud.support.excel.ExcelProperty;
 
 /**
  * 考生信息-Excel格式

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

@@ -24,6 +24,8 @@ public class ExamStudentQuery implements JsonSerializable {
 	 *
 	 */
 	private static final long serialVersionUID = 4853280464208226631L;
+	
+	private Long rootOrgId;
 
 	@ApiModelProperty("当前页数")
     private Integer pageNo;
@@ -189,4 +191,14 @@ public class ExamStudentQuery implements JsonSerializable {
     public void setExamStageId(Long examStageId) {
         this.examStageId = examStageId;
     }
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+    
+    
 }

+ 1 - 1
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/ExamStudentUnFinishedExcel.java

@@ -8,7 +8,7 @@
 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.base.utils.excel.ExcelProperty;
+import cn.com.qmth.examcloud.support.excel.ExcelProperty;
 
 /**
  * 考生信息-Excel格式

+ 1 - 1
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examstudent/OrgCompleteProgressExcel.java

@@ -10,7 +10,7 @@ package cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent;
 
 import java.io.Serializable;
 
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExcelProperty;
+import cn.com.qmth.examcloud.support.excel.ExcelProperty;
 
 /**
  * @Description 学习中心完成进度

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

@@ -0,0 +1,91 @@
+package cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask;
+
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.JsonMapper;
+import cn.com.qmth.examcloud.commons.util.Util;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExportTaskEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
+import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExportTaskService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentQuery;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+public class ExportTask extends Thread {
+	private static final ExamCloudLog log = ExamCloudLogFactory.getLog(ExportTask.class);
+
+	private static int cacheLockTimeout = 60 * 2;
+
+	private AsyncExportService asyncExportService = SpringContextHolder.getBean(AsyncExportService.class);
+
+	private ExportTaskService exportTaskService = SpringContextHolder.getBean(ExportTaskService.class);
+
+	private RedisClient redisClient = SpringContextHolder.getBean(RedisClient.class);
+
+	@Override
+	public void run() {
+		for (;;) {
+			ExportTaskEntity et = exportTaskService.findExportTaskToDispose();
+			if (et == null) {
+				Util.sleep(30);
+				continue;
+			}
+			String cacheLock = "$_CACHE_LOCK:OE_EXPORT_TASK:" + et.getId();
+			Boolean lock = redisClient.setIfAbsent(cacheLock, cacheLock, cacheLockTimeout);
+			if (!lock) {
+				continue;
+			}
+			try {
+				exportTaskService.startExportTask(et.getId());
+				if (ExportTaskType.EXAM_DETAIL.equals(et.getType())) {
+					examRecordDetails(et);
+				} else if (ExportTaskType.SCORE_STATISTIC.equals(et.getType())) {
+					examScores(et);
+				} else if (ExportTaskType.EXAM_SCHEDULING.equals(et.getType())) {
+					examScheduling(et);
+				}
+				exportTaskService.endExportTask(et.getId());
+			} catch (Exception e) {
+				log.error("ExportTask error ", e);
+			} finally {
+				redisClient.delete(cacheLock);
+			}
+		}
+	}
+
+	/**
+	 * 考试明细
+	 * 
+	 * @param et
+	 */
+	private void examRecordDetails(ExportTaskEntity et) {
+		ExamRecordQuery req = new JsonMapper().parseJson(et.getExportParam(), ExamRecordQuery.class);
+		req.setRootOrgId(et.getRootOrgId());
+		asyncExportService.asyncExportExamRecordDetails(et.getId(), req);
+	}
+
+	/**
+	 * 成绩统计
+	 * 
+	 * @param et
+	 */
+	private void examScores(ExportTaskEntity et) {
+		ExamScoreQuery req = new JsonMapper().parseJson(et.getExportParam(), ExamScoreQuery.class);
+		req.setRootOrgId(et.getRootOrgId());
+		asyncExportService.asyncExportExamScoreStatistics(et.getId(), req);
+	}
+
+	/**
+	 * 考试进度
+	 * 
+	 * @param et
+	 */
+	private void examScheduling(ExportTaskEntity et) {
+		ExamStudentQuery req = new JsonMapper().parseJson(et.getExportParam(), ExamStudentQuery.class);
+		req.setRootOrgId(et.getRootOrgId());
+		asyncExportService.asyncExportExamScheduling(et.getId(), req);
+	}
+}

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

@@ -37,6 +37,8 @@ public class ExportTaskInfo implements JsonSerializable {
     
     @ApiModelProperty("创建人ID")
     private Long creator;
+    
+    private String jsonParams;
 
     public Long getId() {
         return id;
@@ -101,6 +103,15 @@ public class ExportTaskInfo implements JsonSerializable {
 	public void setCreator(Long creator) {
 		this.creator = creator;
 	}
+
+	public String getJsonParams() {
+		return jsonParams;
+	}
+
+	public void setJsonParams(String jsonParams) {
+		this.jsonParams = jsonParams;
+	}
     
+	
     
 }

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

@@ -50,6 +50,12 @@ public class ExportTaskListResp implements JsonSerializable {
     private Date updateTime;
     
     private String createName;
+    
+    @ApiModelProperty("开始处理时间")
+    private Date startTime;
+    
+    @ApiModelProperty("结束处理时间")
+    private Date endTime;
 
     public Long getId() {
         return id;
@@ -146,6 +152,22 @@ public class ExportTaskListResp implements JsonSerializable {
 	public void setCreateName(String createName) {
 		this.createName = createName;
 	}
+
+	public Date getStartTime() {
+		return startTime;
+	}
+
+	public void setStartTime(Date startTime) {
+		this.startTime = startTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
     
     
 }

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

@@ -5,38 +5,48 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
-import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+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;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
 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.commons.util.JsonMapper;
 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.AsyncExportService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExportTaskService;
 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.examstudent.ExamStudentEntityConvert;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskInfo;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.support.excel.ExcelExportUtil;
 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;
 
 /**
  * 异步导出相关接口
@@ -44,197 +54,255 @@ 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不能为空!");
-        Check.isNull(req.getCreator(), "创建人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);
-        task.setCreator(req.getCreator());
-        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不能为空!");
-        Check.isNull(req.getCreator(), "创建人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);
-        task.setCreator(req.getCreator());
-        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());
-    }
+	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 ExamScoreService examScoreService;
+
+	@Autowired
+	private SystemProperties systemConfig;
+
+	@Autowired
+	private ExamStudentService examStudentService;
+
+	@Override
+	public void exportExamScheduling(String jsonParams, User user) {
+		ExamStudentQuery req = new JsonMapper().parseJson(jsonParams, ExamStudentQuery.class);
+		req.setRootOrgId(user.getRootOrgId());
+		Check.isNull(req, "请求参数不能为空!");
+		Check.isNull(req.getExamId(), "请先选择考试批次!");
+
+		// 创建导出任务
+		ExportTaskInfo task = new ExportTaskInfo();
+		task.setRootOrgId(user.getRootOrgId());
+		task.setExamId(req.getExamId());
+		task.setType(ExportTaskType.EXAM_SCHEDULING);
+		task.setStatus(ExportTaskStatus.WAITING);
+		task.setCreator(user.getUserId());
+		task.setJsonParams(jsonParams);
+		exportTaskService.addExportTask(task);
+	}
+
+	@Override
+	public void asyncExportExamScheduling(Long taskId, ExamStudentQuery req) {
+		List<ExamStudentExcel> examRecords;
+		List<ExamStudentInfo> examStudentInfoList;
+		try {
+			examStudentInfoList = examStudentService.getExamStudentInfoListForAsync(req);
+			if (CollectionUtils.isEmpty(examStudentInfoList)) {
+				exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "当前条件暂无数据,任务终止");
+				return;
+			}
+			examRecords = ExamStudentEntityConvert.ofExcel(examStudentInfoList);
+
+		} 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;
+		}
+		// 导出文件的存储路径
+		Long examId = examStudentInfoList.get(0).getExamId();
+		ExamSettingsCacheBean exam = CacheHelper.getExamSettings(examId);
+		final String filePath = String.format("/%s/%s/%s/%s/%s_考试进度详情.xlsx", TASK_EXPORT_DIR, req.getRootOrgId(),
+				dateDir(), randomUUID(), exam.getName());
+		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);) {
+
+			ExcelExportUtil.exportExcel(ExamStudentExcel.class, 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("asyncExportExamScheduling finished... " + oss.getRelativePath());
+			exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.SUCCESS, null, oss.getRelativePath());
+		} catch (Exception e) {
+			log.error("asyncExportExamScheduling error... " + e);
+			exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "上传至文件服务器异常");
+		} finally {
+			FileUtils.deleteQuietly(tempFile);
+		}
+	}
+
+	@Override
+	public void exportExamRecordDetails(String jsonParams, User user) {
+		ExamRecordQuery req = new JsonMapper().parseJson(jsonParams, ExamRecordQuery.class);
+		req.setRootOrgId(user.getRootOrgId());
+		Check.isNull(req, "请求参数不能为空!");
+		Check.isNull(req.getRootOrgId(), "学校ID不能为空!");
+		Check.isNull(req.getExamId(), "考试ID不能为空!");
+		Check.isNull(req.getCreator(), "创建人ID不能为空!");
+
+
+		// 创建导出任务
+		ExportTaskInfo task = new ExportTaskInfo();
+		task.setRootOrgId(req.getRootOrgId());
+		task.setExamId(req.getExamId());
+		task.setType(ExportTaskType.EXAM_DETAIL);
+		task.setStatus(ExportTaskStatus.WAITING);
+		task.setCreator(req.getCreator());
+		task.setJsonParams(jsonParams);
+		exportTaskService.addExportTask(task);
+	}
+
+	@Override
+	public void asyncExportExamRecordDetails(Long taskId, ExamRecordQuery req) {
+		List<ExamRecordInfo> examRecords;
+		try {
+			examRecords = examRecordService.getExamRecordDetailListForAsync(req);
+
+			if (CollectionUtils.isEmpty(examRecords)) {
+				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;
+		}
+		// 导出文件的存储路径
+		String examName = examRecords.get(0).getExamName();
+		final String filePath = String.format("/%s/%s/%s/%s/%s_考试明细.xlsx", TASK_EXPORT_DIR, req.getRootOrgId(),
+				dateDir(), randomUUID(), examName);
+		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);) {
+
+			ExcelExportUtil.exportExcel(ExamRecordInfo.class, 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, User user) {
+		ExamScoreQuery req = new JsonMapper().parseJson(jsonParams, ExamScoreQuery.class);
+		req.setRootOrgId(user.getRootOrgId());
+		Check.isNull(req, "请求参数不能为空!");
+		Check.isNull(req.getRootOrgId(), "学校ID不能为空!");
+		Check.isNull(req.getExamId(), "考试ID不能为空!");
+		Check.isNull(req.getCreator(), "创建人ID不能为空!");
+
+		// 创建导出任务
+		ExportTaskInfo task = new ExportTaskInfo();
+		task.setRootOrgId(req.getRootOrgId());
+		task.setExamId(req.getExamId());
+		task.setType(ExportTaskType.SCORE_STATISTIC);
+		task.setStatus(ExportTaskStatus.WAITING);
+		task.setCreator(req.getCreator());
+		task.setJsonParams(jsonParams);
+		exportTaskService.addExportTask(task);
+	}
+
+	@Override
+	public void asyncExportExamScoreStatistics(Long taskId, ExamScoreQuery req) {
+		List<ExamScoreInfo> examScores;
+		try {
+			examScores = examScoreService.exportExamScoreListForAsync(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;
+		}
+		// 导出文件的存储路径
+		String examName = examScores.get(0).getExamName();
+		final String filePath = String.format("/%s/%s/%s/%s/%s_成绩统计.xlsx", TASK_EXPORT_DIR, req.getRootOrgId(),
+				dateDir(), randomUUID(), examName);
+		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);) {
+			ExcelExportUtil.exportExcel(ExamScoreInfo.class, 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());
+	}
 
 }

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

@@ -9,7 +9,6 @@ package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 import cn.com.qmth.examcloud.core.oe.admin.base.jpa.Searcher;
 import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SpecUtils;
 import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SqlWrapper;
@@ -20,7 +19,6 @@ 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.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;
@@ -96,6 +94,7 @@ public class ExamAuditServiceImpl implements ExamAuditService {
 
     @Autowired
     ExamStudentFinalScoreRepo examStudentFinalScoreRepo;
+
     /**
      * 活体检测失败自动审核
      */
@@ -121,6 +120,7 @@ public class ExamAuditServiceImpl implements ExamAuditService {
         SqlWrapper wrapper = new SqlWrapper()
                 .select(columns).from("ec_oe_exam_audit").as("audit")
                 .innerJoin("ec_oe_exam_record_data").as("record").on("record.id", "audit.exam_record_data_id")
+                .leftJoin("ec_oe_exam_process_record").as("process").on("record.id", "process.exam_record_data_id")
                 .where().notEq("record.exam_record_status", ExamRecordStatus.EXAM_INVALID.name());
         if (query.getOrgId() != null) {
             wrapper.and().eq("record.org_id", query.getOrgId());
@@ -176,11 +176,18 @@ public class ExamAuditServiceImpl implements ExamAuditService {
             wrapper.and().lte("audit.update_time", query.getAuditEndTime());
         }
 
+        //查询分页记录
+        wrapper.groupBy("record.id ");
+        //根据ip查询
+        if (StringUtils.isNoneBlank(query.getIp())) {
+            wrapper.having().like("ip",query.getIp());
+        }
+        //需求调整20200816:按审核时间排序
+        wrapper.orderBy("record.update_time", true);
         long totalSize = 0;
         //查询总记录数
         if (query.getSelectType() == null || query.getSelectType() != SelectType.EXPORT) {
-            final String count = "count(0)";
-            String countSql = wrapper.build().replace(columns, count);
+            String countSql = "SELECT count(1) FROM ("+wrapper.build()+") t";
             Query countQuery = entityManager.createNativeQuery(countSql);
             BigInteger element = (BigInteger) countQuery.getSingleResult();
             totalSize = element.longValue();
@@ -189,8 +196,6 @@ public class ExamAuditServiceImpl implements ExamAuditService {
             }
         }
 
-        //查询分页记录
-        wrapper.orderBy("record.update_time", true);//需求调整20200816:按审核时间排序
         Query dataQuery = entityManager.createNativeQuery(wrapper.build());
 
 //        dataQuery.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(HashMap.class));

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

@@ -42,6 +42,7 @@ import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.StringJoiner;
 
 /**
  * 考试抓拍检测相关接口
@@ -190,11 +191,11 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
                 }
             }
         }
-        StringBuffer sb = new StringBuffer();
+        StringJoiner sj = new StringJoiner(";");
         for (String cameraName : virtualCameraNames) {
-            sb.append(cameraName).append(";");
+            sj.add(cameraName);
         }
-        return sb.toString();
+        return sj.toString();
     }
 
 

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

@@ -9,6 +9,7 @@ import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentB
 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.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 import java.util.Date;
@@ -21,6 +22,9 @@ import java.util.Date;
  */
 @Service("examRecordDataService")
 public class ExamRecordDataServiceImpl implements ExamRecordDataService {
+	
+    @Autowired
+    private JdbcTemplate jdbcTemplate;
 
     @Autowired
     private ExamRecordDataRepo examRecordDataRepo;
@@ -61,4 +65,10 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
         return examRecordDataRepo.save(examRecordDataEntity);
     }
 
+	@Override
+	public Long getGetAwaitingAuditCount(Long rootOrgId, Long examId) {
+		Long count=jdbcTemplate.queryForObject("select count(1) from ec_oe_exam_record_data t where t.is_warn=1 and t.is_audit = 0 and t.root_org_id="+rootOrgId+" and t.exam_id="+examId, Long.class);
+		return count;
+	}
+
 }

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

@@ -7,19 +7,67 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
+import java.math.BigInteger;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.stream.Collectors;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
+import org.apache.commons.lang3.StringUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import com.google.common.collect.Lists;
+
+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.basic.api.StudentCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.StudentBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetStudentListByIdsReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetStudentListByIdsResp;
 import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SqlWrapper;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.BatchSetDataUtil;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.DateUtils;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamCaptureRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordQuestionsRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamCaptureEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamQuestionEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordQuestionsEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+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.IsSuccess;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.MarkingType;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.TrueFalse;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
 import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
@@ -30,30 +78,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.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.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;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Pageable;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
-import org.springframework.stereotype.Service;
-
-import javax.persistence.EntityManager;
-import javax.persistence.Query;
-import java.math.BigInteger;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.*;
-import java.util.stream.Collectors;
 
 /**
  * 考试记录接口
@@ -63,6 +93,8 @@ import java.util.stream.Collectors;
  */
 @Service
 public class ExamRecordServiceImpl implements ExamRecordService {
+    private static final ExamCloudLog LOG = ExamCloudLogFactory
+            .getLog(ExamRecordServiceImpl.class);
     @Autowired
     private ExamScoreRepo examScoreRepo;
     @Autowired
@@ -79,6 +111,64 @@ public class ExamRecordServiceImpl implements ExamRecordService {
     private ExamRecordEntityConvert examRecordEntityConvert;
     @Autowired
     private LocalCacheService localCacheService;
+    
+    @Autowired
+    private StudentCloudService studentCloudService;
+    @Autowired
+    private ExamCaptureRepo examCaptureRepo;
+    
+    
+    private static final String EXAM_RECORD_EXPORT_SQL = "select  record_data.id," +
+            "record_data.exam_record_status examRecordStatusValue," +
+            "record_data.start_time startTime," +
+            "record_data.end_time endTime," +
+            "record_data.clean_time cleanTime," +
+            "record_data.used_exam_time usedExamTime," +
+            "record_data.exam_order examOrder," +
+            "record_data.is_warn isWarn," +
+            "record_data.is_audit isAudit," +
+            "record_data.is_illegality isIllegalityValue," +
+            "record_data.is_reexamine isReexamine," +
+            "record_data.is_continued isContinued," +
+            "record_data.is_all_objective_paper isAllObjectivePaper," +
+            "record_data.continued_count continuedCount," +
+            "record_data.face_success_count faceSuccessCount," +
+            "record_data.face_failed_count faceFailedCount," +
+            "record_data.face_stranger_count faceStrangerCount," +
+            "record_data.face_total_count faceTotalCount," +
+            "record_data.face_success_percent faceSuccessPercent," +
+            "record_data.face_verify_result faceVerifyResultValue," +
+            "record_data.baidu_face_liveness_success_percent baiduFaceLivenessSuccessPercent," +
+            "record_data.creation_time creationTime," +
+            "record_data.update_time updateTime," +
+            "record_data.exam_id examId," +
+            "record_data.exam_type examTypeValue," +
+            "record_data.exam_student_id examStudentId," +
+            "record_data.student_id studentId," +
+            "record_data.student_code studentCode," +
+            "record_data.student_name studentName," +
+            "record_data.identity_number identityNumber," +
+            "record_data.course_id courseId," +
+            "record_data.course_level courseLevel," +
+            "record_data.root_org_id rootOrgId," +
+            "record_data.org_id orgId," +
+            "record_data.base_paper_id basePaperId," +
+            "record_data.paper_type paperType," +
+            "record_data.paper_struct_id paperStructId," +
+            "record_data.info_collector infoCollector," +
+            "GROUP_CONCAT(DISTINCT process.source_ip) ip," +
+			"eoes.objective_score objectiveTotalScore," +
+			"eoes.subjective_score subjectiveTotalScore," +
+			"eoes.total_score paperTotalScore,"+
+			"eoest.grade ,"+
+			"eoest.specialty_name specialtyName"+
+            
+            " from ec_oe_exam_record_data record_data " +
+            " LEFT JOIN ec_oe_exam_process_record AS process ON record_data.id = process.exam_record_data_id " +
+            " LEFT JOIN ec_oe_exam_score eoes ON  record_data.id = eoes.exam_record_data_id "+
+            " LEFT JOIN ec_oe_exam_student eoest ON  record_data.exam_student_id = eoest.exam_student_id "+
+            " where 1=1";
+    
     /**
      * ec_oe_exam_record_data  查询sql
      */
@@ -119,9 +209,12 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             "record_data.base_paper_id base_paper_id," +
             "record_data.paper_type paper_type," +
             "record_data.paper_struct_id paper_struct_id," +
-            "record_data.info_collector info_collector" +
+            "record_data.info_collector info_collector," +
+            "GROUP_CONCAT(DISTINCT process.source_ip) ip" +
             " from ec_oe_exam_record_data record_data " +
+            " LEFT JOIN ec_oe_exam_process_record AS process ON record_data.id = process.exam_record_data_id " +
             " where 1=1";
+
     /**
      * 考生作答成绩
      */
@@ -132,6 +225,7 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             "inner join ec_b_course co on erd.course_id=co.id " +
             "inner join ec_e_exam ex on erd.exam_id=ex.id " +
             "where 1=1 ";
+
     @Autowired
     private ExamRecordQuestionsRepo examRecordQuestionsRepo;
 
@@ -153,6 +247,48 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         return this.getExamRecordList(query);
     }
 
+    @Override
+    public Long getExamRecordWaitingAuditNextId(ExamRecordQuery query, Long examRecordDataId, String next) {
+        Check.isNull(query, "查询参数不能为空!");
+        //默认条件
+        query.setIsWarn(true);
+        query.setIsAudit(false);
+        query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
+        query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
+        //查询条件
+        StringBuilder sqlBuilder = new StringBuilder();
+        //待审核(按原先的sql,多了一个ip字段)
+        sqlBuilder.append("select record_data.id from ec_oe_exam_record_data record_data where 1 = 1 ");
+        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+        if (query.getIsWarn() != null) {
+            //只查有异常未审核
+            if (query.getIsWarn()) {
+                sqlBuilder.append(" and record_data.is_warn  = 1 and record_data.is_audit = 0 ");
+            } else {
+                sqlBuilder.append(" and record_data.is_warn  = 0 ");
+            }
+        } else {
+            sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 0))");
+        }
+        sqlBuilder.append(" and record_data.is_illegality = 0");
+        if (StringUtils.isBlank(next)){
+            sqlBuilder.append(" and record_data.id != ");
+            sqlBuilder.append(examRecordDataId);
+            sqlBuilder.append(" order by record_data.id desc");
+        } else if ("1".equals(next)){
+            sqlBuilder.append(" and record_data.id < ");
+            sqlBuilder.append(examRecordDataId);
+            sqlBuilder.append(" order by record_data.id desc");
+        } else if ("0".equals(next)){
+            sqlBuilder.append(" and record_data.id > ");
+            sqlBuilder.append(examRecordDataId);
+            sqlBuilder.append(" order by record_data.id asc");
+        }
+        sqlBuilder.append(" limit 1 ");
+        List<Long> ids = jdbcTemplate.query(sqlBuilder.toString(), (rs, rowNum) -> rs.getLong("id"));
+        return CollectionUtils.isEmpty(ids) ? null : ids.get(0);
+    }
+
     /**
      * 考试明细查询
      *
@@ -166,6 +302,11 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         sqlBuilder.append(EXAM_RECORD_SQL);
         sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
         sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
+        sqlBuilder.append(" group by record_data.id ");
+        //根据ip查询
+        /*if (StringUtils.isNoneBlank(query.getIp())) {
+            sqlBuilder.append(" having ip like '%"+query.getIp()+"%' ");
+        }*/
         sqlBuilder.append(" order by record_data.id desc");
         //分页条件
         int currentNum = (query.getPageNo() - 1) * query.getPageSize();
@@ -206,6 +347,7 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         examRecordData.setFaceSuccessPercent(rs.getDouble("face_success_percent"));
         examRecordData.setFaceVerifyResult(IsSuccess.strToEnum(rs.getString("face_verify_result")));
         examRecordData.setBaiduFaceLivenessSuccessPercent(rs.getDouble("baidu_face_liveness_success_percent"));
+        examRecordData.setIp(rs.getString("ip"));
 
         return examRecordData;
     }
@@ -260,6 +402,13 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         if (query.getExamRecordId() != null) {
             sql.append(" and record_data.id = " + query.getExamRecordId());
         }
+        if (query.getIsIllegality() != null) {
+            if(query.getIsIllegality()){
+                sql.append(" and record_data.is_illegality = 1");
+            } else {
+                sql.append(" and record_data.is_illegality = 0");
+            }
+        }
         if (StringUtils.isNotBlank(query.getStartTime()) && StringUtils.isNotBlank(query.getEndTime())) {
             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')");
@@ -303,7 +452,9 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             if (query.getHasVirtual()) {
                 sql.append(" and record_data.id in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId()+" and t2.has_virtual_camera=1 )");
             } else {
-                sql.append(" and record_data.id in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId()+" and (t2.has_virtual_camera=0 or t2.has_virtual_camera is null) )");
+                //原先写法会跟有虚拟设备的出现重复情况,用有虚拟设备的not in
+                //sql.append(" and record_data.id in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId()+" and (t2.has_virtual_camera=0 or t2.has_virtual_camera is null) )");
+                sql.append(" and record_data.id not in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId()+" and t2.has_virtual_camera=1 )");
             }
         }
         return sql;
@@ -318,10 +469,11 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         Check.isNull(query, "查询参数不能为空!");
         //查询条件
         StringBuilder sqlBuilder = new StringBuilder();
+        //待审核(按原先的sql,多了一个ip字段)
         sqlBuilder.append(EXAM_RECORD_SQL);
         sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
-        //只查有异常未审核
         if (query.getIsWarn() != null) {
+            //只查有异常未审核
             if (query.getIsWarn()) {
                 sqlBuilder.append(" and record_data.is_warn  = 1 and record_data.is_audit = 0 ");
             } else {
@@ -331,6 +483,12 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 0))");
         }
         sqlBuilder.append(" and record_data.is_illegality = 0");
+        //查询分页记录
+        sqlBuilder.append(" group by record_data.id ");
+        //根据ip查询
+        /*if (StringUtils.isNoneBlank(query.getIp())) {
+            sqlBuilder.append(" having ip like '%"+query.getIp()+"%' ");
+        }*/
         sqlBuilder.append(" order by record_data.id desc");
         //分页条件
         int currentNum = (query.getPageNo() - 1) * query.getPageSize();
@@ -472,7 +630,231 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         sqlBuilder.append(" and record_data.is_warn = 1 and record_data.is_audit = 0");
         return jdbcTemplate.queryForObject(sqlBuilder.toString(), Long.class);
     }
+    @Override
+    public List<ExamRecordInfo> getExamRecordDetailListForAsync(ExamRecordQuery query) {
+        Check.isNull(query, "查询参数不能为空!");
+        query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
+        query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
+
+        List<ExamRecordInfo> examRecordDataList=new ArrayList<ExamRecordInfo>();
+        Long startId=0L;
+        for(;;) {
+        	List<ExamRecordInfo> tem=getExamRecordDetailPageForExport(query,  startId);
+        	if(tem==null||tem.size()==0) {
+        		break;
+        	}else {
+        		startId=tem.get(tem.size()-1).getId();
+        		examRecordDataList.addAll(tem);
+        	}
+        }
+        if(examRecordDataList==null||examRecordDataList.size()==0) {
+        	 return examRecordDataList;
+        }
+        for(ExamRecordInfo erInfo:examRecordDataList) {
+        	setInfo(erInfo,query.getExamId());
+        }
+        setPhone(examRecordDataList,query.getRootOrgId());
+        String examType = examRecordDataList.get(0).getExamType();
+		if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
+			setVirtualCameraNames(examRecordDataList);
+		}
+        return examRecordDataList;
+    }
 
+    @Override
+    public void refreshCaptureStatistic(Long examId) {
+        List<ExamRecordDataEntity> data = examRecordDataRepo.findDataByExamId(examId);
+
+        for (ExamRecordDataEntity entity : data) {
+            //如果开启了活检
+            if (FaceBiopsyHelper.isFaceEnable(entity.getRootOrgId(), examId, entity.getStudentId())) {
+                List<ExamCaptureEntity> captureEntities = examCaptureRepo.findByExamRecordDataId(entity.getId());
+
+                Double succPercent = entity.getFaceSuccessPercent();
+                Double livenessSuccessPercent = entity.getBaiduFaceLivenessSuccessPercent();
+
+                //人脸识别阀值
+                String warnThresholdStr = ExamCacheTransferHelper.getCachedExamProperty(examId,
+                        entity.getStudentId(), ExamProperties.WARN_THRESHOLD.name()).getValue();
+                if (StringUtils.isBlank(warnThresholdStr)) {
+                    throw new StatusException("400101", "人脸检测预警阈值未设置");
+                }
+
+                //人脸真实性(百度活体检测)通过阀值
+                String liveWarnThresholdStr = ExamCacheTransferHelper.getCachedExamProperty(examId,
+                        entity.getStudentId(), ExamProperties.LIVING_WARN_THRESHOLD.name()).getValue();
+                if (StringUtils.isBlank(liveWarnThresholdStr)) {
+                    throw new StatusException("400102", "人脸真实性阈值未设置");
+                }
+
+                Double warnThreshold = Double.parseDouble(warnThresholdStr);
+                Double livenessThreshold = Double.parseDouble(liveWarnThresholdStr);
+                boolean checkFailed = succPercent < warnThreshold || livenessSuccessPercent < livenessThreshold;
+
+                //虚拟摄像头进入待审核,且有虚拟摄像头的
+                String valueSql="SELECT value FROM ec_e_exam_prop WHERE exam_id ="+examId+" AND key_id=45";
+                List<Boolean> list = jdbcTemplate.query(valueSql, (rs, rowNum) -> rs.getBoolean("value"));
+                boolean hasVirtualCamera = !CollectionUtils.isEmpty(list) && list.get(0) &&
+                        captureEntities.stream().anyMatch(e->e.getHasVirtualCamera()!=null&&e.getHasVirtualCamera());
+
+                entity.setIsWarn(hasVirtualCamera || checkFailed);
+
+                examRecordDataRepo.save(entity);
+            }
+        }
+    }
+
+    private void setPhone(List<ExamRecordInfo> examRecordDataList,Long rootOrgId) {
+    	GetStudentListByIdsReq req= new GetStudentListByIdsReq();
+        BatchSetDataUtil<ExamRecordInfo> tool = new BatchSetDataUtil<ExamRecordInfo>() {
+			@Override
+			public void setData(List<ExamRecordInfo> dataList) {
+				req.setRootOrgId(rootOrgId);
+				List<Long> ids = dataList.stream().map(dto -> dto.getStudentId()).distinct().collect(Collectors.toList());
+				req.setStudentIdList(ids);
+				GetStudentListByIdsResp resp=studentCloudService.getStudentListByIds(req);
+				if(resp.getStudentBeanList()!=null&&resp.getStudentBeanList().size()>0) {
+					Map<Long, String> map = resp.getStudentBeanList().stream()
+							.collect(Collectors.toMap(StudentBean::getId, account -> (account.getPhoneNumber()==null?"":account.getPhoneNumber())));
+					for(ExamRecordInfo erInfo:dataList) {
+						erInfo.setPhone(map.get(erInfo.getStudentId()));
+					}
+				}
+			}
+
+		};
+		tool.setDataForBatch(examRecordDataList, 100);
+    }
+    private void setVirtualCameraNames(List<ExamRecordInfo> examRecordDataList) {
+        BatchSetDataUtil<ExamRecordInfo> tool = new BatchSetDataUtil<ExamRecordInfo>() {
+			@Override
+			public void setData(List<ExamRecordInfo> dataList) {
+				List<Long> ids = dataList.stream().map(dto -> dto.getId()).distinct().collect(Collectors.toList());
+				Map<Long,Set<String>> map=new HashMap<>();
+		        List<ExamCaptureEntity> examCaptureList = examCaptureRepo.findByExamRecordDataIdIn(ids);
+		        if (examCaptureList != null && examCaptureList.size() > 0) {
+		            for (ExamCaptureEntity examCapture : examCaptureList) {
+		            	Set<String> virtualCameraNames=map.get(examCapture.getExamRecordDataId());
+		            	if(virtualCameraNames==null) {
+		            		virtualCameraNames= new HashSet<String>();
+		            		map.put(examCapture.getExamRecordDataId(), virtualCameraNames);
+		            	}
+		                String cameraInfos = examCapture.getCameraInfos();
+		                if (StringUtils.isNotBlank(cameraInfos)) {
+		                    JSONArray jsonArray;
+		                    try {
+		                        jsonArray = new JSONArray(cameraInfos);
+		                        for (int i = 0; i < jsonArray.length(); i++) {
+		                            try {
+		                                JSONObject jsonObject = (JSONObject) jsonArray.get(i);
+		                                if (StringUtils.isBlank(jsonObject.getString("pid"))) {
+		                                    virtualCameraNames.add(jsonObject.getString("name"));
+		                                }
+		                            } catch (JSONException e) {
+		                                //主要针对json数组最后的空对象处理,不影响业务
+		                                LOG.error("抓拍照片格式不正确", e);
+		                                continue;
+		                            }
+		                        }
+		                    } catch (JSONException e) {
+		                        LOG.error("获取虚拟摄像头名称失败", e);
+		                        throw new StatusException("ExamCaptureService-001", "获取虚拟摄像头名称失败");
+		                    }
+
+		                }
+		            }
+		        }
+		        for(ExamRecordInfo erInfo:dataList) {
+					erInfo.setVirtualCameraNames(getStrFromSet(map.get(erInfo.getId())));
+				}
+			}
+
+		};
+		tool.setDataForBatch(examRecordDataList, 100);
+    }
+    
+    private String getStrFromSet(Set<String> set) {
+    	if(set==null||set.size()==0) {
+    		return null;
+    	}
+    	StringJoiner sj = new StringJoiner(";");
+        for (String cameraName : set) {
+            sj.add(cameraName);
+        }
+        return sj.toString();
+    }
+    
+    private void  setInfo(ExamRecordInfo info,Long examId) {
+        info.setDataId(info.getId());
+        if (info.getExamTypeValue() != null) {
+            info.setExamType(info.getExamTypeValue().name());
+        }
+        CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(info.getCourseId());
+
+        info.setCourseCode(courseBean.getCode());
+        info.setCourseName(courseBean.getName());
+        info.setCourseNameAndCode(courseBean.getName() + "(" + courseBean.getCode() + ")");
+        info.setCourseLevel(CourseLevel.getCourseLevelTitle(courseBean.getLevel()));
+
+        OrgCacheBean orgBean = gainBaseDataService.getOrgBean(info.getOrgId());
+        info.setOrgName(orgBean.getName());
+        //封装详细数据
+        if (info.getStartTime() != null) {
+            info.setPaperStartTime(DateUtils.format(info.getStartTime()));
+        } else {
+            info.setPaperStartTime("");
+        }
+        if (info.getEndTime() != null) {
+            info.setPaperSubmitTime(DateUtils.format(info.getEndTime()));
+            info.setIsSubmit("是");
+        } else {
+            info.setPaperSubmitTime("");
+            info.setIsSubmit("否");
+        }
+
+        //每次考试持续时间
+        if (info.getStartTime() != null && info.getEndTime() != null) {
+            info.setExamTime(info.getUsedExamTime() > 0 ? DateUtils.diff(info.getUsedExamTime()) : "");
+        } else {
+            info.setExamTime("");
+        }
+
+        //违纪标志
+        info.setDisciplineSign(TrueFalse.of(info.getIsWarn()));
+        info.setIsIllegality(TrueFalse.of(info.getIsIllegalityValue()));
+
+        ExamRecordStatus status = info.getExamRecordStatusValue();
+        if (status != null) {
+            info.setExamRecordStatus(status.name());
+        }
+        IsSuccess result = info.getFaceVerifyResultValue();
+        if (result != null) {
+            info.setFaceVerifyResult(result.getDesc());
+        }
+        
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
+        info.setExamName(examBean.getName());
+
+    }
+    
+    private List<ExamRecordInfo> getExamRecordDetailPageForExport(ExamRecordQuery query,Long startId) {
+        StringBuilder sqlBuilder = new StringBuilder();
+        sqlBuilder.append(EXAM_RECORD_EXPORT_SQL);
+        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+        sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
+        sqlBuilder.append(" and record_data.id >"+startId);
+        sqlBuilder.append(" group by record_data.id ");
+        //根据ip查询
+        if (StringUtils.isNoneBlank(query.getIp())) {
+            sqlBuilder.append(" having ip like '%"+query.getIp()+"%' ");
+        }
+        sqlBuilder.append(" order by record_data.id limit 500");
+        
+        RowMapper<ExamRecordInfo> rowMapper=new BeanPropertyRowMapper<ExamRecordInfo>(ExamRecordInfo.class);
+		List<ExamRecordInfo> ret = jdbcTemplate.query(sqlBuilder.toString(), rowMapper);
+
+        return ret;
+    }
     @Override
     public List<ExamRecordInfo> getExamRecordDetailList(ExamRecordQuery query) {
         Check.isNull(query, "查询参数不能为空!");
@@ -483,6 +865,11 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         sqlBuilder.append(EXAM_RECORD_SQL);
         sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
         sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
+        sqlBuilder.append(" group by record_data.id ");
+        //根据ip查询
+        if (StringUtils.isNoneBlank(query.getIp())) {
+            sqlBuilder.append(" having ip like '%"+query.getIp()+"%' ");
+        }
         sqlBuilder.append(" order by record_data.id desc");
 
         List<ExamRecordDataEntity> examRecordDataList = jdbcTemplate.query(sqlBuilder.toString(), new RowMapper<ExamRecordDataEntity>() {

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

@@ -137,6 +137,90 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         return setCommomScoreInfo(examScore, examStudent.getFinished(), examStudent.getExamStudentId(), examBean, markingType);
     }
 
+    @Override
+    public List<ExamScoreInfo> exportExamScoreListForAsync(ExamScoreQuery query) {
+        Check.isNull(query, "查询参数不能为空!");
+        Check.isNull(query.getExamId(), "请先选择考试批次!");
+
+        //阅卷方式
+        String markingType = ExamCacheTransferHelper.
+                getDefaultCachedExamProperty(query.getExamId(), ExamProperties.MARKING_TYPE.name()).getValue();
+        if (query.getStartLimit() != null && query.getEndLimit() != null) {
+        	return getExamStudentInfoListOfScoreExport(query, markingType);
+        }else {
+        	List<ExamStudentEntity> examStudentList=new ArrayList<ExamStudentEntity>();
+            Long startId=0L;
+            for(;;) {
+            	List<ExamStudentEntity> tem=getExamStudentInfoListOfScoreExportByPage(query, markingType, startId);
+            	if(tem==null||tem.size()==0) {
+            		break;
+            	}else {
+            		startId=tem.get(tem.size()-1).getId();
+            		examStudentList.addAll(tem);
+            	}
+            }
+            
+            List<ExamScoreInfo> examScoreInfoList = new ArrayList<ExamScoreInfo>();
+            //缓存
+            for (ExamStudentEntity examStudent : examStudentList) {
+                long courseId = examStudent.getCourseId();
+                CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
+
+                long orgId = examStudent.getOrgId();
+                OrgCacheBean orgBean = gainBaseDataService.getOrgBean(orgId);
+
+                examScoreInfoList.add(convertToExamScoreInfo(examStudent, courseBean, orgBean, markingType));
+            }
+            return examScoreInfoList;
+        }
+    }
+    
+    private List<ExamStudentEntity> getExamStudentInfoListOfScoreExportByPage(ExamScoreQuery query, String markingType,Long startId) {
+        //查询条件
+        StringBuffer sql = new StringBuffer();
+        sql.append("select id,exam_student_id,exam_id,course_id,course_code,course_level,finished,student_id,student_code,student_name,identity_number"
+                + ",info_collector,root_org_id,org_id,paper_type,used_num,extra_num"
+                + ",specialty_code,specialty_name,grade from ec_oe_exam_student where 1=1 ");
+        if (query.getOrgId() != null) {
+            sql.append(" and org_id=" + query.getOrgId());
+        }
+        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() + "%'");
+        }
+        if (StringUtils.isNotBlank(query.getStudentName())) {
+            sql.append(" and student_name LIKE '%" + query.getStudentName() + "%'");
+        }
+        if (StringUtils.isNotBlank(query.getIdentityNumber())) {
+            sql.append(" and identity_number LIKE '" + query.getIdentityNumber() + "%'");
+        }
+        if (query.getCourseId() != null) {
+            sql.append(" and course_id=" + query.getCourseId());
+        }
+        if (StringUtils.isNotBlank(query.getCourseLevel())) {
+            sql.append(" and course_level= '" + query.getCourseLevel() + "'");
+        }
+        if (query.getFinished() != null) {
+            sql.append(" and finished= " + query.getFinished());
+        }
+        sql.append(" and id >"+startId);
+        sql.append(" order by id limit 500 ");
+
+        List<ExamStudentEntity> examStudentList = jdbcTemplate.query(sql.toString(), new RowMapper<ExamStudentEntity>() {
+            @Override
+            public ExamStudentEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
+                return getExamStudentEntityByResultSet(rs);
+            }
+        });
+
+        return examStudentList;
+    }
+    
     @Override
     public List<ExamScoreInfo> exportExamScoreList(ExamScoreQuery query) {
         Check.isNull(query, "查询参数不能为空!");

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

@@ -7,10 +7,53 @@
 
 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 javax.persistence.EntityManager;
+import javax.persistence.Query;
+
+import org.apache.commons.lang3.StringUtils;
+import org.hibernate.query.NativeQuery;
+import org.hibernate.transform.Transformers;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.collect.Lists;
+
 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.commons.util.StringUtil;
+import cn.com.qmth.examcloud.core.basic.api.StudentCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.StudentBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetStudentListByIdsReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetStudentListByIdsResp;
 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.BatchSetDataUtil;
 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;
@@ -23,10 +66,20 @@ 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.ExamRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentFinalScoreService;
+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.LocalCacheService;
 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.bean.examstudent.CourseProgressInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentEntityConvert;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentFinishedStatistic;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentOrgStatistic;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentPartInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentQuery;
 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;
@@ -37,7 +90,12 @@ import cn.com.qmth.examcloud.examwork.api.request.GetOngoingExamListReq;
 import cn.com.qmth.examcloud.examwork.api.response.GetExamStudentResp;
 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.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.ExamStageCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.examing.ExamBoss;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
@@ -45,33 +103,6 @@ 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;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.data.domain.Pageable;
-import org.springframework.jdbc.core.BeanPropertyRowMapper;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-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 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;
 
 /**
  * 考生信息接口
@@ -82,6 +113,7 @@ import static cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamS
 @Service
 public class ExamStudentServiceImpl implements ExamStudentService {
     private static final Logger log = LoggerFactory.getLogger(ExamStudentServiceImpl.class);
+    
     @Autowired
     private ExamStudentRepo examStudentRepo;
     @Autowired
@@ -111,6 +143,8 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     ExamStudentCloudService examStudentCloudService;
     @Autowired
     private ExamRecordFileAnswerRepo examRecordFileAnswerRepo;
+    @Autowired
+    private StudentCloudService studentCloudService;
 
     @Transactional
     @Override
@@ -267,6 +301,93 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         return jdbcTemplate.queryForObject(sql.toString(), Long.class);
     }
 
+    @Override
+    public List<ExamStudentInfo> getExamStudentInfoListForAsync(ExamStudentQuery query) {
+        Check.isNull(query, "查询参数不能为空!");
+        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
+
+        List<ExamStudentEntity> examStudentList=new ArrayList<ExamStudentEntity>();
+        Long startId=0L;
+        for(;;) {
+        	List<ExamStudentEntity> tem=getExamStudentInfoListByPage(query, examBean, startId);
+        	if(tem==null||tem.size()==0) {
+        		break;
+        	}else {
+        		startId=tem.get(tem.size()-1).getId();
+        		examStudentList.addAll(tem);
+        	}
+        }
+        List<ExamStudentInfo> examStudentInfoList = new ArrayList<ExamStudentInfo>();
+        if(examStudentList==null||examStudentList.size()==0) {
+       	 	return examStudentInfoList;
+        }
+        for (ExamStudentEntity examStudentEntity : examStudentList) {
+            ExamStudentInfo examStudentInfo = buildExamStudentInfoForExport(examStudentEntity, examBean.getExamType());
+            examStudentInfoList.add(examStudentInfo);
+            if (ExamType.ONLINE.name().equals(examBean.getExamType()) || ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) {
+                if (query.getFinished() != null) {
+                    if (query.getFinished().intValue() == 1) {
+                        examStudentInfo.setFinished(true);
+                        examStudentInfo.setFinishedStatus(FinishStatus.已完成.name());
+                    }
+                    if (query.getFinished().intValue() == 0) {
+                        examStudentInfo.setFinished(false);
+                        examStudentInfo.setFinishedStatus(FinishStatus.未完成.name());
+                    }
+                }
+                countUseExamTimes(examStudentInfo, examBean.getExamType());
+            }
+        }
+        setPhone(examStudentInfoList, query.getRootOrgId());
+        return examStudentInfoList;
+    }
+    private void setPhone(List<ExamStudentInfo> dataList,Long rootOrgId) {
+    	GetStudentListByIdsReq req= new GetStudentListByIdsReq();
+        BatchSetDataUtil<ExamStudentInfo> tool = new BatchSetDataUtil<ExamStudentInfo>() {
+			@Override
+			public void setData(List<ExamStudentInfo> dataList) {
+				req.setRootOrgId(rootOrgId);
+				List<Long> ids = dataList.stream().map(dto -> dto.getStudentId()).distinct().collect(Collectors.toList());
+				req.setStudentIdList(ids);
+				GetStudentListByIdsResp resp=studentCloudService.getStudentListByIds(req);
+				if(resp.getStudentBeanList()!=null&&resp.getStudentBeanList().size()>0) {
+					Map<Long, String> map = resp.getStudentBeanList().stream()
+							.collect(Collectors.toMap(StudentBean::getId, account -> (account.getPhoneNumber()==null?"":account.getPhoneNumber())));
+					for(ExamStudentInfo erInfo:dataList) {
+						erInfo.setPhone(map.get(erInfo.getStudentId()));
+					}
+				}
+			}
+
+		};
+		tool.setDataForBatch(dataList, 100);
+    }
+    private List<ExamStudentEntity> getExamStudentInfoListByPage(ExamStudentQuery query,ExamSettingsCacheBean examBean,Long startId) {
+    	//查询条件
+        StringBuffer sql = new StringBuffer();
+        sql.append("select id,exam_student_id,exam_id,course_id,course_code,course_level");
+        if ((ExamType.ONLINE.name().equals(examBean.getExamType()) || ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) && query.getFinished() == null) {
+            sql.append(",case when finished = 1 OR t1.exam_student_id in ( SELECT t2.exam_student_id FROM ec_oes_exam_record_data t2 WHERE t2.exam_id=" + query.getExamId() + "  ) then 1 else 0 end finished");
+        } else {
+            sql.append(",finished");
+        }
+
+        sql.append(",student_id,student_code,student_name,identity_number"
+                + ",info_collector,root_org_id,org_id,paper_type,used_num,extra_num"
+                + ",specialty_code,specialty_name,grade from ec_oe_exam_student t1 where 1=1 ");
+        sql.append(selectExamStudentConfitionSql(query, examBean.getExamType()));
+        sql.append(" and id >"+startId);
+        sql.append(" order by id limit 500");
+
+        List<ExamStudentEntity> examStudentList = jdbcTemplate.query(sql.toString(), new RowMapper<ExamStudentEntity>() {
+            @Override
+            public ExamStudentEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
+                return getExamStudentEntityByResultSet(rs);
+            }
+        });
+        
+        return examStudentList;
+    }
     @Override
     public List<ExamStudentInfo> getExamStudentInfoList(ExamStudentQuery query) {
         Check.isNull(query, "查询参数不能为空!");
@@ -373,9 +494,12 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         }
         return sql;
     }
-
-    private ExamStudentInfo buildExamStudentInfo(ExamStudentEntity examStudentEntity, Map<String, Object> cahcheMap, String examType) {
-        ExamStudentInfo examStudentInfo = ExamStudentEntityConvert.of(examStudentEntity);
+    private ExamStudentInfo buildExamStudentInfoForExport(ExamStudentEntity examStudentEntity, String examType) {
+    	ExamStudentInfo examStudentInfo = buildExamStudentInfoBase(examStudentEntity, examType);
+        return examStudentInfo;
+    }
+    private ExamStudentInfo buildExamStudentInfoBase(ExamStudentEntity examStudentEntity,String examType) {
+    	ExamStudentInfo examStudentInfo = ExamStudentEntityConvert.of(examStudentEntity);
         examStudentInfo.setExamType(examType);
         CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(examStudentInfo.getCourseId());
         examStudentInfo.setCourseName(courseBean.getName());
@@ -386,8 +510,8 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         examStudentInfo.setOrgCode(orgBean.getCode());
         examStudentInfo.setOrgName(orgBean.getName());
 
-        String photoNumber = localCacheService.getStudentPhotoNumber(cahcheMap, examStudentInfo.getStudentId());
-        examStudentInfo.setPhone(photoNumber);//电话号码
+//        String photoNumber = localCacheService.getStudentPhotoNumber(cahcheMap, examStudentInfo.getStudentId());
+//        examStudentInfo.setPhone(photoNumber);//电话号码
 
         if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
             //完成状态
@@ -419,6 +543,12 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         }
         return examStudentInfo;
     }
+    private ExamStudentInfo buildExamStudentInfo(ExamStudentEntity examStudentEntity, Map<String, Object> cahcheMap, String examType) {
+        ExamStudentInfo examStudentInfo = buildExamStudentInfoBase(examStudentEntity, examType);
+        String photoNumber = localCacheService.getStudentPhotoNumber(cahcheMap, examStudentInfo.getStudentId());
+        examStudentInfo.setPhone(photoNumber);//电话号码
+        return examStudentInfo;
+    }
 
     private List<ExamRecordFileAnswerInfo> getOfflineFilesFrom(List<ExamRecordFileAnswerEntity> fileAnswerList) {
         List<ExamRecordFileAnswerInfo> resultList = new ArrayList<>();
@@ -553,7 +683,6 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         });
         long totalSize = countReExamine(query, examBean);
         List<ExamStudentInfo> list = new ArrayList<ExamStudentInfo>();
-        Map<String, Object> cahcheMap = new HashMap<String, Object>();
         for (ExamStudentEntity examStudentEntity : examStudentList) {
             ExamStudentInfo examStudentInfo = ExamStudentEntityConvert.of(examStudentEntity);
             OrgCacheBean orgBean = gainBaseDataService.getOrgBean(examStudentInfo.getOrgId());
@@ -734,6 +863,17 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         return ExamStudentEntityConvert.of(entity);
     }
 
+    @Override
+    public boolean isEnableExamStudent(Long examStudentId) {
+        ExamStudentEntity entity = examStudentRepo.findByExamStudentId(examStudentId);
+        if(entity == null){
+            log.warn("ExamStudent is not exist, id is " + examStudentId);
+            throw new StatusException("000500", "考生信息不存在!");
+        }
+
+        return entity.getEnable();
+    }
+
     @Override
     public List<Long> findCoursesFromExamStudent(Long examId, Long examStageId, Long orgId) {
         String sql = "select course_id from ec_oe_exam_student where exam_id = " + examId;
@@ -939,6 +1079,21 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         for (ExamStudentEntity examStudent : examStudents) {
             assemblingExamStudentDto(examStudent, now, examStudentDtoList);
         }
+        
+        for(OnHandExamInfo info:examStudentDtoList) {
+        	ExamPropertyCacheBean examCycleEnabledCache = CacheHelper.getExamProperty(info.getExamId(), ExamProperties.EXAM_CYCLE_ENABLED.name());
+            if(examCycleEnabledCache!=null&&StringUtil.isTrue(examCycleEnabledCache.getValue())) {
+            	info.setExamCycleEnabled(true);
+            	
+            	ExamPropertyCacheBean examCycleWeekCache = CacheHelper.getExamProperty(info.getExamId(), ExamProperties.EXAM_CYCLE_WEEK.name());
+            
+            	info.setExamCycleWeek(JSONObject.parseArray(examCycleWeekCache.getValue()));
+            	ExamPropertyCacheBean examCycleTimeRangeCache = CacheHelper.getExamProperty(info.getExamId(), ExamProperties.EXAM_CYCLE_TIME_RANGE.name());
+            	info.setExamCycleTimeRange(JSONObject.parseArray(examCycleTimeRangeCache.getValue()));
+            }else {
+            	info.setExamCycleEnabled(false);
+            }
+        }
 
         return examStudentDtoList;
     }

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

@@ -5,9 +5,30 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.persistence.criteria.Predicate;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
 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.basic.api.UserCloudService;
 import cn.com.qmth.examcloud.core.basic.api.request.GetUserReq;
 import cn.com.qmth.examcloud.core.basic.api.response.GetUserResp;
@@ -21,19 +42,10 @@ import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTaskInf
 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.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamMapsReq;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamMapsResp;
 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;
 
 /**
  * 导出任务相关接口
@@ -41,16 +53,20 @@ 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;
-    
+
     @Autowired
     private UserCloudService userCloudService;
+    
+    
+    @Override
+    public ExportTaskEntity findExportTaskToDispose() {
+    	return exportTaskRepo.findExportTaskToDispose();
+    }
 
     @Override
     @Transactional
@@ -67,11 +83,30 @@ public class ExportTaskServiceImpl implements ExportTaskService {
         entity.setExamId(info.getExamId());
         entity.setFilePath(info.getFilePath());
         entity.setCreator(info.getCreator());
-
+        entity.setExportParam(info.getJsonParams());
         exportTaskRepo.save(entity);
         return entity.getId();
     }
 
+    @Transactional
+    @Override
+    public void updateExportTaskStatus(Long taskId, ExportTaskStatus status) {
+        this.updateExportTaskStatus(taskId, status, null, null);
+    }
+    
+    @Transactional
+    @Override
+    public void startExportTask(Long taskId) {
+    	exportTaskRepo.startExportTask(taskId, ExportTaskStatus.EXPORTING, new Date());
+    }
+    
+    @Transactional
+    @Override
+    public void endExportTask(Long taskId) {
+    	exportTaskRepo.endExportTask(taskId,new Date());
+    }
+    
+    @Transactional
     @Override
     public void updateExportTaskStatus(Long taskId, ExportTaskStatus status, String statusMsg) {
         this.updateExportTaskStatus(taskId, status, statusMsg, null);
@@ -149,7 +184,7 @@ public class ExportTaskServiceImpl implements ExportTaskService {
     }
 
     @Override
-    public Page<ExportTaskListResp> getExportTaskList(ExportTaskListReq req,Long userId) {
+    public Page<ExportTaskListResp> getExportTaskList(ExportTaskListReq req, Long userId) {
         Check.isNull(req.getRootOrgId(), "学校ID不能为空!");
 
         if (req.getPageNo() == null || req.getPageNo() < 1) {
@@ -164,7 +199,7 @@ public class ExportTaskServiceImpl implements ExportTaskService {
             List<Predicate> predicates = new ArrayList<>();
             predicates.add(cb.equal(root.get("rootOrgId"), req.getRootOrgId()));
             predicates.add(cb.equal(root.get("creator"), userId));
-            
+
             if (req.getExamId() != null) {
                 predicates.add(cb.equal(root.get("examId"), req.getExamId()));
             }
@@ -189,29 +224,28 @@ public class ExportTaskServiceImpl implements ExportTaskService {
         }
 
         Set<Long> examIds = page.getContent().stream().filter(e -> e.getExamId() != null).map(e -> e.getExamId()).collect(Collectors.toSet());
-        Map<Long, String> examNames;
+        Map<Long, ExamBean> examMaps;
         if (CollectionUtils.isNotEmpty(examIds)) {
-            GetExamNamesReq namesReq = new GetExamNamesReq();
-            namesReq.setExamIds(examIds);
-            GetExamNamesResp namesResp = examCloudService.getExamNames(namesReq);
-            examNames = namesResp.getExamNames();
+            GetExamMapsReq examsReq = new GetExamMapsReq();
+            examsReq.setExamIds(examIds);
+            GetExamMapsResp examsResp = examCloudService.getExamMaps(examsReq);
+            examMaps = examsResp.getExamMaps();
         } else {
-            examNames = new HashMap<>();
+            examMaps = new HashMap<>();
         }
-        
-        GetUserReq reqUser=new GetUserReq();
+
+        GetUserReq reqUser = new GetUserReq();
         reqUser.setRootOrgId(req.getRootOrgId());
         reqUser.setUserId(userId);
         GetUserResp namesResp = userCloudService.getUser(reqUser);
-        String createName=namesResp.getUserBean().getName();
-        
+        String createName = namesResp.getUserBean().getName();
 
         List<ExportTaskListResp> list = page.getContent()
-                .stream().map(entity -> ofExportTask(entity, examNames,createName)).collect(Collectors.toList());
+                .stream().map(entity -> ofExportTask(entity, examMaps, createName)).collect(Collectors.toList());
         return new PageImpl<>(list, pageable, page.getTotalElements());
     }
 
-    private ExportTaskListResp ofExportTask(ExportTaskEntity entity, Map<Long, String> examNames,String createName) {
+    private ExportTaskListResp ofExportTask(ExportTaskEntity entity, Map<Long, ExamBean> examMaps, String createName) {
         ExportTaskListResp info = new ExportTaskListResp();
 
         info.setId(entity.getId());
@@ -222,12 +256,15 @@ public class ExportTaskServiceImpl implements ExportTaskService {
         info.setStatusMsg(entity.getStatusMsg());
         info.setExamId(entity.getExamId());
         if (entity.getExamId() != null) {
-            info.setExamName(examNames.get(entity.getExamId()));
+            ExamBean exam = examMaps.get(entity.getExamId());
+            info.setExamName(exam != null ? exam.getName() : "");
         }
         info.setFilePath(FileStorageUtil.realPath(entity.getFilePath()));
         info.setCreationTime(entity.getCreationTime());
         info.setUpdateTime(entity.getUpdateTime());
         info.setCreateName(createName);
+        info.setStartTime(entity.getStartTime());
+        info.setEndTime(entity.getEndTime());
 
         return info;
     }

+ 2 - 6
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/LocalCacheServiceImpl.java

@@ -2,19 +2,15 @@ package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
 import java.util.Map;
 
-import cn.com.qmth.examcloud.support.cache.CacheHelper;
-import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
-import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
 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.basic.api.bean.OrgBean;
-import cn.com.qmth.examcloud.core.basic.api.bean.StudentBean;
 import cn.com.qmth.examcloud.core.oe.admin.base.Constants;
 import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
 import cn.com.qmth.examcloud.core.oe.admin.service.LocalCacheService;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamStudentBean;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
 
 
 /**

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

@@ -29,6 +29,8 @@ 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
@@ -48,6 +50,8 @@ import java.util.List;
 @Service("offlineExamService")
 public class OfflineExamServiceImpl implements OfflineExamService {
 
+    private static final Logger log = LoggerFactory.getLogger(OfflineExamServiceImpl.class);
+
     @Autowired
     private ExamStudentRepo examStudentRepo;
 
@@ -78,8 +82,14 @@ public class OfflineExamServiceImpl implements OfflineExamService {
     @Override
     public List<OfflineExamCourseInfo> getOfflineCourse(Long studentId) {
         List<ExamStudentEntity> examStudents = examStudentRepo.findByStudentId(studentId);
+
         List<OfflineExamCourseInfo> offlineExamCourseInfoList = new ArrayList<>();
         for (ExamStudentEntity examStudent : examStudents) {
+            if(!examStudent.getEnable()){
+                log.warn("ExamStudent is disable, id is " + examStudent.getExamStudentId());
+                continue;
+            }
+
             ExamSettingsCacheBean examBean =
                     ExamCacheTransferHelper.getCachedExam(examStudent.getExamId(), studentId,examStudent.getExamStageId());
             if (!ExamType.OFFLINE.name().equals(examBean.getExamType())) {
@@ -334,7 +344,7 @@ public class OfflineExamServiceImpl implements OfflineExamService {
                 env.setRootOrgId(String.valueOf(bean.getRootOrgId()));
                 env.setRelativePath(relativePath);
                 YunPathInfo pi = FileStorageUtil.saveFile("offlineFile", env, tempFile, null);
-//                tempFile.delete();
+                //                tempFile.delete();
 
                 //保存离线文件至数据库
                 ExamRecordFileAnswerEntity entity = new ExamRecordFileAnswerEntity();

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

@@ -1,12 +1,27 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
-import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.alibaba.fastjson.JSONObject;
+
+import cn.com.qmth.examcloud.commons.util.StringUtil;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.QuestionTypeUtil;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordQuestionsRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamStudentRepo;
-import cn.com.qmth.examcloud.core.oe.admin.dao.entity.*;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamQuestionEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordPaperStructEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordQuestionsEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordPaperStructService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
@@ -15,20 +30,15 @@ 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.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.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.enums.ExamProperties;
 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;
-
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
 
 
 /**
@@ -73,6 +83,20 @@ public class PracticeServiceImpl implements PracticeService {
                     ExamCacheTransferHelper.getCachedExam(examId, studentId,examStudent.getExamStageId());
             practiceCourseInfos.add(buildPracticeCourseInfo(examStudent, examBean));
         }
+        for(PracticeCourseInfo practiceCourseInfo:practiceCourseInfos) {
+        	ExamPropertyCacheBean examCycleEnabledCache = CacheHelper.getExamProperty(practiceCourseInfo.getExamId(), ExamProperties.EXAM_CYCLE_ENABLED.name());
+            if(examCycleEnabledCache!=null&&StringUtil.isTrue(examCycleEnabledCache.getValue())) {
+            	practiceCourseInfo.setExamCycleEnabled(true);
+            	
+            	ExamPropertyCacheBean examCycleWeekCache = CacheHelper.getExamProperty(practiceCourseInfo.getExamId(), ExamProperties.EXAM_CYCLE_WEEK.name());
+            
+            	practiceCourseInfo.setExamCycleWeek(JSONObject.parseArray(examCycleWeekCache.getValue()));
+            	ExamPropertyCacheBean examCycleTimeRangeCache = CacheHelper.getExamProperty(practiceCourseInfo.getExamId(), ExamProperties.EXAM_CYCLE_TIME_RANGE.name());
+            	practiceCourseInfo.setExamCycleTimeRange(JSONObject.parseArray(examCycleTimeRangeCache.getValue()));
+            }else {
+            	practiceCourseInfo.setExamCycleEnabled(false);
+            }
+        }
         return practiceCourseInfos;
     }
 

+ 39 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/util/AsyncExportConcurrentUtil.java

@@ -0,0 +1,39 @@
+package cn.com.qmth.examcloud.core.oe.admin.service.util;
+
+import java.util.concurrent.locks.ReentrantLock;
+
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.exporttask.ExportTask;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+
+public class AsyncExportConcurrentUtil {
+	private static Boolean taskThreadInit = false;
+	private static ReentrantLock checkTaskThreadInitLock = new ReentrantLock();
+
+	private static Integer taskThreadCount = 1;
+
+	public static void checkOrStartJob() {
+		if (taskThreadInit) {
+			return;
+		}
+		try {
+			checkTaskThreadInitLock.lock();
+			if (!taskThreadInit) {
+				taskThreadInit = true;
+				SysPropertyCacheBean spc = CacheHelper.getSysProperty("oe.export.jobCount");
+				if (spc != null && spc.getValue() != null) {
+					taskThreadCount = (Integer) spc.getValue();
+				}
+				for (int i = 0; i < taskThreadCount; i++) {
+					ExportTask thr = new ExportTask();
+					thr.start();
+				}
+			}
+		} finally {
+			if (checkTaskThreadInitLock.isLocked()) {
+				checkTaskThreadInitLock.unlock();
+			}
+		}
+	}
+
+}

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

@@ -39,7 +39,7 @@ public class OEAdminApp {
 	static {
 		String runtimeLevel = System.getProperty("log.commonLevel");
 		if (null == runtimeLevel) {
-			System.setProperty("log.commonLevel", "DEBUG");
+			System.setProperty("log.commonLevel", "INFO");
 		}
 		System.setProperty("hibernate.dialect.storage_engine", "innodb");
 		DataIntegrityViolationTransverter.setUniqueRules(UniqueRuleHolder.getUniqueRuleList());

+ 121 - 106
examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/config/ExamCloudResourceManager.java

@@ -1,28 +1,27 @@
 package cn.com.qmth.examcloud.core.oe.admin.stater.config;
 
-import java.util.List;
-import java.util.Set;
-
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import com.google.common.collect.Sets;
-
-import cn.com.qmth.examcloud.api.commons.security.bean.AccessApp;
-import cn.com.qmth.examcloud.api.commons.security.bean.Role;
-import cn.com.qmth.examcloud.api.commons.security.bean.User;
-import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
+import cn.com.qmth.examcloud.api.commons.security.bean.*;
 import cn.com.qmth.examcloud.api.commons.security.enums.RoleMeta;
 import cn.com.qmth.examcloud.commons.util.PathUtil;
 import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
 import cn.com.qmth.examcloud.commons.util.RegExpUtil;
+import cn.com.qmth.examcloud.core.basic.api.UserDataRuleCloudService;
+import cn.com.qmth.examcloud.core.basic.api.request.QueryUserDataRuleReq;
+import cn.com.qmth.examcloud.core.basic.api.response.QueryUserDataRuleResp;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.support.cache.bean.AppCacheBean;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
 import cn.com.qmth.examcloud.web.security.ResourceManager;
 import cn.com.qmth.examcloud.web.support.ApiInfo;
+import com.google.common.collect.Sets;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.Set;
 
 /**
  * Demo资源管理器
@@ -34,97 +33,113 @@ import cn.com.qmth.examcloud.web.support.ApiInfo;
 @Component
 public class ExamCloudResourceManager implements ResourceManager {
 
-	@Autowired
-	RedisClient redisClient;
-
-	static {
-		PropertiesUtil.loadFromPath(PathUtil.getResoucePath("security.properties"));
-	}
-
-	@Override
-	public AccessApp getAccessApp(Long appId) {
-		AppCacheBean appCacheBean = CacheHelper.getApp(appId);
-		AccessApp app = new AccessApp();
-		app.setAppId(appCacheBean.getId());
-		app.setAppCode(appCacheBean.getCode());
-		app.setAppName(appCacheBean.getName());
-		app.setSecretKey(appCacheBean.getSecretKey());
-		app.setTimeRange(appCacheBean.getTimeRange());
-		return app;
-	}
-
-	@Override
-	public boolean isNaked(ApiInfo apiInfo, String mapping) {
-		if (null == apiInfo) {
-			return true;
-		}
-
-		if (null != apiInfo) {
-			if (apiInfo.isNaked()) {
-				return true;
-			}
-		}
-		if(mapping.matches(".*swagger.*")) {
-			return true;
-		}
-
-		return false;
-	}
-
-	@Override
-	public boolean hasPermission(User user, ApiInfo apiInfo, String mapping) {
-
-		// 学生鉴权
-		if (user.getUserType().equals(UserType.STUDENT)) {
-			return true;
-		}
-
-		List<Role> roleList = user.getRoleList();
-
-		if (CollectionUtils.isEmpty(roleList)) {
-			return false;
-		}
-
-		for (Role role : roleList) {
-			if (role.getRoleCode().equals(RoleMeta.SUPER_ADMIN.name())) {
-				return true;
-			}
-		}
-
-		// 权限组集合
-		String privilegeGroups = PropertiesUtil.getString(mapping);
-		if (StringUtils.isBlank(privilegeGroups)) {
-			return true;
-		}
-
-		// 用户权限集合
-		Set<String> rolePrivilegeList = Sets.newHashSet();
-		Long rootOrgId = user.getRootOrgId();
-		for (Role role : roleList) {
-			String key = "$_P_" + rootOrgId + "_" + role.getRoleId();
-			String rolePrivileges = redisClient.get(key, String.class);
-
-			List<String> rpList = RegExpUtil.findAll(rolePrivileges, "\\w+");
-			rolePrivilegeList.addAll(rpList);
-		}
-
-		List<String> privilegeGroupList = RegExpUtil.findAll(privilegeGroups, "[^\\;]+");
-
-		for (String pg : privilegeGroupList) {
-			pg = pg.trim();
-			if (StringUtils.isBlank(pg)) {
-				continue;
-			}
-
-			List<String> pList = RegExpUtil.findAll(pg, "[^\\,]+");
-			if (rolePrivilegeList.containsAll(pList)) {
-				return true;
-			} else {
-				continue;
-			}
-		}
-
-		return false;
-	}
+    @Autowired
+    RedisClient redisClient;
+
+    @Autowired
+    UserDataRuleCloudService userDataRuleCloudService;
+
+    static {
+        PropertiesUtil.loadFromPath(PathUtil.getResoucePath("security.properties"));
+    }
+
+    @Override
+    public AccessApp getAccessApp(Long appId) {
+        AppCacheBean appCacheBean = CacheHelper.getApp(appId);
+        AccessApp app = new AccessApp();
+        app.setAppId(appCacheBean.getId());
+        app.setAppCode(appCacheBean.getCode());
+        app.setAppName(appCacheBean.getName());
+        app.setSecretKey(appCacheBean.getSecretKey());
+        app.setTimeRange(appCacheBean.getTimeRange());
+        return app;
+    }
+
+    @Override
+    public boolean isNaked(ApiInfo apiInfo, String mapping) {
+        if (null == apiInfo) {
+            return true;
+        }
+
+        if (null != apiInfo) {
+            if (apiInfo.isNaked()) {
+                return true;
+            }
+        }
+        if (mapping.matches(".*swagger.*")) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    public boolean hasPermission(User user, ApiInfo apiInfo, String mapping) {
+
+        // 学生鉴权
+        if (user.getUserType().equals(UserType.STUDENT)) {
+            return true;
+        }
+
+        List<Role> roleList = user.getRoleList();
+
+        if (CollectionUtils.isEmpty(roleList)) {
+            return false;
+        }
+
+        for (Role role : roleList) {
+            if (role.getRoleCode().equals(RoleMeta.SUPER_ADMIN.name())) {
+                return true;
+            }
+        }
+
+        // 权限组集合
+        String privilegeGroups = PropertiesUtil.getString(mapping);
+        if (StringUtils.isBlank(privilegeGroups)) {
+            return true;
+        }
+
+        // 用户权限集合
+        Set<String> rolePrivilegeList = Sets.newHashSet();
+        Long rootOrgId = user.getRootOrgId();
+        for (Role role : roleList) {
+            String key = "$_P_" + rootOrgId + "_" + role.getRoleId();
+            String rolePrivileges = redisClient.get(key, String.class);
+
+            List<String> rpList = RegExpUtil.findAll(rolePrivileges, "\\w+");
+            rolePrivilegeList.addAll(rpList);
+        }
+
+        List<String> privilegeGroupList = RegExpUtil.findAll(privilegeGroups, "[^\\;]+");
+
+        for (String pg : privilegeGroupList) {
+            pg = pg.trim();
+            if (StringUtils.isBlank(pg)) {
+                continue;
+            }
+
+            List<String> pList = RegExpUtil.findAll(pg, "[^\\,]+");
+            if (rolePrivilegeList.containsAll(pList)) {
+                return true;
+            } else {
+                continue;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public UserDataRule loadUserDataRule(Long userId, DataRuleType dataRuleType) {
+        QueryUserDataRuleReq req = new QueryUserDataRuleReq();
+        req.setUserId(userId);
+        req.setType(dataRuleType);
+        QueryUserDataRuleResp resp = userDataRuleCloudService.queryUserDataRule(req);
+
+        UserDataRule userDataRule = new UserDataRule();
+        userDataRule.setGlobalStatus(resp.getGlobalStatus());
+        userDataRule.setRefIds(resp.getRefIds());
+        return userDataRule;
+    }
 
 }

+ 28 - 27
examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/config/ExamCloudWebMvcConfigurer.java

@@ -2,19 +2,19 @@ package cn.com.qmth.examcloud.core.oe.admin.stater.config;
 
 import cn.com.qmth.examcloud.web.interceptor.ApiFlowLimitedInterceptor;
 import cn.com.qmth.examcloud.web.interceptor.ApiStatisticInterceptor;
+import cn.com.qmth.examcloud.web.interceptor.FirstInterceptor;
 import cn.com.qmth.examcloud.web.interceptor.SeqlockInterceptor;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.security.DataRuleInterceptor;
+import cn.com.qmth.examcloud.web.security.RequestPermissionInterceptor;
+import cn.com.qmth.examcloud.web.security.ResourceManager;
+import cn.com.qmth.examcloud.web.security.RpcInterceptor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
-import cn.com.qmth.examcloud.web.interceptor.FirstInterceptor;
-import cn.com.qmth.examcloud.web.redis.RedisClient;
-import cn.com.qmth.examcloud.web.security.RequestPermissionInterceptor;
-import cn.com.qmth.examcloud.web.security.ResourceManager;
-import cn.com.qmth.examcloud.web.security.RpcInterceptor;
-
 /**
  * WebMvcConfigurer
  *
@@ -25,29 +25,30 @@ import cn.com.qmth.examcloud.web.security.RpcInterceptor;
 @Configuration
 public class ExamCloudWebMvcConfigurer implements WebMvcConfigurer {
 
-	@Autowired
-	ResourceManager resourceManager;
+    @Autowired
+    ResourceManager resourceManager;
+
+    @Autowired
+    RedisClient redisClient;
 
-	@Autowired
-	RedisClient redisClient;
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**");
+        registry.addInterceptor(new ApiFlowLimitedInterceptor()).addPathPatterns("/**");
+        registry.addInterceptor(new RpcInterceptor(resourceManager)).addPathPatterns("/**");
 
-	@Override
-	public void addInterceptors(InterceptorRegistry registry) {
-		registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**");
-		registry.addInterceptor(new ApiFlowLimitedInterceptor()).addPathPatterns("/**");
-		registry.addInterceptor(new RpcInterceptor(resourceManager)).addPathPatterns("/**");
+        RequestPermissionInterceptor permissionInterceptor = new RequestPermissionInterceptor(resourceManager, redisClient);
+        registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
+        registry.addInterceptor(new SeqlockInterceptor(redisClient)).addPathPatterns("/**");
+        registry.addInterceptor(new ApiStatisticInterceptor()).addPathPatterns("/**");
 
-		RequestPermissionInterceptor permissionInterceptor = new RequestPermissionInterceptor(
-				resourceManager, redisClient);
-		registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
-		registry.addInterceptor(new SeqlockInterceptor(redisClient)).addPathPatterns("/**");
-		registry.addInterceptor(new ApiStatisticInterceptor()).addPathPatterns("/**");
-	}
+        registry.addInterceptor(new DataRuleInterceptor(resourceManager)).addPathPatterns("/api/**");
+    }
 
-	@Override
-	public void addCorsMappings(CorsRegistry registry) {
-		registry.addMapping("/**").allowedOrigins("*").allowCredentials(false).allowedMethods("*")
-				.maxAge(3600);
-	}
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**").allowedOrigins("*").allowCredentials(false).allowedMethods("*")
+                .maxAge(3600);
+    }
 
-}
+}

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

@@ -13,7 +13,7 @@
         <id>task_export</id>
         <name>导出任务文件上传地址</name>
         <aliyunId>1</aliyunId>
-        <maxSize>50M</maxSize>
+        <maxSize>100M</maxSize>
         <path>/${relativePath}</path>
     </site>
 </sites>