deason 5 years ago
commit
458da2be7f
95 changed files with 7122 additions and 0 deletions
  1. 13 0
      .gitignore
  2. 22 0
      examcloud-task-api-provider/pom.xml
  3. 96 0
      examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/CopyExamStudentController.java
  4. 237 0
      examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/ExamStudentImportController.java
  5. 85 0
      examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/JobController.java
  6. 71 0
      examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/ReportsComputeController.java
  7. 241 0
      examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/bean/ExamStudentImportDomain.java
  8. 196 0
      examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/provider/DataSyncCloudServiceProvider.java
  9. 50 0
      examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/provider/ReportsComputeCloudServiceProvider.java
  10. 23 0
      examcloud-task-base/pom.xml
  11. 100 0
      examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/bean/ReportsComputeBean.java
  12. 27 0
      examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/enums/ReportsComputeStatus.java
  13. 103 0
      examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/multithread/Basket.java
  14. 60 0
      examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/multithread/Consumer.java
  15. 10 0
      examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/multithread/EndObject.java
  16. 136 0
      examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/multithread/Producer.java
  17. 51 0
      examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/util/BatchGetDataUtil.java
  18. 17 0
      examcloud-task-dao/pom.xml
  19. 19 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/CopyExamStudentRepo.java
  20. 28 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/DataSyncRepo.java
  21. 18 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/ExamStudentImportRepo.java
  22. 24 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/ExamStudentTempRepo.java
  23. 44 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/ReportsComputeRepo.java
  24. 17 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/ScheduleJobRepo.java
  25. 27 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/TaskTraceRepo.java
  26. 94 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/CopyExamStudentEntity.java
  27. 65 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/CopyExamStudentPK.java
  28. 103 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/DataSyncEntity.java
  29. 185 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/ExamStudentImportEntity.java
  30. 443 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/ExamStudentTempEntity.java
  31. 111 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/ReportsComputeEntity.java
  32. 180 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/ScheduleJobEntity.java
  33. 105 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/TaskTraceEntity.java
  34. 34 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/enums/CopyExamStudentStatus.java
  35. 35 0
      examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/enums/ExamStudentImportStatus.java
  36. 63 0
      examcloud-task-service/pom.xml
  37. 38 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/DataSyncService.java
  38. 76 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/DefaultTaskTracker.java
  39. 5 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/ExamStudentCountService.java
  40. 24 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/ReportsComputeService.java
  41. 5 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/StudentCountService.java
  42. 5 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/UserCountService.java
  43. 62 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/consumer/MarkWorkCreateConsumer.java
  44. 45 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/consumer/ReportsComputeConsumer.java
  45. 38 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/dto/MarkWorkCreateDto.java
  46. 52 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/dto/TopicScoreDto.java
  47. 10 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/exception/ReportsComputeStopException.java
  48. 87 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/DataSyncServiceImpl.java
  49. 19 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/ExamStudentCountServiceImpl.java
  50. 806 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/ReportsComputeServiceImpl.java
  51. 19 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/StudentCountServiceImpl.java
  52. 19 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/UserCountServiceImpl.java
  53. 136 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/CopyExamStudentTask.java
  54. 74 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/DataSyncTask.java
  55. 32 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/DisposeOverdueNoticeTask.java
  56. 102 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/DisposePublishingNoticeTask.java
  57. 92 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ExamStudentImportCleanTask.java
  58. 320 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ExamStudentImportDataProcessingTask.java
  59. 373 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ExamStudentImportParsingFileTask.java
  60. 29 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ExamStudentOnlineCountTask.java
  61. 147 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/MarkWorkPaperCreateTask.java
  62. 37 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/OeScorePushTask.java
  63. 38 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ReportsComputeTask.java
  64. 29 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/StudentCumulativeCountTask.java
  65. 29 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/StudentOnlineCountTask.java
  66. 140 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/StudentTotalCountTask.java
  67. 55 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/TaskTraceCleanTask.java
  68. 29 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/UserOnlineCountTask.java
  69. 102 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/producer/MarkWorkCreateProducer.java
  70. 42 0
      examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/producer/ReportsComputeProducer.java
  71. 2 0
      examcloud-task-starter/.logs/interface/task.interface.log
  72. 94 0
      examcloud-task-starter/.logs/task/task.log
  73. 30 0
      examcloud-task-starter/assembly.xml
  74. 67 0
      examcloud-task-starter/pom.xml
  75. 18 0
      examcloud-task-starter/shell/jenkins.sh
  76. 1 0
      examcloud-task-starter/shell/start.args
  77. 38 0
      examcloud-task-starter/shell/start.sh
  78. 1 0
      examcloud-task-starter/shell/start.vmoptions
  79. 18 0
      examcloud-task-starter/shell/stop.sh
  80. 1 0
      examcloud-task-starter/shell/version
  81. 72 0
      examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/TaskApp.java
  82. 12 0
      examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/Tester.java
  83. 132 0
      examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/ExamCloudResourceManager.java
  84. 50 0
      examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/ExamCloudWebMvcConfigurer.java
  85. 88 0
      examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/JobsStartup.java
  86. 32 0
      examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/Swagger2.java
  87. 54 0
      examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/SystemStartup.java
  88. 6 0
      examcloud-task-starter/src/main/resources/application.properties
  89. 1 0
      examcloud-task-starter/src/main/resources/classpath.location
  90. 82 0
      examcloud-task-starter/src/main/resources/log4j2.xml
  91. 1 0
      examcloud-task-starter/src/main/resources/security.properties
  92. 19 0
      jenkins-dev.sh
  93. 4 0
      jenkins-prod.sh
  94. 19 0
      jenkins-test.sh
  95. 31 0
      pom.xml

+ 13 - 0
.gitignore

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

+ 22 - 0
examcloud-task-api-provider/pom.xml

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

+ 96 - 0
examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/CopyExamStudentController.java

@@ -0,0 +1,96 @@
+package cn.com.qmth.examcloud.task.api.controller;
+
+import java.util.List;
+import java.util.Optional;
+
+import javax.transaction.Transactional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamReq;
+import cn.com.qmth.examcloud.examwork.api.request.LockExamStudentsReq;
+import cn.com.qmth.examcloud.task.dao.CopyExamStudentRepo;
+import cn.com.qmth.examcloud.task.dao.entity.CopyExamStudentEntity;
+import cn.com.qmth.examcloud.task.dao.entity.CopyExamStudentPK;
+import cn.com.qmth.examcloud.task.dao.enums.CopyExamStudentStatus;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 考生复制
+ *
+ * @author WANGWEI
+ * @date 2018年9月12日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Transactional
+@RestController
+@RequestMapping("${$rmp.ctr.task}" + "copyExamStudent")
+public class CopyExamStudentController extends ControllerSupport {
+
+	@Autowired
+	CopyExamStudentRepo copyExamStudentRepo;
+
+	@Autowired
+	ExamCloudService examCloudService;
+
+	@ApiOperation(value = "添加考生复制任务")
+	@PostMapping("addCopyTask")
+	public void addCopyTask(@RequestParam(required = true) Long examId1,
+			@RequestParam(required = true) Long examId2) {
+
+		User accessUser = getAccessUser();
+		Long rootOrgId = accessUser.getRootOrgId();
+
+		GetExamReq req = new GetExamReq();
+		req.setRootOrgId(rootOrgId);
+
+		req.setId(examId1);
+		ExamBean exam1 = examCloudService.getExam(req).getExamBean();
+
+		req.setId(examId2);
+		ExamBean exam2 = examCloudService.getExam(req).getExamBean();
+
+		CopyExamStudentPK pk = new CopyExamStudentPK(examId1, examId2);
+		Optional<CopyExamStudentEntity> opt = copyExamStudentRepo.findById(pk);
+		CopyExamStudentEntity one = null;
+		if (opt.isPresent()) {
+			one = opt.get();
+		}
+		if (null != one && (!one.getStatus().equals(CopyExamStudentStatus.COMPLETE))) {
+			throw new StatusException("620001", "复制中,请勿重复操作");
+		}
+
+		if (null == one) {
+			one = new CopyExamStudentEntity();
+			one.setExamId1(exam1.getId());
+			one.setExamId2(exam2.getId());
+			one.setRootOrgId(rootOrgId);
+		}
+
+		one.setStart(1L);
+		one.setStatus(CopyExamStudentStatus.NONE);
+
+		// 锁定
+		List<Long> examIdList = Lists.newArrayList();
+		examIdList.add(one.getExamId1());
+		examIdList.add(one.getExamId2());
+
+		LockExamStudentsReq lockReq = new LockExamStudentsReq();
+		lockReq.setExamIdList(examIdList);
+		examCloudService.lockExamStudents(lockReq);
+
+		copyExamStudentRepo.save(one);
+	}
+
+}

+ 237 - 0
examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/ExamStudentImportController.java

@@ -0,0 +1,237 @@
+package cn.com.qmth.examcloud.task.api.controller;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import javax.persistence.criteria.Predicate;
+import javax.transaction.Transactional;
+
+import org.apache.commons.fileupload.disk.DiskFileItem;
+import org.apache.commons.io.FileUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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.commons.CommonsMultipartFile;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.util.DateUtil;
+import cn.com.qmth.examcloud.commons.util.DateUtil.DatePatterns;
+import cn.com.qmth.examcloud.commons.util.PathUtil;
+import cn.com.qmth.examcloud.core.basic.api.OrgCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.OrgBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetOrgReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgResp;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamReq;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamResp;
+import cn.com.qmth.examcloud.task.api.controller.bean.ExamStudentImportDomain;
+import cn.com.qmth.examcloud.task.dao.ExamStudentImportRepo;
+import cn.com.qmth.examcloud.task.dao.entity.ExamStudentImportEntity;
+import cn.com.qmth.examcloud.task.dao.enums.ExamStudentImportStatus;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 考生导入
+ *
+ * @author WANGWEI
+ * @date 2018年7月31日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Transactional
+@RestController
+@RequestMapping("${$rmp.ctr.task}" + "examStudentImport")
+public class ExamStudentImportController extends ControllerSupport {
+
+	@Autowired
+	SystemProperties systemConfig;
+
+	private static final String EXAM_STUDENT_IMPORT_FILES = "exam_student_import_files";
+
+	@Autowired
+	ExamStudentImportRepo examStudentImportRepo;
+
+	@Autowired
+	OrgCloudService orgCloudService;
+
+	@Autowired
+	ExamCloudService examCloudService;
+
+	@ApiOperation(value = "导入考试学生", notes = "导入")
+	@PostMapping("/import")
+	public void importExamStudent(@RequestParam Long examId,
+			@RequestParam CommonsMultipartFile file) throws Exception {
+		User accessUser = getAccessUser();
+		Long rootOrgId = accessUser.getRootOrgId();
+
+		GetExamReq req = new GetExamReq();
+		req.setId(examId);
+		req.setRootOrgId(accessUser.getRootOrgId());
+		examCloudService.getExam(req);
+
+		DiskFileItem item = (DiskFileItem) file.getFileItem();
+		File storeLocation = item.getStoreLocation();
+		String originalFilename = file.getOriginalFilename();
+
+		String suffix = originalFilename.substring(originalFilename.indexOf("."));
+		long batchId = System.currentTimeMillis();
+		String name = new StringBuilder().append(rootOrgId).append("_").append(examId).append("_")
+				.append(DateUtil.now(DatePatterns.YYYYMMDDHHMM)).toString();
+
+		String destFileName = name + suffix;
+		String resultFileName = name + ".txt";
+
+		String destFilePath = PathUtil.getCanonicalPath(
+				systemConfig.getDataDir() + "/" + EXAM_STUDENT_IMPORT_FILES + "/" + destFileName);
+		File destFile = new File(destFilePath);
+
+		FileUtils.copyFile(storeLocation, destFile);
+
+		ExamStudentImportEntity examStudentImport = new ExamStudentImportEntity();
+		examStudentImport.setBatchId(batchId);
+		examStudentImport.setFailNum(0L);
+		examStudentImport.setSuccessNum(0L);
+		examStudentImport.setTotalNum(0L);
+		examStudentImport.setFilePath(destFileName);
+		examStudentImport.setResultFilePath(resultFileName);
+		examStudentImport.setStatus(ExamStudentImportStatus.NONE);
+		examStudentImport.setRootOrgId(rootOrgId);
+		examStudentImport.setExamId(examId);
+		examStudentImport.setFileName(originalFilename);
+
+		examStudentImportRepo.saveAndFlush(examStudentImport);
+	}
+
+	@ApiOperation(value = "查询导入记录", notes = "")
+	@GetMapping("/all/{curPage}/{pageSize}")
+	public PageInfo<ExamStudentImportDomain> getExamStudentImportList(@PathVariable Integer curPage,
+			@PathVariable Integer pageSize) {
+		User accessUser = getAccessUser();
+		Long rootOrgId = accessUser.getRootOrgId();
+
+		Specification<ExamStudentImportEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			if (!isSuperAdmin()) {
+				predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		Pageable pageable = PageRequest.of(curPage, pageSize, Sort.Direction.DESC, "creationTime");
+		Page<ExamStudentImportEntity> list = examStudentImportRepo.findAll(specification, pageable);
+
+		List<ExamStudentImportDomain> ret = Lists.newArrayList();
+
+		for (ExamStudentImportEntity cur : list) {
+			ExamStudentImportDomain domain = new ExamStudentImportDomain();
+			domain.setBatchId(cur.getBatchId());
+			domain.setExamId(cur.getExamId());
+			domain.setFailNum(cur.getFailNum());
+			domain.setFileName(cur.getFileName());
+			domain.setFilePath(cur.getFilePath());
+			domain.setId(cur.getId());
+			domain.setResultFilePath(cur.getResultFilePath());
+			domain.setRootOrgId(cur.getRootOrgId());
+			domain.setStatus(cur.getStatus().name());
+			domain.setStatusDesc(cur.getStatus().getDesc());
+			domain.setSuccessNum(cur.getSuccessNum());
+			domain.setTotalNum(cur.getTotalNum());
+			domain.setErrorDesc(cur.getErrorDesc());
+			domain.setCreationTime(cur.getCreationTime());
+			domain.setUpdateTime(cur.getUpdateTime());
+
+			GetExamReq req = new GetExamReq();
+			req.setId(domain.getExamId());
+			req.setRootOrgId(domain.getRootOrgId());
+			GetExamResp examResp = examCloudService.getExam(req);
+			ExamBean examBean = examResp.getExamBean();
+			domain.setExamName(examBean.getName());
+
+			GetOrgReq getOrgReq = new GetOrgReq();
+			getOrgReq.setOrgId(domain.getRootOrgId());
+			getOrgReq.setRootOrgId(rootOrgId);
+			GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+			OrgBean org = getOrgResp.getOrg();
+			domain.setRootOrgName(org.getName());
+
+			ret.add(domain);
+		}
+
+		return new PageInfo<ExamStudentImportDomain>(list, ret);
+	}
+
+	@ApiOperation(value = "下载报告")
+	@GetMapping("/getReports/{id}")
+	public void importFileTemplate(@PathVariable Long id) {
+		ExamStudentImportEntity entity = null;
+		Optional<ExamStudentImportEntity> opt = examStudentImportRepo.findById(id);
+		if (opt.isPresent()) {
+			entity = opt.get();
+		}
+		String resultFilePath = entity.getResultFilePath();
+
+		if (!isSuperAdmin()) {
+			if (!entity.getRootOrgId().equals(getRootOrgId())) {
+				exportFile(resultFilePath, "非法请求.".getBytes());
+			}
+		}
+
+		String path = PathUtil.getCanonicalPath(
+				systemConfig.getDataDir() + "/" + EXAM_STUDENT_IMPORT_FILES + "/" + resultFilePath);
+
+		File file = new File(path);
+
+		if (!file.exists()) {
+			exportFile(resultFilePath, "报告未生成.".getBytes());
+		}
+
+		exportFile(resultFilePath, file);
+	}
+
+	@ApiOperation(value = "下载导入文件")
+	@GetMapping("/getUploadedFile/{id}")
+	public void getUploadedFile(@PathVariable Long id) {
+		ExamStudentImportEntity entity = null;
+		Optional<ExamStudentImportEntity> opt = examStudentImportRepo.findById(id);
+		if (opt.isPresent()) {
+			entity = opt.get();
+		}
+		String fileName = entity.getFileName();
+
+		if (!isSuperAdmin()) {
+			if (!entity.getRootOrgId().equals(getRootOrgId())) {
+				exportFile(fileName, "非法请求.".getBytes());
+			}
+		}
+
+		String filePath = entity.getFilePath();
+		String path = PathUtil.getCanonicalPath(
+				systemConfig.getDataDir() + "/" + EXAM_STUDENT_IMPORT_FILES + "/" + filePath);
+
+		File file = new File(path);
+
+		if (!file.exists()) {
+			exportFile(fileName, "文件不存在.".getBytes());
+		}
+
+		exportFile(fileName, file);
+	}
+
+}

+ 85 - 0
examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/JobController.java

@@ -0,0 +1,85 @@
+package cn.com.qmth.examcloud.task.api.controller;
+
+import javax.transaction.Transactional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.task.dao.ScheduleJobRepo;
+import cn.com.qmth.examcloud.task.dao.entity.ScheduleJobEntity;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.task.QuartzManager;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import io.swagger.annotations.ApiOperation;
+
+@Transactional
+@RestController
+@RequestMapping("${$rmp.ctr.task}" + "job")
+public class JobController extends ControllerSupport {
+
+	@Autowired
+	private ScheduleJobRepo scheduleJobEntityRepo;
+
+	@Autowired
+	private QuartzManager quartzManager;
+
+	@ApiOperation(value = "启动job")
+	@PostMapping("/executeJob/{jobName}")
+	public void executeJob(@PathVariable String jobName) {
+		ScheduleJob scheduleJob = getEnableScheduleJob(jobName);
+		quartzManager.addJob(scheduleJob);
+	}
+
+	@ApiOperation(value = "暂停job")
+	@PostMapping("/pauseJob/{jobName}")
+	public void pauseJob(@PathVariable String jobName) {
+		ScheduleJob scheduleJob = getEnableScheduleJob(jobName);
+		quartzManager.pauseJob(scheduleJob);
+	}
+
+	@ApiOperation(value = "从暂停中恢复job")
+	@PostMapping("/resumeJob/{jobName}")
+	public void resumeJob(@PathVariable String jobName) {
+		ScheduleJob scheduleJob = getEnableScheduleJob(jobName);
+		quartzManager.resumeJob(scheduleJob);
+	}
+
+	@ApiOperation(value = "删除job")
+	@PostMapping("/deleteJob/{jobName}")
+	public void deleteJob(@PathVariable String jobName) {
+		ScheduleJob scheduleJob = getEnableScheduleJob(jobName);
+		quartzManager.deleteJob(scheduleJob);
+	}
+
+	/**
+	 * 获取可用job
+	 *
+	 * @author WANGWEI
+	 * @param jobName
+	 * @return
+	 */
+	private ScheduleJob getEnableScheduleJob(String jobName) {
+		ScheduleJobEntity jobEntity = scheduleJobEntityRepo.findByJobName(jobName);
+		if (null == jobEntity) {
+			throw new StatusException("100001", "任务不存在");
+		}
+
+		if (!jobEntity.getEnable()) {
+			throw new StatusException("100002", "任务被禁用");
+		}
+
+		ScheduleJob scheduleJob = new ScheduleJob();
+		scheduleJob.setJobName(jobEntity.getJobName());
+		scheduleJob.setJobGroup(jobEntity.getJobGroup());
+		scheduleJob.setSpringBean(jobEntity.getSpringBean());
+		scheduleJob.setCronExpression(jobEntity.getCronExpression());
+		scheduleJob.setStateful(jobEntity.getStateful());
+
+		return scheduleJob;
+	}
+
+}

+ 71 - 0
examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/ReportsComputeController.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.examcloud.task.api.controller;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.task.base.bean.ReportsComputeBean;
+import cn.com.qmth.examcloud.task.dao.entity.ReportsComputeEntity;
+import cn.com.qmth.examcloud.task.service.ReportsComputeService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+
+@RestController
+@RequestMapping("${$rmp.ctr.task}" + "reportsCompute")
+public class ReportsComputeController extends ControllerSupport {
+
+
+	@Autowired
+	ReportsComputeService reportsComputeService;
+
+	@ApiOperation(value = "终止计算任务")
+	@PostMapping("/stopJob/{id}")
+	public void stopJob(@PathVariable @ApiParam(value = "ID") Long id){
+		if (id == null) {
+			throw new StatusException("1000001", "计算任务id不能为空");
+		}
+		User accessUser = getAccessUser();
+		Long rootOrgId = accessUser.getRootOrgId();
+		
+		ReportsComputeEntity re=reportsComputeService.findById(id);
+		
+		if (re == null) {
+			throw new StatusException("1000002", "计算任务不存在");
+		}
+		
+		if(!rootOrgId.equals(re.getRootOrgId())) {
+			throw new StatusException("1000003", "非法操作");
+		}
+		int ret=reportsComputeService.updateToStoping(re);
+		if(ret==0) {
+			throw new StatusException("1000004", "只能终止待处理和处理中的任务");
+		}
+	}
+    @GetMapping("page/{projectId}/{pageNo}/{pageSize}")
+    @ApiOperation(value = "计算任务列表分页查询")
+    public PageInfo<ReportsComputeBean> queryPage(@PathVariable @ApiParam(value = "项目ID") Long projectId,
+                                        @PathVariable @ApiParam(value = "pageNo = 1,2,3...") Integer pageNo, @PathVariable Integer pageSize) {
+    	User user=getAccessUser();
+    	return reportsComputeService.queryPage(projectId, pageNo, pageSize,user.getRootOrgId());
+    }
+    @ApiOperation(value="批量获取")
+	@GetMapping("/getList")
+	public List<ReportsComputeBean> getList(@RequestParam(required = true) String ids) {
+    	List<Long> pids = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+				.collect(Collectors.toList());
+    	User user = getAccessUser();
+		return reportsComputeService.getList(pids, user.getRootOrgId());
+	}
+}

+ 241 - 0
examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/controller/bean/ExamStudentImportDomain.java

@@ -0,0 +1,241 @@
+package cn.com.qmth.examcloud.task.api.controller.bean;
+
+import java.util.Date;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 考生导入
+ *
+ * @author WANGWEI
+ * @date 2018年7月13日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamStudentImportDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = -3080614059689123498L;
+
+	@Id
+	@GeneratedValue
+	private Long id;
+
+	/**
+	 * 批次ID
+	 */
+	private Long batchId;
+
+	/**
+	 * 考试ID
+	 */
+	private Long examId;
+
+	/**
+	 * 考试名称
+	 */
+	private String examName;
+
+	/**
+	 * 顶级机构ID
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 顶级机构名称
+	 */
+	private String rootOrgName;
+
+	/**
+	 * 状态
+	 */
+	private String status;
+
+	/**
+	 * 状态描述
+	 */
+	private String statusDesc;
+
+	/**
+	 * 错误描述
+	 */
+	private String errorDesc;
+
+	/**
+	 * 文件名
+	 */
+	private String fileName;
+
+	/**
+	 * 文件路径
+	 */
+	private String filePath;
+
+	/**
+	 * 结果通知文件
+	 */
+	private String resultFilePath;
+
+	/**
+	 * 总条数
+	 */
+	private Long totalNum;
+
+	/**
+	 * 成功条数
+	 */
+	private Long successNum;
+
+	/**
+	 * 失败条数
+	 */
+	private Long failNum;
+
+	/**
+	 * 更新时间
+	 */
+	private Date updateTime;
+
+	/**
+	 * 创建时间
+	 */
+	private Date creationTime;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getBatchId() {
+		return batchId;
+	}
+
+	public void setBatchId(Long batchId) {
+		this.batchId = batchId;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public String getExamName() {
+		return examName;
+	}
+
+	public void setExamName(String examName) {
+		this.examName = examName;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public String getRootOrgName() {
+		return rootOrgName;
+	}
+
+	public void setRootOrgName(String rootOrgName) {
+		this.rootOrgName = rootOrgName;
+	}
+
+	public String getStatus() {
+		return status;
+	}
+
+	public void setStatus(String status) {
+		this.status = status;
+	}
+
+	public String getStatusDesc() {
+		return statusDesc;
+	}
+
+	public void setStatusDesc(String statusDesc) {
+		this.statusDesc = statusDesc;
+	}
+
+	public String getErrorDesc() {
+		return errorDesc;
+	}
+
+	public void setErrorDesc(String errorDesc) {
+		this.errorDesc = errorDesc;
+	}
+
+	public String getFileName() {
+		return fileName;
+	}
+
+	public void setFileName(String fileName) {
+		this.fileName = fileName;
+	}
+
+	public String getFilePath() {
+		return filePath;
+	}
+
+	public void setFilePath(String filePath) {
+		this.filePath = filePath;
+	}
+
+	public String getResultFilePath() {
+		return resultFilePath;
+	}
+
+	public void setResultFilePath(String resultFilePath) {
+		this.resultFilePath = resultFilePath;
+	}
+
+	public Long getTotalNum() {
+		return totalNum;
+	}
+
+	public void setTotalNum(Long totalNum) {
+		this.totalNum = totalNum;
+	}
+
+	public Long getSuccessNum() {
+		return successNum;
+	}
+
+	public void setSuccessNum(Long successNum) {
+		this.successNum = successNum;
+	}
+
+	public Long getFailNum() {
+		return failNum;
+	}
+
+	public void setFailNum(Long failNum) {
+		this.failNum = failNum;
+	}
+
+	public Date getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Date updateTime) {
+		this.updateTime = updateTime;
+	}
+
+	public Date getCreationTime() {
+		return creationTime;
+	}
+
+	public void setCreationTime(Date creationTime) {
+		this.creationTime = creationTime;
+	}
+
+}

+ 196 - 0
examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/provider/DataSyncCloudServiceProvider.java

@@ -0,0 +1,196 @@
+package cn.com.qmth.examcloud.task.api.provider;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.task.api.DataSyncCloudService;
+import cn.com.qmth.examcloud.task.api.request.SyncCourseReq;
+import cn.com.qmth.examcloud.task.api.request.SyncExamReq;
+import cn.com.qmth.examcloud.task.api.request.SyncExamStudentReq;
+import cn.com.qmth.examcloud.task.api.request.SyncOrgReq;
+import cn.com.qmth.examcloud.task.api.request.SyncSpecialtyReq;
+import cn.com.qmth.examcloud.task.api.request.SyncStudentReq;
+import cn.com.qmth.examcloud.task.api.request.SyncUserReq;
+import cn.com.qmth.examcloud.task.api.response.SyncCourseResp;
+import cn.com.qmth.examcloud.task.api.response.SyncExamResp;
+import cn.com.qmth.examcloud.task.api.response.SyncExamStudentResp;
+import cn.com.qmth.examcloud.task.api.response.SyncOrgResp;
+import cn.com.qmth.examcloud.task.api.response.SyncSpecialtyResp;
+import cn.com.qmth.examcloud.task.api.response.SyncStudentResp;
+import cn.com.qmth.examcloud.task.api.response.SyncUserResp;
+import cn.com.qmth.examcloud.task.service.DataSyncService;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+
+/**
+ * 数据同步
+ *
+ * @author WANGWEI
+ * @date 2018年8月2日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@RestController
+@RequestMapping("${$rmp.cloud.task}" + "dataSync")
+public class DataSyncCloudServiceProvider extends ControllerSupport
+		implements
+			DataSyncCloudService {
+
+	private static final long serialVersionUID = -2880611326177571371L;
+
+	@Autowired
+	DataSyncService dataSyncService;
+
+	private Boolean async() {
+		boolean async = PropertyHolder.getBoolean("$sync.async", true);
+		return async;
+	}
+
+	@PostMapping("syncCourse")
+	@Override
+	public SyncCourseResp syncCourse(@RequestBody SyncCourseReq req) {
+		cn.com.qmth.examcloud.global.api.request.SyncCourseReq r = new cn.com.qmth.examcloud.global.api.request.SyncCourseReq();
+		r.setCode(req.getCode());
+		r.setEnable(req.getEnable());
+		r.setId(req.getId());
+		r.setLevel(req.getLevel());
+		r.setName(req.getName());
+		r.setRootOrgId(req.getRootOrgId());
+		r.setSyncType(req.getSyncType());
+		dataSyncService.sync("syncCourse", r, async(), true);
+
+		SyncCourseResp resp = new SyncCourseResp();
+		return resp;
+	}
+
+	@PostMapping("syncOrg")
+	@Override
+	public SyncOrgResp syncOrg(@RequestBody SyncOrgReq req) {
+		cn.com.qmth.examcloud.global.api.request.SyncOrgReq r = new cn.com.qmth.examcloud.global.api.request.SyncOrgReq();
+		r.setEnable(req.getEnable());
+		r.setId(req.getId());
+		r.setName(req.getName());
+		r.setParentId(req.getParentId());
+		r.setRootId(req.getRootId());
+		r.setSyncType(req.getSyncType());
+		dataSyncService.sync("syncOrg", r, async(), true);
+
+		SyncOrgResp resp = new SyncOrgResp();
+		return resp;
+	}
+
+	@PostMapping("syncStudent")
+	@Override
+	public SyncStudentResp syncStudent(@RequestBody SyncStudentReq req) {
+
+		cn.com.qmth.examcloud.global.api.request.SyncStudentReq r = new cn.com.qmth.examcloud.global.api.request.SyncStudentReq();
+		r.setEnable(req.getEnable());
+		r.setId(req.getId());
+		r.setIdentityNumber(req.getIdentityNumber());
+		r.setName(req.getName());
+		r.setOrgCode(req.getOrgCode());
+		r.setOrgId(req.getOrgId());
+		r.setOrgName(req.getOrgName());
+		r.setPhoneNumber(req.getPhoneNumber());
+		r.setPhotoPath(req.getPhotoPath());
+		r.setRootOrgId(req.getRootOrgId());
+		r.setSecurityPhone(req.getSecurityPhone());
+		r.setStudentCodeList(req.getStudentCodeList());
+		r.setUnboundStudentCodeList(req.getUnboundStudentCodeList());
+		r.setSyncType(req.getSyncType());
+
+		dataSyncService.sync("syncStudent", r, async(), true);
+		SyncStudentResp resp = new SyncStudentResp();
+		return resp;
+	}
+
+	@PostMapping("syncSpecialty")
+	@Override
+	public SyncSpecialtyResp syncSpecialty(@RequestBody SyncSpecialtyReq req) {
+
+		return null;
+	}
+
+	@PostMapping("syncExam")
+	@Override
+	public SyncExamResp syncExam(@RequestBody SyncExamReq req) {
+		cn.com.qmth.examcloud.global.api.request.SyncExamReq r = new cn.com.qmth.examcloud.global.api.request.SyncExamReq();
+		r.setId(req.getId());
+		r.setBeginTime(req.getBeginTime());
+		r.setDuration(req.getDuration());
+		r.setEnable(req.getEnable());
+		r.setEndTime(req.getEndTime());
+		r.setExamTimes(req.getExamTimes());
+		r.setExamType(req.getExamType());
+		r.setName(req.getName());
+		r.setRemark(req.getRemark());
+		r.setRootOrgId(req.getRootOrgId());
+		r.setRootOrgName(req.getRootOrgName());
+		r.setSyncType(req.getSyncType());
+
+		dataSyncService.sync("syncExam", r, async(), true);
+		SyncExamResp resp = new SyncExamResp();
+		return resp;
+	}
+
+	@PostMapping("syncExamStudent")
+	@Override
+	public SyncExamStudentResp syncExamStudent(@RequestBody SyncExamStudentReq req) {
+		cn.com.qmth.examcloud.global.api.request.SyncExamStudentReq r = new cn.com.qmth.examcloud.global.api.request.SyncExamStudentReq();
+		r.setSyncType(req.getSyncType());
+		r.setCourseCode(req.getCourseCode());
+		r.setCourseLevel(req.getCourseLevel());
+		r.setCourseName(req.getCourseName());
+		r.setExamId(req.getExamId());
+		r.setExamName(req.getExamName());
+		r.setId(req.getId());
+		r.setEnable(req.getEnable());
+		r.setIdentityNumber(req.getIdentityNumber());
+		r.setPaperType(req.getPaperType());
+		r.setRootOrgId(req.getRootOrgId());
+		r.setStudentCode(req.getStudentCode());
+		r.setStudentName(req.getStudentName());
+		r.setStudentId(req.getStudentId());
+		r.setOrgId(req.getOrgId());
+		r.setOrgName(req.getOrgName());
+		r.setOrgCode(req.getOrgCode());
+		r.setCourseId(req.getCourseId());
+		r.setSpecialtyName(req.getSpecialtyName());
+		r.setGrade(req.getGrade());
+		r.setRemark(req.getRemark());
+		r.setInfoCollector(req.getInfoCollector());
+		r.setExamSite(req.getExamSite());
+
+		r.setExt1(req.getExt1());
+		r.setExt2(req.getExt2());
+		r.setExt3(req.getExt3());
+		r.setExt4(req.getExt4());
+		r.setExt5(req.getExt5());
+
+		dataSyncService.sync("syncExamStudent", r, async(), true);
+		SyncExamStudentResp resp = new SyncExamStudentResp();
+		return resp;
+	}
+
+	@PostMapping("syncUser")
+	@Override
+	public SyncUserResp syncUser(@RequestBody SyncUserReq req) {
+		cn.com.qmth.examcloud.global.api.request.SyncUserReq r = new cn.com.qmth.examcloud.global.api.request.SyncUserReq();
+		r.setEnable(req.getEnable());
+		r.setId(req.getId());
+		r.setLoginName(req.getLoginName());
+		r.setName(req.getName());
+		r.setOrgId(req.getOrgId());
+		r.setPassword(req.getPassword());
+		r.setPhoneNumber(req.getPhoneNumber());
+		r.setRootOrgId(req.getRootOrgId());
+		r.setSyncType(req.getSyncType());
+
+		dataSyncService.sync("syncUser", r, async(), true);
+		SyncUserResp resp = new SyncUserResp();
+		return resp;
+	}
+
+}

+ 50 - 0
examcloud-task-api-provider/src/main/java/cn/com/qmth/examcloud/task/api/provider/ReportsComputeCloudServiceProvider.java

@@ -0,0 +1,50 @@
+package cn.com.qmth.examcloud.task.api.provider;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.task.api.ReportsComputeCloudService;
+import cn.com.qmth.examcloud.task.api.request.AddReportsComputeReq;
+import cn.com.qmth.examcloud.task.api.response.AddReportsComputeResp;
+import cn.com.qmth.examcloud.task.service.ReportsComputeService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@Api(tags = "报表计算任务接口")
+@RestController
+@RequestMapping("${$rmp.cloud.task}" + "reportsCompute")
+public class ReportsComputeCloudServiceProvider extends ControllerSupport
+		implements
+		ReportsComputeCloudService {
+
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -8948562824083405677L;
+	@Autowired
+	private ReportsComputeService reportsComputeService;
+
+	@ApiOperation(value = "新增报表计算任务")
+	@PostMapping("/add")
+	@Override
+	public AddReportsComputeResp addReportsCompute(@RequestBody AddReportsComputeReq req) {
+		Long projectId=req.getProjectId();
+		if(projectId==null) {
+			throw new StatusException("10001", "projectId不能为空");
+		}
+		Long rootOrgId=req.getRootOrgId();
+		if(rootOrgId==null) {
+			throw new StatusException("10002", "rootOrgId不能为空");
+		}
+		reportsComputeService.add(projectId, rootOrgId);
+		return new AddReportsComputeResp();
+	}
+
+
+}

+ 23 - 0
examcloud-task-base/pom.xml

@@ -0,0 +1,23 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud.task</groupId>
+		<artifactId>examcloud-task</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-task-base</artifactId>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-web</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-support</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+	</dependencies>
+
+</project>

+ 100 - 0
examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/bean/ReportsComputeBean.java

@@ -0,0 +1,100 @@
+package cn.com.qmth.examcloud.task.base.bean;
+
+import java.util.Date;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.task.base.enums.ReportsComputeStatus;
+
+public class ReportsComputeBean implements JsonSerializable {
+
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 4494598544057432645L;
+
+	private Long id;
+	
+	private Long projectId;
+	private Long rootOrgId;
+	private ReportsComputeStatus status;
+	private String statusName;
+	private Date startTime;
+	private Date creationTime;
+	private Date endTime;
+
+	private String errorDesc;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getProjectId() {
+		return projectId;
+	}
+
+	public void setProjectId(Long projectId) {
+		this.projectId = projectId;
+	}
+
+	public ReportsComputeStatus getStatus() {
+		return status;
+	}
+
+	public void setStatus(ReportsComputeStatus status) {
+		this.status = status;
+	}
+
+	public Date getStartTime() {
+		return startTime;
+	}
+
+	public void setStartTime(Date startTime) {
+		this.startTime = startTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public String getErrorDesc() {
+		return errorDesc;
+	}
+
+	public void setErrorDesc(String errorDesc) {
+		this.errorDesc = errorDesc;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public String getStatusName() {
+		return statusName;
+	}
+
+	public void setStatusName(String statusName) {
+		this.statusName = statusName;
+	}
+
+	public Date getCreationTime() {
+		return creationTime;
+	}
+
+	public void setCreationTime(Date creationTime) {
+		this.creationTime = creationTime;
+	}
+	
+}

+ 27 - 0
examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/enums/ReportsComputeStatus.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.examcloud.task.base.enums;
+public enum ReportsComputeStatus {
+
+	NONE("待处理"), COMPUTING("处理中"), SUCCESS("处理成功"), FAIL(
+			"处理失败"), STOPING("待终止"), STOP("已终止");
+
+	// ===========================================================================
+
+	/**
+	 * 描述
+	 */
+	private String desc;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param desc
+	 */
+	private ReportsComputeStatus(String desc) {
+		this.desc = desc;
+	}
+
+	public String getDesc() {
+		return desc;
+	}
+
+}

+ 103 - 0
examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/multithread/Basket.java

@@ -0,0 +1,103 @@
+package cn.com.qmth.examcloud.task.base.multithread;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+
+public  class  Basket {
+	private static final Logger logger = LoggerFactory.getLogger(Basket.class);
+	/**
+	 * 数据阻塞队列
+	 */
+	private BlockingQueue<Object> queue;
+	
+	/**
+	 * 多线程计数器,子线程都结束后主线程才继续执行
+	 */
+	private CountDownLatch endGate;
+	
+	/**
+	 * 消费者数量
+	 */
+	private int consumerCount;
+	
+	
+	/**
+	 * 判断线程执行是否有出错,生产者、消费者出错都需要修改此值为true
+	 */
+	private boolean isExcuteError = false;
+	
+	
+	public Basket(int consumerCount) {
+		this.consumerCount=consumerCount;
+		queue = new ArrayBlockingQueue<Object>(consumerCount*2);
+		endGate = new CountDownLatch(consumerCount);
+	}
+
+	/**
+	 * 生产数据,不采用put方法防止消费线程全部异常后生产线程阻塞
+	 * @param value
+	 * @throws InterruptedException
+	 */
+	protected void offer(final Object value) throws InterruptedException {
+		if(isExcuteError) {
+			logger.error("**********************offer isExcuteError threadId:"+Thread.currentThread().getId());
+			throw new StatusException("1000001","线程异常");
+		}else {
+			boolean ret=queue.offer(value, 1, TimeUnit.MINUTES);
+			if(!ret) {
+				logger.info("**********************offer time out threadId:"+Thread.currentThread().getId()+value);
+				this.offer(value);
+			}
+		}
+	}
+	/**
+	 * 消费数据,不采用take方法防止生产线程全部异常后消费线程阻塞
+	 * @return
+	 * @throws InterruptedException
+	 */
+	protected Object consume() throws InterruptedException {
+		if(isExcuteError) {
+			logger.error("**********************poll isExcuteError  threadId:"+Thread.currentThread().getId());
+			return new EndObject();
+		}else {
+			Object ob=queue.poll(1, TimeUnit.MINUTES);
+			if(ob==null) {
+				logger.info("**********************poll time out  threadId:"+Thread.currentThread().getId());
+				return this.consume();
+			}else {
+				return ob;
+			}
+		}
+	}
+	
+	protected void await() throws InterruptedException {
+		endGate.await();
+	}
+	protected void countDown() {
+		endGate.countDown();
+	}
+
+	protected boolean isExcuteError() {
+		return isExcuteError;
+	}
+
+	protected void setExcuteError(boolean isExcuteError) {
+		this.isExcuteError = isExcuteError;
+	}
+
+	protected int getConsumerCount() {
+		return consumerCount;
+	}
+
+	protected void setConsumerCount(int consumerCount) {
+		this.consumerCount = consumerCount;
+	}
+
+}

+ 60 - 0
examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/multithread/Consumer.java

@@ -0,0 +1,60 @@
+package cn.com.qmth.examcloud.task.base.multithread;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class Consumer<T>  extends Thread{
+	private static final Logger logger = LoggerFactory.getLogger(Consumer.class);
+	private Basket basket;
+	
+	private String traceId;
+	
+	public Consumer() {
+	}
+	@Override
+	public void run() {
+		ThreadContext.put("TRACE_ID", traceId);
+		logger.info("*******************Consumer:"+Thread.currentThread().getId()+" start");
+		try {
+			while (true) {
+				//先判断是否有异常结束
+				if(basket.isExcuteError()) {
+					break;
+				}
+				//取消费数据
+				Object o= basket.consume();
+				//判断消费数据是否是结束
+				if(o instanceof EndObject) {
+					break;
+				}
+				@SuppressWarnings("unchecked")
+				T t=(T)o;
+				logger.info("*******************Consumer:"+Thread.currentThread().getId()+" consume");
+				//消费数据实现
+				consume(t);
+			}
+		} catch (Exception e) {
+			basket.setExcuteError(true);
+			logger.info("消费线程处理出错",e);
+		}finally {
+			basket.countDown();
+			logger.info("*******************Consumer:"+Thread.currentThread().getId()+" stop");
+			ThreadContext.clearAll();
+		}
+	}
+	public abstract void consume(T t);
+	public Basket getBasket() {
+		return basket;
+	}
+	public void setBasket(Basket basket) {
+		this.basket = basket;
+	}
+	public String getTraceId() {
+		return traceId;
+	}
+	public void setTraceId(String traceId) {
+		this.traceId = traceId;
+	}
+	
+}

+ 10 - 0
examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/multithread/EndObject.java

@@ -0,0 +1,10 @@
+package cn.com.qmth.examcloud.task.base.multithread;
+
+/**
+ * 消费结束标识对象
+ * @author xiatian
+ *
+ */
+public class EndObject {
+
+}

+ 136 - 0
examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/multithread/Producer.java

@@ -0,0 +1,136 @@
+package cn.com.qmth.examcloud.task.base.multithread;
+
+import java.util.Map;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+
+public abstract class Producer {
+	private static final Logger logger = LoggerFactory.getLogger(Producer.class);
+	private Basket basket;
+	
+	/**
+	 * 业务参数
+	 */
+	private Map<String, Object> param;
+	
+	/**
+	 * 消费线程class
+	 */
+	private Class<? extends Consumer<?>> consumer;
+	/**
+	 * 	处理开始方法
+	 * @param consumer 消费线程class
+	 * @param consumerCount 消费线程数
+	 * @param param 生产者业务参数
+	 * @throws InstantiationException
+	 * @throws IllegalAccessException
+	 */
+	public void startDispose(Class<? extends Consumer<?>> consumer, int consumerCount,Map<String, Object> param)
+			throws InstantiationException, IllegalAccessException {
+		Basket basket = new Basket(consumerCount);
+		this.basket = basket;
+		this.consumer = consumer;
+		this.param=param;
+		//启动消费者
+		startConsumer();
+		//开始处理
+		dispose();
+	}
+
+	private void dispose() {
+		try {
+			logger.info("*******************Producer:开始处理");
+			// 生产数据
+			produce(param);
+			logger.info("*******************Producer:生产结束");
+			// 发送生产结束信息
+			endConsumer();
+			logger.info("*******************Producer:成功发送生产结束信息");
+			// 等待子线程结束
+			logger.info("*******************Producer:等待消费线程结束");
+			await();
+			logger.info("*******************Producer:消费线程已结束");
+			// 判断子线程是否正常结束
+			if (basket.isExcuteError()) {
+				throw new StatusException("1000001", "处理失败,线程异常");
+			}
+			logger.info("*******************Producer:结束处理");
+		} catch (StatusException e) {
+			// 获取异常时发送异常结束信息
+			endConsumerAsError();
+			throw e;
+		} catch (Exception e) {
+			// 获取异常时发送异常结束信息
+			endConsumerAsError();
+			throw new StatusException("1000002", "处理失败", e);
+		}
+	}
+
+	/**
+	 * 启动消费者
+	 * 
+	 * @param consumer
+	 * @throws InstantiationException
+	 * @throws IllegalAccessException
+	 */
+	private void startConsumer() throws InstantiationException, IllegalAccessException {
+		int count = basket.getConsumerCount();
+		for (int i = 0; i < count; i++) {
+			Consumer<?> co = (Consumer<?>) consumer.newInstance();
+			co.setBasket(basket);
+			co.setTraceId(ThreadContext.get("TRACE_ID"));
+			co.start();
+		}
+
+	}
+
+	/**
+	 * 出异常后修改标识
+	 * 
+	 */
+	private void endConsumerAsError() {
+		basket.setExcuteError(true);
+	}
+
+	/**
+	 * 正常结束消费者
+	 * 
+	 * @throws InterruptedException
+	 */
+	private void endConsumer() throws InterruptedException {
+		int count = basket.getConsumerCount();
+		EndObject eo = new EndObject();
+		for (int i = 0; i < count; i++) {
+			basket.offer(eo);
+		}
+
+	}
+
+	/**
+	 * 生产数据
+	 * 
+	 * @param ob
+	 * @throws InterruptedException
+	 */
+	protected void offer(Object ob) throws InterruptedException {
+		synchronized (basket) {
+			basket.offer(ob);
+		}
+	}
+
+	/**
+	 * 等待所有消费者结束
+	 * 
+	 * @throws InterruptedException
+	 */
+	private void await() throws InterruptedException {
+		basket.await();
+	}
+
+	protected abstract void produce(Map<String, Object> param) throws Exception;
+
+}

+ 51 - 0
examcloud-task-base/src/main/java/cn/com/qmth/examcloud/task/base/util/BatchGetDataUtil.java

@@ -0,0 +1,51 @@
+package cn.com.qmth.examcloud.task.base.util;
+
+import java.util.List;
+
+/**
+ *	多次批量获取数据
+ *	需重写getData方法
+ * @param <R> 结果类
+ * @param <P> 参数类
+ */
+public  class BatchGetDataUtil<R,P> {
+	/**
+	 * @param resultList 全部结果集合
+	 * @param paramList 全部参数集合
+	 * @param batchSize 每批参数数量
+	 */
+	public final void getDataForBatch(List<R> resultList,List<P> paramList,int batchSize) {
+		if(resultList==null||paramList==null||paramList.size()==0) {
+			return;
+		}
+		if(paramList.size()<=batchSize) {
+			List<R> temlist = getData(paramList);
+			if(temlist!=null&&temlist.size()>0) {
+				resultList.addAll(temlist);
+			}
+		}else {
+			int size = paramList.size();
+			int len=batchSize;
+			int count = (size + len - 1) / len;
+
+			for (int i = 0; i < count; i++) {
+				List<P> subList = paramList.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+				List<R> temlist = getData(subList);
+				if(temlist!=null&&temlist.size()>0) {
+					resultList.addAll(temlist);
+				}
+			}
+		}
+	}
+	/**
+	 * 	Need Override
+	 * 	每批获取数据方法
+	 * @param <R>
+	 * @param <P>
+	 * @param paramList 获取每批数据时参数
+	 * @return
+	 */
+	public  List<R> getData(List<P> paramList) {
+		return null;
+	}
+}

+ 17 - 0
examcloud-task-dao/pom.xml

@@ -0,0 +1,17 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud.task</groupId>
+		<artifactId>examcloud-task</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-task-dao</artifactId>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.task</groupId>
+			<artifactId>examcloud-task-base</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+	</dependencies>
+</project>

+ 19 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/CopyExamStudentRepo.java

@@ -0,0 +1,19 @@
+package cn.com.qmth.examcloud.task.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.task.dao.entity.CopyExamStudentEntity;
+import cn.com.qmth.examcloud.task.dao.entity.CopyExamStudentPK;
+import cn.com.qmth.examcloud.task.dao.enums.CopyExamStudentStatus;
+
+@Repository
+public interface CopyExamStudentRepo
+		extends
+			JpaRepository<CopyExamStudentEntity, CopyExamStudentPK>,
+			JpaSpecificationExecutor<CopyExamStudentEntity> {
+
+	CopyExamStudentEntity findFirstByStatusOrderByCreationTime(CopyExamStudentStatus status);
+
+}

+ 28 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/DataSyncRepo.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.task.dao;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.task.dao.entity.DataSyncEntity;
+
+@Repository
+public interface DataSyncRepo
+		extends
+			JpaRepository<DataSyncEntity, Long>,
+			JpaSpecificationExecutor<DataSyncEntity> {
+
+	List<DataSyncEntity> findTop10ByComponentOrderById(String component);
+
+	@Transactional
+	@Modifying
+	@Query("update DataSyncEntity set syncNum = syncNum+1 where id=:id")
+	void increaseSyncNum(@Param("id") Long id);
+
+}

+ 18 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/ExamStudentImportRepo.java

@@ -0,0 +1,18 @@
+package cn.com.qmth.examcloud.task.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.task.dao.entity.ExamStudentImportEntity;
+import cn.com.qmth.examcloud.task.dao.enums.ExamStudentImportStatus;
+
+@Repository
+public interface ExamStudentImportRepo
+		extends
+			JpaRepository<ExamStudentImportEntity, Long>,
+			JpaSpecificationExecutor<ExamStudentImportEntity> {
+
+	ExamStudentImportEntity findFirstByStatusOrderByCreationTime(ExamStudentImportStatus status);
+
+}

+ 24 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/ExamStudentTempRepo.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.examcloud.task.dao;
+
+import java.util.List;
+
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.task.dao.entity.ExamStudentTempEntity;
+
+@Repository
+public interface ExamStudentTempRepo
+		extends
+			JpaRepository<ExamStudentTempEntity, Long>,
+			JpaSpecificationExecutor<ExamStudentTempEntity> {
+
+	List<ExamStudentTempEntity> findByBatchIdAndStatusCodeIsNull(Long batchId, Pageable pageable);
+
+	@Transactional
+	void deleteByBatchId(Long batchId);
+
+}

+ 44 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/ReportsComputeRepo.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.task.dao;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.task.base.enums.ReportsComputeStatus;
+import cn.com.qmth.examcloud.task.dao.entity.ReportsComputeEntity;
+
+@Repository
+public interface ReportsComputeRepo
+		extends
+			JpaRepository<ReportsComputeEntity, Long>,
+			JpaSpecificationExecutor<ReportsComputeEntity> {
+	@Query(value = "SELECT count(1) FROM EC_T_REPORTS_COMPUTE t "+
+			"WHERE t.status ='NONE' or t.status ='STOPING' ", nativeQuery = true)
+	public int getTodoDataCount();
+	@Query(value = "SELECT t.* FROM EC_T_REPORTS_COMPUTE t "+
+			"WHERE t.id>?1 and (t.status ='NONE' or t.status ='STOPING') ORDER BY t.id limit ?2", nativeQuery = true)
+	public List<ReportsComputeEntity> findTodoData(Long startId,Integer limit);
+	
+	@Query(value = "update EC_T_REPORTS_COMPUTE t set t.status ='STOPING' where t.id=?1 and (t.status ='COMPUTING' or t.status ='NONE') ", nativeQuery = true)
+	@Modifying
+	public int updateToStoping(Long id);
+	
+	@Query(value = "update EC_T_REPORTS_COMPUTE t set t.status ='STOP' where t.status ='STOPING' ", nativeQuery = true)
+	@Modifying
+	public int initStopingJob();
+	
+	@Query(value = "update EC_T_REPORTS_COMPUTE t set t.status ='NONE' where t.status ='COMPUTING' ", nativeQuery = true)
+	@Modifying
+	public int initComputingJob();
+
+	public List<ReportsComputeEntity> findByProjectIdAndRootOrgIdAndStatus(Long projectId, Long rootOrgId,ReportsComputeStatus status);
+
+	public List<ReportsComputeEntity> findByStatus(ReportsComputeStatus status);
+	
+	@Query(value = "select * from EC_T_REPORTS_COMPUTE where id in(?1) and root_org_id=?2 ", nativeQuery = true)
+	public List<ReportsComputeEntity> getByIds(List<Long> ids, Long rootOrgId);
+}

+ 17 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/ScheduleJobRepo.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.examcloud.task.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.task.dao.entity.ScheduleJobEntity;
+
+@Repository
+public interface ScheduleJobRepo
+		extends
+			JpaRepository<ScheduleJobEntity, Long>,
+			JpaSpecificationExecutor<ScheduleJobEntity> {
+
+	public ScheduleJobEntity findByJobName(String jobName);
+
+}

+ 27 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/TaskTraceRepo.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.examcloud.task.dao;
+
+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;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.task.dao.entity.TaskTraceEntity;
+
+@Repository
+public interface TaskTraceRepo
+		extends
+			JpaRepository<TaskTraceEntity, Long>,
+			JpaSpecificationExecutor<TaskTraceEntity> {
+
+	TaskTraceEntity findByTraceId(String traceId);
+
+	@Transactional
+	@Modifying
+	@Query(value = "delete from EC_T_TASK_TRACE where creation_time < ?1", nativeQuery = true)
+	void deleteByCreationTimeLessThan(Date date);
+
+}

+ 94 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/CopyExamStudentEntity.java

@@ -0,0 +1,94 @@
+package cn.com.qmth.examcloud.task.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.task.dao.enums.CopyExamStudentStatus;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 考生复制
+ *
+ * @author WANGWEI
+ * @date 2018年8月2日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_T_COPY_EXAM_STUDENT")
+@IdClass(CopyExamStudentPK.class)
+public class CopyExamStudentEntity extends JpaEntity {
+
+	private static final long serialVersionUID = 1066874132817953168L;
+
+	private Long rootOrgId;
+
+	/**
+	 * copy from
+	 */
+	@Id
+	private Long examId1;
+
+	/**
+	 * copy to
+	 */
+	@Id
+	private Long examId2;
+
+	/**
+	 * 考试ID开始值
+	 */
+	private Long start;
+
+	/**
+	 * 状态
+	 */
+	@Column(nullable = false)
+	@Enumerated(EnumType.STRING)
+	private CopyExamStudentStatus status;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getExamId1() {
+		return examId1;
+	}
+
+	public void setExamId1(Long examId1) {
+		this.examId1 = examId1;
+	}
+
+	public Long getExamId2() {
+		return examId2;
+	}
+
+	public void setExamId2(Long examId2) {
+		this.examId2 = examId2;
+	}
+
+	public Long getStart() {
+		return start;
+	}
+
+	public void setStart(Long start) {
+		this.start = start;
+	}
+
+	public CopyExamStudentStatus getStatus() {
+		return status;
+	}
+
+	public void setStatus(CopyExamStudentStatus status) {
+		this.status = status;
+	}
+
+}

+ 65 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/CopyExamStudentPK.java

@@ -0,0 +1,65 @@
+package cn.com.qmth.examcloud.task.dao.entity;
+
+import java.io.Serializable;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年9月12日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class CopyExamStudentPK implements Serializable {
+
+	/**
+	 * 属性注释
+	 */
+	private static final long serialVersionUID = -5781237290039063837L;
+
+	/**
+	 * copy from
+	 */
+	private Long examId1;
+
+	/**
+	 * copy to
+	 */
+	private Long examId2;
+
+	/**
+	 * 构造函数
+	 *
+	 */
+	public CopyExamStudentPK() {
+		super();
+	}
+
+	/**
+	 * 构造函数
+	 *
+	 * @param examId1
+	 * @param examId2
+	 */
+	public CopyExamStudentPK(Long examId1, Long examId2) {
+		super();
+		this.examId1 = examId1;
+		this.examId2 = examId2;
+	}
+
+	public Long getExamId1() {
+		return examId1;
+	}
+
+	public void setExamId1(Long examId1) {
+		this.examId1 = examId1;
+	}
+
+	public Long getExamId2() {
+		return examId2;
+	}
+
+	public void setExamId2(Long examId2) {
+		this.examId2 = examId2;
+	}
+
+}

+ 103 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/DataSyncEntity.java

@@ -0,0 +1,103 @@
+package cn.com.qmth.examcloud.task.dao.entity;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 数据同步
+ *
+ * @author WANGWEI
+ * @date 2018年8月2日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_T_DATA_SYNC")
+public class DataSyncEntity extends JpaEntity {
+
+	private static final long serialVersionUID = 1066874132817953168L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	/**
+	 * 同步请求组件
+	 */
+	private String component;
+
+	/**
+	 * 方法(反射调用)
+	 */
+	private String methodName;
+
+	/**
+	 * 参数类型
+	 */
+	private String paramType;
+
+	/**
+	 * 参数json
+	 */
+	@Lob
+	private String paramJson;
+
+	/**
+	 * 同步次数
+	 */
+	private int syncNum;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getComponent() {
+		return component;
+	}
+
+	public void setComponent(String component) {
+		this.component = component;
+	}
+
+	public String getMethodName() {
+		return methodName;
+	}
+
+	public void setMethodName(String methodName) {
+		this.methodName = methodName;
+	}
+
+	public String getParamType() {
+		return paramType;
+	}
+
+	public void setParamType(String paramType) {
+		this.paramType = paramType;
+	}
+
+	public String getParamJson() {
+		return paramJson;
+	}
+
+	public void setParamJson(String paramJson) {
+		this.paramJson = paramJson;
+	}
+
+	public int getSyncNum() {
+		return syncNum;
+	}
+
+	public void setSyncNum(int syncNum) {
+		this.syncNum = syncNum;
+	}
+
+}

+ 185 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/ExamStudentImportEntity.java

@@ -0,0 +1,185 @@
+package cn.com.qmth.examcloud.task.dao.entity;
+
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.task.dao.enums.ExamStudentImportStatus;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 考生导入表
+ *
+ * @author WANGWEI
+ * @date 2018年7月13日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_T_EXAM_STUDENT_IMPORT", indexes = {
+		@Index(name = "IDX_T_EXAM_STU_IMP_001001", columnList = "batchId", unique = true)})
+public class ExamStudentImportEntity extends JpaEntity {
+
+	private static final long serialVersionUID = -6030503185547639031L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	/**
+	 * 批次ID
+	 */
+	private Long batchId;
+
+	/**
+	 * 考试ID
+	 */
+	private Long examId;
+
+	/**
+	 * 顶级机构ID
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 状态
+	 */
+	@Enumerated(EnumType.STRING)
+	private ExamStudentImportStatus status;
+
+	/**
+	 * 错误描述
+	 */
+	private String errorDesc;
+
+	/**
+	 * 文件名
+	 */
+	private String fileName;
+
+	/**
+	 * 文件路径
+	 */
+	private String filePath;
+
+	/**
+	 * 结果通知文件
+	 */
+	private String resultFilePath;
+
+	/**
+	 * 总条数
+	 */
+	private Long totalNum;
+
+	/**
+	 * 成功条数
+	 */
+	private Long successNum;
+
+	/**
+	 * 失败条数
+	 */
+	private Long failNum;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getBatchId() {
+		return batchId;
+	}
+
+	public void setBatchId(Long batchId) {
+		this.batchId = batchId;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public ExamStudentImportStatus getStatus() {
+		return status;
+	}
+
+	public void setStatus(ExamStudentImportStatus status) {
+		this.status = status;
+	}
+
+	public String getErrorDesc() {
+		return errorDesc;
+	}
+
+	public void setErrorDesc(String errorDesc) {
+		this.errorDesc = errorDesc;
+	}
+
+	public String getFileName() {
+		return fileName;
+	}
+
+	public void setFileName(String fileName) {
+		this.fileName = fileName;
+	}
+
+	public String getFilePath() {
+		return filePath;
+	}
+
+	public void setFilePath(String filePath) {
+		this.filePath = filePath;
+	}
+
+	public String getResultFilePath() {
+		return resultFilePath;
+	}
+
+	public void setResultFilePath(String resultFilePath) {
+		this.resultFilePath = resultFilePath;
+	}
+
+	public Long getTotalNum() {
+		return totalNum;
+	}
+
+	public void setTotalNum(Long totalNum) {
+		this.totalNum = totalNum;
+	}
+
+	public Long getSuccessNum() {
+		return successNum;
+	}
+
+	public void setSuccessNum(Long successNum) {
+		this.successNum = successNum;
+	}
+
+	public Long getFailNum() {
+		return failNum;
+	}
+
+	public void setFailNum(Long failNum) {
+		this.failNum = failNum;
+	}
+
+}

+ 443 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/ExamStudentTempEntity.java

@@ -0,0 +1,443 @@
+package cn.com.qmth.examcloud.task.dao.entity;
+
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 考生临时表
+ *
+ * @author WANGWEI
+ * @date 2018年7月13日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_T_EXAM_STUDENT_TMP", indexes = {
+		@Index(name = "IDX_T_EXAM_STU_001001", columnList = "batchId", unique = false)})
+public class ExamStudentTempEntity extends JpaEntity {
+
+	private static final long serialVersionUID = -6030503185547639031L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	/**
+	 * 批次ID
+	 */
+	private Long batchId;
+
+	/**
+	 * 状态码
+	 */
+	private String statusCode;
+
+	/**
+	 * 状态描述
+	 */
+	private String statusDesc;
+
+	/**
+	 * 姓名
+	 */
+	private String name;
+
+	/**
+	 * 考试ID
+	 */
+	private Long examId;
+
+	/**
+	 * 学校id
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 学习中心id
+	 */
+	private Long orgId;
+
+	/**
+	 * 学习中心code
+	 */
+	private String orgCode;
+
+	/**
+	 * 学习中心名称
+	 */
+	private String orgName;
+
+	/**
+	 * 学号
+	 */
+	private String studentCode;
+
+	/**
+	 * 身份证号
+	 */
+	private String identityNumber;
+
+	/**
+	 * 课程ID
+	 */
+	private Long courseId;
+
+	/**
+	 * 课程code
+	 */
+	private String courseCode;
+
+	/**
+	 * 课程名称
+	 */
+	private String courseName;
+
+	/**
+	 * 课程等级
+	 */
+	private String courseLevel;
+
+	/**
+	 * 试卷类型
+	 */
+	private String paperType;
+
+	/**
+	 * 专业名称
+	 */
+	private String specialtyName;
+
+	/**
+	 * 专业code
+	 */
+	private String specialtyCode;
+
+	/**
+	 * 年级
+	 */
+	private String grade;
+
+	/**
+	 * 学生用户id
+	 */
+	private Long studentId;
+
+	/**
+	 * 考点
+	 */
+	private String examSite;
+
+	/**
+	 * 信息采集人
+	 */
+	private String infoCollector;
+
+	/**
+	 * 学生电话
+	 */
+	private String phone;
+
+	/**
+	 * excel行号
+	 */
+	private Long lineNum;
+
+	/**
+	 * 特殊考试批次开始时间
+	 */
+	private Date specialBeginTime;
+
+	/**
+	 * 特殊考试批次结束时间
+	 */
+	private Date specialEndTime;
+
+	/**
+	 * 扩展属性1
+	 */
+	private String ext1;
+
+	/**
+	 * 扩展属性2
+	 */
+	private String ext2;
+
+	/**
+	 * 扩展属性3
+	 */
+	private String ext3;
+
+	/**
+	 * 扩展属性4
+	 */
+	private String ext4;
+
+	/**
+	 * 扩展属性5
+	 */
+	private String ext5;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getBatchId() {
+		return batchId;
+	}
+
+	public void setBatchId(Long batchId) {
+		this.batchId = batchId;
+	}
+
+	public String getStatusCode() {
+		return statusCode;
+	}
+
+	public void setStatusCode(String statusCode) {
+		this.statusCode = statusCode;
+	}
+
+	public String getStatusDesc() {
+		return statusDesc;
+	}
+
+	public void setStatusDesc(String statusDesc) {
+		this.statusDesc = statusDesc;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
+
+	public String getOrgCode() {
+		return orgCode;
+	}
+
+	public void setOrgCode(String orgCode) {
+		this.orgCode = orgCode;
+	}
+
+	public String getOrgName() {
+		return orgName;
+	}
+
+	public void setOrgName(String orgName) {
+		this.orgName = orgName;
+	}
+
+	public String getStudentCode() {
+		return studentCode;
+	}
+
+	public void setStudentCode(String studentCode) {
+		this.studentCode = studentCode;
+	}
+
+	public String getIdentityNumber() {
+		return identityNumber;
+	}
+
+	public void setIdentityNumber(String identityNumber) {
+		this.identityNumber = identityNumber;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getCourseCode() {
+		return courseCode;
+	}
+
+	public void setCourseCode(String courseCode) {
+		this.courseCode = courseCode;
+	}
+
+	public String getCourseName() {
+		return courseName;
+	}
+
+	public void setCourseName(String courseName) {
+		this.courseName = courseName;
+	}
+
+	public String getCourseLevel() {
+		return courseLevel;
+	}
+
+	public void setCourseLevel(String courseLevel) {
+		this.courseLevel = courseLevel;
+	}
+
+	public String getPaperType() {
+		return paperType;
+	}
+
+	public void setPaperType(String paperType) {
+		this.paperType = paperType;
+	}
+
+	public String getSpecialtyName() {
+		return specialtyName;
+	}
+
+	public void setSpecialtyName(String specialtyName) {
+		this.specialtyName = specialtyName;
+	}
+
+	public String getSpecialtyCode() {
+		return specialtyCode;
+	}
+
+	public void setSpecialtyCode(String specialtyCode) {
+		this.specialtyCode = specialtyCode;
+	}
+
+	public String getGrade() {
+		return grade;
+	}
+
+	public void setGrade(String grade) {
+		this.grade = grade;
+	}
+
+	public Long getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	public String getExamSite() {
+		return examSite;
+	}
+
+	public void setExamSite(String examSite) {
+		this.examSite = examSite;
+	}
+
+	public String getInfoCollector() {
+		return infoCollector;
+	}
+
+	public void setInfoCollector(String infoCollector) {
+		this.infoCollector = infoCollector;
+	}
+
+	public String getPhone() {
+		return phone;
+	}
+
+	public void setPhone(String phone) {
+		this.phone = phone;
+	}
+
+	public Long getLineNum() {
+		return lineNum;
+	}
+
+	public void setLineNum(Long lineNum) {
+		this.lineNum = lineNum;
+	}
+
+	public Date getSpecialBeginTime() {
+		return specialBeginTime;
+	}
+
+	public void setSpecialBeginTime(Date specialBeginTime) {
+		this.specialBeginTime = specialBeginTime;
+	}
+
+	public Date getSpecialEndTime() {
+		return specialEndTime;
+	}
+
+	public void setSpecialEndTime(Date specialEndTime) {
+		this.specialEndTime = specialEndTime;
+	}
+
+	public String getExt1() {
+		return ext1;
+	}
+
+	public void setExt1(String ext1) {
+		this.ext1 = ext1;
+	}
+
+	public String getExt2() {
+		return ext2;
+	}
+
+	public void setExt2(String ext2) {
+		this.ext2 = ext2;
+	}
+
+	public String getExt3() {
+		return ext3;
+	}
+
+	public void setExt3(String ext3) {
+		this.ext3 = ext3;
+	}
+
+	public String getExt4() {
+		return ext4;
+	}
+
+	public void setExt4(String ext4) {
+		this.ext4 = ext4;
+	}
+
+	public String getExt5() {
+		return ext5;
+	}
+
+	public void setExt5(String ext5) {
+		this.ext5 = ext5;
+	}
+
+}

+ 111 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/ReportsComputeEntity.java

@@ -0,0 +1,111 @@
+package cn.com.qmth.examcloud.task.dao.entity;
+
+import java.util.Date;
+
+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 javax.validation.constraints.NotNull;
+
+import cn.com.qmth.examcloud.task.base.enums.ReportsComputeStatus;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+@Entity
+@Table(name = "EC_T_REPORTS_COMPUTE", indexes = {
+		@Index(name = "IDX_T_REPORTS_COMPUTE_01", columnList = "projectId", unique = false)})
+public class ReportsComputeEntity extends JpaEntity {
+
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -1847946836121160945L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+	
+	@NotNull
+	private Long projectId;
+	/**
+	 * 顶级机构ID
+	 */
+	@NotNull
+	private Long rootOrgId;
+	@NotNull
+	@Enumerated(EnumType.STRING)
+	private ReportsComputeStatus status;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	private Date startTime;
+	
+	@Temporal(TemporalType.TIMESTAMP)
+	private Date endTime;
+
+	private String errorDesc;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getProjectId() {
+		return projectId;
+	}
+
+	public void setProjectId(Long projectId) {
+		this.projectId = projectId;
+	}
+
+	public ReportsComputeStatus getStatus() {
+		return status;
+	}
+
+	public void setStatus(ReportsComputeStatus status) {
+		this.status = status;
+	}
+
+	public Date getStartTime() {
+		return startTime;
+	}
+
+	public void setStartTime(Date startTime) {
+		this.startTime = startTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public String getErrorDesc() {
+		return errorDesc;
+	}
+
+	public void setErrorDesc(String errorDesc) {
+		this.errorDesc = errorDesc;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+	
+	
+}

+ 180 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/ScheduleJobEntity.java

@@ -0,0 +1,180 @@
+package cn.com.qmth.examcloud.task.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 任务配置
+ *
+ * @author WANGWEI
+ * @date 2018年3月7日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_T_SCHEDULE_JOB")
+public class ScheduleJobEntity extends JpaEntity {
+	private static final long serialVersionUID = -1638993747996917970L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	/**
+	 * 任务名称
+	 */
+	@Column(unique = true, nullable = false)
+	private String jobName;
+
+	/**
+	 * 任务分组
+	 */
+	@Column(unique = false, nullable = false)
+	private String jobGroup;
+
+	/**
+	 * cron表达式
+	 */
+	@NotNull
+	private String cronExpression;
+
+	/**
+	 * 描述
+	 */
+	private String description;
+
+	/**
+	 * Spring bean
+	 */
+	@NotNull
+	private String springBean;
+
+	/**
+	 * 是否顺序执行
+	 */
+	private Boolean stateful;
+
+	@NotNull
+	private Boolean enable;
+
+	private String ext1;
+
+	private String ext2;
+
+	private String ext3;
+
+	private String ext4;
+
+	private String ext5;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getJobName() {
+		return jobName;
+	}
+
+	public void setJobName(String jobName) {
+		this.jobName = jobName;
+	}
+
+	public String getJobGroup() {
+		return jobGroup;
+	}
+
+	public void setJobGroup(String jobGroup) {
+		this.jobGroup = jobGroup;
+	}
+
+	public String getCronExpression() {
+		return cronExpression;
+	}
+
+	public void setCronExpression(String cronExpression) {
+		this.cronExpression = cronExpression;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	public String getSpringBean() {
+		return springBean;
+	}
+
+	public void setSpringBean(String springBean) {
+		this.springBean = springBean;
+	}
+
+	public Boolean getStateful() {
+		return stateful;
+	}
+
+	public void setStateful(Boolean stateful) {
+		this.stateful = stateful;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public String getExt1() {
+		return ext1;
+	}
+
+	public void setExt1(String ext1) {
+		this.ext1 = ext1;
+	}
+
+	public String getExt2() {
+		return ext2;
+	}
+
+	public void setExt2(String ext2) {
+		this.ext2 = ext2;
+	}
+
+	public String getExt3() {
+		return ext3;
+	}
+
+	public void setExt3(String ext3) {
+		this.ext3 = ext3;
+	}
+
+	public String getExt4() {
+		return ext4;
+	}
+
+	public void setExt4(String ext4) {
+		this.ext4 = ext4;
+	}
+
+	public String getExt5() {
+		return ext5;
+	}
+
+	public void setExt5(String ext5) {
+		this.ext5 = ext5;
+	}
+
+}

+ 105 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/entity/TaskTraceEntity.java

@@ -0,0 +1,105 @@
+package cn.com.qmth.examcloud.task.dao.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 任务执行记录
+ *
+ * @author WANGWEI
+ * @date 2018年3月7日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_T_TASK_TRACE", indexes = {
+		@Index(name = "IDX_TASK_TRACE_TRACEID", columnList = "traceId")})
+public class TaskTraceEntity extends JpaEntity {
+
+	private static final long serialVersionUID = -1638993747996917970L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	@Column(unique = false, nullable = false)
+	private Long jobId;
+
+	@Column(nullable = false)
+	private String traceId;
+
+	private String jobStatus;
+
+	private Date startTime;
+
+	private Date endTime;
+
+	@Lob
+	private String exception;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getJobId() {
+		return jobId;
+	}
+
+	public void setJobId(Long jobId) {
+		this.jobId = jobId;
+	}
+
+	public String getTraceId() {
+		return traceId;
+	}
+
+	public void setTraceId(String traceId) {
+		this.traceId = traceId;
+	}
+
+	public String getJobStatus() {
+		return jobStatus;
+	}
+
+	public void setJobStatus(String jobStatus) {
+		this.jobStatus = jobStatus;
+	}
+
+	public Date getStartTime() {
+		return startTime;
+	}
+
+	public void setStartTime(Date startTime) {
+		this.startTime = startTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public String getException() {
+		return exception;
+	}
+
+	public void setException(String exception) {
+		this.exception = exception;
+	}
+
+}

+ 34 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/enums/CopyExamStudentStatus.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.task.dao.enums;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年7月31日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum CopyExamStudentStatus {
+
+	NONE("未开始"), PROCESSING("数据处理中"), COMPLETE("数据处理完毕");
+
+	// ===========================================================================
+
+	/**
+	 * 描述
+	 */
+	private String desc;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param desc
+	 */
+	private CopyExamStudentStatus(String desc) {
+		this.desc = desc;
+	}
+
+	public String getDesc() {
+		return desc;
+	}
+
+}

+ 35 - 0
examcloud-task-dao/src/main/java/cn/com/qmth/examcloud/task/dao/enums/ExamStudentImportStatus.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.task.dao.enums;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年7月31日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum ExamStudentImportStatus {
+
+	NONE("未开始"), FILE_PARSING("文件解析中"), FILE_PARSING_COMPLETE("文件解析完成"), DATA_PROCESSING(
+			"数据处理中"), DATA_PROCESSING_COMPLETE("数据处理完毕"), ERROR("错误");
+
+	// ===========================================================================
+
+	/**
+	 * 描述
+	 */
+	private String desc;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param desc
+	 */
+	private ExamStudentImportStatus(String desc) {
+		this.desc = desc;
+	}
+
+	public String getDesc() {
+		return desc;
+	}
+
+}

+ 63 - 0
examcloud-task-service/pom.xml

@@ -0,0 +1,63 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud.task</groupId>
+		<artifactId>examcloud-task</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-task-service</artifactId>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.task</groupId>
+			<artifactId>examcloud-task-dao</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-basic-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-examwork-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-global-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-oe-student-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-oe-admin-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-oe-task-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-reports-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-marking-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-questions-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+	</dependencies>
+</project>

+ 38 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/DataSyncService.java

@@ -0,0 +1,38 @@
+package cn.com.qmth.examcloud.task.service;
+
+import cn.com.qmth.examcloud.global.api.request.SyncBaseRequest;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年9月19日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public interface DataSyncService {
+
+	/**
+	 * 同步
+	 *
+	 * @author WANGWEI
+	 * @param methodName
+	 * @param req
+	 * @param retry
+	 * @return
+	 */
+	void sync(String methodName, SyncBaseRequest req, Boolean async, Boolean retry);
+
+	/**
+	 * 同步
+	 *
+	 * @author WANGWEI
+	 * @param component
+	 * @param methodName
+	 * @param req
+	 * @param retry
+	 * @return
+	 */
+	void sync(String component, String methodName, SyncBaseRequest req, Boolean async,
+			Boolean retry);
+
+}

+ 76 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/DefaultTaskTracker.java

@@ -0,0 +1,76 @@
+package cn.com.qmth.examcloud.task.service;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Date;
+
+import javax.transaction.Transactional;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.task.dao.TaskTraceRepo;
+import cn.com.qmth.examcloud.task.dao.entity.TaskTraceEntity;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年7月25日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component
+public class DefaultTaskTracker implements TaskTracker {
+
+	@Autowired
+	TaskTraceRepo taskTraceRepo;
+
+	@Transactional
+	@Override
+	public void start(ScheduleJob scheduleJob, String traceId) {
+		TaskTraceEntity entity = new TaskTraceEntity();
+		entity.setJobId(scheduleJob.getJobId());
+		entity.setStartTime(new Date());
+		entity.setTraceId(traceId);
+		entity.setJobStatus("启动");
+		taskTraceRepo.save(entity);
+	}
+
+	@Transactional
+	@Override
+	public void whenException(ScheduleJob scheduleJob, String traceId, Exception e) {
+		TaskTraceEntity entity = taskTraceRepo.findByTraceId(traceId);
+		entity.setJobStatus("异常");
+		entity.setEndTime(new Date());
+
+		if (e instanceof StatusException) {
+			entity.setException(((StatusException) e).toJson());
+		} else {
+			entity.setException(getStackTrace(e));
+		}
+
+		taskTraceRepo.save(entity);
+	}
+
+	private String getStackTrace(Throwable throwable) {
+		Writer result = new StringWriter();
+		PrintWriter printWriter = new PrintWriter(result);
+		throwable.printStackTrace(printWriter);
+		return result.toString();
+	}
+
+	@Transactional
+	@Override
+	public void onEnd(ScheduleJob scheduleJob, String traceId) {
+		TaskTraceEntity entity = taskTraceRepo.findByTraceId(traceId);
+		entity.setJobStatus("成功");
+		entity.setEndTime(new Date());
+		taskTraceRepo.save(entity);
+
+	}
+
+}

+ 5 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/ExamStudentCountService.java

@@ -0,0 +1,5 @@
+package cn.com.qmth.examcloud.task.service;
+
+public interface ExamStudentCountService {
+	public void saveOnlieCount();
+}

+ 24 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/ReportsComputeService.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.examcloud.task.service;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.task.base.bean.ReportsComputeBean;
+import cn.com.qmth.examcloud.task.dao.entity.ReportsComputeEntity;
+
+public interface ReportsComputeService {
+	public int getTodoDataCount();
+	public List<ReportsComputeEntity> findTodoData(Long startId,Integer limit);
+	public void compute(ReportsComputeEntity et);
+	public void updateToComputing(ReportsComputeEntity et);
+	public void updateToSuccess(ReportsComputeEntity et);
+	public void updateToFail(ReportsComputeEntity et,String errMsg);
+	public int updateToStoping(ReportsComputeEntity et);
+	public void updateToStop(ReportsComputeEntity et);
+	public ReportsComputeEntity findById(Long id);
+	public void initReportsCompute();
+	public void clearStopingFlag(Long id);
+	public void add(Long projectId,Long rootOrgId);
+	public PageInfo<ReportsComputeBean> queryPage(Long projectId,Integer pageNo,Integer pageSize,Long rootOrgId);
+	public List<ReportsComputeBean> getList(List<Long> pids, Long rootOrgId);
+}

+ 5 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/StudentCountService.java

@@ -0,0 +1,5 @@
+package cn.com.qmth.examcloud.task.service;
+
+public interface StudentCountService {
+	public void saveOnlieCount();
+}

+ 5 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/UserCountService.java

@@ -0,0 +1,5 @@
+package cn.com.qmth.examcloud.task.service;
+
+public interface UserCountService {
+	public void saveOnlieCount();
+}

+ 62 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/consumer/MarkWorkCreateConsumer.java

@@ -0,0 +1,62 @@
+package cn.com.qmth.examcloud.task.service.consumer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordForMarkingCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordForMarkingBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.UpdateExamRecordForMarkingBatchNumReq;
+import cn.com.qmth.examcloud.examwork.api.ExamStudentCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamCourseRelationBean;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamStudentBean;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamStudentReq;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamStudentResp;
+import cn.com.qmth.examcloud.marking.api.StudentPaperCloudService;
+import cn.com.qmth.examcloud.marking.api.bean.StudentPaperBean;
+import cn.com.qmth.examcloud.marking.api.request.SaveStudentPaperReq;
+import cn.com.qmth.examcloud.task.base.multithread.Consumer;
+import cn.com.qmth.examcloud.task.service.dto.MarkWorkCreateDto;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+public class MarkWorkCreateConsumer extends Consumer<MarkWorkCreateDto> {
+
+    private StudentPaperCloudService studentPaperCloudService = SpringContextHolder
+            .getBean(StudentPaperCloudService.class);
+
+    private ExamStudentCloudService examStudentCloudService = SpringContextHolder
+            .getBean(ExamStudentCloudService.class);
+    private ExamRecordForMarkingCloudService oeExamRecordForMarkingCloudService = SpringContextHolder
+            .getBean(ExamRecordForMarkingCloudService.class);
+
+    @Override
+    public void consume(MarkWorkCreateDto dto) {
+        List<ExamRecordForMarkingBean> examRecordForMarkingBeanList = dto.getList();
+        ExamCourseRelationBean examCourseRelationBean = dto.getExamCourseRelationBean();
+        Long workId = dto.getWorkId();
+        List<StudentPaperBean> studentPapers = new ArrayList<StudentPaperBean>();
+        for (ExamRecordForMarkingBean markingBean : examRecordForMarkingBeanList) {
+            GetExamStudentReq getExamStudentReq = new GetExamStudentReq();
+            getExamStudentReq.setExamStudentId(markingBean.getExamStudentId());
+            GetExamStudentResp getExamStudentResp = examStudentCloudService.getExamStudent(getExamStudentReq);
+            ExamStudentBean examStudentBean = getExamStudentResp.getExamStudentBean();
+            StudentPaperBean studentPaper = new StudentPaperBean(workId, markingBean.getId(),
+                    markingBean.getBasePaperId(), markingBean.getExamStudentId(), markingBean.getCourseId(),
+                    examStudentBean.getOrgCode() == null ? "" : examStudentBean.getOrgCode(),
+                    markingBean.getExamRecordDataId(), markingBean.getObjectiveScore(), markingBean.getPaperType(),
+                    examCourseRelationBean.getCourseCode(), examStudentBean.getIdentityNumber(),
+                    examStudentBean.getStudentName(), examStudentBean.getStudentCode(),
+                    examCourseRelationBean.getCourseName(), examStudentBean.getSpecialtyName());
+            studentPapers.add(studentPaper);
+        }
+        SaveStudentPaperReq sreq = new SaveStudentPaperReq();
+        sreq.setWorkId(workId);
+        sreq.setStudentPaperBeanList(studentPapers);
+        studentPaperCloudService.saveStudentPaper(sreq);
+        UpdateExamRecordForMarkingBatchNumReq req = new UpdateExamRecordForMarkingBatchNumReq();
+        List<Long> ids = studentPapers.stream().map(m -> m.getExamRecordMarkingPk()).collect(Collectors.toList());
+        req.setIdList(ids);
+        req.setBatchNum(workId + "");
+        oeExamRecordForMarkingCloudService.updateExamRecordForMarkingBatchNum(req);
+    }
+}

+ 45 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/consumer/ReportsComputeConsumer.java

@@ -0,0 +1,45 @@
+package cn.com.qmth.examcloud.task.service.consumer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.task.base.multithread.Consumer;
+import cn.com.qmth.examcloud.task.dao.entity.ReportsComputeEntity;
+import cn.com.qmth.examcloud.task.service.ReportsComputeService;
+import cn.com.qmth.examcloud.task.service.exception.ReportsComputeStopException;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+public class ReportsComputeConsumer extends Consumer<ReportsComputeEntity> {
+
+	private static final Logger logger = LoggerFactory.getLogger(ReportsComputeConsumer.class);
+	private ReportsComputeService reportsComputeService = SpringContextHolder.getBean(ReportsComputeService.class);
+
+	@Override
+	public void consume(ReportsComputeEntity et) {
+		logger.info("***************************报表计算开始,projectId:" + et.getProjectId());
+		try {
+			// 修改报表计算任务状态
+			reportsComputeService.updateToComputing(et);
+			// 计算报表
+			reportsComputeService.compute(et);
+			// 修改报表计算任务状态
+			reportsComputeService.updateToSuccess(et);
+		} catch (ReportsComputeStopException e) {
+			// 计算终止
+			reportsComputeService.updateToStop(et);
+		} catch (StatusException e) {
+			// 计算出错
+			reportsComputeService.updateToFail(et, e.getDesc());
+			logger.error("***************************报表计算出错,projectId:" + et.getProjectId(), e);
+		} catch (Exception e) {
+			// 计算出错
+			reportsComputeService.updateToFail(et, "系统错误");
+			logger.error("***************************报表计算出错,projectId:" + et.getProjectId(), e);
+		} finally {
+			// 清除终止标志
+			reportsComputeService.clearStopingFlag(et.getId());
+		}
+		logger.info("***************************报表计算结束,projectId:" + et.getProjectId());
+	}
+}

+ 38 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/dto/MarkWorkCreateDto.java

@@ -0,0 +1,38 @@
+package cn.com.qmth.examcloud.task.service.dto;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordForMarkingBean;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamCourseRelationBean;
+
+public class MarkWorkCreateDto{
+	private List<ExamRecordForMarkingBean> list;
+	private Long workId;
+	private ExamCourseRelationBean examCourseRelationBean;
+	public List<ExamRecordForMarkingBean> getList() {
+		return list;
+	}
+	public void setList(List<ExamRecordForMarkingBean> list) {
+		this.list = list;
+	}
+	public Long getWorkId() {
+		return workId;
+	}
+	public void setWorkId(Long workId) {
+		this.workId = workId;
+	}
+	public ExamCourseRelationBean getExamCourseRelationBean() {
+		return examCourseRelationBean;
+	}
+	public void setExamCourseRelationBean(ExamCourseRelationBean examCourseRelationBean) {
+		this.examCourseRelationBean = examCourseRelationBean;
+	}
+	public MarkWorkCreateDto(List<ExamRecordForMarkingBean> list, Long workId,
+			ExamCourseRelationBean examCourseRelationBean) {
+		super();
+		this.list = list;
+		this.workId = workId;
+		this.examCourseRelationBean = examCourseRelationBean;
+	}
+	
+}

+ 52 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/dto/TopicScoreDto.java

@@ -0,0 +1,52 @@
+package cn.com.qmth.examcloud.task.service.dto;
+
+public class TopicScoreDto {
+	private String questionId;
+	
+	//套题题序
+	private Integer questionOrder;
+	private double total;
+	private double avg;
+	private long count;
+
+	public double getTotal() {
+		return total;
+	}
+
+	public void setTotal(double total) {
+		this.total = total;
+	}
+
+	public double getAvg() {
+		return avg;
+	}
+
+	public void setAvg(double avg) {
+		this.avg = avg;
+	}
+
+	public long getCount() {
+		return count;
+	}
+
+	public void setCount(long count) {
+		this.count = count;
+	}
+
+	public String getQuestionId() {
+		return questionId;
+	}
+
+	public void setQuestionId(String questionId) {
+		this.questionId = questionId;
+	}
+
+	public Integer getQuestionOrder() {
+		return questionOrder;
+	}
+
+	public void setQuestionOrder(Integer questionOrder) {
+		this.questionOrder = questionOrder;
+	}
+	
+}

+ 10 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/exception/ReportsComputeStopException.java

@@ -0,0 +1,10 @@
+package cn.com.qmth.examcloud.task.service.exception;
+
+public class ReportsComputeStopException extends RuntimeException {
+
+	/**
+	 * 报表计算任务终止异常
+	 */
+	private static final long serialVersionUID = -201437314569122700L;
+
+}

+ 87 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/DataSyncServiceImpl.java

@@ -0,0 +1,87 @@
+package cn.com.qmth.examcloud.task.service.impl;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.RegExpUtil;
+import cn.com.qmth.examcloud.global.api.HandleSyncCloudService;
+import cn.com.qmth.examcloud.global.api.request.SyncBaseRequest;
+import cn.com.qmth.examcloud.task.dao.DataSyncRepo;
+import cn.com.qmth.examcloud.task.dao.entity.DataSyncEntity;
+import cn.com.qmth.examcloud.task.service.DataSyncService;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年9月19日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Service
+public class DataSyncServiceImpl implements DataSyncService {
+
+	@Autowired
+	DataSyncRepo dataSyncRepo;
+
+	@Autowired
+	HandleSyncCloudService handleSyncCloudService;
+
+	@Override
+	public void sync(String methodName, SyncBaseRequest req, Boolean async, Boolean retry) {
+		String group = PropertyHolder.getString("$sync." + methodName + ".group");
+		if (StringUtils.isBlank(group)) {
+			throw new StatusException("001001", "group is not configured");
+		}
+
+		List<String> componentList = RegExpUtil.findAll(group, "[^\\,]+");
+
+		for (String component : componentList) {
+			component = component.trim();
+			sync(component, methodName, req, async, retry);
+		}
+	}
+
+	@Override
+	public void sync(String component, String methodName, SyncBaseRequest req, Boolean async,
+			Boolean retry) {
+		String url = PropertyHolder.getString("$sync." + methodName + ".component." + component);
+		req.setUrl(url);
+
+		if (async) {
+			DataSyncEntity entity = new DataSyncEntity();
+			entity.setComponent(component);
+			entity.setMethodName(methodName);
+			entity.setParamType(req.getClass().getName());
+			entity.setSyncNum(0);
+			entity.setParamJson(JsonUtil.toJson(req));
+			dataSyncRepo.saveAndFlush(entity);
+		} else {
+			try {
+				Method method = handleSyncCloudService.getClass().getMethod(methodName,
+						req.getClass());
+				method.invoke(handleSyncCloudService, req);
+			} catch (Exception e) {
+				if (retry) {
+					DataSyncEntity entity = new DataSyncEntity();
+					entity.setComponent(component);
+					entity.setMethodName(methodName);
+					entity.setParamType(req.getClass().getName());
+					entity.setSyncNum(0);
+					entity.setParamJson(JsonUtil.toJson(req));
+					dataSyncRepo.saveAndFlush(entity);
+				} else {
+					throw new RuntimeException(e);
+				}
+			}
+		}
+
+	}
+
+}

+ 19 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/ExamStudentCountServiceImpl.java

@@ -0,0 +1,19 @@
+package cn.com.qmth.examcloud.task.service.impl;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.core.reports.api.ExamStudentCountCloudService;
+import cn.com.qmth.examcloud.task.service.ExamStudentCountService;
+
+@Service
+public class ExamStudentCountServiceImpl implements ExamStudentCountService {
+	@Autowired
+	private ExamStudentCountCloudService examStudentCountCloudService;
+
+	@Override
+	public void saveOnlieCount() {
+		examStudentCountCloudService.saveCount();
+	}
+
+}

+ 806 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/ReportsComputeServiceImpl.java

@@ -0,0 +1,806 @@
+package cn.com.qmth.examcloud.task.service.impl;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import javax.persistence.criteria.Predicate;
+import javax.transaction.Transactional;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.basic.api.CourseCloudService;
+import cn.com.qmth.examcloud.core.basic.api.OrgCloudService;
+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.request.GetCoursesByIdListReq;
+import cn.com.qmth.examcloud.core.basic.api.request.GetOrgsByIdListReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetCoursesByIdListResp;
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgsByIdListResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamStudentDataCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamStudentDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamStudentScoreDataBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetExamStudentDataReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetExamStudentDataResp;
+import cn.com.qmth.examcloud.core.reports.api.ExamCourseDataReportCloudService;
+import cn.com.qmth.examcloud.core.reports.api.ExamOrgReportCloudService;
+import cn.com.qmth.examcloud.core.reports.api.ProjectCloudService;
+import cn.com.qmth.examcloud.core.reports.api.bean.ExamCourseDataReportBean;
+import cn.com.qmth.examcloud.core.reports.api.bean.ExamOrgReportBean;
+import cn.com.qmth.examcloud.core.reports.api.bean.ProjectInfoBean;
+import cn.com.qmth.examcloud.core.reports.api.request.DeleteExamCourseDataReportByProjectReq;
+import cn.com.qmth.examcloud.core.reports.api.request.DeleteExamOrgReportByProjectReq;
+import cn.com.qmth.examcloud.core.reports.api.request.GetProjectInfoBeanReq;
+import cn.com.qmth.examcloud.core.reports.api.request.SaveExamCourseDataReportListReq;
+import cn.com.qmth.examcloud.core.reports.api.request.SaveExamOrgReportListReq;
+import cn.com.qmth.examcloud.core.reports.api.request.UpdateProjectCourseOrgCountReq;
+import cn.com.qmth.examcloud.core.reports.api.request.UpdateProjectStatusByIdsReq;
+import cn.com.qmth.examcloud.core.reports.api.request.UpdateProjectStatusReq;
+import cn.com.qmth.examcloud.core.reports.api.response.GetProjectInfoBeanResp;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamsByIdListReq;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamsByIdListResp;
+import cn.com.qmth.examcloud.task.base.bean.ReportsComputeBean;
+import cn.com.qmth.examcloud.task.base.enums.ReportsComputeStatus;
+import cn.com.qmth.examcloud.task.base.util.BatchGetDataUtil;
+import cn.com.qmth.examcloud.task.dao.ReportsComputeRepo;
+import cn.com.qmth.examcloud.task.dao.entity.ReportsComputeEntity;
+import cn.com.qmth.examcloud.task.service.ReportsComputeService;
+import cn.com.qmth.examcloud.task.service.dto.TopicScoreDto;
+import cn.com.qmth.examcloud.task.service.exception.ReportsComputeStopException;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+
+@Service
+public class ReportsComputeServiceImpl implements ReportsComputeService {
+	private final static int batchSize = 100;
+
+	private final static Map<Long, Boolean> jobStopFlag = new HashMap<Long, Boolean>();
+	@Autowired
+	private CourseCloudService courseCloudService;
+	@Autowired
+	private ExamCloudService examCloudService;
+	@Autowired
+	private OrgCloudService orgCloudService;
+	@Autowired
+	private ReportsComputeRepo reportsComputeRepo;
+
+	@Autowired
+	private ProjectCloudService projectCloudService;
+
+	@Autowired
+	private ExamStudentDataCloudService examStudentDataCloudService;
+
+	@Autowired
+	private ExamCourseDataReportCloudService examCourseDataReportCloudService;
+
+	@Autowired
+	private ExamOrgReportCloudService examOrgReportBeanCloudService;
+
+	@Override
+	public List<ReportsComputeEntity> findTodoData(Long startId, Integer limit) {
+		return reportsComputeRepo.findTodoData(startId, limit);
+	}
+
+	@Transactional
+	@Override
+	public void initReportsCompute() {
+		List<ReportsComputeEntity> cs = reportsComputeRepo.findByStatus(ReportsComputeStatus.COMPUTING);
+		UpdateProjectStatusByIdsReq req = new UpdateProjectStatusByIdsReq();
+		if (cs != null && cs.size() > 0) {
+			reportsComputeRepo.initComputingJob();
+			List<Long> cids = cs.stream().map(c -> c.getProjectId()).collect(Collectors.toList());
+			req.setComputingProjectIds(cids);
+		}
+		List<ReportsComputeEntity> ss = reportsComputeRepo.findByStatus(ReportsComputeStatus.STOPING);
+		if (ss != null && ss.size() > 0) {
+			reportsComputeRepo.initStopingJob();
+			List<Long> sids = ss.stream().map(c -> c.getProjectId()).collect(Collectors.toList());
+			req.setStopingProjectIds(sids);
+		}
+		if (req.getComputingProjectIds() != null || req.getStopingProjectIds() != null) {
+			projectCloudService.updateProjectStatusByIds(req);
+		}
+	}
+
+	@Override
+	public ReportsComputeEntity findById(Long id) {
+		ReportsComputeEntity re = GlobalHelper.getEntity(reportsComputeRepo, id, ReportsComputeEntity.class);
+		return re;
+	}
+
+	@Transactional
+	@Override
+	public void compute(ReportsComputeEntity et) {
+		// 判断任务终止
+		checkIsStoping(et.getId());
+		// 获取项目信息
+		GetProjectInfoBeanReq req = new GetProjectInfoBeanReq();
+		req.setProjectId(et.getProjectId());
+		GetProjectInfoBeanResp resp = projectCloudService.getProjectBean(req);
+		if (resp.getBean() == null) {
+			updateToFail(et, "未找到项目信息");
+			return;
+		}
+		ProjectInfoBean pro = resp.getBean();
+		// 只处理数据来源是同步
+		if ("SYNC".equals(pro.getDataOrigin())) {
+			computeSync(pro, et);
+		} else if ("IMPORT".equals(pro.getDataOrigin())) {
+			computeImport(pro, et);
+		} else {
+			updateToFail(et, "项目数据来源错误");
+		}
+		// 计算结束清除缓存终止标志
+		jobStopFlag.remove(et.getId());
+	}
+
+	/**
+	 * 数据来源是导入
+	 * 
+	 * @param pro
+	 * @param et
+	 */
+	private void computeImport(ProjectInfoBean pro, ReportsComputeEntity et) {
+		updateToFail(et, "不支持处理数据来源是导入的项目");
+	}
+
+	/**
+	 * 数据来源是同步
+	 * 
+	 * @param pro
+	 * @param et
+	 */
+	private void computeSync(ProjectInfoBean pro, ReportsComputeEntity et) {
+		// 判断任务终止
+		checkIsStoping(et.getId());
+		if (StringUtils.isBlank(pro.getExamIds())) {
+			updateToFail(et, "考试id为空");
+			return;
+		}
+
+		List<Long> examIds = Arrays.asList(pro.getExamIds().split(",")).stream().map(str -> Long.parseLong(str))
+				.collect(Collectors.toList());
+		Map<String, Object> result = new HashMap<String, Object>();
+
+		// 学习中心id集合-记录中心数量
+		Set<Long> ordIds = new HashSet<Long>();
+		result.put("ordIds", ordIds);
+
+		// 课程id集合-记录课程数量
+		Set<Long> courseIds = new HashSet<Long>();
+		result.put("courseIds", courseIds);
+
+		// 考试-学习中心数值分析结果
+		Map<String, ExamOrgReportBean> examOrgReport = new HashMap<String, ExamOrgReportBean>();
+		result.put("examOrgReport", examOrgReport);
+		// 课程数值分析结果
+		Map<String, ExamCourseDataReportBean> examCourseDataReport = new HashMap<String, ExamCourseDataReportBean>();
+		result.put("examCourseDataReport", examCourseDataReport);
+		// 分数结果集合-计算标准差
+		Map<String, List<Double>> scores = new HashMap<String, List<Double>>();
+		result.put("scores", scores);
+
+		// 考试课程对应的basePaperId集合-计算难度
+		Map<String, Set<String>> basePapers = new HashMap<String, Set<String>>();
+		result.put("basePapers", basePapers);
+
+		// basePaperId对应的难度系数-计算难度
+		Map<String, Double> basePapersDegree = new HashMap<String, Double>();
+		result.put("basePapersDegree", basePapersDegree);
+
+		// basePaperId对应的小题平均分和满分-计算难度
+		Map<String, Map<String, TopicScoreDto>> basePapersTopicScore = new HashMap<String, Map<String, TopicScoreDto>>();
+		result.put("basePapersTopicScore", basePapersTopicScore);
+
+		for (Long examId : examIds) {
+			// 根据考试id获取数据并计算结果
+			computeByExamId(result, examId, et.getId(), pro);
+		}
+		// 判断任务终止
+		checkIsStoping(et.getId());
+		// 计算标准差
+		for (String key : examCourseDataReport.keySet()) {
+			ExamCourseDataReportBean bean = examCourseDataReport.get(key);
+			bean.setStd(std(scores.get(key), bean.getAvgScore()));
+		}
+		// 计算差异系数
+		for (String key : examCourseDataReport.keySet()) {
+			ExamCourseDataReportBean bean = examCourseDataReport.get(key);
+			bean.setCdi(getPercentage(bean.getStd(), bean.getAvgScore()));
+			bean.setAvgScore(getTwoDecimal(bean.getAvgScore()));
+		}
+
+		// 判断任务终止
+		checkIsStoping(et.getId());
+		// 计算basePaperId对应的难度系数
+		for (String basePaperId : basePapersTopicScore.keySet()) {
+			Map<String, TopicScoreDto> temmap = basePapersTopicScore.get(basePaperId);
+			basePapersDegree.put(basePaperId, difficultyDegree(temmap));
+		}
+		// 判断任务终止
+		checkIsStoping(et.getId());
+		// 计算平均调卷难度
+		for (String key : examCourseDataReport.keySet()) {
+			ExamCourseDataReportBean bean = examCourseDataReport.get(key);
+			String dataKey = bean.getExamId() + "-" + bean.getCourseId();
+			bean.setAvgDifficultyDegree(avgDifficultyDegree(dataKey, basePapers, basePapersDegree));
+		}
+		// 判断任务终止
+		checkIsStoping(et.getId());
+		// 设置中心、课程、考试code和名称
+		fillOrgNameCode(examOrgReport);
+		fillCourseNameCode(examCourseDataReport);
+		fillExamNameCode(examOrgReport, examCourseDataReport);
+
+		// 判断任务终止
+		checkIsStoping(et.getId());
+
+		// 清除旧数据
+		DeleteExamOrgReportByProjectReq dreq1 = new DeleteExamOrgReportByProjectReq();
+		dreq1.setProjectId(pro.getId());
+		dreq1.setRootOrgId(pro.getRootOrgId());
+		examOrgReportBeanCloudService.deleteExamOrgReportByProject(dreq1);
+
+		DeleteExamCourseDataReportByProjectReq dreq2 = new DeleteExamCourseDataReportByProjectReq();
+		dreq2.setProjectId(pro.getId());
+		dreq2.setRootOrgId(pro.getRootOrgId());
+		examCourseDataReportCloudService.deleteExamCourseDataReportByProject(dreq2);
+
+		// 保存计算结果
+		SaveExamOrgReportListReq req1 = new SaveExamOrgReportListReq();
+		req1.setBeans(new ArrayList<ExamOrgReportBean>(examOrgReport.values()));
+		examOrgReportBeanCloudService.saveExamOrgReportList(req1);
+
+		// 判断任务终止
+		checkIsStoping(et.getId());
+
+		// 保存计算结果
+		SaveExamCourseDataReportListReq req2 = new SaveExamCourseDataReportListReq();
+		req2.setBeans(new ArrayList<ExamCourseDataReportBean>(examCourseDataReport.values()));
+		examCourseDataReportCloudService.saveExamCourseDataReportList(req2);
+
+		// 保存项目课程数量、中心数量
+		UpdateProjectCourseOrgCountReq req = new UpdateProjectCourseOrgCountReq();
+		req.setProjectId(pro.getId());
+		req.setOrgCount(ordIds.size());
+		req.setCourseCount(courseIds.size());
+		projectCloudService.updateProjectCourseOrgCount(req);
+	}
+
+	private void fillOrgNameCode(Map<String, ExamOrgReportBean> examOrgReport) {
+		List<Long> ids = examOrgReport.entrySet().stream().map(dto -> dto.getValue().getOrgId()).distinct()
+				.collect(Collectors.toList());
+		List<OrgBean> orgList = new ArrayList<OrgBean>();
+		GetOrgsByIdListReq req = new GetOrgsByIdListReq();
+		BatchGetDataUtil<OrgBean, Long> tool = new BatchGetDataUtil<OrgBean, Long>() {
+			@Override
+			public List<OrgBean> getData(List<Long> paramList) {
+				req.setOrgIdList(paramList);
+				GetOrgsByIdListResp resp = orgCloudService.getOrgsByIdList(req);
+				return resp.getOrgList();
+			}
+
+		};
+		tool.getDataForBatch(orgList, ids, batchSize);
+		Map<Long, OrgBean> orgMap = orgList.stream()
+				.collect(Collectors.toMap(OrgBean::getId, account -> account, (key1, key2) -> key2));
+		for (ExamOrgReportBean b : examOrgReport.values()) {
+			OrgBean ob = orgMap.get(b.getOrgId());
+			b.setOrgCode(ob.getCode());
+			b.setOrgName(ob.getName());
+		}
+	}
+
+	private void fillCourseNameCode(Map<String, ExamCourseDataReportBean> examCourseDataReport) {
+		List<Long> ids = examCourseDataReport.entrySet().stream().map(dto -> dto.getValue().getCourseId()).distinct()
+				.collect(Collectors.toList());
+		List<CourseBean> courseList = new ArrayList<CourseBean>();
+		GetCoursesByIdListReq req = new GetCoursesByIdListReq();
+		BatchGetDataUtil<CourseBean, Long> tool = new BatchGetDataUtil<CourseBean, Long>() {
+			@Override
+			public List<CourseBean> getData(List<Long> paramList) {
+				req.setCourseIdList(paramList);
+				GetCoursesByIdListResp resp = courseCloudService.getCoursesByIdList(req);
+				return resp.getCourseList();
+			}
+
+		};
+		tool.getDataForBatch(courseList, ids, batchSize);
+		Map<Long, CourseBean> map = courseList.stream()
+				.collect(Collectors.toMap(CourseBean::getId, account -> account, (key1, key2) -> key2));
+		for (ExamCourseDataReportBean b : examCourseDataReport.values()) {
+			CourseBean ob = map.get(b.getCourseId());
+			b.setCourseCode(ob.getCode());
+			b.setCourseName(ob.getName());
+		}
+	}
+
+	private void fillExamNameCode(Map<String, ExamOrgReportBean> examOrgReport,
+			Map<String, ExamCourseDataReportBean> examCourseDataReport) {
+		List<Long> ids = examOrgReport.entrySet().stream().map(dto -> dto.getValue().getExamId()).distinct()
+				.collect(Collectors.toList());
+		List<ExamBean> list = new ArrayList<ExamBean>();
+		GetExamsByIdListReq req = new GetExamsByIdListReq();
+		BatchGetDataUtil<ExamBean, Long> tool = new BatchGetDataUtil<ExamBean, Long>() {
+			@Override
+			public List<ExamBean> getData(List<Long> paramList) {
+				req.setExamIdList(paramList);
+				GetExamsByIdListResp resp = examCloudService.getExamsByIdList(req);
+				return resp.getExamList();
+			}
+
+		};
+		tool.getDataForBatch(list, ids, batchSize);
+		Map<Long, ExamBean> map = list.stream()
+				.collect(Collectors.toMap(ExamBean::getId, account -> account, (key1, key2) -> key2));
+		for (ExamOrgReportBean b : examOrgReport.values()) {
+			ExamBean ob = map.get(b.getExamId());
+			b.setExamCode(ob.getCode());
+			b.setExamName(ob.getName());
+		}
+		for (ExamCourseDataReportBean b : examCourseDataReport.values()) {
+			ExamBean ob = map.get(b.getExamId());
+			b.setExamCode(ob.getCode());
+			b.setExamName(ob.getName());
+		}
+	}
+
+	private Double avgDifficultyDegree(String dataKey, Map<String, Set<String>> basePapers,
+			Map<String, Double> basePapersDegree) {
+		// 考试课程对应的basePapers
+		Set<String> set = basePapers.get(dataKey);
+		if (set == null || set.size() == 0) {
+			return null;
+		}
+		double total = 0.0;
+		// 算平均难度时剔除没有难度系数的试卷
+		int nullCount = 0;
+		for (String s : set) {
+			Double td = basePapersDegree.get(s);
+			if (td == null) {
+				nullCount++;
+			} else {
+				total = total + basePapersDegree.get(s);
+			}
+		}
+		if (nullCount == set.size()) {
+			return null;
+		}
+		return getTwoDecimal(total / (set.size() - nullCount));
+	}
+
+	private Double difficultyDegree(Map<String, TopicScoreDto> map) {
+		// 剔除整张卷子没有小题答题的
+		if (map == null || map.size() == 0) {
+			return null;
+		}
+		double totalAvg = 0.0;
+		double totalFull = 0.0;
+		for (String k : map.keySet()) {
+			TopicScoreDto dto = map.get(k);
+			if (dto.getTotal() == 0) {
+				throw new StatusException("100001",
+						"小题满分不能为0 QuestionId:" + dto.getQuestionId() + " QuestionOrder:" + dto.getQuestionOrder());
+			}
+			totalAvg = totalAvg + dto.getAvg();
+			totalFull = totalFull + dto.getTotal();
+		}
+		return totalAvg / totalFull;
+	}
+
+	private double std(List<Double> list, Double average) {
+		if (list == null || list.size() == 0 || average == null) {
+			return 0.0;
+		}
+		double total = 0.0;
+		for (int i = 0; i < list.size(); i++) {
+			total += Math.pow((list.get(i) - average), 2);
+		}
+		double standardDeviation = Math.sqrt(total / list.size());
+		return getTwoDecimal(standardDeviation);
+	}
+
+	/**
+	 * 根据考试id获取数据并计算
+	 * 
+	 * @param result
+	 * @param examId
+	 * @param jobId
+	 */
+	private void computeByExamId(Map<String, Object> result, Long examId, Long jobId, ProjectInfoBean pro) {
+		Long startId = 0l;
+		GetExamStudentDataReq req = new GetExamStudentDataReq();
+		req.setExamId(examId);
+		req.setSize(200);
+		for (;;) {
+			// 判断任务终止
+			checkIsStoping(jobId);
+			req.setStartId(startId);
+			GetExamStudentDataResp res = examStudentDataCloudService.getExamStudentData(req);
+			List<ExamStudentDataBean> list = res.getList();
+			if (list == null || list.size() == 0) {
+				break;
+			}
+			startId = res.getNextId();
+			for (ExamStudentDataBean st : list) {
+				// 判断任务终止
+				checkIsStoping(jobId);
+				// 计算考试-学习中心数值分析表
+				computeExamOrgReport(result, st, pro);
+				// 计算课程数值分析表
+				computeExamCourseDataReport(result, st, pro);
+			}
+		}
+	}
+
+	/**
+	 * 计算考试-学习中心数值分析表
+	 * 
+	 * @param result
+	 * @param st
+	 * @param pro
+	 */
+	@SuppressWarnings("unchecked")
+	private void computeExamOrgReport(Map<String, Object> result, ExamStudentDataBean st, ProjectInfoBean pro) {
+		// 学习中心id集合
+		Set<Long> ordIds = (Set<Long>) result.get("ordIds");
+		ordIds.add(st.getOrgId());
+		Map<String, ExamOrgReportBean> examOrgReport = (Map<String, ExamOrgReportBean>) result.get("examOrgReport");
+		String key = st.getExamId() + "-" + st.getOrgId();
+		ExamOrgReportBean bean = examOrgReport.get(key);
+		if (bean == null) {
+			bean = new ExamOrgReportBean();
+			bean.init(pro.getPartitionCount());
+			bean.setProjectId(pro.getId());
+			bean.setExamId(st.getExamId());
+			bean.setOrgId(st.getOrgId());
+			bean.setRootOrgId(pro.getRootOrgId());
+			examOrgReport.put(key, bean);
+		}
+		// 报名人数
+		bean.setSignCount(bean.getSignCount() + 1);
+		// 实考人数
+		if (!st.getAbsent()) {
+			bean.setParticipantCount(bean.getParticipantCount() + 1);
+		}
+		// 及格人数
+		if (!st.getAbsent() && st.getScore() == null) {
+			throw new StatusException("10030", "未缺考的考生分数不能为空 ExamStudentId:" + st.getExamStudentId());
+		}
+		if (!st.getAbsent() && st.getScore() >= pro.getPassScore()) {
+			bean.setPassCount(bean.getPassCount() + 1);
+		}
+		// 分段人数
+		if (!st.getAbsent()) {
+			addPartitionData(st.getScore(), pro, bean.getPartitionData());
+		}
+	}
+
+	/**
+	 * 计算分段人数
+	 * 
+	 * @param score
+	 * @param pro
+	 * @param partitionData
+	 */
+	private void addPartitionData(Double score, ProjectInfoBean pro, List<Long> partitionData) {
+		List<Double> partitionDetails = pro.getPartitionDetails();
+		for (int i = 0; i < partitionDetails.size(); i++) {
+			if (i == 0 || i == partitionDetails.size() - 1) {
+				if (i == 0) {
+					if (score < partitionDetails.get(i)) {
+						partitionData.set(i, partitionData.get(i) + 1);
+					}
+				}
+				if (i == partitionDetails.size() - 1) {
+					if (partitionDetails.get(i) <= score && score <= pro.getTotalScore()) {
+						partitionData.set(i+1, partitionData.get(i+1) + 1);
+					}
+				}
+			} else {
+				if (partitionDetails.get(i - 1) <= score && score < partitionDetails.get(i)) {
+					partitionData.set(i, partitionData.get(i) + 1);
+				}
+			}
+		}
+	}
+
+	/**
+	 * 计算课程数值分析表
+	 * 
+	 * @param result
+	 * @param st
+	 * @param pro
+	 */
+	@SuppressWarnings("unchecked")
+	private void computeExamCourseDataReport(Map<String, Object> result, ExamStudentDataBean st, ProjectInfoBean pro) {
+		// 课程id集合
+		Set<Long> courseIds = (Set<Long>) result.get("courseIds");
+		courseIds.add(st.getCourseId());
+		Map<String, ExamCourseDataReportBean> examCourseDataReport = (Map<String, ExamCourseDataReportBean>) result
+				.get("examCourseDataReport");
+		String key = st.getExamId() + "-" + st.getCourseId();
+		ExamCourseDataReportBean bean = examCourseDataReport.get(key);
+		if (bean == null) {
+			bean = new ExamCourseDataReportBean();
+			bean.init(pro.getPartitionCount());
+			bean.setProjectId(pro.getId());
+			bean.setExamId(st.getExamId());
+			bean.setCourseId(st.getCourseId());
+			bean.setRootOrgId(pro.getRootOrgId());
+			examCourseDataReport.put(key, bean);
+		}
+		// 设置满分数值
+		if (st.getTotalScore() != null) {
+			bean.setTotalScore(st.getTotalScore());
+		}
+		// 最高分
+		if (!st.getAbsent() && st.getScore() > bean.getMaxScore()) {
+			bean.setMaxScore(st.getScore());
+		}
+		// 最低分
+		if (!st.getAbsent() && (bean.getMinScore() == null || st.getScore() < bean.getMinScore())) {
+			bean.setMinScore(st.getScore());
+		}
+		// 报名人数
+		bean.setSignCount(bean.getSignCount() + 1);
+		// 实考人数
+		if (!st.getAbsent()) {
+			bean.setParticipantCount(bean.getParticipantCount() + 1);
+		}
+		// 满分人数
+		if (!st.getAbsent() && st.getScore() == st.getTotalScore()) {
+			bean.setFullCount(bean.getFullCount() + 1);
+		}
+		// 零分人数
+		if (!st.getAbsent() && st.getScore() == 0.0) {
+			bean.setZeroCount(bean.getZeroCount() + 1);
+		}
+		// 及格人数
+		if (!st.getAbsent() && st.getScore() >= pro.getPassScore()) {
+			bean.setPassCount(bean.getPassCount() + 1);
+		}
+		// 分段人数
+		if (!st.getAbsent()) {
+			addPartitionData(st.getScore(), pro, bean.getPartitionData());
+		}
+		// 平均分
+		if (!st.getAbsent()) {
+			Double avg = bean.getAvgScore();
+			avg += (st.getScore() - avg) / bean.getParticipantCount();
+			bean.setAvgScore(avg);
+		}
+		// 难度系数计算
+		// 考试课程对应的basePaperId集合
+		Map<String, Set<String>> basePapers = (Map<String, Set<String>>) result.get("basePapers");
+
+		// basePaperId对应的小题平均分和满分
+		Map<String, Map<String, TopicScoreDto>> basePapersTopicScore = (Map<String, Map<String, TopicScoreDto>>) result
+				.get("basePapersTopicScore");
+
+		if (!st.getAbsent()) {
+			Map<String, List<Double>> scores = (Map<String, List<Double>>) result.get("scores");
+			// 分数集合-计算标准差
+			List<Double> list = scores.get(key);
+			if (list == null) {
+				list = new ArrayList<Double>();
+				scores.put(key, list);
+			}
+			list.add(st.getScore());
+			// 考试课程对应的basePaperId集合
+			Set<String> set = basePapers.get(key);
+			if (set == null) {
+				set = new HashSet<String>();
+				basePapers.put(key, set);
+			}
+			set.add(st.getBasePaperId());
+
+			// basePaperId对应的小题平均分和满分
+			Map<String, TopicScoreDto> temmap = basePapersTopicScore.get(st.getBasePaperId());
+			if (temmap == null) {
+				temmap = new HashMap<String, TopicScoreDto>();
+				basePapersTopicScore.put(st.getBasePaperId(), temmap);
+			}
+			// 考生小题得分详情
+			List<ExamStudentScoreDataBean> scoreDetails = st.getScoreDetails();
+			if (scoreDetails == null || scoreDetails.size() == 0) {
+				throw new StatusException("10040", "考生小题得分详情不能为空 ExamStudentId:" + st.getExamStudentId());
+			}
+
+			// 计算每个小题的平均分
+			for (ExamStudentScoreDataBean b : scoreDetails) {
+				if (b.getScore() != null) {
+					String topicScoreKey = b.getQuestionId() + "-"
+							+ (b.getQuestionOrder() == null ? "" : b.getQuestionOrder());
+					TopicScoreDto tem = temmap.get(topicScoreKey);
+					if (tem == null) {
+						tem = new TopicScoreDto();
+						tem.setQuestionId(b.getQuestionId());
+						tem.setQuestionOrder(b.getQuestionOrder());
+						tem.setTotal(b.getTotalScore());
+						temmap.put(topicScoreKey, tem);
+					}
+					tem.setCount(tem.getCount() + 1);
+					double avg = tem.getAvg();
+					avg += (b.getScore() - avg) / tem.getCount();
+					tem.setAvg(avg);
+				}
+			}
+		}
+
+	}
+
+	@Override
+	public void clearStopingFlag(Long id) {
+		// 清除缓存终止标志
+		jobStopFlag.remove(id);
+	}
+
+	private void checkIsStoping(Long id) {
+		if (jobStopFlag.get(id) != null) {
+			// 清除缓存终止标志
+			jobStopFlag.remove(id);
+			throw new ReportsComputeStopException();
+		}
+	}
+
+	@Transactional
+	@Override
+	public void updateToComputing(ReportsComputeEntity et) {
+		et.setStatus(ReportsComputeStatus.COMPUTING);
+		et.setStartTime(new Date());
+		reportsComputeRepo.save(et);
+		UpdateProjectStatusReq req = new UpdateProjectStatusReq();
+		req.setProjectId(et.getProjectId());
+		req.setStatus(2);
+		projectCloudService.updateProjectStatus(req);
+	}
+
+	@Transactional
+	@Override
+	public void updateToSuccess(ReportsComputeEntity et) {
+		et.setStatus(ReportsComputeStatus.SUCCESS);
+		et.setEndTime(new Date());
+		reportsComputeRepo.save(et);
+		UpdateProjectStatusReq req = new UpdateProjectStatusReq();
+		req.setProjectId(et.getProjectId());
+		req.setStatus(3);
+		projectCloudService.updateProjectStatus(req);
+	}
+
+	@Transactional
+	@Override
+	public void updateToFail(ReportsComputeEntity et, String errMsg) {
+		et.setStatus(ReportsComputeStatus.FAIL);
+		et.setErrorDesc(errMsg);
+		et.setEndTime(new Date());
+		reportsComputeRepo.save(et);
+		UpdateProjectStatusReq req = new UpdateProjectStatusReq();
+		req.setProjectId(et.getProjectId());
+		req.setStatus(4);
+		projectCloudService.updateProjectStatus(req);
+	}
+
+	@Transactional
+	@Override
+	public int updateToStoping(ReportsComputeEntity et) {
+		int ret = reportsComputeRepo.updateToStoping(et.getId());
+		if (ret != 0) {
+			jobStopFlag.put(et.getId(), true);
+		}
+		return ret;
+	}
+
+	@Transactional
+	@Override
+	public void updateToStop(ReportsComputeEntity et) {
+		et.setStatus(ReportsComputeStatus.STOP);
+		et.setEndTime(new Date());
+		reportsComputeRepo.save(et);
+		UpdateProjectStatusReq req = new UpdateProjectStatusReq();
+		req.setProjectId(et.getProjectId());
+		req.setStatus(5);
+		projectCloudService.updateProjectStatus(req);
+		// 清除缓存终止标志
+		jobStopFlag.remove(et.getId());
+	}
+
+	@Transactional
+	@Override
+	public void add(Long projectId, Long rootOrgId) {
+		List<ReportsComputeEntity> list = reportsComputeRepo.findByProjectIdAndRootOrgIdAndStatus(projectId, rootOrgId,
+				ReportsComputeStatus.COMPUTING);
+		if (list != null && list.size() > 0) {
+			throw new StatusException("10001", "该项目正在计算中");
+		}
+		ReportsComputeEntity e = new ReportsComputeEntity();
+		e.setProjectId(projectId);
+		e.setRootOrgId(rootOrgId);
+		e.setStatus(ReportsComputeStatus.NONE);
+		reportsComputeRepo.save(e);
+	}
+
+	@Override
+	public int getTodoDataCount() {
+		return reportsComputeRepo.getTodoDataCount();
+	}
+
+	private Double getPercentage(Double a, Double b) {
+		if (b == 0) {
+			return null;
+		}
+		BigDecimal bd = new BigDecimal(a * 100 / b);
+		Double tem = bd.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
+		return tem;
+	}
+
+	private Double getTwoDecimal(Double b) {
+		if (b == null) {
+			return b;
+		}
+		BigDecimal bd = new BigDecimal(b);
+		Double tem = bd.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
+		return tem;
+	}
+
+	@Override
+	public PageInfo<ReportsComputeBean> queryPage(Long projectId, Integer pageNo, Integer pageSize, Long rootOrgId) {
+		Specification<ReportsComputeEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+
+			predicates.add(cb.equal(root.get("projectId"), projectId));
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		PageRequest pageRequest = PageRequest.of(pageNo - 1, pageSize, new Sort(Direction.DESC, "id"));
+
+		Page<ReportsComputeEntity> projects = reportsComputeRepo.findAll(specification, pageRequest);
+
+		List<ReportsComputeBean> ret = Lists.newArrayList();
+
+		for (ReportsComputeEntity cur : projects) {
+			ReportsComputeBean bean = new ReportsComputeBean();
+			BeanUtils.copyProperties(cur, bean);
+			bean.setStatusName(bean.getStatus().getDesc());
+			ret.add(bean);
+		}
+		return new PageInfo<ReportsComputeBean>(projects, ret);
+	}
+
+	@Override
+	public List<ReportsComputeBean> getList(List<Long> pids, Long rootOrgId) {
+		List<ReportsComputeBean> ret = new ArrayList<ReportsComputeBean>();
+		List<ReportsComputeEntity> list = reportsComputeRepo.getByIds(pids, rootOrgId);
+		if (list != null) {
+			for (ReportsComputeEntity pe : list) {
+				ReportsComputeBean bean = new ReportsComputeBean();
+				BeanUtils.copyProperties(pe, bean);
+				bean.setStatusName(bean.getStatus().getDesc());
+				ret.add(bean);
+			}
+		}
+		return ret;
+	}
+}

+ 19 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/StudentCountServiceImpl.java

@@ -0,0 +1,19 @@
+package cn.com.qmth.examcloud.task.service.impl;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.core.reports.api.StudentCountCloudService;
+import cn.com.qmth.examcloud.task.service.StudentCountService;
+
+@Service
+public class StudentCountServiceImpl implements StudentCountService {
+	@Autowired
+	private StudentCountCloudService studentCountCloudService;
+
+	@Override
+	public void saveOnlieCount() {
+		studentCountCloudService.saveCount();
+	}
+
+}

+ 19 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/impl/UserCountServiceImpl.java

@@ -0,0 +1,19 @@
+package cn.com.qmth.examcloud.task.service.impl;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.core.reports.api.UserCountCloudService;
+import cn.com.qmth.examcloud.task.service.UserCountService;
+
+@Service
+public class UserCountServiceImpl implements UserCountService {
+	@Autowired
+	private UserCountCloudService userCountCloudService;
+
+	@Override
+	public void saveOnlieCount() {
+		userCountCloudService.saveCount();
+	}
+
+}

+ 136 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/CopyExamStudentTask.java

@@ -0,0 +1,136 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.ExamStudentCloudService;
+import cn.com.qmth.examcloud.examwork.api.request.CopyExamStudentsReq;
+import cn.com.qmth.examcloud.examwork.api.request.LockExamStudentsReq;
+import cn.com.qmth.examcloud.examwork.api.request.UnlockExamStudentsReq;
+import cn.com.qmth.examcloud.examwork.api.response.CopyExamStudentsResp;
+import cn.com.qmth.examcloud.task.dao.CopyExamStudentRepo;
+import cn.com.qmth.examcloud.task.dao.entity.CopyExamStudentEntity;
+import cn.com.qmth.examcloud.task.dao.enums.CopyExamStudentStatus;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * 考生复制任务
+ *
+ * @author WANGWEI
+ * @date 2018年9月12日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component("copyExamStudentTask")
+public class CopyExamStudentTask extends AbstractTask {
+
+	@Autowired
+	TaskTracker TaskTracker;
+
+	@Autowired
+	CopyExamStudentRepo copyExamStudentRepo;
+
+	@Autowired
+	ExamCloudService examCloudService;
+
+	@Autowired
+	ExamStudentCloudService examStudentCloudService;
+
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		CopyExamStudentEntity entity = copyExamStudentRepo
+				.findFirstByStatusOrderByCreationTime(CopyExamStudentStatus.PROCESSING);
+
+		if (null == entity) {
+			entity = copyExamStudentRepo
+					.findFirstByStatusOrderByCreationTime(CopyExamStudentStatus.NONE);
+		}
+
+		if (null == entity) {
+			return;
+		}
+
+		entity.setStatus(CopyExamStudentStatus.PROCESSING);
+
+		copyExamStudentRepo.save(entity);
+
+		process(entity);
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param entity
+	 */
+	private void process(CopyExamStudentEntity entity) {
+
+		List<Long> examIdList = Lists.newArrayList();
+		examIdList.add(entity.getExamId1());
+		examIdList.add(entity.getExamId2());
+
+		// 锁定
+		LockExamStudentsReq lockReq = new LockExamStudentsReq();
+		lockReq.setExamIdList(examIdList);
+		examCloudService.lockExamStudents(lockReq);
+
+		try {
+			copy(entity);
+		} finally {
+			// 解锁
+			UnlockExamStudentsReq unlockReq = new UnlockExamStudentsReq();
+			unlockReq.setExamIdList(examIdList);
+			examCloudService.unlockExamStudents(unlockReq);
+		}
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param entity
+	 */
+	private void copy(CopyExamStudentEntity entity) {
+		Long start = entity.getStart();
+
+		CopyExamStudentsReq req = new CopyExamStudentsReq();
+		req.setExamId1(entity.getExamId1());
+		req.setExamId2(entity.getExamId2());
+
+		int times = 0;
+		while (true) {
+			times++;
+			req.setStart(start);
+			CopyExamStudentsResp resp = examStudentCloudService.copyExamStudents(req);
+			Long next = resp.getNext();
+
+			if (times % 10 == 0) {
+				entity.setStart(next);
+				copyExamStudentRepo.save(entity);
+			}
+
+			if (next.equals(start)) {
+				break;
+			} else {
+				start = next;
+			}
+		}
+
+		entity.setStart(start);
+		entity.setStatus(CopyExamStudentStatus.COMPLETE);
+		copyExamStudentRepo.save(entity);
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 74 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/DataSyncTask.java

@@ -0,0 +1,74 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.util.List;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.global.api.request.SyncBaseRequest;
+import cn.com.qmth.examcloud.task.dao.DataSyncRepo;
+import cn.com.qmth.examcloud.task.dao.entity.DataSyncEntity;
+import cn.com.qmth.examcloud.task.service.DataSyncService;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * 数据同步任务
+ *
+ * @author WANGWEI
+ * @date 2018年8月2日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component("dataSyncTask")
+public class DataSyncTask extends AbstractTask {
+
+	@Autowired
+	TaskTracker TaskTracker;
+
+	@Autowired
+	DataSyncRepo dataSyncRepo;
+
+	@Autowired
+	DataSyncService dataSyncService;
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+
+		String component = scheduleJob.getExt1();
+
+		while (true) {
+			List<DataSyncEntity> list = dataSyncRepo.findTop10ByComponentOrderById(component);
+
+			if (CollectionUtils.isEmpty(list)) {
+				break;
+			}
+
+			for (DataSyncEntity cur : list) {
+				String paramType = cur.getParamType();
+				Class<?> c = Class.forName(paramType);
+				Object obj = JsonUtil.fromJson(cur.getParamJson(), c);
+				SyncBaseRequest req = (SyncBaseRequest) obj;
+
+				String methodName = cur.getMethodName();
+				try {
+					dataSyncService.sync(component, methodName, req, false, false);
+					dataSyncRepo.delete(cur);
+				} catch (Exception e) {
+					dataSyncRepo.increaseSyncNum(cur.getId());
+					throw new StatusException("102001", "同步失败");
+				}
+			}
+
+		}
+	}
+
+}

+ 32 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/DisposeOverdueNoticeTask.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import cn.com.qmth.examcloud.examwork.api.NoticeCloudService;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author lideyin
+ * 处理过期通知任务
+ */
+@Component("disposeOverdueNoticeTask")
+public class DisposeOverdueNoticeTask extends AbstractTask {
+
+	@Autowired
+	TaskTracker taskTracker;
+	
+	@Autowired
+	private NoticeCloudService noticeCloudService;
+	
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		noticeCloudService.disposeOverdueNotice();
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return taskTracker;
+	}
+}

+ 102 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/DisposePublishingNoticeTask.java

@@ -0,0 +1,102 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.examwork.api.NoticeCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.NoticeRulePublishProgressBean;
+import cn.com.qmth.examcloud.examwork.api.request.DisposePublishingUserNoticeReq;
+import cn.com.qmth.examcloud.examwork.api.request.UpdateNoticeStatusReq;
+import cn.com.qmth.examcloud.examwork.api.response.DisposePublishingUserNoticeResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetNoticeRulePublishProgressListResp;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * @author lideyin
+ * 处理发布中的通知
+ */
+@Component("disposePublishingNoticeTask")
+public class DisposePublishingNoticeTask extends AbstractTask {
+
+    @Autowired
+    TaskTracker taskTracker;
+
+    @Autowired
+    private NoticeCloudService noticeCloudService;
+
+    @Override
+    public void run(ScheduleJob scheduleJob) throws Exception {
+        //获取待处理的通知进度数据
+        GetNoticeRulePublishProgressListResp progressResponse = noticeCloudService.getToBeDisposedNoticeRulePublishProgressList();
+        List<NoticeRulePublishProgressBean> progressList = progressResponse.getList();
+        if (progressList == null || progressList.isEmpty()) {
+            return;
+        }
+
+
+        for (NoticeRulePublishProgressBean progress : progressList) {
+            Long startUserId = 0L;
+            int loopTimes = 0;
+            Long lastMaxUserId = getLastMaxUserId(progress.getNoticeReceiverRuleType(), progress);
+            if (lastMaxUserId != null) {
+                startUserId = lastMaxUserId + 1;
+            }
+            while (true) {
+                DisposePublishingUserNoticeReq disposeReq = new DisposePublishingUserNoticeReq();
+                disposeReq.setStartUserId(startUserId);
+                disposeReq.setNoticeRulePublishProgress(progress);
+                DisposePublishingUserNoticeResp disposeResp = noticeCloudService.disposePublishingUserNotice(disposeReq);
+                Long nextUserId = disposeResp.getNextUserId();
+
+                if (nextUserId.equals(startUserId)) {
+                    updateNoticeStatus(progress.getNoticeId(), NoticeStatus.PUBLISHED);
+                    break;
+                } else {
+                    startUserId = nextUserId;
+                    //处理中的状态只需更新一次
+                    if (loopTimes == 1) {
+                        updateNoticeStatus(progress.getNoticeId(), NoticeStatus.PUBLISHING);
+                    }
+                }
+                loopTimes++;
+            }
+        }
+
+
+    }
+
+    @Override
+    public TaskTracker getTaskTracker() {
+        return taskTracker;
+    }
+
+    private void updateNoticeStatus(Long noticeId, NoticeStatus noticeStatus) {
+        UpdateNoticeStatusReq updateNoticeStatusReq = new UpdateNoticeStatusReq();
+        updateNoticeStatusReq.setNoticeId(noticeId);
+        updateNoticeStatusReq.setNoticeStatus(noticeStatus);
+        noticeCloudService.updateNoticeStatus(updateNoticeStatusReq);
+    }
+
+    /**
+     * 获取上次更新的最大用户id
+     *
+     * @param ruleType
+     * @param process
+     * @return
+     */
+    private Long getLastMaxUserId(NoticeReceiverRuleType ruleType,
+                                  NoticeRulePublishProgressBean process) {
+        if (ruleType == NoticeReceiverRuleType.ALL_STUDENTS_OF_ROOT_ORG
+                || ruleType == NoticeReceiverRuleType.STUDENTS_OF_EXAM) {
+            return process.getMaxStudentId();
+        } else {
+            return process.getMaxCommonUserId();
+        }
+    }
+}

+ 92 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ExamStudentImportCleanTask.java

@@ -0,0 +1,92 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.criteria.Predicate;
+
+import org.apache.commons.io.FileUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.commons.util.PathUtil;
+import cn.com.qmth.examcloud.task.dao.ExamStudentImportRepo;
+import cn.com.qmth.examcloud.task.dao.ExamStudentTempRepo;
+import cn.com.qmth.examcloud.task.dao.entity.ExamStudentImportEntity;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * 考生导入-数据清理
+ *
+ * @author WANGWEI
+ * @date 2018年7月31日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Transactional
+@Component("examStudentImportCleanTask")
+public class ExamStudentImportCleanTask extends AbstractTask {
+
+	@Autowired
+	SystemProperties systemConfig;
+
+	@Autowired
+	TaskTracker TaskTracker;
+
+	private static final String EXAM_STUDENT_IMPORT_FILES = "exam_student_import_files";
+
+	@Autowired
+	ExamStudentImportRepo examStudentImportRepo;
+
+	@Autowired
+	ExamStudentTempRepo examStudentTempRepo;
+
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		Specification<ExamStudentImportEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			// 清理过去30天前的数据
+			Calendar c = Calendar.getInstance();
+			c.setTime(new Date());
+			c.add(Calendar.DATE, -30);
+			Date d = c.getTime();
+			Predicate p3 = cb.lessThan(root.get("creationTime"), d);
+			predicates.add(p3);
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		Pageable pageable = PageRequest.of(0, 100, Sort.Direction.ASC, "creationTime");
+		Page<ExamStudentImportEntity> list = examStudentImportRepo.findAll(specification, pageable);
+		for (ExamStudentImportEntity cur : list) {
+
+			examStudentTempRepo.deleteByBatchId(cur.getBatchId());
+
+			String destFilePath = PathUtil.getCanonicalPath(systemConfig.getDataDir() + "/"
+					+ EXAM_STUDENT_IMPORT_FILES + "/" + cur.getFilePath());
+			String resultFilePath = PathUtil.getCanonicalPath(systemConfig.getDataDir() + "/"
+					+ EXAM_STUDENT_IMPORT_FILES + "/" + cur.getResultFilePath());
+
+			FileUtils.deleteQuietly(new File(destFilePath));
+			FileUtils.deleteQuietly(new File(resultFilePath));
+
+			examStudentImportRepo.delete(cur);
+		}
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 320 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ExamStudentImportDataProcessingTask.java

@@ -0,0 +1,320 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.stereotype.Component;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.PathUtil;
+import cn.com.qmth.examcloud.core.basic.api.CourseCloudService;
+import cn.com.qmth.examcloud.core.basic.api.OrgCloudService;
+import cn.com.qmth.examcloud.core.basic.api.StudentCloudService;
+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.request.GetCourseReq;
+import cn.com.qmth.examcloud.core.basic.api.request.GetOrgReq;
+import cn.com.qmth.examcloud.core.basic.api.request.SaveStudentReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetCourseResp;
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgResp;
+import cn.com.qmth.examcloud.core.basic.api.response.SaveStudentResp;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.ExamStudentCloudService;
+import cn.com.qmth.examcloud.examwork.api.request.SaveExamStudentReq;
+import cn.com.qmth.examcloud.task.dao.ExamStudentImportRepo;
+import cn.com.qmth.examcloud.task.dao.ExamStudentTempRepo;
+import cn.com.qmth.examcloud.task.dao.entity.ExamStudentImportEntity;
+import cn.com.qmth.examcloud.task.dao.entity.ExamStudentTempEntity;
+import cn.com.qmth.examcloud.task.dao.enums.ExamStudentImportStatus;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * 考生导入-文件解析
+ *
+ * @author WANGWEI
+ * @date 2018年7月31日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component("examStudentImportDataProcessingTask")
+public class ExamStudentImportDataProcessingTask extends AbstractTask {
+
+	@Autowired
+	SystemProperties systemConfig;
+
+	private static final String EXAM_STUDENT_IMPORT_FILES = "exam_student_import_files";
+
+	@Autowired
+	TaskTracker TaskTracker;
+
+	@Autowired
+	ExamStudentImportRepo examStudentImportRepo;
+
+	@Autowired
+	ExamStudentTempRepo examStudentTempRepo;
+
+	@Autowired
+	CourseCloudService courseCloudService;
+
+	@Autowired
+	OrgCloudService orgCloudService;
+
+	@Autowired
+	StudentCloudService studentCloudService;
+
+	@Autowired
+	ExamCloudService examCloudService;
+
+	@Autowired
+	ExamStudentCloudService examStudentCloudService;
+
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+
+		ExamStudentImportEntity importEntity = examStudentImportRepo
+				.findFirstByStatusOrderByCreationTime(ExamStudentImportStatus.DATA_PROCESSING);
+
+		if (null == importEntity) {
+			importEntity = examStudentImportRepo.findFirstByStatusOrderByCreationTime(
+					ExamStudentImportStatus.FILE_PARSING_COMPLETE);
+		}
+
+		if (null == importEntity) {
+			return;
+		}
+
+		importEntity.setStatus(ExamStudentImportStatus.DATA_PROCESSING);
+		examStudentImportRepo.saveAndFlush(importEntity);
+
+		process(importEntity);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param importEntity
+	 */
+	private void process(ExamStudentImportEntity importEntity) {
+		List<Map<String, Object>> failRecords = Collections
+				.synchronizedList(new ArrayList<Map<String, Object>>());
+
+		foreach(importEntity, failRecords);
+
+		StringBuilder sb = new StringBuilder();
+		for (Map<String, Object> cur : failRecords) {
+			sb.append("line ").append(cur.get("lineNum")).append(cur.get("msg")).append("\n");
+		}
+		sb.append("====================异常数据\n");
+		String resultFilePath = PathUtil.getCanonicalPath(systemConfig.getDataDir() + "/"
+				+ EXAM_STUDENT_IMPORT_FILES + "/" + importEntity.getResultFilePath());
+		File resultFile = new File(resultFilePath);
+
+		try {
+			FileUtils.writeStringToFile(resultFile, sb.toString(), "UTF-8", true);
+		} catch (IOException e) {
+			debugLog.error("导入报告写入失败", e);
+			importEntity.setStatus(ExamStudentImportStatus.ERROR);
+			importEntity.setErrorDesc("导入报告写入失败");
+			examStudentImportRepo.saveAndFlush(importEntity);
+			return;
+		}
+
+		importEntity.setStatus(ExamStudentImportStatus.DATA_PROCESSING_COMPLETE);
+		examStudentImportRepo.saveAndFlush(importEntity);
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param importEntity
+	 * @param failRecords
+	 */
+	private void foreach(ExamStudentImportEntity importEntity,
+			List<Map<String, Object>> failRecords) {
+		Long batchId = importEntity.getBatchId();
+
+		while (true) {
+			Pageable pageable = PageRequest.of(0, 100, Sort.Direction.ASC, "lineNum");
+			List<ExamStudentTempEntity> list = examStudentTempRepo
+					.findByBatchIdAndStatusCodeIsNull(batchId, pageable);
+			if (CollectionUtils.isEmpty(list)) {
+				break;
+			}
+
+			for (ExamStudentTempEntity cur : list) {
+				saveExamStudent(cur, failRecords);
+			}
+		}
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param entity
+	 * @param failRecords
+	 */
+	private void saveExamStudent(ExamStudentTempEntity entity,
+			List<Map<String, Object>> failRecords) {
+		Long rootOrgId = entity.getRootOrgId();
+		String courseCode = entity.getCourseCode();
+		String orgCode = entity.getOrgCode();
+
+		int error = 0;
+		int disabledCount = 0;
+		Map<String, Object> map = Maps.newHashMap();
+		map.put("lineNum", entity.getLineNum());
+
+		try {
+			GetCourseReq req = new GetCourseReq();
+			req.setRootOrgId(rootOrgId);
+			req.setCode(courseCode);
+			GetCourseResp resp = courseCloudService.getCourse(req);
+			CourseBean courseBean = resp.getCourseBean();
+			entity.setCourseId(courseBean.getId());
+
+			if (null != courseBean.getEnable() && (!courseBean.getEnable())) {
+				map.put("msg", "  " + "课程被禁用");
+				entity.setStatusCode("T-890101");
+				entity.setStatusDesc("课程被禁用");
+				disabledCount++;
+				error++;
+			}
+
+		} catch (StatusException e) {
+			debugLog.error("查询课程异常", e);
+			map.put("msg", "  " + e.getDesc());
+			entity.setStatusCode(e.getCode());
+			entity.setStatusDesc(e.getDesc());
+			error++;
+		}
+
+		try {
+			GetOrgReq req = new GetOrgReq();
+			req.setOrgCode(orgCode);
+			req.setRootOrgId(rootOrgId);
+			GetOrgResp resp = orgCloudService.getOrg(req);
+			OrgBean org = resp.getOrg();
+			entity.setOrgId(org.getId());
+
+			if (null != org.getEnable() && (!org.getEnable())) {
+				map.put("msg", "  " + "机构被禁用");
+				entity.setStatusCode("T-890100");
+				entity.setStatusDesc("机构被禁用");
+				disabledCount++;
+				error++;
+			}
+
+		} catch (StatusException e) {
+			debugLog.error("查询机构异常", e);
+			map.put("msg", "  " + e.getDesc());
+			entity.setStatusCode(e.getCode());
+			entity.setStatusDesc(e.getDesc());
+			error++;
+		}
+
+		if (disabledCount == 2) {
+			map.put("msg", "  " + "机构和课程被禁用");
+			entity.setStatusCode("T-890105");
+			entity.setStatusDesc("机构和课程被禁用");
+		}
+
+		if (error > 0) {
+			failRecords.add(map);
+			examStudentTempRepo.saveAndFlush(entity);
+			return;
+		}
+
+		try {
+			SaveStudentReq saveStudentReq = new SaveStudentReq();
+			saveStudentReq.setIdentityNumber(entity.getIdentityNumber());
+			saveStudentReq.setName(entity.getName());
+			saveStudentReq.setPhoneNumber(entity.getPhone());
+			saveStudentReq.setOrgCode(entity.getOrgCode());
+			saveStudentReq.setOrgId(entity.getOrgId());
+			saveStudentReq.setRootOrgId(rootOrgId);
+			if (StringUtils.isNotBlank(entity.getStudentCode())) {
+				List<String> studentCodeList = Lists.newArrayList();
+				studentCodeList.add(entity.getStudentCode());
+				saveStudentReq.setStudentCodeList(studentCodeList);
+			}
+
+			SaveStudentResp saveStudentResp = studentCloudService.saveStudent(saveStudentReq);
+
+			Long studentId = saveStudentResp.getStudentId();
+
+			SaveExamStudentReq sReq = new SaveExamStudentReq();
+
+			sReq.setCourseId(entity.getCourseId());
+			sReq.setCourseCode(entity.getCourseCode());
+
+			sReq.setExamId(entity.getExamId());
+			sReq.setRootOrgId(rootOrgId);
+
+			sReq.setStudentId(studentId);
+			sReq.setIdentityNumber(entity.getIdentityNumber());
+			sReq.setStudentName(entity.getName());
+			sReq.setStudentCode(entity.getStudentCode());
+
+			sReq.setSpecialtyName(entity.getSpecialtyName());
+			sReq.setPaperType(entity.getPaperType());
+			sReq.setGrade(entity.getGrade());
+			sReq.setInfoCollector(entity.getInfoCollector());
+			sReq.setExamSite(entity.getExamSite());
+
+			sReq.setSpecialBeginTime(entity.getSpecialBeginTime());
+			sReq.setSpecialEndTime(entity.getSpecialEndTime());
+
+			sReq.setExt1(entity.getExt1());
+			sReq.setExt2(entity.getExt2());
+			sReq.setExt3(entity.getExt3());
+			sReq.setExt4(entity.getExt4());
+			sReq.setExt5(entity.getExt5());
+
+			examStudentCloudService.saveExamStudent(sReq);
+
+			entity.setStudentId(studentId);
+			entity.setStatusCode("200");
+			entity.setStatusDesc("成功");
+			examStudentTempRepo.saveAndFlush(entity);
+
+		} catch (StatusException e) {
+			debugLog.error("考生入库异常", e);
+			map.put("msg", "  " + e.getDesc());
+			failRecords.add(map);
+			entity.setStatusCode(e.getCode());
+			entity.setStatusDesc(e.getDesc());
+			examStudentTempRepo.saveAndFlush(entity);
+			return;
+		} catch (Exception e) {
+			// ignore
+			taskLog.error("unexpected.", e);
+		}
+
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 373 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ExamStudentImportParsingFileTask.java

@@ -0,0 +1,373 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import cn.com.qmth.examcloud.commons.helpers.poi.ExcelReader;
+import cn.com.qmth.examcloud.commons.util.BooleanUtil;
+import cn.com.qmth.examcloud.commons.util.DateUtil;
+import cn.com.qmth.examcloud.commons.util.PathUtil;
+import cn.com.qmth.examcloud.task.dao.ExamStudentImportRepo;
+import cn.com.qmth.examcloud.task.dao.ExamStudentTempRepo;
+import cn.com.qmth.examcloud.task.dao.entity.ExamStudentImportEntity;
+import cn.com.qmth.examcloud.task.dao.entity.ExamStudentTempEntity;
+import cn.com.qmth.examcloud.task.dao.enums.ExamStudentImportStatus;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * 考生导入-文件解析
+ *
+ * @author WANGWEI
+ * @date 2018年7月31日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component("examStudentImportParsingFileTask")
+public class ExamStudentImportParsingFileTask extends AbstractTask {
+
+	@Autowired
+	SystemProperties systemConfig;
+
+	private static final String EXAM_STUDENT_IMPORT_FILES = "exam_student_import_files";
+
+	private static final String[] EXCEL_HEADER = new String[]{"姓名", "学号", "身份证号", "学习中心代码",
+			"学习中心名称", "课程代码", "课程名称", "试卷类型", "专业", "考点", "信息采集人", "学生电话", "年级", "试卷袋编码", "考试开始时间",
+			"考试结束时间"};
+
+	@Autowired
+	TaskTracker taskTracker;
+
+	@Autowired
+	ExamStudentImportRepo examStudentImportRepo;
+
+	@Autowired
+	ExamStudentTempRepo examStudentTempRepo;
+
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+
+		ExamStudentImportEntity importEntity = examStudentImportRepo
+				.findFirstByStatusOrderByCreationTime(ExamStudentImportStatus.FILE_PARSING);
+
+		if (null == importEntity) {
+			importEntity = examStudentImportRepo
+					.findFirstByStatusOrderByCreationTime(ExamStudentImportStatus.NONE);
+		}
+
+		if (null == importEntity) {
+			return;
+		}
+
+		importEntity.setStatus(ExamStudentImportStatus.FILE_PARSING);
+		examStudentImportRepo.saveAndFlush(importEntity);
+
+		try {
+			importExcel(importEntity);
+		} catch (Exception e) {
+			debugLog.error("文件解析异常", e);
+			importEntity.setStatus(ExamStudentImportStatus.ERROR);
+			importEntity.setErrorDesc("文件解析异常");
+			examStudentImportRepo.saveAndFlush(importEntity);
+			return;
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param importEntity
+	 */
+	private void importExcel(ExamStudentImportEntity importEntity) {
+		String destFilePath = PathUtil.getCanonicalPath(systemConfig.getDataDir() + "/"
+				+ EXAM_STUDENT_IMPORT_FILES + "/" + importEntity.getFilePath());
+		String resultFilePath = PathUtil.getCanonicalPath(systemConfig.getDataDir() + "/"
+				+ EXAM_STUDENT_IMPORT_FILES + "/" + importEntity.getResultFilePath());
+
+		File file = new File(destFilePath);
+
+		if (!file.exists()) {
+			importEntity.setStatus(ExamStudentImportStatus.ERROR);
+			importEntity.setErrorDesc("文件不存在");
+			examStudentImportRepo.saveAndFlush(importEntity);
+			return;
+		}
+
+		List<String[]> lineList = ExcelReader.readSheetBySax(PathUtil.getCanonicalPath(file), 1,
+				16);
+
+		if (CollectionUtils.isEmpty(lineList)) {
+			importEntity.setStatus(ExamStudentImportStatus.ERROR);
+			importEntity.setErrorDesc("Excel 没有内容");
+			examStudentImportRepo.saveAndFlush(importEntity);
+			return;
+		}
+
+		Long batchId = importEntity.getBatchId();
+		Long rootOrgId = importEntity.getRootOrgId();
+		Long examId = importEntity.getExamId();
+		List<ExamStudentTempEntity> list = Lists.newArrayList();
+
+		Set<String> fullSet = Sets.newHashSet();
+
+		List<Map<String, Object>> failRecords = Collections
+				.synchronizedList(new ArrayList<Map<String, Object>>());
+
+		StringBuilder sb = new StringBuilder();
+		sb.append("====================重复数据\n");
+		for (int i = 0; i < lineList.size(); i++) {
+			String[] line = lineList.get(i);
+			if (0 == i) {
+				if (headerError(line)) {
+					importEntity.setStatus(ExamStudentImportStatus.ERROR);
+					importEntity.setErrorDesc("EXCEL表头错误");
+					examStudentImportRepo.saveAndFlush(importEntity);
+					return;
+				}
+				continue;
+			}
+
+			ExamStudentTempEntity es = new ExamStudentTempEntity();
+			es.setLineNum((long) 1 + i);
+			es.setRootOrgId(rootOrgId);
+			es.setExamId(examId);
+			es.setBatchId(batchId);
+			es.setName(trimAndNullIfBlank(line[0]));
+			es.setStudentCode(trimAndNullIfBlank(line[1]));
+			String identityNumber = trimAndNullIfBlank(line[2]);
+			if (StringUtils.isNotBlank(identityNumber)) {
+				identityNumber = identityNumber.toUpperCase(Locale.US);
+			}
+			es.setIdentityNumber(identityNumber);
+			es.setOrgCode(trimAndNullIfBlank(line[3]));
+			es.setOrgName(trimAndNullIfBlank(line[4]));
+			es.setCourseCode(trimAndNullIfBlank(line[5]));
+			es.setCourseName(trimAndNullIfBlank(line[6]));
+			es.setPaperType(trimAndNullIfBlank(line[7]));
+			es.setSpecialtyName(trimAndNullIfBlank(line[8]));
+			es.setExamSite(trimAndNullIfBlank(line[9]));
+			es.setInfoCollector(trimAndNullIfBlank(line[10]));
+			es.setPhone(trimAndNullIfBlank(line[11]));
+			es.setGrade(trimAndNullIfBlank(line[12]));
+
+			es.setExt1(trimAndNullIfBlank(line[13]));
+
+			String d1 = trimAndNullIfBlank(line[14]);
+			String d2 = trimAndNullIfBlank(line[15]);
+
+			if (!new Boolean(null == d1).equals(new Boolean(null == d2))) {
+				failRecords.add(buildFailRecord(es.getLineNum(), "考试时间不匹配"));
+				continue;
+			}
+
+			int trueNum = BooleanUtil.countTrue(null != d1, null != d2);
+			if (2 == trueNum) {
+				Date specialBeginTime = null;
+				Date specialEndTime = null;
+				try {
+					if (NumberUtils.isCreatable(d1)) {
+						specialBeginTime = DateUtil.parseExcel(d1);
+					} else {
+						specialBeginTime = DateUtil.parseRandomly(d1);
+					}
+				} catch (Exception e) {
+					failRecords.add(buildFailRecord(es.getLineNum(), "考试开始时间格式错误"));
+					continue;
+				}
+				try {
+					if (NumberUtils.isCreatable(d2)) {
+						specialEndTime = DateUtil.parseExcel(d2);
+					} else {
+						specialEndTime = DateUtil.parseRandomly(d2);
+					}
+				} catch (Exception e) {
+					failRecords.add(buildFailRecord(es.getLineNum(), "考试结束时间格式错误"));
+					continue;
+				}
+
+				if (specialBeginTime.after(specialEndTime)) {
+					failRecords.add(buildFailRecord(es.getLineNum(), "考试开始时间不能大于考试结束时间"));
+					continue;
+				}
+				es.setSpecialBeginTime(specialBeginTime);
+				es.setSpecialEndTime(specialEndTime);
+			}
+
+			if (hasError(failRecords, es)) {
+				continue;
+			}
+
+			list.add(es);
+			String key = es.getIdentityNumber() + "____" + es.getCourseCode();
+			if (fullSet.contains(key)) {
+				sb.append("line ").append(es.getLineNum()).append("  重复数据,执行更新").append("\n");
+			} else {
+				fullSet.add(key);
+			}
+			if (0 == i % 100) {
+				examStudentTempRepo.saveAll(list);
+
+				list = Lists.newArrayList();
+			}
+		}
+
+		examStudentTempRepo.saveAll(list);
+		sb.append("====================数据规格错误数据\n");
+		for (Map<String, Object> cur : failRecords) {
+			sb.append("line ").append(cur.get("lineNum")).append(cur.get("msg")).append("\n");
+		}
+
+		File resultFile = new File(resultFilePath);
+
+		try {
+			FileUtils.writeStringToFile(resultFile, sb.toString(), "UTF-8", true);
+		} catch (IOException e) {
+			importEntity.setStatus(ExamStudentImportStatus.ERROR);
+			importEntity.setErrorDesc("导入报告写入失败");
+			examStudentImportRepo.saveAndFlush(importEntity);
+			return;
+		}
+
+		importEntity.setStatus(ExamStudentImportStatus.FILE_PARSING_COMPLETE);
+		examStudentImportRepo.saveAndFlush(importEntity);
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param failRecords
+	 * @param entity
+	 * @return
+	 */
+	public boolean hasError(List<Map<String, Object>> failRecords, ExamStudentTempEntity entity) {
+		boolean hasError = false;
+		StringBuilder sb = new StringBuilder();
+
+		String name = entity.getName();
+		if (StringUtils.isBlank(name)) {
+			sb.append("  姓名不能为空");
+			hasError = true;
+		} else if (20 < name.length()) {
+			sb.append("  姓名不能超过20个字符");
+			hasError = true;
+		}
+
+		String identityNumber = entity.getIdentityNumber();
+		if (StringUtils.isBlank(identityNumber)) {
+			sb.append("  身份证号不能为空");
+			hasError = true;
+		} else if (identityNumber.length() < 6) {
+			sb.append("  身份证号至少为6个字符");
+			hasError = true;
+		} else if (identityNumber.length() > 30) {
+			sb.append("  身份证号不能超过30个字符");
+			hasError = true;
+		}
+
+		String studentCode = entity.getStudentCode();
+		if (StringUtils.isNotBlank(studentCode)) {
+			if (studentCode.length() < 6) {
+				sb.append("  学号至少为6个字符");
+				hasError = true;
+			} else if (studentCode.length() > 30) {
+				sb.append("  学号不能超过30个字符");
+				hasError = true;
+			}
+		}
+
+		String courseCode = entity.getCourseCode();
+		if (StringUtils.isBlank(courseCode)) {
+			sb.append("  课程代码不能为空");
+			hasError = true;
+		} else if (courseCode.length() > 30) {
+			sb.append("  课程代码不能超过30个字符");
+			hasError = true;
+		}
+
+		String orgCode = entity.getOrgCode();
+		if (StringUtils.isBlank(orgCode)) {
+			sb.append("  学习中心代码不能为空");
+			hasError = true;
+		} else if (orgCode.length() > 30) {
+			sb.append("  学习中心代码不能超过30个字符");
+			hasError = true;
+		}
+
+		if (hasError) {
+			failRecords.add(buildFailRecord(entity.getLineNum(), sb.toString()));
+		}
+
+		return hasError;
+	}
+
+	/**
+	 * 构建错误信息
+	 *
+	 * @author WANGWEI
+	 * @param lineNum
+	 * @param msg
+	 * @return
+	 */
+	private Map<String, Object> buildFailRecord(Long lineNum, String msg) {
+		Map<String, Object> map = Maps.newHashMap();
+		map.put("lineNum", lineNum);
+		map.put("msg", msg);
+		return map;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	private String trimAndNullIfBlank(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		return s.trim();
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param header
+	 */
+	private boolean headerError(String[] header) {
+		for (int i = 0; i < EXCEL_HEADER.length; i++) {
+			if (null == header[i] || !EXCEL_HEADER[i].equals(header[i].trim())) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return taskTracker;
+	}
+}

+ 29 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ExamStudentOnlineCountTask.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.task.service.ExamStudentCountService;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+@Component("examStudentOnlineCountTask")
+public class ExamStudentOnlineCountTask extends AbstractTask {
+
+	@Autowired
+	ExamStudentCountService examStudentCountService;
+
+	@Autowired
+	TaskTracker TaskTracker;
+	
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		examStudentCountService.saveOnlieCount();
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 147 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/MarkWorkPaperCreateTask.java

@@ -0,0 +1,147 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordForMarkingCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordForMarkingBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.FindExamRecordForMarkingInfoReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.FindExamRecordForMarkingInfoResp;
+import cn.com.qmth.examcloud.core.questions.api.ExtractConfigCloudService;
+import cn.com.qmth.examcloud.core.questions.api.request.GetBasePaperReq;
+import cn.com.qmth.examcloud.core.questions.api.response.GetBasePaperResp;
+import cn.com.qmth.examcloud.marking.api.MarkItemCloudService;
+import cn.com.qmth.examcloud.marking.api.MarkWorkCloudService;
+import cn.com.qmth.examcloud.marking.api.bean.MarkItemBean;
+import cn.com.qmth.examcloud.marking.api.bean.MarkWorkBean;
+import cn.com.qmth.examcloud.marking.api.bean.MarkWorkMainBean;
+import cn.com.qmth.examcloud.marking.api.request.SaveMarkItemReq;
+import cn.com.qmth.examcloud.marking.api.request.UpdateMarkWorkReq;
+import cn.com.qmth.examcloud.marking.api.response.GetTodoMarkWorkMainResp;
+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.question.commons.core.paper.DefaultQuestionStructureWrapper;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionUnitWrapper;
+import cn.com.qmth.examcloud.task.service.consumer.MarkWorkCreateConsumer;
+import cn.com.qmth.examcloud.task.service.producer.MarkWorkCreateProducer;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+@Component("markWorkPaperCreateTask")
+public class MarkWorkPaperCreateTask extends AbstractTask {
+
+    private static final double SCORE_INTERVAL = 0.5;
+
+    @Autowired
+    private MarkWorkCloudService markWorkCloudService;
+
+    @Autowired
+    private MarkItemCloudService markItemCloudService;
+
+    @Autowired
+    private ExtractConfigCloudService extractConfigCloudService;
+
+    @Autowired
+    private ExamRecordForMarkingCloudService examRecordForMarkingCloudService;
+
+    @Autowired
+    TaskTracker TaskTracker;
+
+    @Override
+    public void run(ScheduleJob scheduleJob) throws Exception {
+        GetTodoMarkWorkMainResp res = markWorkCloudService.getTodoMarkWorkMain();
+        MarkWorkMainBean bean = res.getBean();
+        if (res.getBean() != null) {
+            UpdateMarkWorkReq req = new UpdateMarkWorkReq();
+            MarkWorkBean wbean = new MarkWorkBean();
+            wbean.setId(bean.getId());
+            wbean.setName(bean.getName());
+            wbean.setRemark(bean.getRemark());
+            wbean.setAppendTime(bean.getAppendTime());
+            req.setMarkWorkBean(wbean);
+            try {
+                for (Long examId : bean.getExamIds()) {
+                    createMarkItems(examId, bean.getId());
+                    createStudentPapers(examId, bean.getId());
+                }
+                wbean.setStatus(1);
+            } catch (StatusException e) {
+                wbean.setStatus(2);
+                throw e;
+            } catch (Exception e) {
+                wbean.setStatus(2);
+                throw new StatusException("100001", "评卷工作试卷生成出错 " + e.getMessage(), e);
+            } finally {
+                markWorkCloudService.updateMarkWork(req);
+            }
+        }
+    }
+
+    private void createMarkItems(Long examId, Long workId) {
+        // 根据考试批次ID获取需要阅卷的试卷集合
+        FindExamRecordForMarkingInfoReq markingReq = new FindExamRecordForMarkingInfoReq();
+        markingReq.setExamId(examId);
+        markingReq.setBatchNum(workId + "");
+        FindExamRecordForMarkingInfoResp markingResp = examRecordForMarkingCloudService
+                .findExamRecordForMarkingInfo(markingReq);
+        List<ExamRecordForMarkingBean> examRecordForMarkingBeanList = markingResp.getExamRecordForMarkingBeanList();
+        List<MarkItemBean> markItems = new ArrayList<MarkItemBean>();
+        GetBasePaperReq getBasePaperReq = new GetBasePaperReq();
+        for (ExamRecordForMarkingBean markingBean : examRecordForMarkingBeanList) {
+            getBasePaperReq.setPaperId(markingBean.getBasePaperId());
+            GetBasePaperResp getBasePaperResp = extractConfigCloudService.getBasePaper(getBasePaperReq);
+            DefaultPaper defaultPaper = getBasePaperResp.getDefaultPaper();
+            List<DefaultQuestionGroup> defaultQuestionGroupList = defaultPaper.getQuestionGroupList();
+            // 大题号
+            int mainNumber = 1;
+            // 每个小题在试卷中的位置
+            int order = 0;
+            for (DefaultQuestionGroup defaultQuestionGroup : defaultQuestionGroupList) {
+                List<DefaultQuestionStructureWrapper> defaultQuestionStructureWrapperList = defaultQuestionGroup
+                        .getQuestionWrapperList();
+                // 小题号
+                int subNumber = 1;
+                for (DefaultQuestionStructureWrapper defaultQuestionStructureWrapper : defaultQuestionStructureWrapperList) {
+                    List<DefaultQuestionUnitWrapper> defaultQuestionUnitWrapperList = defaultQuestionStructureWrapper
+                            .getQuestionUnitWrapperList();
+                    for (DefaultQuestionUnitWrapper defaultQuestionUnitWrapper : defaultQuestionUnitWrapperList) {
+                        order++;
+                        if ("FILL_UP".equals(defaultQuestionUnitWrapper.getQuestionType().toString())
+                                || "ESSAY".equals(defaultQuestionUnitWrapper.getQuestionType().toString())) {
+                            MarkItemBean markItem = new MarkItemBean(markingBean.getBasePaperId(), mainNumber,
+                                    subNumber, defaultQuestionUnitWrapper.getQuestionScore(), SCORE_INTERVAL, order,
+                                    defaultQuestionStructureWrapper.getQuestionId());
+                            markItems.add(markItem);
+                        }
+                        subNumber++;
+                    }
+                }
+                mainNumber++;
+            }
+        }
+        SaveMarkItemReq req = new SaveMarkItemReq();
+        req.setWorkId(workId);
+        req.setMarkItemBeanList(markItems);
+        markItemCloudService.saveMarkWork(req);
+    }
+
+    private void createStudentPapers(Long examId, Long workId) throws InstantiationException, IllegalAccessException {
+        MarkWorkCreateProducer producer = new MarkWorkCreateProducer();
+        Map<String, Object> param = new HashMap<String, Object>();
+        param.put("examId", examId);
+        param.put("workId", workId);
+        producer.startDispose(MarkWorkCreateConsumer.class, 10, param);
+    }
+
+    @Override
+    public TaskTracker getTaskTracker() {
+        return TaskTracker;
+    }
+}

+ 37 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/OeScorePushTask.java

@@ -0,0 +1,37 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.OeAdminScorePushCloudService;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * 
+ * @author  	chenken
+ * @date    	2018年10月19日 上午10:51:59
+ * @company 	QMTH
+ * @description 网考推送成绩
+ */
+@Component("oeScorePushTask")
+public class OeScorePushTask extends AbstractTask{
+
+	@Autowired
+	TaskTracker taskTracker;
+	
+	@Autowired
+	private OeAdminScorePushCloudService oeAdminScorePushCloudService;
+	
+	@Override
+	public TaskTracker getTaskTracker() {
+		return taskTracker;
+	}
+
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		oeAdminScorePushCloudService.disposeScoreQueue();
+	}
+
+}

+ 38 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/ReportsComputeTask.java

@@ -0,0 +1,38 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.task.service.ReportsComputeService;
+import cn.com.qmth.examcloud.task.service.consumer.ReportsComputeConsumer;
+import cn.com.qmth.examcloud.task.service.producer.ReportsComputeProducer;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+@Component("reportsComputeTask")
+public class ReportsComputeTask extends AbstractTask {
+
+	@Autowired
+	ReportsComputeService reportsComputeService;
+
+	@Autowired
+	TaskTracker TaskTracker;
+	
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		//每次运行任务之前重置计算中和待终止的任务
+		reportsComputeService.initReportsCompute();
+		//取待处理的数据
+		int c=reportsComputeService.getTodoDataCount();
+		if(c>0) {
+			ReportsComputeProducer pro=new ReportsComputeProducer();
+			pro.startDispose(ReportsComputeConsumer.class, c>10?10:c, null);
+		}
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 29 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/StudentCumulativeCountTask.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.core.reports.api.StudentCumulativeCountCloudService;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+@Component("studentCumulativeCountTask")
+public class StudentCumulativeCountTask extends AbstractTask {
+
+	@Autowired
+	StudentCumulativeCountCloudService studentCumulativeCountCloudService;
+
+	@Autowired
+	TaskTracker TaskTracker;
+	
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		studentCumulativeCountCloudService.saveCount();
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 29 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/StudentOnlineCountTask.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.task.service.StudentCountService;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+@Component("studentOnlineCountTask")
+public class StudentOnlineCountTask extends AbstractTask {
+
+	@Autowired
+	StudentCountService studentCountService;
+
+	@Autowired
+	TaskTracker TaskTracker;
+	
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		studentCountService.saveOnlieCount();
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 140 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/StudentTotalCountTask.java

@@ -0,0 +1,140 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.core.basic.api.OrgCloudService;
+import cn.com.qmth.examcloud.core.basic.api.StudentCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.OrgBean;
+import cn.com.qmth.examcloud.core.basic.api.request.CountStudentReq;
+import cn.com.qmth.examcloud.core.basic.api.request.GetOrgsReq;
+import cn.com.qmth.examcloud.core.basic.api.response.CountStudentResp;
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgsResp;
+import cn.com.qmth.examcloud.core.oe.admin.api.OeExamStudentCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.GetExamStudentCountReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.GetExamStudentCountResp;
+import cn.com.qmth.examcloud.core.reports.api.ExamDataCloudService;
+import cn.com.qmth.examcloud.core.reports.api.StudentTotalCountCloudService;
+import cn.com.qmth.examcloud.core.reports.api.request.SaveExamDataReq;
+import cn.com.qmth.examcloud.core.reports.api.request.SaveStudentTotalCountReq;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamsReq;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamsResp;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+@Component("studentTotalCountTask")
+public class StudentTotalCountTask extends AbstractTask {
+
+    @Autowired
+    private OeExamStudentCloudService oeExamStudentCloudService;
+
+    @Autowired
+    private ExamCloudService examCloudService;
+
+    @Autowired
+    private ExamDataCloudService examDataCloudService;
+
+    @Autowired
+    StudentTotalCountCloudService studentTotalCountCloudService;
+
+    @Autowired
+    private OrgCloudService orgCloudService;
+
+    @Autowired
+    private StudentCloudService studentCloudService;
+
+    @Autowired
+    TaskTracker TaskTracker;
+
+    @Override
+    public void run(ScheduleJob scheduleJob) throws Exception {
+        GetOrgsReq orgsreq = new GetOrgsReq();
+        Long orgStartId = 0L;
+        orgsreq.setWithoutParentId(true);
+        orgsreq.setEnable(true);
+        for (;;) {
+            orgsreq.setStart(orgStartId);
+            GetOrgsResp orgRes = orgCloudService.getOrgs(orgsreq);
+            if (CollectionUtils.isEmpty(orgRes.getOrgBeanList())) {
+                break;
+            }
+            for (OrgBean org : orgRes.getOrgBeanList()) {
+                computeStudentTotalCount(org.getId());
+                computeExamData(org.getId());
+            }
+            orgStartId = orgRes.getNext();
+        }
+    }
+
+    private void computeStudentTotalCount(Long rootOrgId) {
+        CountStudentReq csr = new CountStudentReq();
+        csr.setRootOrgId(rootOrgId);
+        CountStudentResp csres = studentCloudService.countStudent(csr);
+        Long totalCount = 0L;
+        if (csres.getCount() != null) {
+            totalCount = csres.getCount();
+        }
+        SaveStudentTotalCountReq req = new SaveStudentTotalCountReq();
+        req.setRootOrgId(rootOrgId);
+        req.setCount(totalCount.intValue());
+        studentTotalCountCloudService.save(req);
+    }
+
+    private void computeExamData(Long rootOrgId) {
+        GetExamsReq req = new GetExamsReq();
+        Long startId = 0L;
+        req.setRootOrgId(rootOrgId);
+        req.setEnable(true);
+        for (;;) {
+            req.setStart(startId);
+            GetExamsResp res = examCloudService.getExams(req);
+            if (CollectionUtils.isEmpty(res.getExamBeanList())) {
+                break;
+            }
+            for (ExamBean exam : res.getExamBeanList()) {
+                computeExamData(exam, rootOrgId);
+            }
+            startId = res.getNext();
+        }
+    }
+
+    private void computeExamData(ExamBean exam, Long rootOrgId) {
+        Long planCount = 0l;
+        Long compCount = 0l;
+        GetExamStudentCountReq req = new GetExamStudentCountReq();
+        List<Long> eids = new ArrayList<Long>();
+        eids.add(exam.getId());
+        req.setExamIdList(eids);
+        GetExamStudentCountResp res = oeExamStudentCloudService.getExamStudentCount(req);
+        if (CollectionUtils.isNotEmpty(res.getCountList())) {
+            planCount = res.getCountList().get(0).getCount();
+        }
+        req.setFinishedExam(true);
+        res = oeExamStudentCloudService.getExamStudentCount(req);
+        if (CollectionUtils.isNotEmpty(res.getCountList())) {
+            compCount = res.getCountList().get(0).getCount();
+        }
+        SaveExamDataReq sreq = new SaveExamDataReq();
+        sreq.setEndTime(exam.getEndTime());
+        sreq.setStartTime(exam.getBeginTime());
+        sreq.setExamId(exam.getId());
+        sreq.setExamName(exam.getName());
+        sreq.setExamType(exam.getExamType());
+        sreq.setRootOrgId(exam.getRootOrgId());
+        sreq.setPlanCount(planCount.intValue());
+        sreq.setCompleteCount(compCount.intValue());
+        examDataCloudService.save(sreq);
+    }
+
+    @Override
+    public TaskTracker getTaskTracker() {
+        return TaskTracker;
+    }
+}

+ 55 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/TaskTraceCleanTask.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.task.dao.TaskTraceRepo;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+/**
+ * 任务跟踪记录清理
+ *
+ * @author WANGWEI
+ * @date 2018年11月7日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component("taskTraceCleanTask")
+public class TaskTraceCleanTask extends AbstractTask {
+
+	@Autowired
+	TaskTracker TaskTracker;
+
+	@Autowired
+	TaskTraceRepo taskTraceRepo;
+
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		int amount = 2;
+		try {
+			amount = Integer.parseInt(scheduleJob.getExt1());
+		} catch (Exception e) {
+			// ignore
+		}
+
+		if (1 > amount || amount > 10) {
+			amount = 2;
+		}
+
+		// 清理过去amount天前的数据
+		Calendar c = Calendar.getInstance();
+		c.setTime(new Date());
+		c.add(Calendar.DATE, 0 - amount);
+		Date d = c.getTime();
+		taskTraceRepo.deleteByCreationTimeLessThan(d);
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 29 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/job/UserOnlineCountTask.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.task.service.job;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.task.service.UserCountService;
+import cn.com.qmth.examcloud.web.task.AbstractTask;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+import cn.com.qmth.examcloud.web.task.TaskTracker;
+
+@Component("userOnlineCountTask")
+public class UserOnlineCountTask extends AbstractTask {
+
+	@Autowired
+	UserCountService userCountService;
+
+	@Autowired
+	TaskTracker TaskTracker;
+	
+	@Override
+	public void run(ScheduleJob scheduleJob) throws Exception {
+		userCountService.saveOnlieCount();
+	}
+
+	@Override
+	public TaskTracker getTaskTracker() {
+		return TaskTracker;
+	}
+}

+ 102 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/producer/MarkWorkCreateProducer.java

@@ -0,0 +1,102 @@
+package cn.com.qmth.examcloud.task.service.producer;
+
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordForMarkingCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordForMarkingBean;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.QueryValidExamRecordInfoPageReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.QueryValidExamRecordInfoPageResp;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamCourseRelationBean;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamCourseListReq;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamCourseListResp;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.SysPropertyCacheBean;
+import cn.com.qmth.examcloud.task.base.multithread.Producer;
+import cn.com.qmth.examcloud.task.service.dto.MarkWorkCreateDto;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+public class MarkWorkCreateProducer extends Producer {
+
+    private ExamRecordForMarkingCloudService examRecordForMarkingCloudService = SpringContextHolder
+            .getBean(ExamRecordForMarkingCloudService.class);
+
+    private ExamCloudService examCloudService = SpringContextHolder.getBean(ExamCloudService.class);
+
+    private static final Logger logger = LoggerFactory.getLogger(MarkWorkCreateProducer.class);
+
+    @Override
+    protected void produce(Map<String, Object> param) throws Exception {
+        Long examId = (Long) param.get("examId");
+        Long workId = (Long) param.get("workId");
+        // 根据考试批次Id去考务中获取课程信息集合
+        GetExamCourseListReq getExamCourseListReq = new GetExamCourseListReq();
+        getExamCourseListReq.setExamId(examId);
+        Long batchSize = 100L;
+        SysPropertyCacheBean spc = CacheHelper.getSysProperty("marking.rpc.savePaperList.batchSize");
+        if (spc != null && spc.getValue() != null) {
+            batchSize = (Long) spc.getValue();
+        }
+        Long start = 1l;
+        while (true) {// 每次去考务调用100条记录(目前暂无处理没有考生的课程)
+            getExamCourseListReq.setStart(start);
+            GetExamCourseListResp getExamCourseListResp = examCloudService.getExamCourseList(getExamCourseListReq);
+
+            for (ExamCourseRelationBean course : getExamCourseListResp.getRelationList()) {
+                createStudentPapers(examId, workId, course, batchSize);
+            }
+            if (start == getExamCourseListResp.getNext()) {
+                break;
+            }
+            start = getExamCourseListResp.getNext();
+        }
+    }
+
+    private void createStudentPapers(Long examId, Long workId, ExamCourseRelationBean examCourseRelationBean,
+            Long batchSize) throws InterruptedException {
+        int count = 0;
+        logger.info("创建评卷工作课程答卷:" + examCourseRelationBean.getCourseCode() + examCourseRelationBean.getCourseName());
+        // 通过“考试批次”和“课程Code”获取需要阅卷的考卷信息集合
+        QueryValidExamRecordInfoPageReq req = new QueryValidExamRecordInfoPageReq();
+        req.setBatchNum(workId + "");
+        req.setExamId(examId);
+        req.setCourseId(examCourseRelationBean.getCourseId());
+        req.setSize(batchSize);
+        Long start = 1l;
+        while (true) {
+            req.setStart(start);
+            QueryValidExamRecordInfoPageResp resp = examRecordForMarkingCloudService.queryValidExamRecordInfoPage(req);
+            count = resp.getExamRecordForMarkingBeanList().size();
+            if (count > 0) {
+                subListDispose(resp.getExamRecordForMarkingBeanList(), workId, examCourseRelationBean, batchSize);
+            }
+            if (start.equals(resp.getNext())) {
+                break;
+            }
+            start = resp.getNext();
+        }
+    }
+
+    private void subListDispose(List<ExamRecordForMarkingBean> list, Long workId,
+            ExamCourseRelationBean examCourseRelationBean, Long batchSize) throws InterruptedException {
+        if (list.size() <= batchSize) {
+            // 生产需处理的数据
+            offer(new MarkWorkCreateDto(list, workId, examCourseRelationBean));
+        } else {
+            int size = list.size();
+            int len = batchSize.intValue();
+            int count = (size + len - 1) / len;
+
+            for (int i = 0; i < count; i++) {
+                List<ExamRecordForMarkingBean> subList = list.subList(i * len,
+                        ((i + 1) * len > size ? size : len * (i + 1)));
+                // 生产需处理的数据
+                offer(new MarkWorkCreateDto(subList, workId, examCourseRelationBean));
+            }
+        }
+    }
+}

+ 42 - 0
examcloud-task-service/src/main/java/cn/com/qmth/examcloud/task/service/producer/ReportsComputeProducer.java

@@ -0,0 +1,42 @@
+package cn.com.qmth.examcloud.task.service.producer;
+
+import java.util.List;
+import java.util.Map;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.com.qmth.examcloud.task.base.multithread.Producer;
+import cn.com.qmth.examcloud.task.dao.entity.ReportsComputeEntity;
+import cn.com.qmth.examcloud.task.service.ReportsComputeService;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+public class ReportsComputeProducer extends Producer {
+	private static final Logger logger = LoggerFactory.getLogger(ReportsComputeProducer.class);
+	private ReportsComputeService reportsComputeService = SpringContextHolder.getBean(ReportsComputeService.class);
+
+	@Override
+	protected void produce(Map<String, Object> param) throws Exception {
+		logger.info("***************************报表计算任务生产开始");
+		Long sartId = 0l;
+		Integer limit = 100;
+		for (;;) {
+			List<ReportsComputeEntity> list = reportsComputeService.findTodoData(sartId, limit);
+			int count=0;
+			if(list!=null) {
+				count=list.size();
+			}
+			logger.info("***************************startId:"+sartId+" count:"+count);
+			if (list != null && list.size() > 0) {
+				for(ReportsComputeEntity et:list) {
+					sartId=et.getId();
+					//生产数据
+					offer(et);
+				}
+			} else {
+				break;
+			}
+		}
+		logger.info("***************************报表计算任务生产结束");
+	}
+}

+ 2 - 0
examcloud-task-starter/.logs/interface/task.interface.log

@@ -0,0 +1,2 @@
+2018-07-25 15:30:00.123| INFO |  -  | [CALL-IN]. url=http://EXAMCLOUD-SERVICE-OE/api/core/oe/cleanExamRecord
+2018-07-25 15:30:00.123| INFO |  -  | [CALL-REQ]. request=null

+ 94 - 0
examcloud-task-starter/.logs/task/task.log

@@ -0,0 +1,94 @@
+2018-07-25 15:24:59.217| DEBUG | main -  | add a job. job detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:24:59.245| DEBUG | main -  | add a job successfully. job detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:00.012| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:00.114| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:00.242| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:10.005| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:10.068| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:10.136| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:20.003| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:20.062| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:20.131| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:30.003| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:30.447| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:30.518| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:40.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:40.063| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:40.124| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:50.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:50.051| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:25:50.114| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:00.003| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:00.047| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:00.107| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:10.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:10.065| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:10.136| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:20.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:20.022| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:20.090| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:30.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:30.056| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:30.126| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:40.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:40.057| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:40.121| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:50.000| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:50.032| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:26:50.096| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:00.003| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:00.051| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:00.126| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:10.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:10.060| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:10.123| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:20.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:20.051| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:20.092| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:30.000| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:30.021| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:30.083| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:40.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:40.040| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:40.111| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:50.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:50.053| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:27:50.113| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:00.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:00.046| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:00.104| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:10.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:10.031| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:10.176| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:20.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:20.065| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:20.134| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:30.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:30.057| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:30.114| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:40.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:40.058| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:40.119| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:50.000| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:50.045| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:28:50.096| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:00.003| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:00.054| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:00.122| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:10.000| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:10.020| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:10.084| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:20.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:20.031| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:20.090| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:30.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:30.052| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:30.133| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:40.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:40.056| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:40.126| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:50.001| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:50.023| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:29:50.079| DEBUG |  -  | [TASK OUT]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:30:00.002| DEBUG |  -  | distribute job. job detail :{"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}
+2018-07-25 15:30:00.048| DEBUG |  -  | [TASK IN]. detail: {"jobId":1,"jobName":"fuck","jobGroup":"you","cronExpression":"0/10 * * * * ?","springBean":"oeCleanExamRecordTask","stateful":true}

+ 30 - 0
examcloud-task-starter/assembly.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+	<id>distribution</id>
+	<formats>
+		<format>zip</format>
+	</formats>
+	<fileSets>
+		<fileSet>
+			<directory>${project.basedir}/src/main/resources</directory>
+			<outputDirectory>/config</outputDirectory>
+		</fileSet>
+		<fileSet>
+			<directory>${project.basedir}/shell</directory>
+			<excludes>
+				<exclude>start.args</exclude>
+				<exclude>start.vmoptions</exclude>
+			</excludes>
+			<outputDirectory>/</outputDirectory>
+			<fileMode>0777</fileMode>
+		</fileSet>
+	</fileSets>
+	<dependencySets>
+		<dependencySet>
+			<useProjectArtifact>true</useProjectArtifact>
+			<outputDirectory>lib</outputDirectory>
+			<scope>runtime</scope>
+		</dependencySet>
+	</dependencySets>
+</assembly>

+ 67 - 0
examcloud-task-starter/pom.xml

@@ -0,0 +1,67 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud.task</groupId>
+		<artifactId>examcloud-task</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-task-starter</artifactId>
+
+	<dependencies>
+		<dependency>
+			<groupId>com.github.xiaoymin</groupId>
+			<artifactId>swagger-bootstrap-ui</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.task</groupId>
+			<artifactId>examcloud-task-api-provider</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<configuration>
+					<archive>
+						<manifest>
+							<mainClass>cn.com.qmth.examcloud.task.starter.TaskApp</mainClass>
+							<addClasspath>true</addClasspath>
+							<classpathPrefix>./</classpathPrefix>
+						</manifest>
+						<manifestEntries>
+							<Class-Path>../config/</Class-Path>
+						</manifestEntries>
+					</archive>
+					<excludes>
+						<exclude>*.properties</exclude>
+						<exclude>*.xml </exclude>
+						<exclude>classpath.location</exclude>
+					</excludes>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-assembly-plugin</artifactId>
+				<configuration>
+					<finalName>examcloud-task</finalName>
+					<descriptors>
+						<descriptor>assembly.xml</descriptor>
+					</descriptors>
+				</configuration>
+				<executions>
+					<execution>
+						<id>make-assembly</id>
+						<phase>install</phase>
+						<goals>
+							<goal>assembly</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>

+ 18 - 0
examcloud-task-starter/shell/jenkins.sh

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

+ 1 - 0
examcloud-task-starter/shell/start.args

@@ -0,0 +1 @@
+--spring.profiles.active=dev

+ 38 - 0
examcloud-task-starter/shell/start.sh

@@ -0,0 +1,38 @@
+#!/bin/bash
+
+FILE_PATH=$(cd `dirname $0`; pwd)
+APP_VERSION=`cat $FILE_PATH/version`
+APP_MAIN_JAR="examcloud-task-starter-"$APP_VERSION"-SNAPSHOT.jar"
+
+JAVA_OPTS=`cat $FILE_PATH/start.vmoptions`
+APP_ARGS=`cat $FILE_PATH/start.args`
+
+PID_LIST=`ps -ef|grep $APP_MAIN_JAR|grep java|awk '{print $2}'`
+
+if [ ! -z "$PID_LIST" ]; then
+    echo "[ERROR] : APP is already running!"
+    exit -1
+fi
+
+if [ "$1" ];then
+    echo "startupCode:"$1;
+else
+    echo "[ERROR] : no arguments"
+    exit -1
+fi
+
+APP_ARGS=$APP_ARGS" --examcloud.startup.startupCode="$1
+
+echo "java options:"
+echo "$JAVA_OPTS"
+echo "args:"
+echo "$APP_ARGS"
+    
+nohup java $JAVA_OPTS -jar $FILE_PATH/lib/$APP_MAIN_JAR $APP_ARGS >/dev/null 2>&1 &
+
+echo "starting......"
+
+exit 0
+
+
+

+ 1 - 0
examcloud-task-starter/shell/start.vmoptions

@@ -0,0 +1 @@
+-server -Xms256m -Xmx256m

+ 18 - 0
examcloud-task-starter/shell/stop.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+
+FILE_PATH=$(cd `dirname $0`; pwd)
+APP_VERSION=`cat $FILE_PATH/version`
+APP_MAIN_JAR="examcloud-task-starter-"$APP_VERSION"-SNAPSHOT.jar"
+
+PID_LIST=`ps -ef|grep $APP_MAIN_JAR|grep java|awk '{print $2}'`
+
+if [ ! -z "$PID_LIST" ]; then
+    echo "Runnable jar is $APP_MAIN_JAR."
+    for PID in $PID_LIST 
+    do
+        kill -9 $PID
+    done
+    echo "stopped !"
+fi
+
+exit 0

+ 1 - 0
examcloud-task-starter/shell/version

@@ -0,0 +1 @@
+2019

+ 72 - 0
examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/TaskApp.java

@@ -0,0 +1,72 @@
+package cn.com.qmth.examcloud.task.starter;
+
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
+import org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+import org.springframework.web.multipart.MultipartResolver;
+import org.springframework.web.multipart.commons.CommonsMultipartResolver;
+
+import cn.com.qmth.examcloud.web.bootstrap.AppBootstrap;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+@SpringBootApplication
+@Configuration
+@EnableJpaAuditing
+@EnableTransactionManagement
+@EnableEurekaClient
+@EnableDiscoveryClient
+@ComponentScan(basePackages = {"cn.com.qmth"})
+@EntityScan(basePackages = {"cn.com.qmth.examcloud.task.dao.entity"})
+@EnableJpaRepositories(basePackages = {"cn.com.qmth.examcloud.task.dao"})
+@EnableAutoConfiguration(exclude = {MultipartAutoConfiguration.class})
+public class TaskApp {
+
+	static {
+		String runtimeLevel = System.getProperty("log.commonLevel");
+		if (null == runtimeLevel) {
+			System.setProperty("log.commonLevel", "DEBUG");
+		}
+		System.setProperty("hibernate.dialect.storage_engine", "innodb");
+	}
+
+	/**
+	 * main
+	 *
+	 * @author WANGWEI
+	 * @param args
+	 */
+	public static void main(String[] args) {
+		AppBootstrap.run(TaskApp.class, args);
+		test();
+	}
+
+	/**
+	 * 测试方法
+	 *
+	 * @author WANGWEI
+	 */
+	private static void test() {
+		Tester tester = SpringContextHolder.getBean(Tester.class);
+		tester.test();
+	}
+
+	@Bean(name = "multipartResolver")
+	public MultipartResolver multipartResolver() {
+		CommonsMultipartResolver resolver = new CommonsMultipartResolver();
+		resolver.setDefaultEncoding("UTF-8");
+		resolver.setResolveLazily(true);
+		resolver.setMaxInMemorySize(2);
+		resolver.setMaxUploadSize(200 * 1024 * 1024);
+		return resolver;
+	}
+
+}

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

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

+ 132 - 0
examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/ExamCloudResourceManager.java

@@ -0,0 +1,132 @@
+package cn.com.qmth.examcloud.task.starter.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.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.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;
+
+/**
+ * Demo资源管理器
+ *
+ * @author WANGWEI
+ * @date 2019年2月18日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
+ */
+@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 (mapping.matches(".*swagger.*")) {
+			return true;
+		}
+
+		if (null != apiInfo) {
+			if (apiInfo.isNaked()) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	@Override
+	public boolean hasPermission(User user, ApiInfo apiInfo, String mapping) {
+
+		// 学生鉴权
+		if (user.getUserType().equals(UserType.STUDENT)) {
+			String key = "[s]" + mapping;
+			return PropertiesUtil.getBoolean(key, false);
+		}
+
+		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;
+	}
+
+}

+ 50 - 0
examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/ExamCloudWebMvcConfigurer.java

@@ -0,0 +1,50 @@
+package cn.com.qmth.examcloud.task.starter.config;
+
+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.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.RequestPermissionInterceptor;
+import cn.com.qmth.examcloud.web.security.ResourceManager;
+import cn.com.qmth.examcloud.web.security.RpcInterceptor;
+
+/**
+ * WebMvcConfigurer
+ *
+ * @author WANGWEI
+ * @date 2019年1月30日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
+ */
+@Configuration
+public class ExamCloudWebMvcConfigurer implements WebMvcConfigurer {
+	@Autowired
+	ResourceManager resourceManager;
+
+	@Autowired
+	RedisClient redisClient;
+
+	@Override
+	public void addInterceptors(InterceptorRegistry registry) {
+		registry.addInterceptor(new FirstInterceptor()).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("/**");
+	}
+
+	@Override
+	public void addCorsMappings(CorsRegistry registry) {
+		registry.addMapping("/**").allowedOrigins("*").allowCredentials(false).allowedMethods("*")
+				.maxAge(3600);
+	}
+
+}

+ 88 - 0
examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/JobsStartup.java

@@ -0,0 +1,88 @@
+package cn.com.qmth.examcloud.task.starter.config;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import cn.com.qmth.examcloud.task.dao.ScheduleJobRepo;
+import cn.com.qmth.examcloud.task.dao.entity.ScheduleJobEntity;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import cn.com.qmth.examcloud.web.task.QuartzManager;
+import cn.com.qmth.examcloud.web.task.ScheduleJob;
+
+/**
+ * 定时任务启动
+ *
+ * @author WANGWEI
+ * @date 2018年11月29日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Component
+@Order(100)
+public class JobsStartup implements ApplicationRunner {
+
+	@Autowired
+	private ScheduleJobRepo scheduleJobEntityRepo;
+
+	@Autowired
+	private QuartzManager quartzManager;
+
+	public void start() {
+
+		Boolean enabled = PropertyHolder.getBoolean("task.scheduleJob.enabled", true);
+
+		if (!enabled) {
+			return;
+		}
+
+		new Thread(new Runnable() {
+
+			@Override
+			public void run() {
+
+				// 延迟启动
+				try {
+					TimeUnit.SECONDS.sleep(30);
+				} catch (InterruptedException e) {
+					// ignore
+				}
+
+				List<ScheduleJobEntity> jobEntityList = scheduleJobEntityRepo.findAll();
+
+				for (ScheduleJobEntity jobEntity : jobEntityList) {
+					if (!jobEntity.getEnable()) {
+						continue;
+					}
+					ScheduleJob scheduleJob = new ScheduleJob();
+					scheduleJob.setJobId(jobEntity.getId());
+					scheduleJob.setJobName(jobEntity.getJobName());
+					scheduleJob.setJobGroup(jobEntity.getJobGroup());
+					scheduleJob.setSpringBean(jobEntity.getSpringBean());
+					scheduleJob.setCronExpression(jobEntity.getCronExpression());
+					scheduleJob.setStateful(jobEntity.getStateful());
+
+					scheduleJob.setExt1(jobEntity.getExt1());
+					scheduleJob.setExt2(jobEntity.getExt2());
+					scheduleJob.setExt3(jobEntity.getExt3());
+					scheduleJob.setExt4(jobEntity.getExt4());
+					scheduleJob.setExt5(jobEntity.getExt5());
+
+					quartzManager.addJob(scheduleJob);
+				}
+
+			}
+		}).start();
+
+	}
+
+	@Override
+	public void run(ApplicationArguments args) throws Exception {
+		start();
+	}
+
+}

+ 32 - 0
examcloud-task-starter/src/main/java/cn/com/qmth/examcloud/task/starter/config/Swagger2.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.task.starter.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Contact;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@Configuration
+@EnableSwagger2
+public class Swagger2 {
+
+	@Bean
+	public Docket createRestApi() {
+		return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
+				.apis(RequestHandlerSelectors.basePackage("cn.com.qmth.examcloud.task.api"))
+				.paths(PathSelectors.any()).build();
+	}
+
+	private ApiInfo apiInfo() {
+		return new ApiInfoBuilder().title("API doc")
+				.contact(new Contact("qmth", "http://xxxxx/", "")).version("xxx")
+				.description("API文档").build();
+	}
+
+}

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

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

+ 6 - 0
examcloud-task-starter/src/main/resources/application.properties

@@ -0,0 +1,6 @@
+spring.profiles.active=dev
+
+examcloud.startup.startupCode=8007
+examcloud.startup.configCenterHost=127.0.0.1
+examcloud.startup.configCenterPort=9999
+examcloud.startup.appCode=T

+ 1 - 0
examcloud-task-starter/src/main/resources/classpath.location

@@ -0,0 +1 @@
+classpath 定位文件

+ 82 - 0
examcloud-task-starter/src/main/resources/log4j2.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN" monitorInterval="30">
+
+	<Properties>
+		<Property name="commonLevel" value="${sys:log.commonLevel}" />
+	</Properties>
+
+	<Appenders>
+		<!-- 控制台 日志 -->
+		<Console name="Console" target="SYSTEM_OUT">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m | %l%n" />
+		</Console>
+		<!-- debug 日志 -->
+		<RollingFile name="DEBUG_APPERDER" fileName="./logs/debug/debug.log" filePattern="./logs/debug/debug-%d{yyyy.MM.dd.HH}-%i.log">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m | %l%n" />
+			<Policies>
+				<TimeBasedTriggeringPolicy interval="1" />
+				<SizeBasedTriggeringPolicy size="100 MB" />
+			</Policies>
+			<DefaultRolloverStrategy max="10000">
+				<Delete basePath="./logs/debug" maxDepth="1">
+					<IfFileName glob="debug-*.log">
+						<IfAccumulatedFileSize exceeds="2 GB" />
+					</IfFileName>
+				</Delete>
+			</DefaultRolloverStrategy>
+		</RollingFile>
+		<!-- 接口日志 -->
+		<RollingFile name="INTERFACE_APPENDER" fileName="./logs/interface/interface.log" filePattern="./logs/interface/interface-%d{yyyy.MM.dd.HH}-%i.log">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m%n" />
+			<Policies>
+				<TimeBasedTriggeringPolicy interval="1" />
+				<SizeBasedTriggeringPolicy size="100 MB" />
+			</Policies>
+			<DefaultRolloverStrategy max="10000">
+				<Delete basePath="./logs/interface" maxDepth="1">
+					<IfFileName glob="interface-*.log">
+						<IfAccumulatedFileSize exceeds="10 GB" />
+					</IfFileName>
+				</Delete>
+			</DefaultRolloverStrategy>
+		</RollingFile>
+		<!-- 任务日志 -->
+		<RollingFile name="TASK_APPENDER" fileName="./logs/task/task.log" filePattern="./logs/task/task-%d{yyyy.MM.dd.HH}-%i.log">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m%n" />
+			<Policies>
+				<TimeBasedTriggeringPolicy interval="1" />
+				<SizeBasedTriggeringPolicy size="100 MB" />
+			</Policies>
+			<DefaultRolloverStrategy max="10000">
+				<Delete basePath="./logs/task" maxDepth="1">
+					<IfFileName glob="task-*.log">
+						<IfAccumulatedFileSize exceeds="2 GB" />
+					</IfFileName>
+				</Delete>
+			</DefaultRolloverStrategy>
+		</RollingFile>
+	</Appenders>
+
+	<Loggers>
+		<Logger name="cn.com.qmth" level="${commonLevel}" additivity="false">
+			<AppenderRef ref="DEBUG_APPERDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Logger name="INTERFACE_LOGGER" level="INFO" additivity="false">
+			<AppenderRef ref="INTERFACE_APPENDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Logger name="TASK_LOGGER" level="INFO" additivity="false">
+			<AppenderRef ref="TASK_APPENDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Root level="INFO">
+			<AppenderRef ref="Console" />
+			<AppenderRef ref="DEBUG_APPERDER" />
+		</Root>
+	</Loggers>
+
+</Configuration>

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

@@ -0,0 +1 @@
+

+ 19 - 0
jenkins-dev.sh

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

+ 4 - 0
jenkins-prod.sh

@@ -0,0 +1,4 @@
+#!/bin/bash
+pwd
+
+cp examcloud-task-starter/target/examcloud-task-distribution.zip ~/packages

+ 19 - 0
jenkins-test.sh

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

+ 31 - 0
pom.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud</groupId>
+		<artifactId>examcloud-parent</artifactId>
+		<version>2019</version>
+	</parent>
+	<groupId>cn.com.qmth.examcloud.task</groupId>
+	<artifactId>examcloud-task</artifactId>
+	<version>2019-SNAPSHOT</version>
+	<packaging>pom</packaging>
+	<dependencyManagement>
+		<dependencies>
+			<dependency>
+				<groupId>com.github.xiaoymin</groupId>
+				<artifactId>swagger-bootstrap-ui</artifactId>
+				<version>1.9.3</version>
+			</dependency>
+		</dependencies>
+	</dependencyManagement>
+	<modules>
+		<module>examcloud-task-api-provider</module>
+		<module>examcloud-task-service</module>
+		<module>examcloud-task-base</module>
+		<module>examcloud-task-dao</module>
+		<module>examcloud-task-starter</module>
+	</modules>
+</project>