deason 5 жил өмнө
commit
b4bf0d35d5
100 өөрчлөгдсөн 13507 нэмэгдсэн , 0 устгасан
  1. 23 0
      .gitignore
  2. 1 0
      README.md
  3. 30 0
      examcloud-core-examwork-api-provider/pom.xml
  4. 1562 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/ExamController.java
  5. 493 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/ExamStudentController.java
  6. 415 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/NoticeController.java
  7. 114 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/AddNoticeDomain.java
  8. 45 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/CopyExamDomain.java
  9. 76 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/CourseDomain.java
  10. 124 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/ExamCourseGroupDomain.java
  11. 255 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/ExamDomain.java
  12. 156 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/ExamOrgSettingsDomain.java
  13. 383 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/ExamStudentDomain.java
  14. 131 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/NoticeDomain.java
  15. 47 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/NoticeDomainQuery.java
  16. 157 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/StudentSpecialSettingsDomain.java
  17. 126 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/UpdateNoticeDomain.java
  18. 102 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/UserNoticeDomain.java
  19. 34 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/UserNoticeDomainQuery.java
  20. 808 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/provider/ExamCloudServiceProvider.java
  21. 606 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/provider/ExamStudentCloudServiceProvider.java
  22. 150 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/provider/HandleSyncCloudServiceProvider.java
  23. 110 0
      examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/provider/NoticeCloudServiceProvider.java
  24. 29 0
      examcloud-core-examwork-base/pom.xml
  25. 35 0
      examcloud-core-examwork-base/src/main/java/cn/com/qmth/examcloud/core/examwork/base/enums/ExamProperty.java
  26. 28 0
      examcloud-core-examwork-base/src/main/java/cn/com/qmth/examcloud/core/examwork/base/processor/HttpMethodProcessorImpl.java
  27. 25 0
      examcloud-core-examwork-dao/pom.xml
  28. 33 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamCourseRelationRepo.java
  29. 32 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamOrgPropertyRepo.java
  30. 24 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamPaperTypeRelationRepo.java
  31. 21 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamPropertyRepo.java
  32. 35 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamRepo.java
  33. 54 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamSpecialSettingsRepo.java
  34. 85 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamStudentRepo.java
  35. 26 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/NoticeReceiverRuleRepo.java
  36. 29 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/NoticeRepo.java
  37. 32 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/NoticeRulePublishProgressRepo.java
  38. 30 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/UniqueRuleHolder.java
  39. 28 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/UserNoticeRepo.java
  40. 86 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamCourseRelationEntity.java
  41. 39 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamCourseRelationPK.java
  42. 234 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamEntity.java
  43. 84 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamOrgPropertyEntity.java
  44. 57 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamPaperTypeRelationEntity.java
  45. 46 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamPaperTypeRelationPK.java
  46. 76 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamPropertyEntity.java
  47. 97 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamSiteBatchEntity.java
  48. 293 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamSpecialSettingsEntity.java
  49. 358 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamStudentEntity.java
  50. 118 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/NoticeEntity.java
  51. 89 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/NoticeReceiverRuleEntity.java
  52. 94 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/NoticeRulePublishProgressEntity.java
  53. 72 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/SiteEntity.java
  54. 99 0
      examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/UserNoticeEntity.java
  55. 45 0
      examcloud-core-examwork-service/pom.xml
  56. 82 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/ExamService.java
  57. 24 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/ExamStudentService.java
  58. 104 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/NoticeService.java
  59. 29 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/OnGoingExamService.java
  60. 127 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/AddNoticeInfo.java
  61. 209 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/ExamInfo.java
  62. 143 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/ExamSpecialSettingsInfo.java
  63. 401 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/ExamStudentInfo.java
  64. 48 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/GetLimitUserIdResp.java
  65. 142 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/NoticeInfo.java
  66. 77 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/NoticeInfoQuery.java
  67. 137 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/UpdateNoticeInfo.java
  68. 99 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/UserNoticeInfo.java
  69. 75 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/UserNoticeInfoQuery.java
  70. 60 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamOrgPropertyCache.java
  71. 71 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamOrgSettingsCache.java
  72. 55 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamPropertyCache.java
  73. 64 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamSettingsCache.java
  74. 41 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamStudentPropertyCache.java
  75. 71 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamStudentSettingsCache.java
  76. 809 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/impl/ExamServiceImpl.java
  77. 507 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/impl/ExamStudentServiceImpl.java
  78. 706 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/impl/NoticeServiceImpl.java
  79. 206 0
      examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/impl/OnGoingExamServiceImpl.java
  80. 16 0
      examcloud-core-examwork-starter/.springBeans
  81. 30 0
      examcloud-core-examwork-starter/assembly.xml
  82. 68 0
      examcloud-core-examwork-starter/pom.xml
  83. 1 0
      examcloud-core-examwork-starter/shell/start.args
  84. 38 0
      examcloud-core-examwork-starter/shell/start.sh
  85. 1 0
      examcloud-core-examwork-starter/shell/start.vmoptions
  86. 18 0
      examcloud-core-examwork-starter/shell/stop.sh
  87. 1 0
      examcloud-core-examwork-starter/shell/version
  88. 80 0
      examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/CoreExamWorkApp.java
  89. 12 0
      examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/Tester.java
  90. 135 0
      examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/config/ExamCloudResourceManager.java
  91. 53 0
      examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/config/ExamCloudWebMvcConfigurer.java
  92. 33 0
      examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/config/Swagger2.java
  93. 8 0
      examcloud-core-examwork-starter/src/main/resources/application.properties
  94. 1 0
      examcloud-core-examwork-starter/src/main/resources/classpath.location
  95. 0 0
      examcloud-core-examwork-starter/src/main/resources/config.properties
  96. 261 0
      examcloud-core-examwork-starter/src/main/resources/exam-properties.xml
  97. 1 0
      examcloud-core-examwork-starter/src/main/resources/limited.properties
  98. 67 0
      examcloud-core-examwork-starter/src/main/resources/log4j2.xml
  99. 15 0
      examcloud-core-examwork-starter/src/main/resources/security.properties
  100. BIN
      examcloud-core-examwork-starter/src/main/resources/templates/studentImportTemplate.xlsx

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+*.class
+
+# Proguard folder generated by ide
+.project
+.classpath
+.settings
+target/
+.idea/
+*.iml
+
+# Log Files
+*.log
+*.class
+
+
+# Package Files #
+*.jar
+*.war
+*.ear
+/exam-work-main/src/test/
+logs/
+fileImport/
+fileExport/

+ 1 - 0
README.md

@@ -0,0 +1 @@
+#考试云平台考务服务

+ 30 - 0
examcloud-core-examwork-api-provider/pom.xml

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

+ 1562 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/ExamController.java

@@ -0,0 +1,1562 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import javax.persistence.criteria.Subquery;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.fileupload.disk.DiskFileItem;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import cn.com.qmth.examcloud.api.commons.enums.CURD;
+import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnum;
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnumManager;
+import cn.com.qmth.examcloud.commons.helpers.poi.ExcelWriter;
+import cn.com.qmth.examcloud.commons.util.DateUtil;
+import cn.com.qmth.examcloud.commons.util.DateUtil.DatePatterns;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.RegExpUtil;
+import cn.com.qmth.examcloud.commons.util.StringUtil;
+import cn.com.qmth.examcloud.commons.util.Util;
+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.GetOrgReq;
+import cn.com.qmth.examcloud.core.basic.api.request.GetOrgsReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgResp;
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgsResp;
+import cn.com.qmth.examcloud.core.examwork.api.controller.bean.CopyExamDomain;
+import cn.com.qmth.examcloud.core.examwork.api.controller.bean.ExamDomain;
+import cn.com.qmth.examcloud.core.examwork.api.controller.bean.ExamOrgSettingsDomain;
+import cn.com.qmth.examcloud.core.examwork.api.controller.bean.StudentSpecialSettingsDomain;
+import cn.com.qmth.examcloud.core.examwork.base.enums.ExamProperty;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamCourseRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamOrgPropertyRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamPaperTypeRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamPropertyRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamSpecialSettingsRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamCourseRelationEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamOrgPropertyEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPaperTypeRelationEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPropertyEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.examwork.service.ExamService;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamInfo;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamSpecialSettingsInfo;
+import cn.com.qmth.examcloud.core.examwork.service.cache.ExamSettingsCache;
+import cn.com.qmth.examcloud.core.examwork.service.impl.ExamStudentServiceImpl;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.CheckExamIsStartedReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.CheckExamIsStartedResp;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.ExamOrgPropertyCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamOrgSettingsCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamStudentPropertyCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.ExamStudentSettingsCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.OrgPropertyCacheBean;
+import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
+import cn.com.qmth.examcloud.support.privilege.PrivilegeDefine;
+import cn.com.qmth.examcloud.support.privilege.PrivilegeManager;
+import cn.com.qmth.examcloud.task.api.DataSyncCloudService;
+import cn.com.qmth.examcloud.task.api.request.SyncExamReq;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 重构
+ *
+ * @author WANGWEI
+ * @date 2018年8月17日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@RestController
+@RequestMapping("${$rmp.ctr.examwork}/exam")
+public class ExamController extends ControllerSupport {
+
+	@Autowired
+	SystemProperties systemConfig;
+
+	@Autowired
+	ExamPropertyRepo examPropertyRepo;
+
+	@Autowired
+	RedisClient redisClient;
+
+	@Autowired
+	ExamRepo examRepo;
+
+	@Autowired
+	ExamService examService;
+
+	@Autowired
+	ExamStudentRepo examStudentRepo;
+
+	@Autowired
+	ExamStudentServiceImpl examStudentService;
+
+	@Autowired
+	ExamSpecialSettingsRepo examSpecialSettingsRepo;
+
+	@Autowired
+	OrgCloudService orgCloudService;
+
+	@Autowired
+	ExamOrgPropertyRepo examOrgPropertyRepo;
+
+	@Autowired
+	StudentCloudService studentCloudService;
+
+	@Autowired
+	ExamCourseRelationRepo examCourseRelationRepo;
+
+	@Autowired
+	ExamPaperTypeRelationRepo examPaperTypeRelationRepo;
+
+	@Autowired
+	ExamRecordCloudService examRecordCloudService;
+
+	@Autowired
+	DataSyncCloudService dataSyncCloudService;
+
+	@Autowired
+	ExamSettingsCache examSettingsCache;
+
+	private static final String[] EXAM_ORG_SETTINGS_EXCEL_HEADER = new String[]{"学习中心ID", "学习中心代码",
+			"学习中心名称", "是否可以考试(是/否)", "开始考试时间 yyyy-MM-dd hh:mm:ss", "结束考试时间 yyyy-MM-dd hh:mm:ss"};
+
+	@ApiOperation(value = "查询考试课程的试卷类型集合")
+	@GetMapping("queryExamCoursePaperTypeList")
+	public List<ExamPaperTypeRelationEntity> queryExamCoursePaperTypeList(
+			@RequestParam(required = true) Long examId,
+			@RequestParam(required = true) Long courseId) {
+
+		if (null == examId) {
+			throw new StatusException("001251", "examId is null");
+		}
+		if (null == courseId) {
+			throw new StatusException("001252", "courseId is null");
+		}
+		ExamEntity one = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == one) {
+			throw new StatusException("001253", "examId is wrong");
+		}
+
+		validateRootOrgIsolation(one.getRootOrgId());
+
+		List<ExamPaperTypeRelationEntity> list = examPaperTypeRelationRepo
+				.findByExamIdAndCourseId(examId, courseId);
+
+		return list;
+	}
+
+	@ApiOperation(value = "查询考试的课程集合")
+	@GetMapping("queryExamCourseList")
+	public List<ExamCourseRelationEntity> getExamCourseList(
+			@RequestParam(required = true) Long examId, @RequestParam(required = false) String name,
+			@RequestParam(required = false) String level,
+			@RequestParam(required = false) Boolean enable) {
+
+		ExamEntity one = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == one) {
+			throw new StatusException("001250", "examId is wrong");
+		}
+		validateRootOrgIsolation(one.getRootOrgId());
+
+		Specification<ExamCourseRelationEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+
+			predicates.add(cb.equal(root.get("examId"), examId));
+
+			Predicate pr1 = cb.like(root.get("courseName"), toSqlSearchPattern(name));
+			Predicate pr2 = cb.like(root.get("courseCode"), toSqlSearchPattern(name));
+
+			predicates.add(cb.or(pr1, pr2));
+
+			if (StringUtils.isNotBlank(level)) {
+				predicates.add(cb.equal(root.get("level"), toSqlSearchPattern(level)));
+			}
+
+			if (null != enable) {
+				predicates.add(cb.equal(root.get("courseEnable"), enable));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		PageRequest pageRequest = PageRequest.of(0, 50, new Sort(Direction.DESC, "updateTime"));
+
+		Page<ExamCourseRelationEntity> page = examCourseRelationRepo.findAll(specification,
+				pageRequest);
+
+		Iterator<ExamCourseRelationEntity> iterator = page.iterator();
+		List<ExamCourseRelationEntity> list = Lists.newArrayList();
+
+		while (iterator.hasNext()) {
+			ExamCourseRelationEntity next = iterator.next();
+			list.add(next);
+		}
+
+		return list;
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param curPage
+	 * @param pageSize
+	 * @param name
+	 * @param examType
+	 * @param enable
+	 * @return
+	 */
+	@ApiOperation(value = "分页查询考试批次")
+	@GetMapping("queryPage/{curPage}/{pageSize}")
+	public PageInfo<ExamDomain> queryPage(@PathVariable Integer curPage,
+			@PathVariable Integer pageSize, @RequestParam(required = false) String name,
+			@RequestParam(required = false) String examType,
+			@RequestParam(required = false) Boolean enable,
+			@RequestParam(required = false) String propertyKeys) {
+
+		User accessUser = getAccessUser();
+
+		Specification<ExamEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("rootOrgId"), accessUser.getRootOrgId()));
+			if (StringUtils.isNotBlank(name)) {
+				predicates.add(cb.like(root.get("name"), toSqlSearchPattern(name)));
+			}
+			if (null != enable) {
+				predicates.add(cb.equal(root.get("enable"), enable));
+			}
+			if (StringUtils.isNotBlank(examType)) {
+				predicates.add(cb.equal(root.get("examType"), ExamType.valueOf(examType)));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		PageRequest pageRequest = PageRequest.of(curPage, pageSize,
+				new Sort(Direction.DESC, "updateTime", "id"));
+
+		Page<ExamEntity> page = examRepo.findAll(specification, pageRequest);
+
+		Iterator<ExamEntity> iterator = page.iterator();
+		List<ExamDomain> list = Lists.newArrayList();
+
+		List<String> propertyKeyList = null;
+		if (StringUtils.isNotBlank(propertyKeys)) {
+			propertyKeyList = RegExpUtil.findAll(propertyKeys, "\\w+");
+		}
+
+		while (iterator.hasNext()) {
+			ExamEntity next = iterator.next();
+			ExamDomain bean = new ExamDomain();
+			list.add(bean);
+
+			bean.setId(next.getId());
+			bean.setCode(next.getCode());
+			bean.setName(next.getName());
+			bean.setEnable(next.getEnable());
+			bean.setRootOrgId(next.getRootOrgId());
+			bean.setBeginTime(next.getBeginTime());
+			bean.setEndTime(next.getEndTime());
+			bean.setExamType(next.getExamType());
+			bean.setDuration(next.getDuration());
+			bean.setEnable(next.getEnable());
+			bean.setRemark(next.getRemark());
+			bean.setExamTimes(next.getExamTimes());
+			bean.setCreationTime(next.getCreationTime());
+			bean.setUpdateTime(next.getUpdateTime());
+			bean.setExamLimit(next.getExamLimit());
+			bean.setSpecialSettingsEnabled(next.getSpecialSettingsEnabled());
+			bean.setSpecialSettingsType(next.getSpecialSettingsType());
+
+			if (CollectionUtils.isNotEmpty(propertyKeyList)) {
+				Map<String, String> properties = getProperties(bean.getId(), propertyKeyList);
+				bean.setProperties(properties);
+			}
+		}
+
+		PageInfo<ExamDomain> ret = new PageInfo<ExamDomain>(page, list);
+		return ret;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param name
+	 * @param examTypes
+	 * @param enable
+	 * @param studentId
+	 *            筛选学生关联的考试
+	 * @return
+	 */
+	@ApiOperation(value = "查询考试批次")
+	@GetMapping("queryByNameLike")
+	public List<ExamDomain> query(@RequestParam(required = true) String name,
+			@RequestParam(required = false) String examTypes,
+			@RequestParam(required = false) Boolean enable,
+			@RequestParam(required = false) String propertyKeys,
+			@RequestParam(required = false) Long studentId,
+			@RequestParam(required = false) Long rootOrgId) {
+
+		if (null == rootOrgId) {
+			rootOrgId = getRootOrgId();
+		}
+		final Long finalRootOrgId = rootOrgId;
+
+		Specification<ExamEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("rootOrgId"), finalRootOrgId));
+			if (StringUtils.isNotBlank(name)) {
+				predicates.add(cb.like(root.get("name"), toSqlSearchPattern(name)));
+			}
+			if (null != enable) {
+				predicates.add(cb.equal(root.get("enable"), enable));
+			}
+			if (StringUtils.isNotBlank(examTypes)) {
+				List<String> examTypeList = RegExpUtil.findAll(examTypes, "\\w+");
+
+				List<ExamType> etList = Lists.newArrayList();
+				for (String cur : examTypeList) {
+					etList.add(ExamType.valueOf(cur));
+				}
+
+				if (CollectionUtils.isNotEmpty(etList)) {
+					if (10 < examTypeList.size()) {
+						throw new StatusException("001120", "too many examTypes");
+					}
+					if (1 == examTypeList.size()) {
+						predicates.add(cb.equal(root.get("examType"), etList.get(0)));
+					} else {
+						predicates.add(root.get("examType").in(etList));
+					}
+				}
+			}
+
+			if (null != studentId) {
+				Subquery<ExamStudentEntity> subquery = query.subquery(ExamStudentEntity.class);
+				Root<ExamStudentEntity> subRoot = subquery.from(ExamStudentEntity.class);
+				subquery.select(subRoot.get("id"));
+				Predicate p1 = cb.equal(subRoot.get("studentId"), studentId);
+				Predicate p2 = cb.equal(subRoot.get("examId"), root.get("id"));
+				subquery.where(cb.and(p1, p2));
+				predicates.add(cb.exists(subquery));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		PageRequest pageRequest = PageRequest.of(0, 100,
+				new Sort(Direction.DESC, "updateTime", "id"));
+		Page<ExamEntity> page = examRepo.findAll(specification, pageRequest);
+
+		Iterator<ExamEntity> iterator = page.iterator();
+		List<ExamDomain> list = Lists.newArrayList();
+
+		List<String> propertyKeyList = null;
+		if (StringUtils.isNotBlank(propertyKeys)) {
+			propertyKeyList = RegExpUtil.findAll(propertyKeys, "\\w+");
+		}
+
+		while (iterator.hasNext()) {
+			ExamEntity next = iterator.next();
+			ExamDomain bean = new ExamDomain();
+			list.add(bean);
+
+			bean.setId(next.getId());
+			bean.setName(next.getName());
+			bean.setEnable(next.getEnable());
+			bean.setRootOrgId(next.getRootOrgId());
+			bean.setBeginTime(next.getBeginTime());
+			bean.setEndTime(next.getEndTime());
+			bean.setExamType(next.getExamType());
+
+			if (CollectionUtils.isNotEmpty(propertyKeyList)) {
+				Map<String, String> properties = getProperties(bean.getId(), propertyKeyList);
+				bean.setProperties(properties);
+			}
+		}
+
+		return list;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @param propertyKeys
+	 * @return
+	 */
+	private Map<String, String> getProperties(Long examId, List<String> propertyKeys) {
+		Map<String, String> map = Maps.newHashMap();
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+
+		for (String key : propertyKeys) {
+			DynamicEnum de = manager.getByName(key);
+			ExamPropertyEntity one = examPropertyRepo.findByExamIdAndKeyId(examId, de.getId());
+			if (null != one) {
+				map.put(key, one.getValue());
+			}
+		}
+
+		return map;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @return
+	 */
+	@ApiOperation(value = "按ID查询考试批次", notes = "ID查询")
+	@GetMapping("{examId}")
+	public ExamDomain getExamById(@PathVariable Long examId) {
+
+		ExamSettingsCacheBean cache = examSettingsCache.get(examId);
+		validateRootOrgIsolation(cache.getRootOrgId());
+
+		ExamDomain domain = new ExamDomain();
+		domain.setBeginTime(cache.getBeginTime());
+		domain.setDuration(cache.getDuration());
+		domain.setEnable(cache.getEnable());
+		domain.setEndTime(cache.getEndTime());
+		domain.setExamTimes(cache.getExamTimes());
+		domain.setExamType(ExamType.valueOf(cache.getExamType()));
+		domain.setId(cache.getId());
+		domain.setName(cache.getName());
+		domain.setCode(cache.getCode());
+		domain.setRemark(cache.getRemark());
+		domain.setRootOrgId(cache.getRootOrgId());
+		domain.setStarted(isStarted(cache.getId()));
+		domain.setExamLimit(cache.getExamLimit());
+		domain.setSpecialSettingsEnabled(cache.getSpecialSettingsEnabled());
+		domain.setSpecialSettingsType(cache.getSpecialSettingsType());
+
+		return domain;
+	}
+
+	/**
+	 * 是否开考
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @return
+	 */
+	private boolean isStarted(Long examId) {
+		CheckExamIsStartedReq checkExamIsStartedReq = new CheckExamIsStartedReq();
+		checkExamIsStartedReq.setExamId(examId);
+		try {
+			CheckExamIsStartedResp checkExamIsStartedResp = examRecordCloudService
+					.checkExamIsStarted(checkExamIsStartedReq);
+			return checkExamIsStartedResp.getIsStarted();
+		} catch (Exception e) {
+			log.error("fail to rmi[oe.examRecordCloudService.checkExamIsStarted]", e);
+			return true;
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param domain
+	 * @return
+	 */
+	@ApiOperation(value = "新增考试批次", notes = "新增")
+	@PostMapping()
+	@Transactional
+	public ExamEntity addExam(@RequestBody ExamDomain domain) {
+		return saveExam(domain, CURD.CREATION);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param domain
+	 * @return
+	 */
+	@ApiOperation(value = "更新考试批次", notes = "更新")
+	@PutMapping()
+	@Transactional
+	public ExamEntity updateExam(@RequestBody ExamDomain domain) {
+		return saveExam(domain, CURD.UPDATE);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param domain
+	 * @return
+	 */
+	private ExamEntity saveExam(ExamDomain domain, CURD es) {
+		trim(domain, false);
+
+		User accessUser = getAccessUser();
+
+		ExamInfo examInfo = new ExamInfo();
+		examInfo.setBeginTime(domain.getBeginTime());
+		examInfo.setDuration(domain.getDuration());
+		examInfo.setEnable(domain.getEnable());
+		examInfo.setEndTime(domain.getEndTime());
+		examInfo.setExamTimes(domain.getExamTimes());
+		examInfo.setExamType(domain.getExamType());
+		examInfo.setCode(domain.getCode());
+		examInfo.setName(domain.getName());
+		examInfo.setRemark(domain.getRemark());
+		examInfo.setRootOrgId(accessUser.getRootOrgId());
+		examInfo.setExamLimit(domain.getExamLimit());
+		examInfo.setSpecialSettingsEnabled(domain.getSpecialSettingsEnabled());
+		examInfo.setSpecialSettingsType(domain.getSpecialSettingsType());
+
+		Map<String, String> properties = domain.getProperties();
+		if (null == properties) {
+			properties = Maps.newHashMap();
+		}
+		examInfo.setProperties(properties);
+
+		ExamEntity saved = examService.saveExam(examInfo, es);
+
+		examSettingsCache.remove(saved.getId());
+
+		return saved;
+	}
+
+	@ApiOperation(value = "复制考试批次", notes = "")
+	@PostMapping("copyExam")
+	@Transactional
+	public ExamEntity copyExam(@RequestBody CopyExamDomain domain) {
+
+		Long srcExamId = domain.getSrcExamId();
+
+		ExamEntity srcExam = GlobalHelper.getEntity(examRepo, srcExamId, ExamEntity.class);
+		if (null == srcExam) {
+			throw new StatusException("001259", "examId is wrong");
+		}
+
+		validateRootOrgIsolation(srcExam.getRootOrgId());
+
+		ExamDomain ed = new ExamDomain();
+		ed.setBeginTime(srcExam.getBeginTime());
+		ed.setDuration(srcExam.getDuration());
+		ed.setEnable(srcExam.getEnable());
+		ed.setEndTime(srcExam.getEndTime());
+		ed.setExamTimes(srcExam.getExamTimes());
+		ed.setExamType(srcExam.getExamType());
+		ed.setRemark(srcExam.getRemark());
+		ed.setRootOrgId(srcExam.getRootOrgId());
+		ed.setStarted(isStarted(srcExam.getId()));
+		ed.setExamLimit(srcExam.getExamLimit());
+		ed.setSpecialSettingsEnabled(srcExam.getSpecialSettingsEnabled());
+		ed.setSpecialSettingsType(srcExam.getSpecialSettingsType());
+
+		ed.setName(domain.getDestExamName());
+		ed.setCode(domain.getDestExamCode());
+
+		Map<String, String> map = Maps.newHashMap();
+		List<ExamPropertyEntity> list = examPropertyRepo.findByExamId(srcExam.getId());
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+
+		List<String> excludedProps = Util.buildList("MARKING_TASK_BUILDED");
+		for (ExamPropertyEntity cur : list) {
+			DynamicEnum de = manager.getById(cur.getKeyId());
+			if (excludedProps.contains(de.getName())) {
+				continue;
+			}
+			map.put(de.getName(), cur.getValue());
+		}
+
+		ed.setProperties(map);
+
+		ExamEntity savedExam = saveExam(ed, CURD.CREATION);
+		return savedExam;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @return
+	 */
+	@ApiOperation(value = "查询考试批次所有属性")
+	@GetMapping("allProperties/{examId}")
+	public Map<String, String> getAllExamProperties(@PathVariable Long examId) {
+
+		ExamEntity examEntity = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == examEntity) {
+			throw new StatusException("001250", "examId is wrong");
+		}
+		validateRootOrgIsolation(examEntity.getRootOrgId());
+
+		Map<String, String> map = Maps.newHashMap();
+		List<ExamPropertyEntity> list = examPropertyRepo.findByExamId(examId);
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+		for (ExamPropertyEntity cur : list) {
+			DynamicEnum de = manager.getById(cur.getKeyId());
+			map.put(de.getName(), cur.getValue());
+		}
+
+		return map;
+	}
+
+	@ApiOperation(value = "查询考生的考试批次配置")
+	@GetMapping("getExamSettingsFromCacheByStudentSession/{examId}")
+	public ExamDomain getExamSettingsFromCacheByStudentSession(@PathVariable Long examId) {
+
+		User accessUser = getAccessUser();
+		if (!accessUser.getUserType().equals(UserType.STUDENT)) {
+			throw new StatusException("001259", "just allowed by student");
+		}
+
+		Long studentId = accessUser.getUserId();
+
+		StudentCacheBean student = CacheHelper.getStudent(studentId);
+		Long orgId = student.getOrgId();
+
+		ExamSettingsCacheBean examSettings = CacheHelper.getExamSettings(examId);
+
+		Boolean specialSettingsEnabled = examSettings.getSpecialSettingsEnabled();
+		ExamSpecialSettingsType specialSettingsType = examSettings.getSpecialSettingsType();
+
+		ExamDomain domain = new ExamDomain();
+
+		domain.setBeginTime(examSettings.getBeginTime());
+		domain.setDuration(examSettings.getDuration());
+		domain.setEnable(examSettings.getEnable());
+		domain.setEndTime(examSettings.getEndTime());
+		domain.setExamTimes(examSettings.getExamTimes());
+		domain.setExamType(ExamType.valueOf(examSettings.getExamType()));
+		domain.setId(examSettings.getId());
+		domain.setName(examSettings.getName());
+		domain.setCode(examSettings.getCode());
+		domain.setRemark(examSettings.getRemark());
+		domain.setRootOrgId(examSettings.getRootOrgId());
+		domain.setSpecialSettingsEnabled(examSettings.getSpecialSettingsEnabled());
+		domain.setSpecialSettingsType(examSettings.getSpecialSettingsType());
+		domain.setExamLimit(examSettings.getExamLimit());
+		domain.setStarted(examSettings.getBeginTime().before(new Date()));
+
+		if (specialSettingsEnabled && null != specialSettingsType) {
+
+			if (specialSettingsType.equals(ExamSpecialSettingsType.ORG_BASED)) {
+
+				ExamOrgSettingsCacheBean examOrgSettings = CacheHelper.getExamOrgSettings(examId,
+						orgId);
+
+				if (null != examOrgSettings.getBeginTime()) {
+					domain.setBeginTime(examOrgSettings.getBeginTime());
+					domain.setStarted(examOrgSettings.getBeginTime().before(new Date()));
+				}
+				if (null != examOrgSettings.getEndTime()) {
+					domain.setEndTime(examOrgSettings.getEndTime());
+				}
+				if (null != examOrgSettings.getDuration()) {
+					domain.setDuration(examOrgSettings.getDuration());
+				}
+				if (null != examOrgSettings.getExamTimes()) {
+					domain.setExamTimes(examOrgSettings.getExamTimes());
+				}
+				if (null != examOrgSettings.getExamLimit()) {
+					domain.setExamLimit(examOrgSettings.getExamLimit());
+				}
+
+			} else if (specialSettingsType.equals(ExamSpecialSettingsType.STUDENT_BASED)) {
+
+				ExamStudentSettingsCacheBean examStudentSettings = CacheHelper
+						.getExamStudentSettings(examId, studentId);
+
+				if (null != examStudentSettings.getBeginTime()) {
+					domain.setBeginTime(examStudentSettings.getBeginTime());
+					domain.setStarted(examStudentSettings.getBeginTime().before(new Date()));
+				}
+				if (null != examStudentSettings.getEndTime()) {
+					domain.setEndTime(examStudentSettings.getEndTime());
+				}
+				if (null != examStudentSettings.getDuration()) {
+					domain.setDuration(examStudentSettings.getDuration());
+				}
+				if (null != examStudentSettings.getExamTimes()) {
+					domain.setExamTimes(examStudentSettings.getExamTimes());
+				}
+				if (null != examStudentSettings.getExamLimit()) {
+					domain.setExamLimit(examStudentSettings.getExamLimit());
+				}
+
+			}
+		}
+
+		return domain;
+	}
+
+	@ApiOperation(value = "查询考生的考试批次属性集")
+	@GetMapping("getExamPropertyFromCacheByStudentSession/{examId}/{keys}")
+	public Map<String, String> getExamPropertyFromCacheByStudentSession(@PathVariable Long examId,
+			@PathVariable String keys) {
+
+		String[] keyArray = StringUtils.splitByWholeSeparator(keys, ",");
+
+		User accessUser = getAccessUser();
+		if (!accessUser.getUserType().equals(UserType.STUDENT)) {
+			throw new StatusException("001258", "just allowed by student");
+		}
+
+		Long studentId = accessUser.getUserId();
+
+		StudentCacheBean student = CacheHelper.getStudent(studentId);
+		Long orgId = student.getOrgId();
+
+		Map<String, String> map = Maps.newHashMap();
+
+		ExamSettingsCacheBean examSettings = CacheHelper.getExamSettings(examId);
+
+		Boolean specialSettingsEnabled = examSettings.getSpecialSettingsEnabled();
+		ExamSpecialSettingsType specialSettingsType = examSettings.getSpecialSettingsType();
+
+		for (String key : keyArray) {
+			ExamPropertyCacheBean propCache = CacheHelper.getExamProperty(examId, key);
+			map.put(key, propCache.getValue());
+		}
+
+		if (specialSettingsEnabled || null != specialSettingsType) {
+			if (specialSettingsType.equals(ExamSpecialSettingsType.ORG_BASED)) {
+				for (String key : keyArray) {
+					ExamOrgPropertyCacheBean propCache = CacheHelper.getExamOrgProperty(examId,
+							orgId, key);
+					if (StringUtils.isNotBlank(propCache.getValue())) {
+						map.put(key, propCache.getValue());
+					}
+				}
+			} else if (specialSettingsType.equals(ExamSpecialSettingsType.STUDENT_BASED)) {
+				for (String key : keyArray) {
+					ExamStudentPropertyCacheBean propCache = CacheHelper
+							.getExamStudentProperty(examId, studentId, key);
+					if (StringUtils.isNotBlank(propCache.getValue())) {
+						map.put(key, propCache.getValue());
+					}
+				}
+			}
+		}
+
+		return map;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @return
+	 */
+	@ApiOperation(value = "查询考试批次单个属性")
+	@GetMapping("property/{examId}/{key}")
+	public String getExamProperty(@PathVariable Long examId, @PathVariable String key) {
+		ExamEntity examEntity = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == examEntity) {
+			throw new StatusException("001250", "examId is wrong");
+		}
+		validateRootOrgIsolation(examEntity.getRootOrgId());
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+		DynamicEnum de = manager.getByName(key);
+		ExamPropertyEntity one = examPropertyRepo.findByExamIdAndKeyId(examId, de.getId());
+		if (null == one) {
+			return null;
+		}
+		return one.getValue();
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @return
+	 */
+	@ApiOperation(value = "查询考试批次多个属性")
+	@GetMapping("propertys/{examId}/{keys}")
+	public Map<String, String> getExamPropertyList(@PathVariable Long examId,
+			@PathVariable String keys) {
+		String[] keyArray = StringUtils.splitByWholeSeparator(keys, ",");
+		ExamEntity examEntity = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == examEntity) {
+			throw new StatusException("001250", "examId is wrong");
+		}
+		validateRootOrgIsolation(examEntity.getRootOrgId());
+
+		Map<String, String> properties = Maps.newHashMap();
+		for (String key : keyArray) {
+			DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+			DynamicEnum de = manager.getByName(key);
+			ExamPropertyEntity one = examPropertyRepo.findByExamIdAndKeyId(examId, de.getId());
+			if (null != one) {
+				properties.put(key, one.getValue());
+			}
+		}
+		return properties;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @return
+	 */
+	@ApiOperation(value = "查询考试批次机构属性")
+	@GetMapping("examOrgProperty/{examId}/{orgId}/{key}")
+	public String getExamOrgProperty(@PathVariable Long examId, @PathVariable Long orgId,
+			@PathVariable String key) {
+		ExamEntity examEntity = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == examEntity) {
+			throw new StatusException("001250", "examId is wrong");
+		}
+		validateRootOrgIsolation(examEntity.getRootOrgId());
+
+		if (null == orgId) {
+			throw new StatusException("001251", "orgId is null");
+		}
+
+		GetOrgReq getOrgReq = new GetOrgReq();
+		getOrgReq.setOrgId(orgId);
+		GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+		validateRootOrgIsolation(getOrgResp.getOrg().getRootId());
+
+		return examService.getExamOrgProperty(examId, orgId, key);
+	}
+
+	@ApiOperation(value = "启用考试", notes = "启用考试")
+	@PutMapping("enable/{ids}")
+	@Transactional
+	public void enableExam(@PathVariable String ids) {
+		List<Long> examIds = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+				.collect(Collectors.toList());
+		for (Long examId : examIds) {
+			ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+			exam.setEnable(true);
+			examRepo.saveAndFlush(exam);
+		}
+
+		for (Long examId : examIds) {
+			ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+			SyncExamReq req = new SyncExamReq();
+			req.setId(exam.getId());
+			req.setBeginTime(exam.getBeginTime());
+			req.setDuration(exam.getDuration());
+			req.setEnable(exam.getEnable());
+			req.setEndTime(exam.getEndTime());
+			req.setExamTimes(exam.getExamTimes());
+			req.setExamType(exam.getExamType().name());
+			req.setName(exam.getName());
+			req.setRemark(exam.getRemark());
+			req.setRootOrgId(exam.getRootOrgId());
+
+			GetOrgReq getOrgReq = new GetOrgReq();
+			getOrgReq.setOrgId(exam.getRootOrgId());
+			GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+			OrgBean rootOrg = getOrgResp.getOrg();
+			req.setRootOrgName(rootOrg.getName());
+
+			req.setSyncType("update");
+			dataSyncCloudService.syncExam(req);
+		}
+
+		for (Long examId : examIds) {
+			examSettingsCache.remove(examId);
+		}
+
+	}
+
+	@ApiOperation(value = "禁用考试", notes = "禁用考试")
+	@PutMapping("disable/{ids}")
+	@Transactional
+	public void disableExam(@PathVariable String ids) {
+		List<Long> examIds = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+				.collect(Collectors.toList());
+		for (Long examId : examIds) {
+			ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+			exam.setEnable(false);
+			examRepo.saveAndFlush(exam);
+		}
+
+		for (Long examId : examIds) {
+			ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+			SyncExamReq req = new SyncExamReq();
+			req.setId(exam.getId());
+			req.setBeginTime(exam.getBeginTime());
+			req.setDuration(exam.getDuration());
+			req.setEnable(exam.getEnable());
+			req.setEndTime(exam.getEndTime());
+			req.setExamTimes(exam.getExamTimes());
+			req.setExamType(exam.getExamType().name());
+			req.setName(exam.getName());
+			req.setRemark(exam.getRemark());
+			req.setRootOrgId(exam.getRootOrgId());
+
+			GetOrgReq getOrgReq = new GetOrgReq();
+			getOrgReq.setOrgId(exam.getRootOrgId());
+			GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+			OrgBean rootOrg = getOrgResp.getOrg();
+			req.setRootOrgName(rootOrg.getName());
+
+			req.setSyncType("update");
+			dataSyncCloudService.syncExam(req);
+		}
+
+		for (Long examId : examIds) {
+			examSettingsCache.remove(examId);
+		}
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param curPage
+	 * @param pageSize
+	 * @param examOrgDomain
+	 * @return
+	 */
+	@ApiOperation(value = "查询考试相关的学习中心设置", notes = "")
+	@GetMapping("getExamOrgSettingsList/{curPage}/{pageSize}")
+	public PageInfo<ExamOrgSettingsDomain> getExamOrgSettingsList(@PathVariable Integer curPage,
+			@PathVariable Integer pageSize, @ModelAttribute ExamOrgSettingsDomain examOrgDomain) {
+
+		Long examId = examOrgDomain.getExamId();
+		if (null == examOrgDomain.getExamId()) {
+			throw new StatusException("001210", "examId is null");
+		}
+		ExamEntity examEntity = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == examEntity) {
+			throw new StatusException("001250", "examId is wrong");
+		}
+		validateRootOrgIsolation(examEntity.getRootOrgId());
+
+		Specification<ExamSpecialSettingsEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("examId"), examOrgDomain.getExamId()));
+			predicates.add(cb.isNull(root.get("courseId")));
+			predicates.add(cb.isNull(root.get("studentId")));
+
+			if (null != examOrgDomain.getOrgId()) {
+				predicates.add(cb.equal(root.get("orgId"), examOrgDomain.getOrgId()));
+			} else {
+				predicates.add(cb.isNotNull(root.get("orgId")));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		Pageable pageable = PageRequest.of(curPage, pageSize, Sort.Direction.DESC, "updateTime",
+				"id");
+		Page<ExamSpecialSettingsEntity> page = examSpecialSettingsRepo.findAll(specification,
+				pageable);
+
+		Iterator<ExamSpecialSettingsEntity> iterator = page.iterator();
+		List<ExamOrgSettingsDomain> domainList = Lists.newArrayList();
+
+		while (iterator.hasNext()) {
+			ExamSpecialSettingsEntity next = iterator.next();
+			ExamOrgSettingsDomain bean = new ExamOrgSettingsDomain();
+			domainList.add(bean);
+
+			bean.setBeginTime(next.getBeginTime());
+			bean.setEndTime(next.getEndTime());
+			bean.setExamId(next.getExamId());
+			bean.setId(next.getId());
+			bean.setOrgId(next.getOrgId());
+			bean.setRootOrgId(next.getRootOrgId());
+			bean.setUpdateTime(next.getUpdateTime());
+			bean.setExamLimit(next.getExamLimit());
+
+			GetOrgReq getOrgReq = new GetOrgReq();
+			getOrgReq.setOrgId(bean.getOrgId());
+			GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+			OrgBean org = getOrgResp.getOrg();
+			bean.setOrgName(org.getName());
+			bean.setOrgCode(org.getCode());
+
+			List<ExamOrgPropertyEntity> propList = examOrgPropertyRepo
+					.findByExamIdAndOrgId(next.getExamId(), next.getOrgId());
+
+			Map<String, String> map = Maps.newHashMap();
+			DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+			for (ExamOrgPropertyEntity cur : propList) {
+				DynamicEnum de = manager.getById(cur.getKeyId());
+				map.put(de.getName(), cur.getValue());
+			}
+
+			bean.setProperties(map);
+		}
+
+		PageInfo<ExamOrgSettingsDomain> ret = new PageInfo<ExamOrgSettingsDomain>();
+		ret.setList(domainList);
+		ret.setTotal(page.getTotalElements());
+		return ret;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examOrg
+	 * @return
+	 */
+	@ApiOperation(value = "新增考试相关的学习中心设置", notes = "")
+	@PostMapping("examOrgSettings")
+	@Transactional
+	public ExamSpecialSettingsEntity addExamOrgSettings(@RequestBody ExamOrgSettingsDomain domain) {
+		return saveExamOrgSettings(domain);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examOrg
+	 * @return
+	 */
+	@ApiOperation(value = "更新考试相关的学习中心设置", notes = "")
+	@PutMapping("examOrgSettings")
+	@Transactional
+	public ExamSpecialSettingsEntity updateExamOrgSettings(
+			@RequestBody ExamOrgSettingsDomain domain) {
+		return saveExamOrgSettings(domain);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param domain
+	 * @return
+	 */
+	private ExamSpecialSettingsEntity saveExamOrgSettings(ExamOrgSettingsDomain domain) {
+		Long examId = domain.getExamId();
+
+		ExamEntity examEntity = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == examEntity) {
+			throw new StatusException("001250", "examId is wrong");
+		}
+		validateRootOrgIsolation(examEntity.getRootOrgId());
+
+		Long orgId = domain.getOrgId();
+
+		GetOrgReq getOrgReq = new GetOrgReq();
+		getOrgReq.setOrgId(orgId);
+		GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+		OrgBean org = getOrgResp.getOrg();
+		org.getRootId();
+		validateRootOrgIsolation(org.getRootId());
+
+		User accessUser = getAccessUser();
+		ExamSpecialSettingsInfo info = new ExamSpecialSettingsInfo();
+		info.setId(domain.getId());
+		info.setBeginTime(domain.getBeginTime());
+		info.setEndTime(domain.getEndTime());
+		info.setExamId(domain.getExamId());
+		info.setOrgId(domain.getOrgId());
+		info.setExamLimit(domain.getExamLimit());
+		info.setRootOrgId(accessUser.getRootOrgId());
+
+		Map<String, String> properties = domain.getProperties();
+		if (null == properties) {
+			properties = Maps.newHashMap();
+		}
+		info.setProperties(domain.getProperties());
+
+		ExamSpecialSettingsEntity ret = examService.saveExamSpecialSettings(info);
+		return ret;
+	}
+
+	@ApiOperation(value = "下载学习中心特殊设置", notes = "")
+	@GetMapping("exportExamOrgSettings/{examId}")
+	public void exportExamOrgSettings(@PathVariable Long examId, HttpServletResponse response) {
+		User accessUser = getAccessUser();
+		Long rootOrgId = accessUser.getRootOrgId();
+
+		Map<Long, OrgBean> orgMap = Maps.newHashMap();
+		GetOrgsReq getOrgsReq = new GetOrgsReq();
+		getOrgsReq.setRootOrgId(rootOrgId);
+		Long start = null;
+		while (true) {
+			getOrgsReq.setStart(start);
+			GetOrgsResp getOrgsResp = orgCloudService.getOrgs(getOrgsReq);
+			Long next = getOrgsResp.getNext();
+			List<OrgBean> orgBeanList = getOrgsResp.getOrgBeanList();
+
+			if (next.equals(start)) {
+				break;
+			} else {
+				start = next;
+			}
+			for (OrgBean cur : orgBeanList) {
+				if (null == cur.getParentId()) {
+					continue;
+				}
+				orgMap.put(cur.getId(), cur);
+			}
+		}
+
+		List<Object[]> datas = Lists.newArrayList();
+
+		List<ExamSpecialSettingsEntity> orgSettingsList = examSpecialSettingsRepo
+				.findAllByExamIdAndCourseIdIsNullAndStudentIdIsNullAndOrgIdIsNotNull(examId);
+		Set<Long> orgIdSet = Sets.newHashSet();
+		for (ExamSpecialSettingsEntity cur : orgSettingsList) {
+			orgIdSet.add(cur.getOrgId());
+			OrgBean orgBean = orgMap.get(cur.getOrgId());
+			String examLimit = null == cur.getExamLimit() ? "否" : cur.getExamLimit() ? "否" : "是";
+			String beginTime = null == cur.getBeginTime()
+					? null
+					: DateUtil.format(cur.getBeginTime(), DatePatterns.CHINA_DEFAULT);
+			String endTime = null == cur.getEndTime()
+					? null
+					: DateUtil.format(cur.getEndTime(), DatePatterns.CHINA_DEFAULT);
+			datas.add(new Object[]{String.valueOf(orgBean.getId()), orgBean.getCode(),
+					orgBean.getName(), examLimit, beginTime, endTime});
+		}
+
+		for (Entry<Long, OrgBean> entry : orgMap.entrySet()) {
+			Long key = entry.getKey();
+			if (orgIdSet.contains(key)) {
+				continue;
+			}
+			OrgBean orgBean = entry.getValue();
+			if (!orgBean.getEnable()) {
+				continue;
+			}
+
+			datas.add(new Object[]{String.valueOf(orgBean.getId()), orgBean.getCode(),
+					orgBean.getName(), null, null, null});
+		}
+
+		String filePath = systemConfig.getTempDataDir() + File.separator
+				+ System.currentTimeMillis() + ".xlsx";
+		File file = new File(filePath);
+
+		ExcelWriter.write(
+				EXAM_ORG_SETTINGS_EXCEL_HEADER, new Class[]{String.class, String.class,
+						String.class, String.class, String.class, String.class},
+				datas, new File(filePath));
+
+		exportFile("学习中心特殊设置-" + getRootOrgId() + ".xlsx", file);
+
+		FileUtils.deleteQuietly(file);
+	}
+
+	@ApiOperation(value = "导入学习中心设置", notes = "导入")
+	@PostMapping("importExamOrgSettings/{examId}")
+	@Transactional
+	public Map<String, Object> importExamOrgSettings(@PathVariable Long examId,
+			@RequestParam CommonsMultipartFile file) {
+
+		ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == exam) {
+			throw new StatusException("001010", "考试不存在");
+		}
+
+		validateRootOrgIsolation(exam.getRootOrgId());
+
+		DiskFileItem item = (DiskFileItem) file.getFileItem();
+		File storeLocation = item.getStoreLocation();
+		List<Map<String, Object>> failRecords = examService.importExamOrgSettings(examId,
+				storeLocation);
+		Map<String, Object> map = Maps.newHashMap();
+		map.put("hasError", CollectionUtils.isNotEmpty(failRecords));
+		map.put("failRecords", failRecords);
+		return map;
+	}
+
+	@ApiOperation(value = "学习中心禁考", notes = "")
+	@PutMapping("setOrgExamLimited/{ids}")
+	@Transactional
+	public List<String> setOrgExamLimited(@PathVariable String ids) {
+		List<Long> orgSettingsIds = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+				.collect(Collectors.toList());
+		List<String> ret = Lists.newArrayList();
+		for (Long cur : orgSettingsIds) {
+			ExamSpecialSettingsEntity entity = GlobalHelper.getEntity(examSpecialSettingsRepo, cur,
+					ExamSpecialSettingsEntity.class);
+			entity.setExamLimit(true);
+			examSpecialSettingsRepo.save(entity);
+			ret.add(entity.getExamId() + ":" + entity.getOrgId());
+		}
+		return ret;
+	}
+
+	@ApiOperation(value = "学习中心开考", notes = "")
+	@PutMapping("setOrgExamNotLimited/{ids}")
+	@Transactional
+	public List<String> setOrgExamNotLimited(@PathVariable String ids) {
+		List<Long> orgSettingsIds = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+				.collect(Collectors.toList());
+		List<String> ret = Lists.newArrayList();
+		for (Long cur : orgSettingsIds) {
+			ExamSpecialSettingsEntity entity = GlobalHelper.getEntity(examSpecialSettingsRepo, cur,
+					ExamSpecialSettingsEntity.class);
+			entity.setExamLimit(false);
+			examSpecialSettingsRepo.save(entity);
+			ret.add(entity.getExamId() + ":" + entity.getOrgId());
+		}
+		return ret;
+	}
+
+	@ApiOperation(value = "删除学习中心特殊设置", notes = "")
+	@PutMapping("deleteExamOrgSettings/{ids}")
+	@Transactional
+	public List<String> deleteExamOrgSettings(@PathVariable String ids) {
+		List<Long> orgSettingsIds = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+				.collect(Collectors.toList());
+		List<String> ret = Lists.newArrayList();
+		for (Long cur : orgSettingsIds) {
+			ExamSpecialSettingsEntity entity = GlobalHelper.getEntity(examSpecialSettingsRepo, cur,
+					ExamSpecialSettingsEntity.class);
+
+			examOrgPropertyRepo.deleteByExamIdAndOrgId(entity.getExamId(), entity.getOrgId());
+
+			examSpecialSettingsRepo.delete(entity);
+			ret.add(entity.getExamId() + ":" + entity.getOrgId());
+		}
+		return ret;
+	}
+
+	@ApiOperation(value = "删除所有学习中心特殊设置", notes = "")
+	@PutMapping("deleteAllExamOrgSettings/{examId}")
+	@Transactional
+	public void deleteAllExamOrgSettings(@PathVariable Long examId) {
+
+		ExamEntity exam = GlobalHelper.getPresentEntity(examRepo, examId, ExamEntity.class);
+
+		validateRootOrgIsolation(exam.getRootOrgId());
+
+		examOrgPropertyRepo.deleteByExamId(examId);
+
+		examSpecialSettingsRepo
+				.deleteByExamIdAndOrgIdIsNotNullAndCourseIdIsNullAndStudentIdIsNull(examId);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param request
+	 * @param examId
+	 * @return
+	 */
+	@ApiOperation(value = "考试IP限制", notes = "")
+	@GetMapping("ipLimit/{examId}")
+	public Map<String, Object> ipLimit(HttpServletRequest request, @PathVariable Long examId) {
+		User accessUser = getAccessUser();
+		StudentCacheBean studentCache = CacheHelper.getStudent(accessUser.getUserId());
+		Long orgId = studentCache.getOrgId();
+		ExamPropertyCacheBean ipLimitProperty = CacheHelper.getExamProperty(examId, "IP_LIMIT");
+
+		Map<String, Object> map = Maps.newHashMap();
+
+		Boolean ipLimit = null;
+		if (null != ipLimitProperty) {
+			ipLimit = StringUtil.isTrue(ipLimitProperty.getValue());
+		}
+
+		if (null == ipLimit || !ipLimit) {
+			map.put("limited", false);
+			map.put("desc", "未配置IP限制");
+			return map;
+		}
+
+		String realIp = request.getHeader("x-forwarded-for");
+		if (StringUtils.isBlank(realIp)) {
+			realIp = request.getHeader("x-real-ip");
+		}
+		if (StringUtils.isBlank(realIp)) {
+			map.put("limited", true);
+			map.put("desc", "网络受限");
+			return map;
+		}
+		realIp = realIp.trim();
+
+		ExamPropertyCacheBean ipAddressesProperty = CacheHelper.getExamProperty(examId,
+				"IP_ADDRESSES");
+
+		String ipAddresses = null;
+		if (null != ipAddressesProperty) {
+			ipAddresses = ipAddressesProperty.getValue();
+		}
+
+		boolean limited = true;
+		if (StringUtils.isNotBlank(ipAddresses)) {
+			String[] arr = StringUtils.split(ipAddresses, ';');
+
+			for (String cur : arr) {
+				String ip = StringUtils.replace(cur.trim(), ".", "\\.");
+				ip = StringUtils.replace(ip, "*", "\\w+");
+				if (realIp.matches(ip)) {
+					limited = false;
+					map.put("desc", "IP段配置放行");
+					break;
+				}
+			}
+		}
+
+		if (limited) {
+			String key = "IP_" + orgId;
+			String value = redisClient.get(key, String.class);
+			if (null == value) {
+				map.put("desc", "无机构管理员登录");
+			} else {
+				@SuppressWarnings("unchecked")
+				Set<String> userKeyList = JsonUtil.fromJson(value, Set.class);
+
+				for (String userKey : userKeyList) {
+					User curUser = redisClient.get(userKey, User.class);
+					if (null != curUser) {
+						String clientIp = curUser.getClientIp();
+						if (null != clientIp) {
+							// IP取前三段
+							clientIp = clientIp.substring(0, clientIp.lastIndexOf(".") + 1);
+							if (realIp.startsWith(clientIp)) {
+								limited = false;
+								map.put("desc", "机构管理员[key=" + userKey + "]登录放行");
+								break;
+							}
+						}
+					}
+
+				}
+			}
+		}
+
+		map.put("limited", limited);
+
+		return map;
+	}
+
+	@ApiOperation(value = "是否可以微信作答", notes = "")
+	@GetMapping("weixinAnswerEnabled/{examId}")
+	public Boolean weixinAnswerEnabled(@PathVariable Long examId) {
+
+		ExamSettingsCacheBean examSettings = CacheHelper.getExamSettings(examId);
+
+		OrgPropertyCacheBean orgConf = CacheHelper.getOrgProperty(examSettings.getRootOrgId(),
+				"WEIXIN_ANSWER_ENABLED");
+		ExamPropertyCacheBean examConf = CacheHelper.getExamProperty(examId,
+				"WEIXIN_ANSWER_ENABLED");
+
+		String orgValue = orgConf.getValue();
+		String examValue = examConf.getValue();
+
+		if (!orgConf.getHasValue()) {
+			return false;
+		}
+		if (StringUtils.isBlank(orgValue)) {
+			return false;
+		}
+
+		if (StringUtils.isBlank(examValue)) {
+			return false;
+		}
+
+		if (StringUtil.isTrue(orgValue.toString()) && StringUtil.isTrue(examValue)) {
+			return true;
+		}
+
+		return false;
+	}
+
+	@ApiOperation(value = "是否支持人脸识别", notes = "")
+	@GetMapping("faceCheckEnabled/{examId}")
+	public Boolean faceCheckEnabled(@PathVariable Long examId) {
+		ExamSettingsCacheBean examSettings = CacheHelper.getExamSettings(examId);
+
+		return faceCheckEnabled(examSettings.getRootOrgId(), examId);
+	}
+
+	/**
+	 * 判断是否有人脸识别权限
+	 *
+	 * @author WANGWEI
+	 * @param rootOrgId
+	 * @param examId
+	 * @return
+	 */
+	private Boolean faceCheckEnabled(Long rootOrgId, Long examId) {
+		String faceCheck = PrivilegeDefine.RootOrgFunctions.OnlineExamFunctions.FaceCheck.CODE;
+		Boolean hasFaceCheckFunction = PrivilegeManager.judge(rootOrgId, faceCheck);
+
+		ExamPropertyCacheBean examConf = CacheHelper.getExamProperty(examId, "IS_FACE_ENABLE");
+		String examValue = examConf.getValue();
+
+		if (!hasFaceCheckFunction) {
+			return false;
+		}
+
+		if (StringUtils.isBlank(examValue)) {
+			return false;
+		}
+
+		if (StringUtil.isTrue(examValue)) {
+			return true;
+		}
+
+		return false;
+	}
+
+	@ApiOperation(value = "是否支持活体检测", notes = "")
+	@GetMapping("identificationOfLivingEnabled/{examId}")
+	public Boolean identificationOfLivingEnabled(@PathVariable Long examId) {
+		ExamSettingsCacheBean examSettings = CacheHelper.getExamSettings(examId);
+
+		Boolean faceCheckEnabled = faceCheckEnabled(examSettings.getRootOrgId(), examId);
+		if (!faceCheckEnabled) {
+			return false;
+		}
+
+		String IdentificationOfLivingBody = PrivilegeDefine.RootOrgFunctions.OnlineExamFunctions.IdentificationOfLivingBody.CODE;
+		Boolean hasIdentificationOfLivingBodyFunction = PrivilegeManager
+				.judge(examSettings.getRootOrgId(), IdentificationOfLivingBody);
+
+		ExamPropertyCacheBean examConf = CacheHelper.getExamProperty(examId, "IS_FACE_VERIFY");
+		String examValue = examConf.getValue();
+
+		if (!hasIdentificationOfLivingBodyFunction) {
+			return false;
+		}
+
+		if (StringUtils.isBlank(examValue)) {
+			return false;
+		}
+
+		if (StringUtil.isTrue(examValue)) {
+			return true;
+		}
+
+		return false;
+	}
+
+	@ApiOperation(value = "查询考试相关的学生设置", notes = "")
+	@GetMapping("getStudentSpecialSettingsList/{curPage}/{pageSize}")
+	public PageInfo<StudentSpecialSettingsDomain> getStudentSpecialSettingsList(
+			@PathVariable Integer curPage, @PathVariable Integer pageSize,
+			@ModelAttribute StudentSpecialSettingsDomain domain) {
+
+		Long examId = domain.getExamId();
+		if (null == domain.getExamId()) {
+			throw new StatusException("001210", "examId is null");
+		}
+		ExamEntity examEntity = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		if (null == examEntity) {
+			throw new StatusException("001250", "examId is wrong");
+		}
+		validateRootOrgIsolation(examEntity.getRootOrgId());
+
+		Specification<ExamSpecialSettingsEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("examId"), domain.getExamId()));
+			predicates.add(cb.isNull(root.get("courseId")));
+			predicates.add(cb.isNull(root.get("orgId")));
+
+			if (null != domain.getStudentId()) {
+				predicates.add(cb.equal(root.get("studentId"), domain.getStudentId()));
+			} else {
+				predicates.add(cb.isNotNull(root.get("studentId")));
+			}
+
+			if (null != domain.getIdentityNumber()) {
+				predicates.add(cb.equal(root.get("ext1"), domain.getIdentityNumber()));
+			} else {
+				predicates.add(cb.isNotNull(root.get("ext1")));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		Pageable pageable = PageRequest.of(curPage, pageSize, Sort.Direction.DESC, "updateTime",
+				"id");
+		Page<ExamSpecialSettingsEntity> page = examSpecialSettingsRepo.findAll(specification,
+				pageable);
+
+		Iterator<ExamSpecialSettingsEntity> iterator = page.iterator();
+		List<StudentSpecialSettingsDomain> domainList = Lists.newArrayList();
+
+		while (iterator.hasNext()) {
+			ExamSpecialSettingsEntity next = iterator.next();
+			StudentSpecialSettingsDomain bean = new StudentSpecialSettingsDomain();
+			domainList.add(bean);
+
+			bean.setBeginTime(next.getBeginTime());
+			bean.setEndTime(next.getEndTime());
+			bean.setExamId(next.getExamId());
+			bean.setId(next.getId());
+			bean.setRootOrgId(next.getRootOrgId());
+			bean.setUpdateTime(next.getUpdateTime());
+			bean.setExamLimit(next.getExamLimit());
+			bean.setStudentId(next.getStudentId());
+			bean.setIdentityNumber(next.getExt1());
+		}
+
+		PageInfo<StudentSpecialSettingsDomain> ret = new PageInfo<StudentSpecialSettingsDomain>();
+		ret.setList(domainList);
+		ret.setTotal(page.getTotalElements());
+		return ret;
+	}
+
+}

+ 493 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/ExamStudentController.java

@@ -0,0 +1,493 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.enums.CourseLevel;
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.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.OrgBean;
+import cn.com.qmth.examcloud.core.basic.api.bean.StudentBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetOrgReq;
+import cn.com.qmth.examcloud.core.basic.api.request.GetStudentReq;
+import cn.com.qmth.examcloud.core.basic.api.request.SaveStudentReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetOrgResp;
+import cn.com.qmth.examcloud.core.basic.api.response.GetStudentResp;
+import cn.com.qmth.examcloud.core.basic.api.response.SaveStudentResp;
+import cn.com.qmth.examcloud.core.examwork.api.controller.bean.ExamStudentDomain;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.examwork.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.CheckExamIsStartedReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.CheckExamIsStartedResp;
+import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import com.google.common.collect.Lists;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import javax.persistence.criteria.Predicate;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 考生服务API Created by songyue on 17/1/13.
+ */
+@RestController
+@RequestMapping("${$rmp.ctr.examwork}/exam_student")
+public class ExamStudentController extends ControllerSupport {
+
+    @Autowired
+    ExamStudentRepo examStudentRepo;
+
+    @Autowired
+    ExamStudentService examStudentService;
+
+    @Autowired
+    ExamRepo examRepo;
+
+    @Autowired
+    StudentCloudService studentCloudService;
+
+    @Autowired
+    CourseCloudService courseCloudService;
+
+    @Autowired
+    OrgCloudService orgCloudService;
+
+    @Autowired
+    ExamRecordCloudService examRecordCloudService;
+
+    /**
+     * 方法注释
+     *
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "查询考生的专业集合")
+    @GetMapping("specialtyNameList")
+    public List<String> getSpecialtyNameListByStudentId() {
+        User accessUser = getAccessUser();
+        if (!accessUser.getUserType().equals(UserType.STUDENT)) {
+            throw new StatusException("005002", "用户类型错误");
+        }
+        List<String> querySpecialtyNameList = examStudentRepo
+                .querySpecialtyNameList(accessUser.getUserId());
+        querySpecialtyNameList.removeAll(Collections.singleton(null));
+        return querySpecialtyNameList;
+    }
+
+    @ApiOperation(value = "查询考生的专业集合")
+    @GetMapping("courseLevelList")
+    public List<String> getCourseLevelByStudentId() {
+        User accessUser = getAccessUser();
+        if (!accessUser.getUserType().equals(UserType.STUDENT)) {
+            throw new StatusException("005001", "用户类型错误");
+        }
+        List<String> courseLevelList = examStudentRepo.queryCourseLevelList(accessUser.getUserId());
+        List<String> ret = Lists.newArrayList();
+        for (String cur : courseLevelList) {
+            if (null != cur) {
+                CourseLevel courseLevel = CourseLevel.getCourseLevel(cur);
+                ret.add(courseLevel.getName());
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param studentId
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "查询学生的所有考试")
+    @GetMapping("byStudentId/{studentId}")
+    public List<ExamStudentEntity> getExamStudentListByStudentId(@PathVariable Long studentId) {
+
+        return examStudentRepo.findByStudentId(studentId);
+    }
+
+    /**
+     * 重构
+     *
+     * @param curPage
+     * @param pageSize
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "查询考试学生带条件和分页", notes = "带条件带分页")
+    @GetMapping("examStudentPage/{curPage}/{pageSize}")
+    public PageInfo<ExamStudentDomain> getExamStudentPage(
+            @PathVariable Integer curPage,
+            @PathVariable Integer pageSize,
+            @RequestParam(required = false) Long orgId,
+            @RequestParam(required = false) Long examId,
+            @RequestParam(required = false) String studentName,
+            @RequestParam(required = false) String studentCode,
+            @RequestParam(required = false) Long courseId,
+            @RequestParam(required = false) String courseCode,
+            @RequestParam(required = false) String courseLevel,
+            @RequestParam(required = false) String courseName,
+            @RequestParam(required = false) String examSite,
+            @RequestParam(required = false) String identityNumber,
+            @RequestParam(required = false) Boolean identityNumberLike,
+            @RequestParam(required = false) String specialtyName,
+            @RequestParam(required = false) String infoCollector,
+            @RequestParam(required = false) Boolean withStarted) {
+
+        User accessUser = getAccessUser();
+
+        Specification<ExamStudentEntity> specification = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(cb.equal(root.get("rootOrgId"), accessUser.getRootOrgId()));
+            if (null != orgId) {
+                predicates.add(cb.equal(root.get("orgId"), orgId));
+            }
+            if (null != examId) {
+                predicates.add(cb.equal(root.get("examId"), examId));
+            }
+            if (StringUtils.isNotEmpty(studentName)) {
+                predicates.add(cb.like(root.get("name"), toSqlSearchPattern(studentName)));
+            }
+            if (StringUtils.isNotBlank(studentCode)) {
+                predicates.add(cb.like(root.get("studentCode"), toSqlRightLike(studentCode)));
+            }
+            if (null != courseId) {
+                predicates.add(cb.equal(root.get("courseId"), courseId));
+            }
+            if (StringUtils.isNotEmpty(courseCode)) {
+                predicates.add(cb.equal(root.get("courseCode"), courseCode));
+            }
+            if (StringUtils.isNotEmpty(courseLevel)) {
+                predicates.add(cb.equal(root.get("courseLevel"), courseLevel));
+            }
+            if (StringUtils.isNotEmpty(courseName)) {
+                predicates.add(cb.like(root.get("courseName"), toSqlSearchPattern(courseName)));
+            }
+            if (!StringUtils.isEmpty(examSite)) {
+                predicates.add(cb.like(root.get("examSite"), toSqlSearchPattern(examSite)));
+            }
+
+            if (StringUtils.isNotBlank(identityNumber)) {
+                final Boolean queryLike = (identityNumberLike == null) ? true : identityNumberLike;
+                if (queryLike) {
+                    predicates.add(cb.like(root.get("identityNumber"), toSqlRightLike(identityNumber)));
+                } else {
+                    predicates.add(cb.equal(root.get("identityNumber"), identityNumber));
+                }
+            }
+
+            if (StringUtils.isNotEmpty(specialtyName)) {
+                predicates.add(cb.like(root.get("specialtyName"), toSqlSearchPattern(specialtyName)));
+            }
+
+            if (StringUtils.isNotEmpty(infoCollector)) {
+                predicates.add(cb.like(root.get("infoCollector"), toSqlSearchPattern(infoCollector)));
+            }
+
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        PageRequest pageRequest = PageRequest.of(curPage, pageSize,
+                new Sort(Direction.DESC, "updateTime", "id"));
+
+        Page<ExamStudentEntity> examStudents = examStudentRepo.findAll(specification, pageRequest);
+
+        List<ExamStudentDomain> ret = Lists.newArrayList();
+
+        for (ExamStudentEntity cur : examStudents) {
+            ExamEntity exam = GlobalHelper.getEntity(examRepo, cur.getExamId(), ExamEntity.class);
+
+            GetOrgReq getOrgReq = new GetOrgReq();
+            getOrgReq.setOrgId(cur.getOrgId());
+            GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+            OrgBean org = getOrgResp.getOrg();
+
+            GetStudentReq getStudentReq = new GetStudentReq();
+            getStudentReq.setRootOrgId(accessUser.getRootOrgId());
+            getStudentReq.setIdentityNumber(cur.getIdentityNumber());
+            GetStudentResp getStudentResp = studentCloudService.getStudent(getStudentReq);
+            StudentBean studentBean = getStudentResp.getStudentInfo();
+
+            Boolean started = null;
+            if (null != withStarted && withStarted) {
+                CheckExamIsStartedReq checkExamIsStartedReq = new CheckExamIsStartedReq();
+                checkExamIsStartedReq.setExamId(exam.getId());
+                checkExamIsStartedReq.setCourseId(cur.getCourseId());
+                checkExamIsStartedReq.setStudentId(studentBean.getId());
+                checkExamIsStartedReq.setExamId(exam.getId());
+                try {
+                    CheckExamIsStartedResp checkExamIsStartedResp = examRecordCloudService
+                            .checkExamIsStarted(checkExamIsStartedReq);
+                    started = checkExamIsStartedResp.getIsStarted();
+                } catch (Exception e) {
+                    log.error("fail to invoke remote method: checkExamIsStarted()", e);
+                }
+
+            }
+
+            ExamStudentDomain bean = new ExamStudentDomain();
+            bean.setRootOrgId(cur.getRootOrgId());
+            bean.setId(cur.getId());
+            bean.setExamId(exam.getId());
+            bean.setExamName(exam.getName());
+            bean.setStudentId(cur.getStudentId());
+            bean.setStudentName(cur.getName());
+            bean.setStudentCode(cur.getStudentCode());
+            bean.setIdentityNumber(cur.getIdentityNumber());
+            bean.setCourseId(cur.getCourseId());
+            bean.setCourseCode(cur.getCourseCode());
+            bean.setCourseName(cur.getCourseName());
+            bean.setCourseLevel(cur.getCourseLevel());
+            bean.setInfoCollector(cur.getInfoCollector());
+            bean.setOrgId(cur.getOrgId());
+            bean.setOrgCode(org.getCode());
+            bean.setOrgName(org.getName());
+            bean.setPaperType(cur.getPaperType());
+            bean.setPhone(studentBean.getPhoneNumber());
+            bean.setGrade(cur.getGrade());
+            bean.setSpecialtyName(cur.getSpecialtyName());
+            bean.setExamSite(cur.getExamSite());
+            bean.setExamType(exam.getExamType().name());
+            bean.setUpdateTime(cur.getUpdateTime());
+            bean.setEnable(cur.getEnable());
+            bean.setLocked(
+                    null == exam.getExamStudentLocked() ? false : exam.getExamStudentLocked());
+            bean.setStarted(started);
+            bean.setPhotoPath(studentBean.getPhotoPath());
+
+            bean.setExt1(cur.getExt1());
+            bean.setExt2(IdentityNumberHelper.conceal(cur.getRootOrgId(), cur.getIdentityNumber()));//特殊处理:把ext2当作隐私模式下的身份证号
+            bean.setExt3(cur.getExt3());
+            bean.setExt4(cur.getExt4());
+            bean.setExt5(cur.getExt5());
+            ret.add(bean);
+        }
+        return new PageInfo<ExamStudentDomain>(examStudents, ret);
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param id
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "按ID查询考试学生", notes = "ID查询")
+    @GetMapping("{id}")
+    public ExamStudentEntity getExamStudentById(@PathVariable Long id) {
+        ExamStudentEntity es = GlobalHelper.getEntity(examStudentRepo, id, ExamStudentEntity.class);
+        if (null == es) {
+            throw new StatusException("520001", "考生不存在");
+        }
+        return es;
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param examStudent
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "新增考试学生", notes = "新增")
+    @PostMapping()
+    @Transactional
+    public ExamStudentDomain addExamStudent(@RequestBody ExamStudentDomain examStudent) {
+        trim(examStudent);
+        Long rootOrgId = getRootOrgId();
+        examStudent.setRootOrgId(rootOrgId);
+        examStudent.setInfoCollector(examStudent.getInfoCollector());
+        return saveExamStudent(examStudent);
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param examStudent
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "更新考试学生", notes = "更新")
+    @PutMapping()
+    @Transactional
+    public ExamStudentDomain updateExamStudent(@RequestBody ExamStudentDomain examStudent) {
+        trim(examStudent);
+        Long rootOrgId = getRootOrgId();
+        examStudent.setRootOrgId(rootOrgId);
+        examStudent.setInfoCollector(examStudent.getInfoCollector());
+        return saveExamStudent(examStudent);
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param examStudent
+     * @return
+     * @author WANGWEI
+     */
+    private ExamStudentDomain saveExamStudent(ExamStudentDomain examStudent) {
+        SaveStudentReq saveStudentReq = new SaveStudentReq();
+        saveStudentReq.setIdentityNumber(examStudent.getIdentityNumber().toUpperCase(Locale.US));
+        saveStudentReq.setName(examStudent.getStudentName());
+        saveStudentReq.setPhoneNumber(examStudent.getPhone());
+        saveStudentReq.setOrgId(examStudent.getOrgId());
+        saveStudentReq.setRemark(examStudent.getRemark());
+        saveStudentReq.setRootOrgId(examStudent.getRootOrgId());
+        if (StringUtils.isNotBlank(examStudent.getStudentCode())) {
+            List<String> studentCodeList = Lists.newArrayList();
+            studentCodeList.add(examStudent.getStudentCode());
+            saveStudentReq.setStudentCodeList(studentCodeList);
+        }
+        SaveStudentResp saveStudentResp = studentCloudService.saveStudent(saveStudentReq);
+
+        Long studentId = saveStudentResp.getStudentId();
+
+        ExamStudentInfo info = new ExamStudentInfo();
+        info.setCourseId(examStudent.getCourseId());
+        info.setExamId(examStudent.getExamId());
+        info.setIdentityNumber(examStudent.getIdentityNumber());
+        info.setPaperType(examStudent.getPaperType());
+        info.setRootOrgId(examStudent.getRootOrgId());
+        info.setStudentCode(examStudent.getStudentCode());
+        info.setStudentName(examStudent.getStudentName());
+        info.setStudentId(studentId);
+        info.setInfoCollector(examStudent.getInfoCollector());
+        info.setGrade(examStudent.getGrade());
+        info.setExamSite(examStudent.getExamSite());
+        info.setSpecialtyName(examStudent.getSpecialtyName());
+        info.setRemark(examStudent.getRemark());
+
+        info.setExt1(examStudent.getExt1());
+        info.setExt2(examStudent.getExt2());
+        info.setExt3(examStudent.getExt3());
+        info.setExt4(examStudent.getExt4());
+        info.setExt5(examStudent.getExt5());
+
+        ExamStudentInfo savedExamStudent = examStudentService.saveExamStudent(info);
+
+        ExamStudentDomain ret = new ExamStudentDomain();
+        ret.setId(savedExamStudent.getId());
+        ret.setStudentId(studentId);
+        ret.setStudentCode(savedExamStudent.getStudentCode());
+        ret.setStudentName(savedExamStudent.getStudentName());
+        ret.setCourseCode(savedExamStudent.getCourseCode());
+        ret.setCourseName(savedExamStudent.getCourseName());
+        return ret;
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param ids
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "按ID删除考试学生", notes = "删除")
+    @DeleteMapping("/{ids}")
+    @Transactional
+    public List<Long> deleteExamStudent(@PathVariable String ids) {
+        List<Long> idList = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+                .collect(Collectors.toList());
+        examStudentService.deleteExamStudentsByStudentIds(idList);
+        return idList;
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param examId
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "按考试删除考试学生", notes = "按考试删除")
+    @DeleteMapping("exam/{examId}")
+    @Transactional
+    public Long deleteExamStudents(@PathVariable Long examId) {
+        examStudentService.deleteExamStudentsByExamId(examId);
+        return examId;
+    }
+
+    @ApiOperation(value = "下载导入模板", notes = "下载导入模板")
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        String resoucePath = PathUtil.getResoucePath("templates/studentImportTemplate.xlsx");
+        exportFile("考生导入模板.xlsx", new File(resoucePath));
+    }
+
+    @ApiOperation(value = "启用考生")
+    @PutMapping("enable/{ids}")
+    @Transactional
+    public List<String> enableExamStudent(@PathVariable String ids) {
+        List<Long> examStuIds = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+                .collect(Collectors.toList());
+        List<String> ret = Lists.newArrayList();
+        for (Long cur : examStuIds) {
+            ExamStudentEntity s = GlobalHelper.getEntity(examStudentRepo, cur,
+                    ExamStudentEntity.class);
+            s.setEnable(true);
+            examStudentRepo.save(s);
+            ret.add(s.getId() + ":" + s.getName());
+        }
+
+        for (Long cur : examStuIds) {
+            examStudentService.syncExamStudent(cur);
+        }
+
+        return ret;
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param ids
+     * @return
+     * @author WANGWEI
+     */
+    @ApiOperation(value = "禁用考生")
+    @PutMapping("disable/{ids}")
+    @Transactional
+    public List<String> disableExamStudent(@PathVariable String ids) {
+        List<Long> examStuIds = Stream.of(ids.split(",")).map(s -> Long.parseLong(s.trim()))
+                .collect(Collectors.toList());
+        List<String> ret = Lists.newArrayList();
+        for (Long cur : examStuIds) {
+            ExamStudentEntity s = GlobalHelper.getEntity(examStudentRepo, cur,
+                    ExamStudentEntity.class);
+            s.setEnable(false);
+            examStudentRepo.save(s);
+            ret.add(s.getId() + ":" + s.getName());
+        }
+
+        for (Long cur : examStuIds) {
+            examStudentService.syncExamStudent(cur);
+        }
+
+        return ret;
+    }
+
+}

+ 415 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/NoticeController.java

@@ -0,0 +1,415 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:28:03.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.examwork.api.controller.bean.*;
+import cn.com.qmth.examcloud.core.examwork.dao.NoticeRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.NoticeEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.NoticeRulePublishProgressEntity;
+import cn.com.qmth.examcloud.core.examwork.service.NoticeService;
+import cn.com.qmth.examcloud.core.examwork.service.bean.*;
+import cn.com.qmth.examcloud.reports.commons.bean.OnlineStudentReport;
+import cn.com.qmth.examcloud.reports.commons.bean.OnlineUserReport;
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import com.mysql.cj.util.StringUtils;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/**
+ * 公告类
+ */
+@RestController
+@Api(tags = "公告类")
+@RequestMapping("${$rmp.ctr.examwork}/notice")
+public class NoticeController extends ControllerSupport {
+    private static Pattern pattern = Pattern.compile("\\s*");
+    @Autowired
+    private NoticeService noticeService;
+
+    @Autowired
+    private NoticeRepo noticeRepo;
+
+    @ApiOperation(value = "分页查询消息列表", notes = "带条件带分页")
+    @GetMapping("getPagedNoticeList/{curPage}/{pageSize}")
+    public PageInfo<NoticeDomain> getPagedNoticeList(@PathVariable Integer curPage,
+                                                     @PathVariable Integer pageSize, NoticeDomainQuery query) {
+        User accessUser = this.getAccessUser();
+        NoticeInfoQuery infoQuery = new NoticeInfoQuery();
+        infoQuery.setTitle(query.getTitle());
+        infoQuery.setRootOrgId(accessUser.getRootOrgId());
+        infoQuery.setUserId(accessUser.getUserId());
+        PageInfo<NoticeInfo> pagedNoticeInfo = noticeService.getPagedNoticeList(curPage, pageSize,
+                infoQuery);
+        return getPageInfoFrom(pagedNoticeInfo);
+    }
+
+    @ApiOperation(value = "添加新通知")
+    @PostMapping("addNotice")
+    public void addNotice(@Validated @RequestBody AddNoticeDomain addNoticeDomain) {
+        validateAddNotice(addNoticeDomain);
+
+        AddNoticeInfo info = getAddNoticeInfoFrom(addNoticeDomain);
+        int result = noticeService.addNotice(info);
+        if (result == 0) {
+            throw new StatusException("500008", "添加新通知失败");
+        }
+    }
+
+    @ApiOperation(value = "修改通知信息")
+    @PostMapping("updateNotice")
+    public void updateNotice(@Validated @RequestBody UpdateNoticeDomain updateNoticeDomain) {
+        validateUpdateNotice(updateNoticeDomain);
+
+        UpdateNoticeInfo info = getUpdateNoticeInfoFrom(updateNoticeDomain);
+        noticeService.updateNotice(info);
+    }
+
+    @ApiOperation(value = "删除通知信息")
+    @DeleteMapping("/{noticeId}")
+    public void deleteNotice(
+            @Validated @ApiParam(value = "通知id,多个以逗号分隔") @PathVariable(required = true) String noticeId) {
+        List<Long> noticeIdList = validateDeleteNotice(noticeId);
+        noticeService.deleteNotice(getRootOrgId(), noticeIdList);
+    }
+
+    @GetMapping("getUserNoticeList")
+    @ApiOperation(value = "获取用户公告列表")
+    public List<UserNoticeDomain> getUserNoticeList(UserNoticeDomainQuery query) {
+        List<UserNoticeDomain> resultList = new ArrayList<>();
+        UserNoticeInfoQuery noticeQuery = getNoticeInfoQueryFrom(query);
+
+        List<UserNoticeInfo> noticeInfoList = noticeService.getNoticeList(noticeQuery);
+
+        if (null != noticeInfoList && !noticeInfoList.isEmpty()) {
+            resultList = getNoticeDomainListFrom(noticeInfoList);
+        }
+
+        User user = this.getAccessUser();
+        //在线数据打点 start
+        if (UserType.STUDENT.equals(user.getUserType())) {
+            //在线学生登录打点
+            ReportsUtil.report(new OnlineStudentReport(user.getRootOrgId(), user.getUserId()));
+        } else if (UserType.COMMON.equals(user.getUserType())) {
+            //在线用户登录打点
+            ReportsUtil.report(new OnlineUserReport(user.getRootOrgId(), user.getUserId()));
+        }
+        return resultList;
+    }
+
+    @PostMapping("updateNoticeReadStatus")
+    @ApiOperation(value = "更新通知状态为已读")
+    public void updateNoticeReadStatus(
+            @ApiParam(value = "通知id,多个以逗号分隔") @RequestParam(required = true) String noticeId) {
+        if (StringUtils.isNullOrEmpty(noticeId)) {
+            throw new StatusException("500001", "通知id不允许为空");
+        }
+        User user = this.getAccessUser();
+        noticeService.updateNoticeReadStatus(noticeId, user.getUserType(), user.getUserId());
+    }
+
+    @ApiOperation(value = "获取消息")
+    @GetMapping("{noticeId}")
+    public NoticeDomain getNotice(@PathVariable Long noticeId) {
+        NoticeEntity ni = GlobalHelper.getEntity(noticeRepo, noticeId, NoticeEntity.class);
+        if (ni == null) {
+            throw new StatusException("600001", "该通知已不存在,请刷新后重试");
+        }
+        NoticeDomain domain = new NoticeDomain();
+        domain.setId(ni.getId());
+        domain.setPublisher(ni.getPublisher());
+        domain.setPublishStatus(ni.getNoticeStatus());
+        domain.setPublishTime(ni.getPublishTime());
+        domain.setTitle(ni.getTitle());
+        domain.setContent(ni.getContent());
+        return domain;
+    }
+
+    private UserNoticeInfoQuery getNoticeInfoQueryFrom(UserNoticeDomainQuery query) {
+        User user = this.getAccessUser();
+        UserNoticeInfoQuery noticeQuery = new UserNoticeInfoQuery();
+        noticeQuery.setHasRead(query.getHasRead());
+        noticeQuery.setRootOrgId(user.getRootOrgId());
+        noticeQuery.setUserId(user.getUserId());
+        noticeQuery.setUserType(user.getUserType());
+        return noticeQuery;
+    }
+
+    private AddNoticeInfo getAddNoticeInfoFrom(AddNoticeDomain addNoticeDomain) {
+        AddNoticeInfo info = new AddNoticeInfo();
+        User accessUser = this.getAccessUser();
+        info.setContent(addNoticeDomain.getContent());
+        info.setPublisher(addNoticeDomain.getPublisher());
+        info.setPublishObjectId(addNoticeDomain.getPublishObjectId());
+        info.setRootOrgId(accessUser.getRootOrgId());
+        info.setRuleType(addNoticeDomain.getRuleType());
+        info.setUserId(accessUser.getUserId());
+        info.setNoticeStatus(addNoticeDomain.getNoticeStatus());
+        info.setTitle(addNoticeDomain.getTitle());
+        return info;
+    }
+
+    private UpdateNoticeInfo getUpdateNoticeInfoFrom(UpdateNoticeDomain updateNoticeDomain) {
+        UpdateNoticeInfo info = new UpdateNoticeInfo();
+        User accessUser = this.getAccessUser();
+        info.setId(updateNoticeDomain.getId());
+        info.setTitle(updateNoticeDomain.getTitle());
+        info.setContent(updateNoticeDomain.getContent());
+        info.setPublisher(updateNoticeDomain.getPublisher());
+        info.setPublishObjectId(updateNoticeDomain.getPublishObjectId());
+        info.setRootOrgId(accessUser.getRootOrgId());
+        info.setRuleType(updateNoticeDomain.getRuleType());
+        info.setUserId(accessUser.getUserId());
+        info.setNoticeStatus(updateNoticeDomain.getNoticeStatus());
+        return info;
+    }
+
+    private List<UserNoticeDomain> getNoticeDomainListFrom(List<UserNoticeInfo> noticeInfoList) {
+        List<UserNoticeDomain> resultList = new ArrayList<>();
+        for (UserNoticeInfo info : noticeInfoList) {
+            UserNoticeDomain domain = new UserNoticeDomain();
+            domain.setContent(info.getContent());
+            domain.setId(info.getId());
+            domain.setPublisher(info.getPublisher());
+            domain.setPublishTime(info.getPublishTime());
+            domain.setHasRead(info.getHasRead());
+            domain.setTitle(info.getTitle());
+            resultList.add(domain);
+        }
+        return resultList;
+    }
+
+    private PageInfo<NoticeDomain> getPageInfoFrom(PageInfo<NoticeInfo> pagedNoticeInfo) {
+        PageInfo<NoticeDomain> resultPageInfo = new PageInfo<>();
+        resultPageInfo.setTotal(pagedNoticeInfo.getTotal());
+        resultPageInfo.setIndex(pagedNoticeInfo.getIndex());
+        resultPageInfo.setLimit(pagedNoticeInfo.getLimit());
+        resultPageInfo.setPages(pagedNoticeInfo.getPages());
+        resultPageInfo.setSize(pagedNoticeInfo.getSize());
+        List<NoticeInfo> infoList = pagedNoticeInfo.getList();
+        List<NoticeDomain> domainList = new ArrayList<>();
+        if (infoList != null && !infoList.isEmpty()) {
+            for (NoticeInfo ni : infoList) {
+                NoticeDomain domain = new NoticeDomain();
+                domain.setId(ni.getId());
+                domain.setPublisher(ni.getPublisher());
+                domain.setPublishStatus(ni.getPublishStatus());
+                domain.setPublishTime(ni.getPublishTime());
+                domain.setTitle(ni.getTitle());
+                domain.setPublishObject(ni.getPublishObject());
+                domain.setRuleType(ni.getRuleType());
+                domain.setContent(ni.getContent());
+                domainList.add(domain);
+            }
+        }
+        resultPageInfo.setList(domainList);
+        return resultPageInfo;
+    }
+
+    private void validateAddNotice(AddNoticeDomain addNoticeDomain) {
+        if (addNoticeDomain.getRuleType() == NoticeReceiverRuleType.STUDENTS_OF_EXAM
+                || addNoticeDomain.getRuleType() == NoticeReceiverRuleType.TEACHER_OF_MARK_WORK) {
+            if (StringUtils.isNullOrEmpty(addNoticeDomain.getPublishObjectId())) {
+                throw new StatusException("500009", "发送对象不允许为空");
+            }
+        }
+        String content = addNoticeDomain.getContent();
+        // 普通文本内容不允许超过500个字
+        int simpleTextLength = getSimpleTextLength(content);
+        if (simpleTextLength > 500) {
+            throw new StatusException("500010", "通知内容不得超过500个字符,当前字数为:" + simpleTextLength);
+        }
+        // 图片总大小不得超过2M
+        int imgSize = getImageSize(content);
+        if (imgSize > (2 << 20)) {
+            DecimalFormat decimalFormat = new DecimalFormat("#.##");
+            decimalFormat.setRoundingMode(RoundingMode.DOWN);//windows系统默认采用的是舍弃法,所以和它保持一致
+            String currentLength = decimalFormat
+                    .format(((double) imgSize / (double) 1024 / (double) 1024));
+            throw new StatusException("500011", "图片大小不得超过2MB,当前大小为:" + currentLength + "MB");
+        }
+    }
+
+
+    private int getSimpleTextLength(String content) {
+        Document doc = Jsoup.parse(content);
+        Matcher m = pattern.matcher(doc.text());
+        return m.replaceAll("").replaceAll(" ", "").length();
+    }
+
+    /**
+     * 获取图片大小,单位:字节
+     *
+     * @param content
+     * @return
+     */
+    private int getImageSize(String content) {
+        Document doc = Jsoup.parse(content);
+        Elements imgElements = doc.select("img[src]");
+        if (imgElements == null || imgElements.isEmpty()) {
+            return 0;
+        }
+        int totalSize = 0;
+        for (Element el : imgElements) {
+            String src = el.attr("src");
+            Integer beginIndex;
+            if (src.indexOf(",") > 0) {
+                beginIndex = src.indexOf(",") + 1;
+            } else {
+                beginIndex = 0;
+            }
+            Integer endIndex;
+            if (src.indexOf("=") > 0) {
+                endIndex = src.indexOf("=");
+            } else {
+                endIndex = src.length();
+            }
+            String imgOriginalData = src.substring(beginIndex, endIndex);
+            //原字节符流大小
+            int originalLength = imgOriginalData.length();
+            totalSize += originalLength - (originalLength / 8) * 2;//最终的文件大小(单位字节Byte)
+        }
+        return totalSize;
+    }
+
+    private void validateUpdateNotice(UpdateNoticeDomain updateNoticeDomain) {
+        if (updateNoticeDomain.getRuleType() == NoticeReceiverRuleType.STUDENTS_OF_EXAM
+                || updateNoticeDomain
+                .getRuleType() == NoticeReceiverRuleType.TEACHER_OF_MARK_WORK) {
+            if (StringUtils.isNullOrEmpty(updateNoticeDomain.getPublishObjectId())) {
+                throw new StatusException("500011", "发送对象不允许为空");
+            }
+        }
+        String content = updateNoticeDomain.getContent();
+        // 普通文本内容不允许超过500个字
+        int simpleTextLength = getSimpleTextLength(content);
+        if (simpleTextLength > 500) {
+            throw new StatusException("500010", "通知内容不得超过500个字符,当前字数为:" + simpleTextLength);
+        }
+        // 图片总大小不得超过2M
+        int imgSize = getImageSize(content);
+        if (imgSize > (2 << 20)) {
+            DecimalFormat decimalFormat = new DecimalFormat("#.##");
+            decimalFormat.setRoundingMode(RoundingMode.DOWN);
+            String currentLength = decimalFormat
+                    .format(((double) imgSize / (double) 1024 / (double) 1024));
+            throw new StatusException("500015", "图片总大小不得超过2MB,当前大小为:" + currentLength + "MB");
+        }
+
+        NoticeEntity notice = GlobalHelper.getEntity(noticeRepo, updateNoticeDomain.getId(),
+                NoticeEntity.class);
+        if (notice == null) {
+            throw new StatusException("500013", "该通知已不存在,请刷新后重试");
+        }
+        if (notice.getNoticeStatus() != NoticeStatus.DRAFT) {
+            throw new StatusException("500014", "该通知状态已变更,请刷新后重试");
+        }
+    }
+
+    private List<Long> validateDeleteNotice(String noticeId) {
+        if (noticeId.indexOf(",") > -1 && noticeId.lastIndexOf(",") == noticeId.length() - 1) {
+            noticeId = noticeId.substring(0, noticeId.length() - 1);
+        }
+        String[] noticeIdArr = noticeId.split(",");
+        List<Long> noticeIdList = null;
+        try {
+            noticeIdList = Arrays.asList(noticeIdArr).stream().map(p -> Long.parseLong(p))
+                    .collect(Collectors.toList());
+        } catch (Exception e) {
+            throw new StatusException("500015", "通知id格式不正确", e);
+        }
+        List<NoticeEntity> noticeList = noticeRepo.findByIdIn(noticeIdList);
+        boolean existPublishingData = noticeList.stream()
+                .anyMatch(p -> p.getNoticeStatus() != NoticeStatus.DRAFT);
+        if (existPublishingData) {
+            throw new StatusException("500016", "通知状态已改变,不允许删除");
+        }
+        return noticeIdList;
+    }
+
+    @Naked
+    @GetMapping("disposetest")
+    @ApiOperation(value = "获取用户公告列表")
+    public void disposetest() {
+        // 获取待处理的通知进度数据
+        List<NoticeRulePublishProgressEntity> progressList = noticeService
+                .getToBeDisposedNoticeRulePublishProgressList();
+        if (progressList == null || progressList.isEmpty()) {
+            return;
+        }
+
+        for (NoticeRulePublishProgressEntity progress : progressList) {
+            Long startUserId = 0L;
+            int loopTimes = 0;
+            Long lastMaxUserId = getLastMaxUserId(progress.getNoticeReceiverRuleType(), progress);
+            if (lastMaxUserId != null) {
+                startUserId = lastMaxUserId + 1;
+            }
+            while (true) {
+                Long nextUserId = noticeService.disposePublishingUserNotice(startUserId, progress);
+
+                if (nextUserId.equals(startUserId)) {
+                    noticeService.updateNoticeStatus(progress.getNoticeId(),
+                            NoticeStatus.PUBLISHED);
+                    break;
+                } else {
+                    startUserId = nextUserId;
+                    // 处理中的状态只需更新一次
+                    if (loopTimes == 1) {
+                        noticeService.updateNoticeStatus(progress.getNoticeId(),
+                                NoticeStatus.PUBLISHING);
+                    }
+                }
+                loopTimes++;
+            }
+        }
+    }
+
+    /**
+     * 获取上次更新的最大用户id
+     *
+     * @param ruleType
+     * @param process
+     * @return
+     */
+    private Long getLastMaxUserId(NoticeReceiverRuleType ruleType,
+                                  NoticeRulePublishProgressEntity process) {
+        if (ruleType == NoticeReceiverRuleType.ALL_STUDENTS_OF_ROOT_ORG
+                || ruleType == NoticeReceiverRuleType.STUDENTS_OF_EXAM) {
+            return process.getMaxStudentId();
+        } else {
+            return process.getMaxCommonUserId();
+        }
+    }
+
+}

+ 114 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/AddNoticeDomain.java

@@ -0,0 +1,114 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+import org.hibernate.validator.constraints.Length;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 添加通知信息实体
+ */
+public class AddNoticeDomain implements JsonSerializable {
+
+    private static final long serialVersionUID = -7739022488162924094L;
+
+    /**
+     * 标题
+     */
+    @ApiModelProperty("标题")
+    @NotNull(message = "标题不允许为空")
+    @Length(min = 1, max = 50, message = "标题长度不得超过50")
+    private String title;
+    /**
+     *  内容
+     */
+    @ApiModelProperty("内容")
+    @NotNull(message = "内容不允许为空")
+    private String content;
+
+    /**
+     * 规则类型
+     */
+    @Enumerated(EnumType.STRING)
+    @NotNull(message = "发送方式不允许为空")
+    private NoticeReceiverRuleType ruleType;
+
+    /**
+     * 公告状态
+     */
+    @Enumerated(EnumType.STRING)
+    @NotNull(message = "公告状态")
+    private NoticeStatus noticeStatus;
+    /**
+     * 发送对象
+     */
+    @ApiModelProperty("发送对象,多个以逗号分隔")
+    private String publishObjectId;
+    /**
+     * 发布者
+     */
+    @ApiModelProperty("发布者")
+    @NotNull(message = "发送人不允许为空")
+    private String publisher;
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getPublishObjectId() {
+        return publishObjectId;
+    }
+
+    public void setPublishObjectId(String publishObjectId) {
+        this.publishObjectId = publishObjectId;
+    }
+
+    public String getPublisher() {
+        return publisher;
+    }
+
+    public void setPublisher(String publisher) {
+        this.publisher = publisher;
+    }
+
+
+    public NoticeReceiverRuleType getRuleType() {
+        return ruleType;
+    }
+
+    public void setRuleType(NoticeReceiverRuleType ruleType) {
+        this.ruleType = ruleType;
+    }
+
+    public NoticeStatus getNoticeStatus() {
+        return noticeStatus;
+    }
+
+    public void setNoticeStatus(NoticeStatus noticeStatus) {
+        this.noticeStatus = noticeStatus;
+    }
+}

+ 45 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/CopyExamDomain.java

@@ -0,0 +1,45 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+public class CopyExamDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = 8371681629986030229L;
+
+	@NotNull(message = "srcExamId不能为空")
+	private Long srcExamId;
+
+	@NotBlank(message = "destExamCode不能为空")
+	private String destExamCode;
+
+	@NotBlank(message = "destExamName不能为空")
+	private String destExamName;
+
+	public Long getSrcExamId() {
+		return srcExamId;
+	}
+
+	public void setSrcExamId(Long srcExamId) {
+		this.srcExamId = srcExamId;
+	}
+
+	public String getDestExamCode() {
+		return destExamCode;
+	}
+
+	public void setDestExamCode(String destExamCode) {
+		this.destExamCode = destExamCode;
+	}
+
+	public String getDestExamName() {
+		return destExamName;
+	}
+
+	public void setDestExamName(String destExamName) {
+		this.destExamName = destExamName;
+	}
+
+}

+ 76 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/CourseDomain.java

@@ -0,0 +1,76 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年8月27日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class CourseDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = -6261302618070108336L;
+
+	private Long id;
+
+	private Long rootOrgId;
+
+	private String code;
+
+	private String name;
+
+	private String level;
+
+	private Boolean enable;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getLevel() {
+		return level;
+	}
+
+	public void setLevel(String level) {
+		this.level = level;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+}

+ 124 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/ExamCourseGroupDomain.java

@@ -0,0 +1,124 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import java.util.Date;
+import java.util.List;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 考试--课程组
+ * 
+ * @author WANG
+ *
+ */
+public class ExamCourseGroupDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = -3335725218626631530L;
+
+	private Long id;
+
+	private Long examId;
+
+	private String name;
+
+	private String description;
+
+	List<Long> courseIdList;
+
+	/**
+	 * 考试批次开始时间
+	 */
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date beginTime;
+
+	/**
+	 * 考试批次结束时间
+	 */
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date endTime;
+
+	/**
+	 * 更新时间
+	 */
+	private Date updateTime;
+
+	/**
+	 * 创建时间
+	 */
+	private Date creationTime;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getDescription() {
+		return description;
+	}
+
+	public void setDescription(String description) {
+		this.description = description;
+	}
+
+	public List<Long> getCourseIdList() {
+		return courseIdList;
+	}
+
+	public void setCourseIdList(List<Long> courseIdList) {
+		this.courseIdList = courseIdList;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	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;
+	}
+
+}

+ 255 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/ExamDomain.java

@@ -0,0 +1,255 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import java.util.Date;
+import java.util.Map;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.validation.constraints.NotNull;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年8月17日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = 4009839764353162256L;
+
+	private Long id;
+
+	/**
+	 * 考试编码
+	 */
+	private String code;
+
+	/**
+	 * 顶级机构Id
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 考试批次开始时间
+	 */
+	private Date beginTime;
+
+	/**
+	 * 考试批次结束时间
+	 */
+	private Date endTime;
+
+	/**
+	 * 考试名称
+	 */
+	@NotNull
+	private String name;
+
+	/**
+	 * 考试类型
+	 */
+	@Enumerated(EnumType.STRING)
+	private ExamType examType;
+
+	/**
+	 * 考试时长
+	 */
+	private Integer duration;
+
+	/**
+	 * 是否可用
+	 */
+	private Boolean enable;
+
+	/**
+	 * 考试备注
+	 */
+	private String remark;
+
+	/**
+	 * 考试次数
+	 */
+	private Long examTimes;
+
+	/**
+	 * 更新时间
+	 */
+	private Date updateTime;
+
+	/**
+	 * 创建时间
+	 */
+	private Date creationTime;
+
+	/**
+	 * 是否开考
+	 */
+	private Boolean started;
+
+	/**
+	 * 是否禁止考试
+	 */
+	private Boolean examLimit;
+
+	/**
+	 * 是否开启特殊设置
+	 */
+	private Boolean specialSettingsEnabled;
+
+	/**
+	 * 特殊设置类型
+	 */
+	private ExamSpecialSettingsType specialSettingsType;
+
+	private Map<String, String> properties;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public ExamType getExamType() {
+		return examType;
+	}
+
+	public void setExamType(ExamType examType) {
+		this.examType = examType;
+	}
+
+	public Integer getDuration() {
+		return duration;
+	}
+
+	public void setDuration(Integer duration) {
+		this.duration = duration;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	public Long getExamTimes() {
+		return examTimes;
+	}
+
+	public void setExamTimes(Long examTimes) {
+		this.examTimes = examTimes;
+	}
+
+	public Map<String, String> getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Map<String, String> properties) {
+		this.properties = properties;
+	}
+
+	public Boolean getStarted() {
+		return started;
+	}
+
+	public void setStarted(Boolean started) {
+		this.started = started;
+	}
+
+	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;
+	}
+
+	public Boolean getExamLimit() {
+		return examLimit;
+	}
+
+	public void setExamLimit(Boolean examLimit) {
+		this.examLimit = examLimit;
+	}
+
+	public ExamSpecialSettingsType getSpecialSettingsType() {
+		return specialSettingsType;
+	}
+
+	public void setSpecialSettingsType(ExamSpecialSettingsType specialSettingsType) {
+		this.specialSettingsType = specialSettingsType;
+	}
+
+	public Boolean getSpecialSettingsEnabled() {
+		return specialSettingsEnabled;
+	}
+
+	public void setSpecialSettingsEnabled(Boolean specialSettingsEnabled) {
+		this.specialSettingsEnabled = specialSettingsEnabled;
+	}
+
+}

+ 156 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/ExamOrgSettingsDomain.java

@@ -0,0 +1,156 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import java.util.Date;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 考试--机构设置
+ *
+ * @author WANGWEI
+ * @date 2018年5月16日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamOrgSettingsDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = -3335725218626631530L;
+
+	private Long id;
+
+	/**
+	 * 考试ID
+	 */
+	private Long examId;
+
+	/**
+	 * 顶级机构ID
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 机构ID
+	 */
+	private Long orgId;
+
+	/**
+	 * 机构名称
+	 */
+	private String orgName;
+
+	/**
+	 * 机构编码
+	 */
+	private String orgCode;
+
+	/**
+	 * 考试批次开始时间
+	 */
+	private Date beginTime;
+
+	/**
+	 * 考试批次结束时间
+	 */
+	private Date endTime;
+
+	/**
+	 * 更新时间
+	 */
+	private Date updateTime;
+
+	/**
+	 * 是否禁止考试
+	 */
+	private Boolean examLimit;
+
+	private Map<String, String> properties;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public 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 getOrgName() {
+		return orgName;
+	}
+
+	public void setOrgName(String orgName) {
+		this.orgName = orgName;
+	}
+
+	public String getOrgCode() {
+		return orgCode;
+	}
+
+	public void setOrgCode(String orgCode) {
+		this.orgCode = orgCode;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public Map<String, String> getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Map<String, String> properties) {
+		this.properties = properties;
+	}
+
+	public Date getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Date updateTime) {
+		this.updateTime = updateTime;
+	}
+
+	public Boolean getExamLimit() {
+		return examLimit;
+	}
+
+	public void setExamLimit(Boolean examLimit) {
+		this.examLimit = examLimit;
+	}
+
+}

+ 383 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/ExamStudentDomain.java

@@ -0,0 +1,383 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import java.util.Date;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 考生
+ *
+ * @author WANGWEI
+ * @date 2018年7月9日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamStudentDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = 757982847766860857L;
+
+	private Long id;
+
+	private Long studentId;
+
+	private String identityNumber;
+
+	private String studentName;
+
+	private String studentCode;
+
+	private Long examId;
+
+	private String examName;
+
+	private Long rootOrgId;
+
+	private Long orgId;
+
+	private String orgCode;
+
+	private String orgName;
+
+	private String examNumber;
+
+	private Long courseId;
+
+	private String courseCode;
+
+	private String courseName;
+
+	private String courseLevel;
+
+	private String paperType;
+
+	private String specialtyCode;
+
+	private String specialtyName;
+
+	private String examSite;
+
+	private String infoCollector;
+
+	private String phone;
+
+	private String remark;
+
+	private String grade;
+
+	private String examType;
+
+	private Date updateTime;
+
+	private Boolean enable;
+
+	private Boolean locked;
+
+	private Boolean started;
+
+	private String photoPath;
+
+	/**
+	 * 扩展属性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 getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	public String getIdentityNumber() {
+		return identityNumber;
+	}
+
+	public void setIdentityNumber(String identityNumber) {
+		this.identityNumber = identityNumber;
+	}
+
+	public String getStudentName() {
+		return studentName;
+	}
+
+	public void setStudentName(String studentName) {
+		this.studentName = studentName;
+	}
+
+	public String getStudentCode() {
+		return studentCode;
+	}
+
+	public void setStudentCode(String studentCode) {
+		this.studentCode = studentCode;
+	}
+
+	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 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 getExamNumber() {
+		return examNumber;
+	}
+
+	public void setExamNumber(String examNumber) {
+		this.examNumber = examNumber;
+	}
+
+	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 getSpecialtyCode() {
+		return specialtyCode;
+	}
+
+	public void setSpecialtyCode(String specialtyCode) {
+		this.specialtyCode = specialtyCode;
+	}
+
+	public String getSpecialtyName() {
+		return specialtyName;
+	}
+
+	public void setSpecialtyName(String specialtyName) {
+		this.specialtyName = specialtyName;
+	}
+
+	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 String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	public String getGrade() {
+		return grade;
+	}
+
+	public void setGrade(String grade) {
+		this.grade = grade;
+	}
+
+	public String getExamType() {
+		return examType;
+	}
+
+	public void setExamType(String examType) {
+		this.examType = examType;
+	}
+
+	public Date getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Date updateTime) {
+		this.updateTime = updateTime;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public Boolean getLocked() {
+		return locked;
+	}
+
+	public void setLocked(Boolean locked) {
+		this.locked = locked;
+	}
+
+	public Boolean getStarted() {
+		return started;
+	}
+
+	public void setStarted(Boolean started) {
+		this.started = started;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	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;
+	}
+
+	public String getPhotoPath() {
+		return photoPath;
+	}
+
+	public void setPhotoPath(String photoPath) {
+		this.photoPath = photoPath;
+	}
+
+}

+ 131 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/NoticeDomain.java

@@ -0,0 +1,131 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 通知信息实体
+ */
+public class NoticeDomain implements JsonSerializable {
+
+    private static final long serialVersionUID = -7739022488162924094L;
+    /**
+     * 公告id
+     */
+    @ApiModelProperty("公告id")
+    private Long id;
+    /**
+     * 标题
+     */
+    @ApiModelProperty("标题")
+    private String title;
+    /**
+     * 发送对象
+     */
+    @ApiModelProperty("发送对象")
+    private java.util.List<Map<String,Object>> publishObject;
+    /**
+     * 发布者
+     */
+    @ApiModelProperty("发布者")
+    private String publisher;
+    /**
+     * 发布时间
+     */
+    @ApiModelProperty("发布时间")
+    private Date publishTime;
+    /**
+     * 发送状态
+     */
+    @ApiModelProperty("发送状态")
+    @Enumerated(EnumType.STRING)
+    private NoticeStatus publishStatus;
+    /**
+     * 规则类型
+     */
+    @Enumerated(EnumType.STRING)
+    private NoticeReceiverRuleType ruleType;
+    /**
+     * 内容
+     */
+    private String content;
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public NoticeReceiverRuleType getRuleType() {
+        return ruleType;
+    }
+
+    public void setRuleType(NoticeReceiverRuleType ruleType) {
+        this.ruleType = ruleType;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public List<Map<String, Object>> getPublishObject() {
+        return publishObject;
+    }
+
+    public void setPublishObject(List<Map<String, Object>> publishObject) {
+        this.publishObject = publishObject;
+    }
+
+    public String getPublisher() {
+        return publisher;
+    }
+
+    public void setPublisher(String publisher) {
+        this.publisher = publisher;
+    }
+
+    public Date getPublishTime() {
+        return publishTime;
+    }
+
+    public void setPublishTime(Date publishTime) {
+        this.publishTime = publishTime;
+    }
+
+    public NoticeStatus getPublishStatus() {
+        return publishStatus;
+    }
+
+    public void setPublishStatus(NoticeStatus publishStatus) {
+        this.publishStatus = publishStatus;
+    }
+}

+ 47 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/NoticeDomainQuery.java

@@ -0,0 +1,47 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:03.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 公告查询类
+ */
+@ApiModel
+public class NoticeDomainQuery implements JsonSerializable {
+
+    private static final long serialVersionUID = -1962646957678995495L;
+    /**
+     * 标题
+     */
+    @ApiModelProperty("标题")
+    private String title;
+    /**
+     * 是否已读(true,已读;false,未读)
+     */
+    @ApiModelProperty("是否已读")
+    private Boolean read;
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Boolean getRead() {
+        return read;
+    }
+
+    public void setRead(Boolean read) {
+        this.read = read;
+    }
+}

+ 157 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/StudentSpecialSettingsDomain.java

@@ -0,0 +1,157 @@
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 考试--学生设置
+ *
+ * @author WANGWEI
+ * @date 2018年5月16日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class StudentSpecialSettingsDomain implements JsonSerializable {
+
+	private static final long serialVersionUID = -3335725218626631530L;
+
+	private Long id;
+
+	/**
+	 * 考试ID
+	 */
+	private Long examId;
+
+	/**
+	 * 顶级机构ID
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 学生ID
+	 */
+	private Long studentId;
+
+	/**
+	 * 学生身份证
+	 */
+	private String identityNumber;
+
+	/**
+	 * 学生学号
+	 */
+	private List<String> studentCodeList;
+
+	/**
+	 * 考试批次开始时间
+	 */
+	private Date beginTime;
+
+	/**
+	 * 考试批次结束时间
+	 */
+	private Date endTime;
+
+	/**
+	 * 更新时间
+	 */
+	private Date updateTime;
+
+	/**
+	 * 是否禁止考试
+	 */
+	private Boolean examLimit;
+
+	private Map<String, String> properties;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	public String getIdentityNumber() {
+		return identityNumber;
+	}
+
+	public void setIdentityNumber(String identityNumber) {
+		this.identityNumber = identityNumber;
+	}
+
+	public List<String> getStudentCodeList() {
+		return studentCodeList;
+	}
+
+	public void setStudentCodeList(List<String> studentCodeList) {
+		this.studentCodeList = studentCodeList;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public Date getUpdateTime() {
+		return updateTime;
+	}
+
+	public void setUpdateTime(Date updateTime) {
+		this.updateTime = updateTime;
+	}
+
+	public Boolean getExamLimit() {
+		return examLimit;
+	}
+
+	public void setExamLimit(Boolean examLimit) {
+		this.examLimit = examLimit;
+	}
+
+	public Map<String, String> getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Map<String, String> properties) {
+		this.properties = properties;
+	}
+
+}

+ 126 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/UpdateNoticeDomain.java

@@ -0,0 +1,126 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+import org.hibernate.validator.constraints.Length;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 添加通知信息实体
+ */
+public class UpdateNoticeDomain implements JsonSerializable {
+
+    private static final long serialVersionUID = -7739022488162924094L;
+
+    /**
+     * 通知id
+     */
+    @ApiModelProperty("通知id")
+    @NotNull(message = "通知id不允许为空")
+    private Long id;
+    /**
+     * 标题
+     */
+    @ApiModelProperty("标题")
+    @NotNull(message = "标题不允许为空")
+    @Length(min = 1, max = 50, message = "标题长度不得超过50")
+    private String title;
+    /**
+     *  内容
+     */
+    @ApiModelProperty("内容")
+    @NotNull(message = "内容不允许为空")
+    private String content;
+
+    /**
+     * 规则类型
+     */
+    @Enumerated(EnumType.STRING)
+    @NotNull(message = "发送方式不允许为空")
+    private NoticeReceiverRuleType ruleType;
+    /**
+     * 发送对象
+     */
+    @ApiModelProperty("发送对象,多个以逗号分隔")
+    private String publishObjectId;
+    /**
+     * 发布者
+     */
+    @ApiModelProperty("发布者")
+    @NotNull(message = "发送人不允许为空")
+    private String publisher;
+    /**
+     * 公告状态
+     */
+    @Enumerated(EnumType.STRING)
+    @NotNull(message = "公告状态")
+    private NoticeStatus noticeStatus;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getPublishObjectId() {
+        return publishObjectId;
+    }
+
+    public void setPublishObjectId(String publishObjectId) {
+        this.publishObjectId = publishObjectId;
+    }
+
+    public String getPublisher() {
+        return publisher;
+    }
+
+    public void setPublisher(String publisher) {
+        this.publisher = publisher;
+    }
+
+    public NoticeReceiverRuleType getRuleType() {
+        return ruleType;
+    }
+
+    public void setRuleType(NoticeReceiverRuleType ruleType) {
+        this.ruleType = ruleType;
+    }
+
+    public NoticeStatus getNoticeStatus() {
+        return noticeStatus;
+    }
+
+    public void setNoticeStatus(NoticeStatus noticeStatus) {
+        this.noticeStatus = noticeStatus;
+    }
+}

+ 102 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/UserNoticeDomain.java

@@ -0,0 +1,102 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.Date;
+
+/**
+ * 考试分数信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/31
+ */
+public class UserNoticeDomain implements JsonSerializable {
+
+    private static final long serialVersionUID = -7739022488162924094L;
+    /**
+     * 公告id
+     */
+    @ApiModelProperty("公告id")
+    private Long id;
+    /**
+     * 标题
+     */
+    @ApiModelProperty("标题")
+    private String title;
+    /**
+     * 内容
+     */
+    @ApiModelProperty("内容")
+    private String content;
+    /**
+     * 发布者
+     */
+    @ApiModelProperty("发布者")
+    private String publisher;
+    /**
+     * 发布时间
+     */
+    @ApiModelProperty("发布时间")
+    private Date publishTime;
+    /**
+     * 读取状态
+     */
+    @ApiModelProperty("读取状态")
+    private Boolean hasRead;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getPublisher() {
+        return publisher;
+    }
+
+    public void setPublisher(String publisher) {
+        this.publisher = publisher;
+    }
+
+    public Date getPublishTime() {
+        return publishTime;
+    }
+
+    public void setPublishTime(Date publishTime) {
+        this.publishTime = publishTime;
+    }
+
+    public Boolean getHasRead() {
+        return hasRead;
+    }
+
+    public void setHasRead(Boolean hasRead) {
+        this.hasRead = hasRead;
+    }
+}

+ 34 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/controller/bean/UserNoticeDomainQuery.java

@@ -0,0 +1,34 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:03.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.api.controller.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 公告查询类
+ */
+@ApiModel
+public class UserNoticeDomainQuery implements JsonSerializable {
+
+    private static final long serialVersionUID = -1962646957678995495L;
+    /**
+     * 是否已读(true,已读;false,未读)
+     */
+    @ApiModelProperty("是否已读")
+    private Boolean hasRead;
+
+    public Boolean getHasRead() {
+        return hasRead;
+    }
+
+    public void setHasRead(Boolean hasRead) {
+        this.hasRead = hasRead;
+    }
+}

+ 808 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/provider/ExamCloudServiceProvider.java

@@ -0,0 +1,808 @@
+package cn.com.qmth.examcloud.core.examwork.api.provider;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.criteria.Predicate;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+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.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import cn.com.qmth.examcloud.api.commons.enums.CURD;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnum;
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnumManager;
+import cn.com.qmth.examcloud.core.examwork.base.enums.ExamProperty;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamCourseRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamPaperTypeRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamPropertyRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamSpecialSettingsRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamCourseRelationEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPaperTypeRelationEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPropertyEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.examwork.service.ExamService;
+import cn.com.qmth.examcloud.core.examwork.service.OnGoingExamService;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamInfo;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamSpecialSettingsInfo;
+import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamCourseRelationBean;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamPaperTypeRelation;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamSpecialSettingsBean;
+import cn.com.qmth.examcloud.examwork.api.request.CountExamStudentReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamCourseListReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamCoursePaperTypeListReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamListReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamOrgListReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamPropertyListReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamPropertyReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamStudentPropertyValueListReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamsByIdListReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetExamsReq;
+import cn.com.qmth.examcloud.examwork.api.request.GetOngoingExamListReq;
+import cn.com.qmth.examcloud.examwork.api.request.LockExamStudentsReq;
+import cn.com.qmth.examcloud.examwork.api.request.SaveExamReq;
+import cn.com.qmth.examcloud.examwork.api.request.SaveStudentSpecialSettingsReq;
+import cn.com.qmth.examcloud.examwork.api.request.SetExamPropertyReq;
+import cn.com.qmth.examcloud.examwork.api.request.UnlockExamStudentsReq;
+import cn.com.qmth.examcloud.examwork.api.response.CountExamStudentResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamCourseListResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamCoursePaperTypeListResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamListResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamOrgListResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamPropertyListResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamPropertyResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamStudentPropertyValueListResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamsByIdListResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetExamsResp;
+import cn.com.qmth.examcloud.examwork.api.response.GetOngoingExamListResp;
+import cn.com.qmth.examcloud.examwork.api.response.LockExamStudentsResp;
+import cn.com.qmth.examcloud.examwork.api.response.SaveExamResp;
+import cn.com.qmth.examcloud.examwork.api.response.SaveStudentSpecialSettingsResp;
+import cn.com.qmth.examcloud.examwork.api.response.SetExamPropertyResp;
+import cn.com.qmth.examcloud.examwork.api.response.UnlockExamStudentsResp;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * {@link StatusException} 状态码范围:012XXX<br>
+ * 考试云服务
+ *
+ * @author WANGWEI
+ * @date 2018年11月23日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@RestController
+@RequestMapping("${$rmp.cloud.examwork}" + "exam")
+public class ExamCloudServiceProvider extends ControllerSupport implements ExamCloudService {
+
+	private static final long serialVersionUID = 6850508158980856785L;
+
+	@Autowired
+	RedisClient redisClient;
+
+	@Autowired
+	private ExamService examService;
+
+	@Autowired
+	ExamSpecialSettingsRepo examSpecialSettingsRepo;
+
+	@Autowired
+	ExamRepo examRepo;
+
+	@Autowired
+	ExamStudentRepo examStudentRepo;
+
+	@Autowired
+	ExamPropertyRepo examPropertyRepo;
+
+	@Autowired
+	ExamCourseRelationRepo examCourseRelationRepo;
+
+	@Autowired
+	ExamPaperTypeRelationRepo examPaperTypeRelationRepo;
+
+	@Autowired
+	OnGoingExamService onGoingExamService;
+
+	@ApiOperation(value = "保存考试批次", notes = "保存")
+	@PostMapping("saveExam")
+	@Transactional
+	@Override
+	public SaveExamResp saveExam(@RequestBody SaveExamReq req) {
+		ExamInfo examInfo = new ExamInfo();
+
+		examInfo.setBeginTime(req.getBeginTime());
+		examInfo.setDuration(req.getDuration());
+		examInfo.setEnable(req.getEnable());
+		examInfo.setEndTime(req.getEndTime());
+		examInfo.setExamTimes(req.getExamTimes());
+
+		examInfo.setExamType(req.getExamType());
+		examInfo.setCode(req.getCode());
+		examInfo.setName(req.getName());
+		examInfo.setRemark(req.getRemark());
+		examInfo.setRootOrgId(req.getRootOrgId());
+		examInfo.setExamLimit(req.getExamLimit());
+
+		Map<String, String> properties = req.getProperties();
+		examInfo.setProperties(properties);
+
+		ExamEntity saved = examService.saveExam(examInfo, CURD.CREATION_OR_UPDATE);
+
+		SaveExamResp resp = new SaveExamResp();
+		resp.setExamId(saved.getId());
+
+		ExamBean bean = new ExamBean();
+		resp.setExamBean(bean);
+
+		bean.setId(saved.getId());
+		bean.setBeginTime(saved.getBeginTime());
+		bean.setDuration(saved.getDuration());
+		bean.setEnable(saved.getEnable());
+		bean.setEndTime(saved.getEndTime());
+		bean.setExamTimes(saved.getExamTimes());
+		bean.setExamType(saved.getExamType().name());
+		bean.setName(saved.getName());
+		bean.setCode(saved.getCode());
+		bean.setRemark(saved.getRemark());
+		bean.setRootOrgId(saved.getRootOrgId());
+		bean.setExamLimit(saved.getExamLimit());
+
+		return resp;
+	}
+
+	@ApiOperation(value = "查询考试")
+	@PostMapping("getExam")
+	@Override
+	public GetExamResp getExam(@RequestBody GetExamReq req) {
+		Long id = req.getId();
+		Long rootOrgId = req.getRootOrgId();
+		String name = req.getName();
+		String code = req.getCode();
+
+		ExamEntity exam = null;
+
+		if (null != id) {
+			exam = GlobalHelper.getEntity(examRepo, id, ExamEntity.class);
+		} else if (StringUtils.isNotBlank(name)) {
+			if (null == rootOrgId) {
+				throw new StatusException("002004", "rootOrgId is null");
+			}
+			exam = examRepo.findByNameAndRootOrgId(name, rootOrgId);
+		} else if (StringUtils.isNotBlank(code)) {
+			if (null == rootOrgId) {
+				throw new StatusException("002003", "rootOrgId is null");
+			}
+			exam = examRepo.findByCodeAndRootOrgId(code, rootOrgId);
+		} else {
+			throw new StatusException("002002", "id,name,code are all null");
+		}
+
+		if (null == exam) {
+			throw new StatusException("002005", "考试不存在");
+		}
+		GetExamResp examResp = new GetExamResp();
+		examResp.setId(exam.getId());
+		ExamBean bean = new ExamBean();
+		examResp.setExamBean(bean);
+
+		bean.setId(exam.getId());
+		bean.setBeginTime(exam.getBeginTime());
+		bean.setDuration(exam.getDuration());
+		bean.setEnable(exam.getEnable());
+		bean.setEndTime(exam.getEndTime());
+		bean.setExamTimes(exam.getExamTimes());
+		bean.setExamType(exam.getExamType().name());
+		bean.setName(exam.getName());
+		bean.setCode(exam.getCode());
+		bean.setRemark(exam.getRemark());
+		bean.setRootOrgId(exam.getRootOrgId());
+		bean.setExamLimit(exam.getExamLimit());
+
+		return examResp;
+	}
+
+	@ApiOperation(value = "设置考试属性")
+	@PostMapping("setExamProperty")
+	@Transactional
+	@Override
+	public SetExamPropertyResp setExamProperty(@RequestBody SetExamPropertyReq req) {
+		Long examId = req.getExamId();
+		String key = req.getKey();
+		String value = req.getValue();
+
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+		DynamicEnum de = manager.getByName(key);
+
+		ExamPropertyEntity entity = examPropertyRepo.findByExamIdAndKeyId(examId, de.getId());
+		if (null == entity) {
+			entity = new ExamPropertyEntity();
+			entity.setExamId(examId);
+			entity.setKeyId(de.getId());
+		}
+		entity.setValue(value);
+
+		ExamPropertyEntity saved = examPropertyRepo.save(entity);
+
+		SetExamPropertyResp resp = new SetExamPropertyResp();
+		resp.setPropertyId(saved.getId());
+		return resp;
+	}
+
+	@ApiOperation(value = "查询考试属性")
+	@PostMapping("getExamProperty")
+	@Override
+	public GetExamPropertyResp getExamProperty(@RequestBody GetExamPropertyReq req) {
+		Long examId = req.getExamId();
+		Long orgId = req.getOrgId();
+		String key = req.getKey();
+
+		String value = examService.getExamOrgProperty(examId, orgId, key);
+
+		GetExamPropertyResp resp = new GetExamPropertyResp();
+		resp.setValue(value);
+		return resp;
+	}
+
+	@ApiOperation(value = "查询考试属性集合")
+	@PostMapping("getExamPropertyList")
+	@Override
+	public GetExamPropertyListResp getExamPropertyList(@RequestBody GetExamPropertyListReq req) {
+
+		Long examId = req.getExamId();
+		Long orgId = req.getOrgId();
+		List<String> keys = req.getKeys();
+		Map<String, String> properties = Maps.newHashMap();
+		for (String key : keys) {
+			String value = examService.getExamOrgProperty(examId, orgId, key);
+			properties.put(key, value);
+		}
+
+		GetExamPropertyListResp resp = new GetExamPropertyListResp();
+		resp.setProperties(properties);
+		return resp;
+	}
+
+	@ApiOperation(value = "查询正在考试的考试集合")
+	@PostMapping("getOngoingExamList")
+	@Override
+	public GetOngoingExamListResp getOngoingExamList(@RequestBody GetOngoingExamListReq req) {
+		Long rootOrgId = req.getRootOrgId();
+		String examType = req.getExamType();
+		Long orgId = req.getOrgId();
+		Long studentId = req.getStudentId();
+
+		List<ExamSpecialSettingsEntity> resultList = onGoingExamService
+				.getOngoingExamList(rootOrgId, examType, orgId, studentId);
+
+		List<ExamSpecialSettingsBean> list = Lists.newArrayList();
+		for (ExamSpecialSettingsEntity cur : resultList) {
+			ExamSpecialSettingsBean bean = new ExamSpecialSettingsBean();
+			list.add(bean);
+			bean.setId(cur.getId());
+			bean.setOrgId(cur.getOrgId());
+			bean.setCourseId(cur.getCourseId());
+			bean.setStudentId(cur.getStudentId());
+			cur.setRootOrgId(cur.getRootOrgId());
+			cur.setUpdateTime(cur.getUpdateTime());
+			bean.setBeginTime(cur.getBeginTime());
+			bean.setEndTime(cur.getEndTime());
+			bean.setExamEnable(cur.getExamEnable());
+			bean.setExamId(cur.getExamId());
+			bean.setExamLimit(cur.getExamLimit());
+			bean.setExamType(cur.getExamType().name());
+			bean.setSpecialSettingsEnabled(cur.getSpecialSettingsEnabled());
+			bean.setSpecialSettingsType(cur.getSpecialSettingsType());
+		}
+
+		GetOngoingExamListResp resp = new GetOngoingExamListResp();
+		resp.setExamSpecialSettingsList(list);
+
+		return resp;
+	}
+
+	@ApiOperation(value = "锁定考试")
+	@PostMapping("lockExamStudents")
+	@Transactional
+	@Override
+	public LockExamStudentsResp lockExamStudents(@RequestBody LockExamStudentsReq req) {
+
+		List<Long> examIdList = req.getExamIdList();
+
+		if (CollectionUtils.isEmpty(examIdList)) {
+			throw new StatusException("002003", "examIdList is empty");
+		}
+		for (Long examId : examIdList) {
+			if (null == examId) {
+				throw new StatusException("002001", "examId is null");
+			}
+			ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+			if (null == exam) {
+				throw new StatusException("002002", "ExamEntity is null");
+			}
+			exam.setExamStudentLocked(true);
+			examRepo.save(exam);
+		}
+
+		LockExamStudentsResp resp = new LockExamStudentsResp();
+		resp.setExamIdList(examIdList);
+		return resp;
+	}
+
+	@ApiOperation(value = "解锁考试")
+	@PostMapping("unlockExamStudents")
+	@Transactional
+	@Override
+	public UnlockExamStudentsResp unlockExamStudents(@RequestBody UnlockExamStudentsReq req) {
+
+		List<Long> examIdList = req.getExamIdList();
+
+		if (CollectionUtils.isEmpty(examIdList)) {
+			throw new StatusException("002003", "examIdList is empty");
+		}
+
+		for (Long examId : examIdList) {
+			if (null == examId) {
+				throw new StatusException("002001", "examId is null");
+			}
+			ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+			if (null == exam) {
+				throw new StatusException("002002", "ExamEntity is null");
+			}
+			exam.setExamStudentLocked(false);
+			examRepo.save(exam);
+		}
+
+		UnlockExamStudentsResp resp = new UnlockExamStudentsResp();
+		resp.setExamIdList(examIdList);
+		return resp;
+	}
+
+	@ApiOperation(value = "查询考试集合")
+	@PostMapping("getExamList")
+	@Override
+	public GetExamListResp getExamList(@RequestBody GetExamListReq req) {
+
+		Long rootOrgId = req.getRootOrgId();
+		Boolean enable = req.getEnable();
+		List<String> examTypeList = req.getExamTypeList();
+
+		List<ExamType> examTypes = Lists.newArrayList();
+		if (CollectionUtils.isNotEmpty(examTypeList)) {
+			for (String cur : examTypeList) {
+				examTypes.add(ExamType.valueOf(cur));
+			}
+		}
+
+		final long start = null == req.getStart() ? 1 : req.getStart();
+
+		Pageable pageable = PageRequest.of(0, 100, Sort.Direction.ASC, "id");
+
+		Specification<ExamEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+
+			predicates.add(cb.greaterThanOrEqualTo(root.get("id"), start));
+
+			if (null != enable) {
+				predicates.add(cb.equal(root.get("enable"), enable));
+			}
+			if (CollectionUtils.isNotEmpty(examTypes)) {
+				if (1 == examTypeList.size()) {
+					predicates.add(cb.equal(root.get("examType"), examTypes.get(0)));
+				} else {
+					predicates.add(root.get("examType").in(examTypes));
+				}
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		Page<ExamEntity> page = examRepo.findAll(specification, pageable);
+
+		Iterator<ExamEntity> iterator = page.iterator();
+
+		List<ExamBean> list = Lists.newArrayList();
+		long next = start;
+		boolean has = false;
+		while (iterator.hasNext()) {
+			ExamEntity cur = iterator.next();
+			ExamBean bean = new ExamBean();
+			list.add(bean);
+
+			bean.setId(cur.getId());
+			bean.setBeginTime(cur.getBeginTime());
+			bean.setDuration(cur.getDuration());
+			bean.setEnable(cur.getEnable());
+			bean.setEndTime(cur.getEndTime());
+			bean.setExamTimes(cur.getExamTimes());
+			bean.setExamType(cur.getExamType().name());
+			bean.setName(cur.getName());
+			bean.setRemark(cur.getRemark());
+			bean.setRootOrgId(cur.getRootOrgId());
+
+			next = cur.getId();
+			has = true;
+		}
+
+		GetExamListResp resp = new GetExamListResp();
+		if (has) {
+			next++;
+		}
+		resp.setNext(next);
+		resp.setExamList(list);
+		return resp;
+	}
+
+	@ApiOperation(value = "查询考试的课程集合")
+	@PostMapping("getExamCourseList")
+	@Override
+	public GetExamCourseListResp getExamCourseList(@RequestBody GetExamCourseListReq req) {
+		Long examId = req.getExamId();
+
+		final long start = null == req.getStart() ? 1 : req.getStart();
+
+		Pageable pageable = PageRequest.of(0, 100, Sort.Direction.ASC, "courseId");
+
+		Specification<ExamCourseRelationEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("examId"), examId));
+
+			predicates.add(cb.greaterThanOrEqualTo(root.get("courseId"), start));
+
+			if (null != req.getCourseEnable()) {
+				predicates.add(cb.equal(root.get("courseEnable"), req.getCourseEnable()));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		Page<ExamCourseRelationEntity> page = examCourseRelationRepo.findAll(specification,
+				pageable);
+
+		Iterator<ExamCourseRelationEntity> iterator = page.iterator();
+
+		List<ExamCourseRelationBean> list = Lists.newArrayList();
+		long next = start;
+		boolean has = false;
+		while (iterator.hasNext()) {
+			ExamCourseRelationEntity e = iterator.next();
+			ExamCourseRelationBean b = new ExamCourseRelationBean();
+			b.setCourseCode(e.getCourseCode());
+			b.setCourseId(e.getCourseId());
+			b.setCourseLevel(e.getCourseLevel());
+			b.setCourseName(e.getCourseName());
+			b.setExamId(examId);
+			b.setCourseEnable(e.getCourseEnable());
+
+			next = e.getCourseId();
+			list.add(b);
+			has = true;
+		}
+
+		GetExamCourseListResp resp = new GetExamCourseListResp();
+		if (has) {
+			next++;
+		}
+		resp.setNext(next);
+		resp.setRelationList(list);
+
+		return resp;
+	}
+
+	@ApiOperation(value = "查询考试课程的试卷类型集")
+	@PostMapping("getExamCoursePaperTypeList")
+	@Override
+	public GetExamCoursePaperTypeListResp getExamCoursePaperTypeList(
+			@RequestBody GetExamCoursePaperTypeListReq req) {
+
+		Long examId = req.getExamId();
+
+		final long start = null == req.getStart() ? 1 : req.getStart();
+
+		Pageable pageable = PageRequest.of(0, 100, Sort.Direction.ASC, "courseId");
+
+		Specification<ExamPaperTypeRelationEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("examId"), examId));
+
+			predicates.add(cb.greaterThanOrEqualTo(root.get("courseId"), start));
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		Page<ExamPaperTypeRelationEntity> page = examPaperTypeRelationRepo.findAll(specification,
+				pageable);
+
+		Iterator<ExamPaperTypeRelationEntity> iterator = page.iterator();
+
+		List<ExamPaperTypeRelation> list = Lists.newArrayList();
+		long next = start;
+		boolean has = false;
+		while (iterator.hasNext()) {
+			ExamPaperTypeRelationEntity e = iterator.next();
+			ExamPaperTypeRelation b = new ExamPaperTypeRelation();
+			b.setCourseId(e.getCourseId());
+			b.setExamId(e.getExamId());
+			b.setPaperType(e.getPaperType());
+
+			next = e.getCourseId();
+			list.add(b);
+			has = true;
+		}
+
+		GetExamCoursePaperTypeListResp resp = new GetExamCoursePaperTypeListResp();
+		if (has) {
+			next++;
+		}
+		resp.setNext(next);
+		resp.setRelationList(list);
+
+		return resp;
+	}
+
+	@ApiOperation(value = "统计考生数量")
+	@PostMapping("countExamStudent")
+	@Override
+	public CountExamStudentResp countExamStudent(@RequestBody CountExamStudentReq req) {
+
+		Specification<ExamStudentEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+
+			if (null != req.getExamId()) {
+				predicates.add(cb.equal(root.get("examId"), req.getExamId()));
+			}
+			if (null != req.getCourseId()) {
+				predicates.add(cb.equal(root.get("courseId"), req.getCourseId()));
+			}
+			if (StringUtils.isNotEmpty(req.getPaperType())) {
+				predicates.add(cb.equal(root.get("paperType"), req.getPaperType()));
+			}
+			if (StringUtils.isNotEmpty(req.getExamSite())) {
+				predicates.add(cb.equal(root.get("examSite"), req.getExamSite()));
+			}
+			if (null != req.getOrgId()) {
+				predicates.add(cb.equal(root.get("orgId"), req.getOrgId()));
+			}
+			if (null != req.getExt1()) {
+				predicates.add(cb.equal(root.get("ext1"), req.getExt1()));
+			}
+			if (null != req.getExt2()) {
+				predicates.add(cb.equal(root.get("ext2"), req.getExt2()));
+			}
+			if (null != req.getExt3()) {
+				predicates.add(cb.equal(root.get("ext3"), req.getExt3()));
+			}
+			if (null != req.getExt4()) {
+				predicates.add(cb.equal(root.get("ext4"), req.getExt4()));
+			}
+			if (null != req.getExt5()) {
+				predicates.add(cb.equal(root.get("ext5"), req.getExt5()));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		long count = examStudentRepo.count(specification);
+		CountExamStudentResp resp = new CountExamStudentResp();
+		resp.setCount(count);
+		return resp;
+	}
+
+	@ApiOperation(value = "获取考试机构列表")
+	@PostMapping("getExamOrgList")
+	@Override
+	public GetExamOrgListResp getExamOrgList(@RequestBody GetExamOrgListReq req) {
+
+		long start = null == req.getStart() ? 1 : req.getStart();
+		Long examId = req.getExamId();
+		long next = start;
+
+		List<Long> bigIntegerList = examStudentRepo.queryOrgIdList(examId, start);
+		List<Long> orgIdList = Lists.newArrayList();
+		for (Object bigInteger : bigIntegerList) {
+			orgIdList.add(Long.parseLong(String.valueOf(bigInteger)));
+		}
+		if (CollectionUtils.isNotEmpty(orgIdList)) {
+			next = orgIdList.get(orgIdList.size() - 1);
+			next++;
+		}
+
+		GetExamOrgListResp resp = new GetExamOrgListResp();
+		resp.setNext(next);
+		resp.setOrgIdList(orgIdList);
+
+		return resp;
+	}
+
+	@ApiOperation(value = " 获取考生属性值集合")
+	@PostMapping("getExamStudentPropertyValueList")
+	@Override
+	public GetExamStudentPropertyValueListResp getExamStudentPropertyValueList(
+			@RequestBody GetExamStudentPropertyValueListReq req) {
+
+		Long examId = req.getExamId();
+		String start = null == req.getStart() ? "" : req.getStart();
+
+		List<String> valueList = null;
+		String name = req.getPropertyName();
+
+		if (StringUtils.isBlank(name)) {
+			throw new StatusException("003005", "propertyName is blank");
+		}
+
+		if (name.equals("examSite")) {
+			valueList = examStudentRepo.queryExamSiteList(examId, start);
+		} else if (name.equals("ext1")) {
+			valueList = examStudentRepo.queryExt1List(examId, start);
+		} else if (name.equals("ext2")) {
+			valueList = examStudentRepo.queryExt2List(examId, start);
+		} else if (name.equals("ext3")) {
+			valueList = examStudentRepo.queryExt3List(examId, start);
+		} else if (name.equals("ext4")) {
+			valueList = examStudentRepo.queryExt4List(examId, start);
+		} else if (name.equals("ext5")) {
+			valueList = examStudentRepo.queryExt5List(examId, start);
+		} else {
+			throw new StatusException("003006", "propertyName is wrong");
+		}
+
+		GetExamStudentPropertyValueListResp resp = new GetExamStudentPropertyValueListResp();
+		resp.setValueList(valueList);
+
+		return resp;
+	}
+
+	@ApiOperation(value = "按ID查询考试", notes = "")
+	@PostMapping("getExamsByIdList")
+	@Override
+	public GetExamsByIdListResp getExamsByIdList(@RequestBody GetExamsByIdListReq req) {
+		List<Long> examIdList = req.getExamIdList();
+		List<ExamBean> list = Lists.newArrayList();
+		for (Long cur : examIdList) {
+			ExamEntity exam = GlobalHelper.getEntity(examRepo, cur, ExamEntity.class);
+			if (null == exam) {
+				throw new StatusException("012001", "no exam [id=" + cur + "]");
+			}
+
+			ExamBean bean = new ExamBean();
+
+			bean.setId(exam.getId());
+			bean.setBeginTime(exam.getBeginTime());
+			bean.setDuration(exam.getDuration());
+			bean.setEnable(exam.getEnable());
+			bean.setEndTime(exam.getEndTime());
+			bean.setExamTimes(exam.getExamTimes());
+			bean.setExamType(exam.getExamType().name());
+			bean.setName(exam.getName());
+			bean.setCode(exam.getCode());
+			bean.setRemark(exam.getRemark());
+			bean.setRootOrgId(exam.getRootOrgId());
+			bean.setExamLimit(exam.getExamLimit());
+
+			list.add(bean);
+		}
+
+		GetExamsByIdListResp resp = new GetExamsByIdListResp();
+		resp.setExamList(list);
+		return resp;
+	}
+
+	@ApiOperation(value = "遍历考试", notes = "")
+	@PostMapping("getExams")
+	@Override
+	public GetExamsResp getExams(@RequestBody GetExamsReq req) {
+		Long rootOrgId = req.getRootOrgId();
+		Boolean enable = req.getEnable();
+
+		final long start = null == req.getStart() ? 1 : req.getStart();
+
+		Pageable pageable = PageRequest.of(0, 100, Sort.Direction.ASC, "id");
+
+		Specification<ExamEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+
+			predicates.add(cb.greaterThanOrEqualTo(root.get("id"), start));
+
+			if (null != enable) {
+				predicates.add(cb.equal(root.get("enable"), enable));
+			}
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		Page<ExamEntity> page = examRepo.findAll(specification, pageable);
+
+		Iterator<ExamEntity> iterator = page.iterator();
+
+		List<ExamBean> list = Lists.newArrayList();
+		long next = start;
+		boolean has = false;
+		while (iterator.hasNext()) {
+			ExamEntity exam = iterator.next();
+			ExamBean bean = new ExamBean();
+			bean.setId(exam.getId());
+			bean.setBeginTime(exam.getBeginTime());
+			bean.setDuration(exam.getDuration());
+			bean.setEnable(exam.getEnable());
+			bean.setEndTime(exam.getEndTime());
+			bean.setExamTimes(exam.getExamTimes());
+			bean.setExamType(exam.getExamType().name());
+			bean.setName(exam.getName());
+			bean.setCode(exam.getCode());
+			bean.setRemark(exam.getRemark());
+			bean.setRootOrgId(exam.getRootOrgId());
+			bean.setExamLimit(exam.getExamLimit());
+
+			next = exam.getId();
+			list.add(bean);
+			has = true;
+		}
+
+		GetExamsResp resp = new GetExamsResp();
+		if (has) {
+			next++;
+		}
+		resp.setNext(next);
+		resp.setExamBeanList(list);
+
+		return resp;
+	}
+
+	@ApiOperation(value = "保存学生特殊设置", notes = "")
+	@PostMapping("saveStudentSpecialSettings")
+	@Override
+	public SaveStudentSpecialSettingsResp saveStudentSpecialSettings(
+			@RequestBody SaveStudentSpecialSettingsReq req) {
+
+		Long rootOrgId = req.getRootOrgId();
+		Long studentId = req.getStudentId();
+		Long examId = req.getExamId();
+		Date beginTime = req.getBeginTime();
+		Date endTime = req.getEndTime();
+
+		ExamSpecialSettingsInfo examSpecialInfo = new ExamSpecialSettingsInfo();
+		examSpecialInfo.setBeginTime(beginTime);
+		examSpecialInfo.setEndTime(endTime);
+		examSpecialInfo.setExamId(examId);
+		examSpecialInfo.setExamLimit(false);
+		examSpecialInfo.setRootOrgId(rootOrgId);
+		examSpecialInfo.setStudentId(studentId);
+
+		ExamSpecialSettingsEntity saved = examService.saveExamSpecialSettings(examSpecialInfo);
+
+		SaveStudentSpecialSettingsResp resp = new SaveStudentSpecialSettingsResp();
+		resp.setSpecialSettingsId(saved.getId());
+		return resp;
+	}
+
+}

+ 606 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/provider/ExamStudentCloudServiceProvider.java

@@ -0,0 +1,606 @@
+package cn.com.qmth.examcloud.core.examwork.api.provider;
+
+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.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.bean.StudentBean;
+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.GetStudentReq;
+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.GetStudentResp;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamCourseRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamPaperTypeRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.examwork.service.ExamService;
+import cn.com.qmth.examcloud.core.examwork.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamStudentInfo;
+import cn.com.qmth.examcloud.examwork.api.ExamStudentCloudService;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamStudentBean;
+import cn.com.qmth.examcloud.examwork.api.bean.ExamStudentBean4Reset;
+import cn.com.qmth.examcloud.examwork.api.request.*;
+import cn.com.qmth.examcloud.examwork.api.response.*;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import com.google.common.collect.Lists;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.persistence.criteria.Predicate;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * {@link StatusException} 状态码范围:088XXX<br>
+ *
+ * @author WANGWEI
+ * @date 2018年7月18日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@RestController
+@RequestMapping("${$rmp.cloud.examwork}" + "examStudent")
+public class ExamStudentCloudServiceProvider extends ControllerSupport
+        implements
+        ExamStudentCloudService {
+
+    private static final long serialVersionUID = -89062090947597841L;
+
+    @Autowired
+    ExamRepo examRepo;
+
+    @Autowired
+    ExamService examService;
+
+    @Autowired
+    OrgCloudService orgCloudService;
+
+    @Autowired
+    ExamStudentService examStudentService;
+
+    @Autowired
+    StudentCloudService studentCloudService;
+
+    @Autowired
+    ExamStudentRepo examStudentRepo;
+
+    @Autowired
+    CourseCloudService courseCloudService;
+
+    @Autowired
+    ExamCourseRelationRepo examCourseRelationRepo;
+
+    @Autowired
+    ExamPaperTypeRelationRepo examPaperTypeRelationRepo;
+
+    @ApiOperation(value = "保存考生")
+    @PostMapping("saveExamStudent")
+    @Transactional
+    @Override
+    public SaveExamStudentResp saveExamStudent(@RequestBody SaveExamStudentReq req) {
+        trim(req);
+
+        ExamStudentInfo info = new ExamStudentInfo();
+        info.setCourseCode(req.getCourseCode());
+        info.setCourseName(req.getCourseName());
+        info.setCourseLevel(req.getCourseLevel());
+        info.setCourseId(req.getCourseId());
+        info.setRootOrgId(req.getRootOrgId());
+        info.setExamId(req.getExamId());
+        info.setExamName(req.getExamName());
+        info.setExamCode(req.getExamCode());
+        info.setIdentityNumber(req.getIdentityNumber());
+        info.setPaperType(req.getPaperType());
+        info.setStudentCode(req.getStudentCode());
+        info.setStudentName(req.getStudentName());
+        info.setInfoCollector(req.getInfoCollector());
+        info.setGrade(req.getGrade());
+        info.setExamSite(req.getExamSite());
+        info.setSpecialtyName(req.getSpecialtyName());
+        info.setRemark(req.getRemark());
+
+        info.setSpecialBeginTime(req.getSpecialBeginTime());
+        info.setSpecialEndTime(req.getSpecialEndTime());
+
+        info.setExt1(req.getExt1());
+        info.setExt2(req.getExt2());
+        info.setExt3(req.getExt3());
+        info.setExt4(req.getExt4());
+        info.setExt5(req.getExt5());
+
+        ExamStudentInfo saved = examStudentService.saveExamStudent(info);
+
+        SaveExamStudentResp resp = new SaveExamStudentResp();
+
+        ExamStudentBean examStudentBean = new ExamStudentBean();
+        examStudentBean.setId(saved.getId());
+        examStudentBean.setCourseCode(saved.getCourseCode());
+        examStudentBean.setCourseLevel(saved.getCourseLevel());
+        examStudentBean.setCourseName(saved.getCourseName());
+        examStudentBean.setExamId(saved.getExamId());
+        examStudentBean.setExamName(saved.getExamName());
+        examStudentBean.setIdentityNumber(saved.getIdentityNumber());
+        examStudentBean.setStudentCode(saved.getStudentCode());
+        examStudentBean.setPaperType(saved.getPaperType());
+        examStudentBean.setRootOrgId(saved.getRootOrgId());
+        examStudentBean.setStudentName(saved.getStudentName());
+        examStudentBean.setGrade(saved.getGrade());
+        examStudentBean.setCourseId(saved.getCourseId());
+        examStudentBean.setInfoCollector(saved.getInfoCollector());
+        examStudentBean.setExamSite(saved.getExamSite());
+        examStudentBean.setOrgId(saved.getOrgId());
+        examStudentBean.setOrgCode(saved.getOrgCode());
+        examStudentBean.setOrgName(saved.getOrgName());
+
+        resp.setExamStudentBean(examStudentBean);
+
+        return resp;
+    }
+
+    @ApiOperation(value = "复制考生")
+    @PostMapping("copyExamStudents")
+    @Transactional
+    @Override
+    public CopyExamStudentsResp copyExamStudents(@RequestBody CopyExamStudentsReq req) {
+
+        Long examId1 = req.getExamId1();
+        Long examId2 = req.getExamId2();
+
+        if (null == examId1) {
+            throw new StatusException("210001", "examId1 is null");
+        }
+        if (null == examId2) {
+            throw new StatusException("210002", "examId2 is null");
+        }
+
+        ExamEntity exam1 = GlobalHelper.getEntity(examRepo, examId1, ExamEntity.class);
+        ExamEntity exam2 = GlobalHelper.getEntity(examRepo, examId2, ExamEntity.class);
+        if (null == exam1) {
+            throw new StatusException("210003", "ExamEntity is null");
+        }
+        if (null == exam2) {
+            throw new StatusException("210004", "ExamEntity is null");
+        }
+
+        if (!exam1.getRootOrgId().equals(exam2.getRootOrgId())) {
+            throw new StatusException("210005", "examId1 and examId2  is not matched");
+        }
+
+        final Long rootOrgId = exam1.getRootOrgId();
+
+        final long start = null == req.getStart() ? 1 : req.getStart();
+
+        Pageable pageable = PageRequest.of(0, 10, Sort.Direction.ASC, "id");
+
+        Specification<ExamStudentEntity> specification = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+            predicates.add(cb.greaterThanOrEqualTo(root.get("id"), start));
+            predicates.add(cb.equal(root.get("examId"), examId1));
+
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        Page<ExamStudentEntity> page = examStudentRepo.findAll(specification, pageable);
+
+        Iterator<ExamStudentEntity> iterator = page.iterator();
+
+        List<Long> examStudentIds = Lists.newArrayList();
+
+        long next = start;
+        boolean has = false;
+        while (iterator.hasNext()) {
+            ExamStudentEntity es = iterator.next();
+
+            ExamStudentEntity finded = examStudentRepo.findByExamIdAndStudentIdAndCourseId(examId2,
+                    es.getStudentId(), es.getCourseId());
+
+            if (null != finded) {
+                continue;
+            }
+
+            ExamStudentInfo one = new ExamStudentInfo();
+            one.setExamId(examId2);
+            one.setRootOrgId(es.getRootOrgId());
+            one.setEnable(es.getEnable());
+
+            one.setCourseId(es.getCourseId());
+            one.setCourseCode(es.getCourseCode());
+            one.setCourseLevel(es.getCourseLevel());
+            one.setCourseName(es.getCourseName());
+
+            one.setOrgId(es.getOrgId());
+            one.setOrgCode(es.getOrgCode());
+
+            one.setStudentName(es.getName());
+            one.setStudentCode(es.getStudentCode());
+            one.setStudentId(es.getStudentId());
+            one.setIdentityNumber(es.getIdentityNumber());
+
+            one.setExamSite(es.getExamSite());
+            one.setGrade(es.getGrade());
+            one.setPaperType(es.getPaperType());
+            one.setRemark(es.getRemark());
+            one.setSpecialtyName(es.getSpecialtyName());
+            one.setInfoCollector(es.getInfoCollector());
+
+            one.setExt1(es.getExt1());
+            one.setExt2(es.getExt2());
+            one.setExt3(es.getExt3());
+            one.setExt4(es.getExt4());
+            one.setExt5(es.getExt5());
+
+            ExamStudentInfo saved = examStudentService.saveExamStudent(one);
+            examStudentIds.add(saved.getId());
+
+            next = es.getId();
+            has = true;
+        }
+
+        if (has) {
+            next++;
+        }
+
+        CopyExamStudentsResp resp = new CopyExamStudentsResp();
+        resp.setNext(next);
+        resp.setExamStudentIds(examStudentIds);
+
+        return resp;
+    }
+
+    @ApiOperation(value = "获取考生")
+    @PostMapping("getExamStudent")
+    @Override
+    public GetExamStudentResp getExamStudent(@RequestBody GetExamStudentReq req) {
+        Long examStudentId = req.getExamStudentId();
+
+        ExamStudentEntity saved = GlobalHelper.getEntity(examStudentRepo, examStudentId,
+                ExamStudentEntity.class);
+
+        ExamStudentBean examStudentBean = new ExamStudentBean();
+        examStudentBean.setId(saved.getId());
+        examStudentBean.setCourseCode(saved.getCourseCode());
+        examStudentBean.setCourseLevel(saved.getCourseLevel());
+        examStudentBean.setCourseName(saved.getCourseName());
+        examStudentBean.setExamId(saved.getExamId());
+        examStudentBean.setIdentityNumber(saved.getIdentityNumber());
+        examStudentBean.setStudentCode(saved.getStudentCode());
+        examStudentBean.setPaperType(saved.getPaperType());
+        examStudentBean.setRootOrgId(saved.getRootOrgId());
+        examStudentBean.setGrade(saved.getGrade());
+        examStudentBean.setCourseId(saved.getCourseId());
+        examStudentBean.setInfoCollector(saved.getInfoCollector());
+        examStudentBean.setExamSite(saved.getExamSite());
+        examStudentBean.setOrgId(saved.getOrgId());
+        examStudentBean.setOrgCode(saved.getOrgCode());
+        examStudentBean.setStudentName(saved.getName());
+        examStudentBean.setSpecialtyName(saved.getSpecialtyName());
+
+        examStudentBean.setExt1(saved.getExt1());
+        examStudentBean.setExt2(saved.getExt2());
+        examStudentBean.setExt3(saved.getExt3());
+        examStudentBean.setExt4(saved.getExt4());
+        examStudentBean.setExt5(saved.getExt5());
+
+        GetExamStudentResp resp = new GetExamStudentResp();
+        resp.setExamStudentBean(examStudentBean);
+        return resp;
+    }
+
+    @ApiOperation(value = " 分页查询考生")
+    @PostMapping("getExamStudentPage")
+    @Override
+    public GetExamStudentPageResp getExamStudentPage(@RequestBody GetExamStudentPageReq req) {
+        Long rootOrgId = req.getRootOrgId();
+        String courseCode = req.getCourseCode();
+        String courseLevel = req.getCourseLevel();
+        String courseName = req.getCourseName();
+        Integer curPage = req.getCurPage();
+        Long examId = req.getExamId();
+        String examSite = req.getExamSite();
+        String identityNumber = req.getIdentityNumber();
+        String infoCollector = req.getInfoCollector();
+        Long orgId = req.getOrgId();
+        String specialtyName = req.getSpecialtyName();
+        String studentCode = req.getStudentCode();
+        String studentName = req.getStudentName();
+        Integer pageSize = req.getPageSize();
+
+        if (null == rootOrgId) {
+            throw new StatusException("210005", "rootOrgId is null");
+        }
+
+        Specification<ExamStudentEntity> specification = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+
+            if (null != orgId) {
+                predicates.add(cb.equal(root.get("orgId"), orgId));
+            }
+
+            if (null != examId) {
+                predicates.add(cb.equal(root.get("examId"), examId));
+            }
+            if (StringUtils.isNotEmpty(studentName)) {
+                predicates.add(cb.like(root.get("name"), toSqlSearchPattern(studentName)));
+            }
+            if (StringUtils.isNotBlank(studentCode)) {
+                predicates.add(cb.like(root.get("studentCode"), toSqlRightLike(studentCode)));
+            }
+            if (StringUtils.isNotEmpty(courseCode)) {
+                predicates.add(cb.equal(root.get("courseCode"), courseCode));
+            }
+            if (StringUtils.isNotEmpty(courseLevel)) {
+                predicates.add(cb.equal(root.get("courseLevel"), courseLevel));
+            }
+            if (StringUtils.isNotEmpty(courseName)) {
+                predicates.add(cb.like(root.get("courseName"), toSqlSearchPattern(courseName)));
+            }
+            if (!StringUtils.isEmpty(examSite)) {
+                predicates.add(cb.like(root.get("examSite"), toSqlSearchPattern(examSite)));
+            }
+
+            if (StringUtils.isNotBlank(identityNumber)) {
+                predicates.add(cb.like(root.get("identityNumber"), toSqlRightLike(identityNumber)));
+            }
+
+            if (StringUtils.isNotEmpty(specialtyName)) {
+                predicates.add(cb.like(root.get("specialtyName"), toSqlSearchPattern(specialtyName)));
+            }
+            if (StringUtils.isNotEmpty(infoCollector)) {
+                predicates.add(cb.like(root.get("infoCollector"), toSqlSearchPattern(infoCollector)));
+            }
+
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        PageRequest pageRequest = PageRequest.of(curPage, pageSize,
+                new Sort(Direction.DESC, "updateTime", "id"));
+
+        Page<ExamStudentEntity> examStudents = examStudentRepo.findAll(specification, pageRequest);
+
+        List<ExamStudentBean> list = Lists.newArrayList();
+
+        for (ExamStudentEntity cur : examStudents) {
+            ExamEntity exam = GlobalHelper.getEntity(examRepo, cur.getExamId(), ExamEntity.class);
+
+            GetOrgReq getOrgReq = new GetOrgReq();
+            getOrgReq.setOrgId(cur.getOrgId());
+            GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+            OrgBean org = getOrgResp.getOrg();
+
+            ExamStudentBean bean = new ExamStudentBean();
+            bean.setId(cur.getId());
+            bean.setExamId(exam.getId());
+            bean.setExamName(exam.getName());
+            bean.setStudentId(cur.getStudentId());
+            bean.setStudentName(cur.getName());
+            bean.setStudentCode(cur.getStudentCode());
+            bean.setIdentityNumber(cur.getIdentityNumber());
+            bean.setCourseCode(cur.getCourseCode());
+            bean.setCourseName(cur.getCourseName());
+            bean.setInfoCollector(cur.getInfoCollector());
+            bean.setOrgId(cur.getOrgId());
+            bean.setOrgCode(org.getCode());
+            bean.setOrgName(org.getName());
+            bean.setPaperType(cur.getPaperType());
+            bean.setGrade(cur.getGrade());
+            bean.setSpecialtyName(cur.getSpecialtyName());
+            bean.setExamSite(cur.getExamSite());
+
+            list.add(bean);
+        }
+
+        GetExamStudentPageResp resp = new GetExamStudentPageResp();
+        resp.setList(list);
+        resp.setTotal(examStudents.getTotalElements());
+
+        return resp;
+    }
+
+    @ApiOperation(value = "更新考生状态")
+    @PostMapping("updateExamStudentStatus")
+    @Transactional
+    @Override
+    public UpdateExamStudentStatusResp updateExamStudentStatus(
+            @RequestBody UpdateExamStudentStatusReq req) {
+        Long rootOrgId = req.getRootOrgId();
+        Long examId = req.getExamId();
+        String examCode = req.getExamCode();
+        String examName = req.getExamName();
+        Long studentId = req.getStudentId();
+        String identityNumber = req.getIdentityNumber();
+        Long courseId = req.getCourseId();
+        String courseCode = req.getCourseCode();
+        Boolean enable = req.getEnable();
+
+        if (null == rootOrgId) {
+            throw new StatusException("088001", "rootOrgId is null");
+        }
+
+        ExamEntity examEntity = null;
+        if (null != examId) {
+            examEntity = GlobalHelper.getPresentEntity(examRepo, examId, ExamEntity.class);
+            GlobalHelper.uniformRootOrg(examEntity.getRootOrgId(), rootOrgId);
+        } else if (StringUtils.isNotBlank(examCode)) {
+            examEntity = examRepo.findByCodeAndRootOrgId(examCode, rootOrgId);
+            if (null == examEntity) {
+                throw new StatusException("088002", "exam is not present");
+            }
+        } else if (StringUtils.isNotBlank(examName)) {
+            examEntity = examRepo.findByNameAndRootOrgId(examName, rootOrgId);
+            if (null == examEntity) {
+                throw new StatusException("088003", "exam is not present");
+            }
+        } else {
+            throw new StatusException("088079", "examId,examCode,examName cannot be all empty");
+        }
+
+        if (null == courseId && StringUtils.isBlank(courseCode)) {
+            throw new StatusException("088003", "courseId & courseCode cannot be all null");
+        }
+        GetCourseReq gcR = new GetCourseReq();
+        gcR.setRootOrgId(rootOrgId);
+        gcR.setId(courseId);
+        gcR.setCode(courseCode);
+        GetCourseResp gcResp = courseCloudService.getCourse(gcR);
+        CourseBean courseBean = gcResp.getCourseBean();
+
+        GetStudentReq gsReq = new GetStudentReq();
+        gsReq.setRootOrgId(rootOrgId);
+        gsReq.setIdentityNumber(identityNumber);
+        gsReq.setStudentId(studentId);
+
+        GetStudentResp gsResp = studentCloudService.getStudent(gsReq);
+        StudentBean studentBean = gsResp.getStudentInfo();
+
+        ExamStudentEntity es = examStudentRepo.findByExamIdAndStudentIdAndCourseId(
+                examEntity.getId(), studentBean.getId(), courseBean.getId());
+
+        es.setEnable(enable);
+
+        ExamStudentEntity saved = examStudentRepo.save(es);
+
+        examStudentService.syncExamStudent(saved.getId());
+
+        UpdateExamStudentStatusResp resp = new UpdateExamStudentStatusResp();
+        resp.setExamStudentId(saved.getId());
+        return resp;
+    }
+
+    @ApiOperation(value = "重置考生")
+    @PostMapping("resetExamStudent")
+    @Transactional
+    @Override
+    public ResetExamStudentResp resetExamStudent(@RequestBody ResetExamStudentReq req) {
+
+        Long rootOrgId = req.getRootOrgId();
+        Long examId = req.getExamId();
+        String examCode = req.getExamCode();
+        String examName = req.getExamName();
+        Long studentId = req.getStudentId();
+        String identityNumber = req.getIdentityNumber();
+        String studentName = req.getStudentName();
+        String studentCode = req.getStudentCode();
+
+        if (null == rootOrgId) {
+            throw new StatusException("088001", "rootOrgId is null");
+        }
+
+        ExamEntity examEntity = null;
+        if (null != examId) {
+            examEntity = GlobalHelper.getPresentEntity(examRepo, examId, ExamEntity.class);
+            GlobalHelper.uniformRootOrg(examEntity.getRootOrgId(), rootOrgId);
+        } else if (StringUtils.isNotBlank(examCode)) {
+            examEntity = examRepo.findByCodeAndRootOrgId(examCode, rootOrgId);
+            if (null == examEntity) {
+                throw new StatusException("088002", "exam is not present");
+            }
+        } else if (StringUtils.isNotBlank(examName)) {
+            examEntity = examRepo.findByNameAndRootOrgId(examName, rootOrgId);
+            if (null == examEntity) {
+                throw new StatusException("088003", "exam is not present");
+            }
+        } else {
+            throw new StatusException("088075", "examId,examCode,examName cannot be all empty");
+        }
+
+        GetStudentReq gsReq = new GetStudentReq();
+        gsReq.setRootOrgId(rootOrgId);
+        gsReq.setIdentityNumber(identityNumber);
+        gsReq.setStudentId(studentId);
+        gsReq.setStudentCode(studentCode);
+
+        GetStudentResp gsResp = studentCloudService.getStudent(gsReq);
+        StudentBean studentBean = gsResp.getStudentInfo();
+
+        List<ExamStudentEntity> presentExamStudentList = examStudentRepo
+                .findByExamIdAndStudentId(examId, studentBean.getId());
+
+        for (ExamStudentEntity cur : presentExamStudentList) {
+            cur.setEnable(false);
+            examStudentRepo.save(cur);
+            examStudentService.syncExamStudent(cur.getId());
+        }
+
+        List<ExamStudentBean4Reset> examStudentList = req.getExamStudentList();
+        List<ExamStudentBean> savedExamStudentList = Lists.newArrayList();
+
+        for (ExamStudentBean4Reset cur : examStudentList) {
+
+            ExamStudentInfo info = new ExamStudentInfo();
+            info.setCourseCode(cur.getCourseCode());
+            info.setCourseName(cur.getCourseName());
+            info.setCourseLevel(cur.getCourseLevel());
+            info.setCourseId(cur.getCourseId());
+            info.setRootOrgId(req.getRootOrgId());
+            info.setExamId(req.getExamId());
+            info.setExamCode(req.getExamCode());
+            info.setExamName(examEntity.getName());
+            info.setIdentityNumber(identityNumber);
+            info.setPaperType(cur.getPaperType());
+            info.setStudentCode(studentCode);
+            info.setStudentName(studentName);
+            info.setInfoCollector(cur.getInfoCollector());
+            info.setGrade(cur.getGrade());
+            info.setExamSite(cur.getExamSite());
+            info.setSpecialtyName(cur.getSpecialtyName());
+            info.setRemark(cur.getRemark());
+            info.setEnable(true);
+
+            info.setExt1(cur.getExt1());
+            info.setExt2(cur.getExt2());
+            info.setExt3(cur.getExt3());
+            info.setExt4(cur.getExt4());
+            info.setExt5(cur.getExt5());
+
+            ExamStudentInfo saved = examStudentService.saveExamStudent(info);
+
+            ExamStudentBean examStudentBean = new ExamStudentBean();
+            examStudentBean.setId(saved.getId());
+            examStudentBean.setCourseCode(saved.getCourseCode());
+            examStudentBean.setCourseLevel(saved.getCourseLevel());
+            examStudentBean.setCourseName(saved.getCourseName());
+            examStudentBean.setExamId(saved.getExamId());
+            examStudentBean.setExamName(saved.getExamName());
+            examStudentBean.setIdentityNumber(saved.getIdentityNumber());
+            examStudentBean.setStudentCode(saved.getStudentCode());
+            examStudentBean.setPaperType(saved.getPaperType());
+            examStudentBean.setRootOrgId(saved.getRootOrgId());
+            examStudentBean.setStudentName(saved.getStudentName());
+            examStudentBean.setGrade(saved.getGrade());
+            examStudentBean.setCourseId(saved.getCourseId());
+            examStudentBean.setInfoCollector(saved.getInfoCollector());
+            examStudentBean.setExamSite(saved.getExamSite());
+            examStudentBean.setOrgId(saved.getOrgId());
+            examStudentBean.setOrgCode(saved.getOrgCode());
+            examStudentBean.setOrgName(saved.getOrgName());
+
+            savedExamStudentList.add(examStudentBean);
+        }
+
+        ResetExamStudentResp resp = new ResetExamStudentResp();
+        resp.setExamStudentList(savedExamStudentList);
+        return resp;
+    }
+
+}

+ 150 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/provider/HandleSyncCloudServiceProvider.java

@@ -0,0 +1,150 @@
+package cn.com.qmth.examcloud.core.examwork.api.provider;
+
+import java.util.List;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamCourseRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.global.api.HandleSyncCloudService;
+import cn.com.qmth.examcloud.global.api.request.SyncCourseReq;
+import cn.com.qmth.examcloud.global.api.request.SyncExamReq;
+import cn.com.qmth.examcloud.global.api.request.SyncExamStudentReq;
+import cn.com.qmth.examcloud.global.api.request.SyncOrgReq;
+import cn.com.qmth.examcloud.global.api.request.SyncSpecialtyReq;
+import cn.com.qmth.examcloud.global.api.request.SyncStudentReq;
+import cn.com.qmth.examcloud.global.api.request.SyncUserReq;
+import cn.com.qmth.examcloud.global.api.response.SyncCourseResp;
+import cn.com.qmth.examcloud.global.api.response.SyncExamResp;
+import cn.com.qmth.examcloud.global.api.response.SyncExamStudentResp;
+import cn.com.qmth.examcloud.global.api.response.SyncOrgResp;
+import cn.com.qmth.examcloud.global.api.response.SyncSpecialtyResp;
+import cn.com.qmth.examcloud.global.api.response.SyncStudentResp;
+import cn.com.qmth.examcloud.global.api.response.SyncUserResp;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年9月18日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Transactional
+@RestController
+@RequestMapping("${$rmp.cloud.examwork}" + "dataSync")
+public class HandleSyncCloudServiceProvider extends ControllerSupport
+		implements
+			HandleSyncCloudService {
+
+	private static final long serialVersionUID = 9122543745597481498L;
+
+	@Autowired
+	ExamStudentRepo examStudentRepo;
+
+	@Autowired
+	ExamCourseRelationRepo examCourseRelationRepo;
+
+	@ApiOperation(value = "同步课程")
+	@PostMapping("syncCourse")
+	@Override
+	public SyncCourseResp syncCourse(@RequestBody SyncCourseReq req) {
+		String courseName = req.getName();
+		Long courseId = req.getId();
+		String courseLevel = req.getLevel();
+		Long rootOrgId = req.getRootOrgId();
+		Boolean enable = req.getEnable();
+
+		if (StringUtils.isBlank(courseName)) {
+			throw new StatusException("100001", "courseName is null");
+		}
+		if (null == courseId) {
+			throw new StatusException("100002", "courseId is null");
+		}
+		if (StringUtils.isBlank(courseLevel)) {
+			throw new StatusException("100003", "courseLevel is null");
+		}
+		if (null == rootOrgId) {
+			throw new StatusException("100004", "rootOrgId is null");
+		}
+
+		examStudentRepo.updateCourse(courseName, courseLevel, courseId);
+
+		examCourseRelationRepo.updateCourse(courseName, courseLevel, enable, courseId);
+
+		SyncCourseResp resp = new SyncCourseResp();
+		return resp;
+	}
+
+	@ApiOperation(value = "同步机构")
+	@PostMapping("syncOrg")
+	@Override
+	public SyncOrgResp syncOrg(@RequestBody SyncOrgReq req) {
+		return null;
+	}
+
+	@ApiOperation(value = "同步学生")
+	@PostMapping("syncStudent")
+	@Override
+	public SyncStudentResp syncStudent(@RequestBody SyncStudentReq req) {
+		String name = req.getName();
+		Long id = req.getId();
+		List<String> unboundStudentCodeList = req.getUnboundStudentCodeList();
+
+		if (StringUtils.isBlank(name)) {
+			throw new StatusException("100001", "name is null");
+		}
+		if (null == id) {
+			throw new StatusException("100002", "id is null");
+		}
+
+		examStudentRepo.updateStudentName(name, id);
+
+		if (CollectionUtils.isNotEmpty(unboundStudentCodeList)) {
+			for (String cur : unboundStudentCodeList) {
+				examStudentRepo.unboundStudentCode(cur, id);
+			}
+		}
+
+		SyncStudentResp resp = new SyncStudentResp();
+		return resp;
+	}
+
+	@ApiOperation(value = "同步考生")
+	@PostMapping("syncExamStudent")
+	@Override
+	public SyncExamStudentResp syncExamStudent(@RequestBody SyncExamStudentReq req) {
+		return null;
+	}
+
+	@ApiOperation(value = "同步专业")
+	@PostMapping("syncSpecialty")
+	@Override
+	public SyncSpecialtyResp syncSpecialty(@RequestBody SyncSpecialtyReq req) {
+		return null;
+	}
+
+	@ApiOperation(value = "同步考试")
+	@PostMapping("syncExam")
+	@Override
+	public SyncExamResp syncExam(@RequestBody SyncExamReq req) {
+		return null;
+	}
+
+	@ApiOperation(value = "同步考试")
+	@PostMapping("syncUser")
+	@Override
+	public SyncUserResp syncUser(@RequestBody SyncUserReq req) {
+		return null;
+	}
+
+}

+ 110 - 0
examcloud-core-examwork-api-provider/src/main/java/cn/com/qmth/examcloud/core/examwork/api/provider/NoticeCloudServiceProvider.java

@@ -0,0 +1,110 @@
+package cn.com.qmth.examcloud.core.examwork.api.provider;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.NoticeRulePublishProgressEntity;
+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 org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.core.examwork.service.NoticeService;
+import cn.com.qmth.examcloud.examwork.api.NoticeCloudService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 考试云服务
+ *
+ * @author WANGWEI
+ * @date 2018年11月23日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@RestController
+@RequestMapping("${$rmp.cloud.examwork}" + "notice")
+public class NoticeCloudServiceProvider extends ControllerSupport implements NoticeCloudService {
+
+	private static final long serialVersionUID = 4359829563744893206L;
+
+	@Autowired
+	private NoticeService noticeService;
+
+	@ApiOperation(value = "获取待处理的通知进度数据")
+	@PostMapping("getToBeDisposedNoticeRulePublishProgressList")
+	@Override
+	public GetNoticeRulePublishProgressListResp getToBeDisposedNoticeRulePublishProgressList() {
+		GetNoticeRulePublishProgressListResp resp = new GetNoticeRulePublishProgressListResp();
+		List<NoticeRulePublishProgressEntity> progressEntityList = noticeService
+				.getToBeDisposedNoticeRulePublishProgressList();
+		if (progressEntityList == null || progressEntityList.isEmpty()) {
+			return resp;
+		}
+		List<NoticeRulePublishProgressBean> beanList = new ArrayList<>();
+		for (NoticeRulePublishProgressEntity entity : progressEntityList) {
+			NoticeRulePublishProgressBean bean = new NoticeRulePublishProgressBean();
+			bean.setId(entity.getId());
+			bean.setMaxCommonUserId(entity.getMaxCommonUserId());
+			bean.setMaxStudentId(entity.getMaxStudentId());
+			bean.setNoticeId(entity.getNoticeId());
+			bean.setNoticeReceiverRuleType(entity.getNoticeReceiverRuleType());
+			bean.setRootOrgId(entity.getRootOrgId());
+			beanList.add(bean);
+		}
+		resp.setList(beanList);
+		return resp;
+	}
+
+	@ApiOperation(value = "处理待发布和发布中的数据")
+	@PostMapping("disposePublishingUserNotice")
+	@Transactional
+	@Override
+	public DisposePublishingUserNoticeResp disposePublishingUserNotice(
+			@RequestBody DisposePublishingUserNoticeReq req) {
+		// 处理发布中的通知数据,并返回下一个要处理的用户id
+		Long nextUserId = noticeService.disposePublishingUserNotice(req.getStartUserId(),
+				copytNoticeRulePublishProgressEntityFrom(req.getNoticeRulePublishProgress()));
+
+		DisposePublishingUserNoticeResp resp = new DisposePublishingUserNoticeResp();
+		resp.setNextUserId(nextUserId);
+		return resp;
+	}
+
+	// 实体转换
+	private NoticeRulePublishProgressEntity copytNoticeRulePublishProgressEntityFrom(
+			NoticeRulePublishProgressBean processBean) {
+		NoticeRulePublishProgressEntity processEntity = new NoticeRulePublishProgressEntity();
+		processEntity.setId(processBean.getId());
+		processEntity.setMaxCommonUserId(processBean.getMaxCommonUserId());
+		processEntity.setMaxStudentId(processBean.getMaxStudentId());
+		processEntity.setNoticeId(processBean.getNoticeId());
+		processEntity.setNoticeReceiverRuleType(processBean.getNoticeReceiverRuleType());
+		processEntity.setRootOrgId(processBean.getRootOrgId());
+		return processEntity;
+	}
+
+	@ApiOperation(value = "清理过期的通知数据")
+	@PostMapping("disposeOverdueNotice")
+	@Transactional
+	@Override
+	public void disposeOverdueNotice() {
+		noticeService.disposeOverdueNotice();
+	}
+
+	@ApiOperation(value = "更新通知状态")
+	@PostMapping("updateNoticeStatus")
+	@Transactional
+	@Override
+	public void updateNoticeStatus(@RequestBody UpdateNoticeStatusReq updateNoticeStatusReq) {
+		noticeService.updateNoticeStatus(updateNoticeStatusReq.getNoticeId(),
+				updateNoticeStatusReq.getNoticeStatus());
+	}
+
+}

+ 29 - 0
examcloud-core-examwork-base/pom.xml

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

+ 35 - 0
examcloud-core-examwork-base/src/main/java/cn/com/qmth/examcloud/core/examwork/base/enums/ExamProperty.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.core.examwork.base.enums;
+
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnumManager;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年8月6日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamProperty {
+
+	private static DynamicEnumManager manager;
+
+	/**
+	 * 启动初始化
+	 *
+	 * @author WANGWEI
+	 */
+	public static void init() {
+		manager = DynamicEnumManager.newInstance("exam-properties.xml");
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static DynamicEnumManager getDynamicEnumManager() {
+		return manager;
+	}
+
+}

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

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

+ 25 - 0
examcloud-core-examwork-dao/pom.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud.core.examwork</groupId>
+		<artifactId>examcloud-core-examwork</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-core-examwork-dao</artifactId>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.core.examwork</groupId>
+			<artifactId>examcloud-core-examwork-base</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.scala-lang</groupId>
+			<artifactId>scala-library</artifactId>
+			<version>2.11.0</version>
+		</dependency>
+	</dependencies>
+
+</project>

+ 33 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamCourseRelationRepo.java

@@ -0,0 +1,33 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamCourseRelationEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamCourseRelationPK;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年9月20日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public interface ExamCourseRelationRepo
+		extends
+			JpaRepository<ExamCourseRelationEntity, ExamCourseRelationPK>,
+			QueryByExampleExecutor<ExamCourseRelationEntity>,
+			JpaSpecificationExecutor<ExamCourseRelationEntity> {
+
+	void deleteByExamId(Long examId);
+
+	void deleteByExamIdAndCourseId(Long examId, Long courseId);
+
+	@Modifying
+	@Query("update ExamCourseRelationEntity s set s.courseName = ?1, s.courseLevel  = ?2,s.courseEnable=?3 where s.courseId=?4")
+	void updateCourse(String courseName, String courseLevel, Boolean courseEnable, Long courseId);
+
+}

+ 32 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamOrgPropertyRepo.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamOrgPropertyEntity;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年8月20日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public interface ExamOrgPropertyRepo
+		extends
+			JpaRepository<ExamOrgPropertyEntity, Long>,
+			QueryByExampleExecutor<ExamOrgPropertyEntity>,
+			JpaSpecificationExecutor<ExamOrgPropertyEntity> {
+
+	ExamOrgPropertyEntity findByExamIdAndOrgIdAndKeyId(Long examId, Long orgId, Long keyId);
+
+	List<ExamOrgPropertyEntity> findByExamIdAndOrgId(Long examId, Long orgId);
+
+	void deleteByExamIdAndOrgId(Long examId, Long orgId);
+
+	void deleteByExamId(Long examId);
+
+}

+ 24 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamPaperTypeRelationRepo.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPaperTypeRelationEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPaperTypeRelationPK;
+
+public interface ExamPaperTypeRelationRepo
+		extends
+			JpaRepository<ExamPaperTypeRelationEntity, ExamPaperTypeRelationPK>,
+			QueryByExampleExecutor<ExamPaperTypeRelationEntity>,
+			JpaSpecificationExecutor<ExamPaperTypeRelationEntity> {
+
+	List<ExamPaperTypeRelationEntity> findByExamIdAndCourseId(Long examId, Long courseId);
+
+	void deleteByExamId(Long examId);
+
+	void deleteByExamIdAndCourseIdAndPaperType(Long examId, Long courseId, String paperType);
+
+}

+ 21 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamPropertyRepo.java

@@ -0,0 +1,21 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPropertyEntity;
+
+public interface ExamPropertyRepo
+		extends
+			JpaRepository<ExamPropertyEntity, Long>,
+			QueryByExampleExecutor<ExamPropertyEntity>,
+			JpaSpecificationExecutor<ExamPropertyEntity> {
+
+	ExamPropertyEntity findByExamIdAndKeyId(Long examId, Long keyId);
+
+	List<ExamPropertyEntity> findByExamId(Long examId);
+
+}

+ 35 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamRepo.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+
+public interface ExamRepo
+		extends
+			JpaRepository<ExamEntity, Long>,
+			QueryByExampleExecutor<ExamEntity>,
+			JpaSpecificationExecutor<ExamEntity> {
+
+	int countByNameLike(String name);
+
+	List<ExamEntity> findByRootOrgId(Long rootOrgId);
+
+	List<ExamEntity> findByIdIn(List<Long> ids);
+
+	ExamEntity findByNameAndRootOrgId(String name, Long rootOrgId);
+
+	ExamEntity findByCodeAndRootOrgId(String code, Long rootOrgId);
+
+	Long countByName(String name);
+
+	Long countByNameAndRootOrgId(String name, Long rootOrgId);
+
+	Long countByNameAndIdNot(String name, Long id);
+
+	Long countByNameAndRootOrgIdAndIdNot(String name, Long rootOrgId, Long id);
+
+}

+ 54 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamSpecialSettingsRepo.java

@@ -0,0 +1,54 @@
+package cn.com.qmth.examcloud.core.examwork.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.data.repository.query.QueryByExampleExecutor;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+
+public interface ExamSpecialSettingsRepo
+		extends
+			JpaRepository<ExamSpecialSettingsEntity, Long>,
+			QueryByExampleExecutor<ExamSpecialSettingsEntity>,
+			JpaSpecificationExecutor<ExamSpecialSettingsEntity> {
+
+	ExamSpecialSettingsEntity findByExamIdAndOrgIdAndCourseIdIsNullAndStudentIdIsNull(Long examId,
+			Long orgId);
+
+	ExamSpecialSettingsEntity findByExamIdAndStudentIdAndOrgIdIsNullAndCourseIdIsNull(Long examId,
+			Long studentId);
+
+	ExamSpecialSettingsEntity findAllByExamIdAndOrgIdIsNullAndCourseIdIsNullAndStudentIdIsNull(
+			Long examId);
+
+	List<ExamSpecialSettingsEntity> findAllByExamIdAndCourseIdIsNullAndStudentIdIsNullAndOrgIdIsNotNull(
+			Long examId);
+
+	void deleteByExamIdAndOrgIdIsNotNullAndCourseIdIsNullAndStudentIdIsNull(Long examId);
+
+	@Transactional
+	@Modifying
+	@Query("update ExamSpecialSettingsEntity set examEnable = :examEnable where examId = :examId")
+	void updateExamEnableByExamId(@Param("examId") long examId,
+			@Param("examEnable") Boolean examEnable);
+
+	@Transactional
+	@Modifying
+	@Query("update ExamSpecialSettingsEntity set specialSettingsEnabled = :specialSettingsEnabled where examId = :examId")
+	void updateSpecialSettingsEnabledByExamId(@Param("examId") long examId,
+			@Param("specialSettingsEnabled") Boolean specialSettingsEnabled);
+
+	@Transactional
+	@Modifying
+	@Query("update ExamSpecialSettingsEntity set specialSettingsType = :specialSettingsType where examId = :examId")
+	void updateSpecialSettingsTypeByExamId(@Param("examId") long examId,
+			@Param("specialSettingsType") ExamSpecialSettingsType specialSettingsType);
+
+}

+ 85 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/ExamStudentRepo.java

@@ -0,0 +1,85 @@
+package cn.com.qmth.examcloud.core.examwork.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.data.repository.query.QueryByExampleExecutor;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamStudentEntity;
+
+public interface ExamStudentRepo
+		extends
+			JpaRepository<ExamStudentEntity, Long>,
+			QueryByExampleExecutor<ExamStudentEntity>,
+			JpaSpecificationExecutor<ExamStudentEntity> {
+
+	List<ExamStudentEntity> findByStudentId(Long examId);
+
+	List<ExamStudentEntity> findByIdIn(List<Long> ids);
+
+	@Modifying
+	@Query("delete from ExamStudentEntity s where s.examId = ?1")
+	void deleteByExamId(Long examId);
+
+	ExamStudentEntity findByExamIdAndStudentIdAndCourseId(Long examId, Long studentId,
+			Long courseId);
+
+	@Modifying
+	@Query("update ExamStudentEntity s set s.studentCode=null where s.studentCode= ?1 and s.studentId = ?2")
+	void unboundStudentCode(String studentCode, Long studentId);
+
+	@Modifying
+	@Query("update ExamStudentEntity s set s.name = ?1 where s.studentId = ?2")
+	void updateStudentName(String name, Long studentId);
+
+	@Modifying
+	@Query("update ExamStudentEntity s set s.courseName = ?1, s.courseLevel  = ?2  where s.courseId = ?3")
+	void updateCourse(String courseName, String courseLevel, Long courseId);
+
+	List<ExamStudentEntity> findTop2ByExamIdAndCourseIdAndPaperType(Long examId, Long courseId,
+			String paperType);
+
+	List<ExamStudentEntity> findTop2ByExamIdAndCourseId(Long examId, Long courseId);
+
+	@Query(value = "select distinct org_id from ec_e_exam_student t where t.org_id>?2 and t.exam_id=?1 order by org_id limit 500", nativeQuery = true)
+	List<Long> queryOrgIdList(Long examId, Long start);
+
+	@Query(value = "select distinct exam_site from ec_e_exam_student t where t.exam_site>?2 and t.exam_id=?1 order by exam_site limit 500", nativeQuery = true)
+	List<String> queryExamSiteList(Long examId, String start);
+
+	@Query(value = "select distinct ext1 from ec_e_exam_student t where t.ext1>?2 and t.exam_id=?1 order by ext1 limit 500", nativeQuery = true)
+	List<String> queryExt1List(Long examId, String start);
+
+	@Query(value = "select distinct ext2 from ec_e_exam_student t where t.ext2>?2 and t.exam_id=?1 order by ext2 limit 500", nativeQuery = true)
+	List<String> queryExt2List(Long examId, String start);
+
+	@Query(value = "select distinct ext3 from ec_e_exam_student t where t.ext3>?2 and t.exam_id=?1 order by ext3 limit 500", nativeQuery = true)
+	List<String> queryExt3List(Long examId, String start);
+
+	@Query(value = "select distinct ext4 from ec_e_exam_student t where t.ext4>?2 and t.exam_id=?1 order by ext4 limit 500", nativeQuery = true)
+	List<String> queryExt4List(Long examId, String start);
+
+	@Query(value = "select distinct ext5 from ec_e_exam_student t where t.ext5>?2 and t.exam_id=?1 order by ext5 limit 500", nativeQuery = true)
+	List<String> queryExt5List(Long examId, String start);
+
+	@Query(value = "select distinct specialty_name from ec_e_exam_student t where t.student_id=?1", nativeQuery = true)
+	List<String> querySpecialtyNameList(Long studentId);
+
+	@Query(value = "select distinct course_level from ec_e_exam_student t where t.student_id=?1", nativeQuery = true)
+	List<String> queryCourseLevelList(Long studentId);
+
+	@Query(value = "select DISTINCT student_id  from ec_e_exam_student where root_org_id=?1 and student_id>=?2 order by student_id asc limit ?3", nativeQuery = true)
+	List<Long> findLimitStudentIdList(Long rootOrgId, Long maxStudentId, int rowNumber);
+
+	@Query(value = "select DISTINCT student_id  from ec_e_exam_student where root_org_id= :rootOrgId and student_id>= :maxStudentId and exam_id in (:examIds) order by student_id asc limit :rowNumber", nativeQuery = true)
+	List<Long> findByExamIdLimitStudentIdList(@Param("rootOrgId") Long rootOrgId,
+			@Param("examIds") List<Long> examIds, @Param("maxStudentId") Long maxStudentId,
+			@Param("rowNumber") int rowNumber);
+
+	List<ExamStudentEntity> findByExamIdAndStudentId(Long examId, Long studentId);
+
+}

+ 26 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/NoticeReceiverRuleRepo.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.NoticeReceiverRuleEntity;
+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.QueryByExampleExecutor;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+public interface NoticeReceiverRuleRepo extends JpaRepository<NoticeReceiverRuleEntity, Long>,
+        QueryByExampleExecutor<NoticeReceiverRuleEntity>, JpaSpecificationExecutor<NoticeReceiverRuleEntity> {
+
+    List<NoticeReceiverRuleEntity> findByRootOrgIdAndNoticeIdIn(Long rootOrgId,List<Long> noticeIdList);
+
+    @Transactional
+    @Modifying
+    @Query(value="delete from EC_E_NOTICE_RECEIVER_RULE where root_org_id=?1 and notice_id in ?2",nativeQuery = true)
+    int deleteByRootOrgIdAndNoticeIdIn(Long rootOrgId, List<Long> noticeIdList);
+
+    @Transactional
+    @Modifying
+    void deleteByNoticeId(Long noticeId);
+}

+ 29 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/NoticeRepo.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.NoticeEntity;
+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.QueryByExampleExecutor;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.List;
+
+public interface NoticeRepo extends JpaRepository<NoticeEntity, Long>,
+        QueryByExampleExecutor<NoticeEntity>, JpaSpecificationExecutor<NoticeEntity> {
+    List<NoticeEntity> findByIdIn(List<Long> ids);
+
+    int deleteByIdIn(List<Long> noticeIdList);
+
+    List<NoticeEntity> findByCreationTimeBefore(Date lastYear);
+
+    List<NoticeEntity> findByNoticeStatus(NoticeStatus noticeStatus);
+
+    @Transactional
+    @Modifying
+    @Query(value = "update EC_E_NOTICE set notice_status=?2 where id=?1",nativeQuery = true)
+    int updateNoticeStatus(Long noticeId,String noticeStatus);
+}

+ 32 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/NoticeRulePublishProgressRepo.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.NoticeRulePublishProgressEntity;
+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.QueryByExampleExecutor;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+public interface NoticeRulePublishProgressRepo extends JpaRepository<NoticeRulePublishProgressEntity, Long>,
+        QueryByExampleExecutor<NoticeRulePublishProgressEntity>, JpaSpecificationExecutor<NoticeRulePublishProgressEntity> {
+
+    List<NoticeRulePublishProgressEntity> findByNoticeIdIn(List<Long> noticeIdList);
+
+    @Query(value="select t1.* from ec_e_notice_rule_publish_progress t1 " +
+            " inner join ec_e_notice t2 on t1.notice_id=t2.id" +
+            " where t2.notice_status in ('PUBLISHING','TO_BE_PUBLISHED')" +
+            " order by t2.notice_status,t2.creation_time",nativeQuery = true)
+    List<NoticeRulePublishProgressEntity> findUnpublishedNoticeProcessList();
+
+    @Transactional
+    @Modifying
+    @Query(value = "delete from EC_E_NOTICE_RULE_PUBLISH_PROGRESS where root_org_id=?1 and notice_id in ?2", nativeQuery = true)
+    int deleteByRootOrgIdAndNoticeIdIn(Long rootOrgId, List<Long> noticeId);
+
+    @Transactional
+    @Modifying
+    void deleteByNoticeId(Long noticeId);
+}

+ 30 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/UniqueRuleHolder.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import java.util.List;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.examcloud.web.jpa.UniqueRule;
+
+/**
+ * 状态码范围: 111XXX
+ *
+ * @author WANGWEI
+ * @date 2019年3月8日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class UniqueRuleHolder {
+
+	private static final List<UniqueRule> LIST = Lists.newArrayList();
+
+	public static List<UniqueRule> getUniqueRuleList() {
+		return LIST;
+	}
+
+	static {
+		// ExamEntity
+		LIST.add(new UniqueRule("IDX_E_E_001001", "111001", "考试名称已被占用"));
+		LIST.add(new UniqueRule("IDX_E_E_001002", "111011", "考试编码已被占用"));
+	}
+
+}

+ 28 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/UserNoticeRepo.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.examwork.dao;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.UserNoticeEntity;
+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.QueryByExampleExecutor;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+public interface UserNoticeRepo extends JpaRepository<UserNoticeEntity, Long>,
+        QueryByExampleExecutor<UserNoticeEntity>, JpaSpecificationExecutor<UserNoticeEntity> {
+    List<UserNoticeEntity> findByRootOrgIdAndUserTypeAndUserIdOrderByCreationTimeDesc(Long rootOrgId,UserType userType,Long userId);
+
+    List<UserNoticeEntity> findByRootOrgIdAndUserTypeAndUserIdAndHasReadOrderByCreationTimeDesc(Long rootOrgId,UserType userType,Long userId,Boolean hasRead);
+
+    @Transactional
+    @Modifying
+    @Query(value = "update EC_E_USER_NOTICE set has_read = 1 where notice_id in ?1 and user_type = ?2 and user_id = ?3",nativeQuery = true)
+    int updateNoticeReadStatus(List<Long> noticeId, String userType, Long userId);
+
+    @Transactional
+    @Modifying
+    void deleteByNoticeId(Long noticeId);
+}

+ 86 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamCourseRelationEntity.java

@@ -0,0 +1,86 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+@Entity
+@Table(name = "EC_E_EXAM_COURSE_RELATION", indexes = {
+		@Index(name = "IDX_E_E_C_R_008001", columnList = "courseId", unique = false)})
+@IdClass(ExamCourseRelationPK.class)
+public class ExamCourseRelationEntity extends JpaEntity {
+
+	private static final long serialVersionUID = 757531976286006550L;
+
+	@Id
+	private Long examId;
+
+	@Id
+	private Long courseId;
+
+	@Column(nullable = false)
+	private String courseName;
+
+	@Column(nullable = false)
+	private String courseCode;
+
+	@Column(nullable = false)
+	private String courseLevel;
+
+	@Column(nullable = false)
+	private Boolean courseEnable;
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getCourseName() {
+		return courseName;
+	}
+
+	public void setCourseName(String courseName) {
+		this.courseName = courseName;
+	}
+
+	public String getCourseCode() {
+		return courseCode;
+	}
+
+	public void setCourseCode(String courseCode) {
+		this.courseCode = courseCode;
+	}
+
+	public String getCourseLevel() {
+		return courseLevel;
+	}
+
+	public void setCourseLevel(String courseLevel) {
+		this.courseLevel = courseLevel;
+	}
+
+	public Boolean getCourseEnable() {
+		return courseEnable;
+	}
+
+	public void setCourseEnable(Boolean courseEnable) {
+		this.courseEnable = courseEnable;
+	}
+
+}

+ 39 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamCourseRelationPK.java

@@ -0,0 +1,39 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import java.io.Serializable;
+
+public class ExamCourseRelationPK implements Serializable {
+
+	private static final long serialVersionUID = -9214147168423303504L;
+
+	private Long examId;
+
+	private Long courseId;
+
+	public ExamCourseRelationPK() {
+		super();
+	}
+
+	public ExamCourseRelationPK(Long examId, Long courseId) {
+		super();
+		this.examId = examId;
+		this.courseId = courseId;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+}

+ 234 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamEntity.java

@@ -0,0 +1,234 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * Created by songyue on 17/1/13.
+ */
+@Entity
+@Table(name = "EC_E_EXAM", indexes = {
+		@Index(name = "IDX_E_E_001001", columnList = "rootOrgId,name", unique = true),
+		@Index(name = "IDX_E_E_001002", columnList = "rootOrgId,code", unique = true)})
+public class ExamEntity extends JpaEntity {
+
+	private static final long serialVersionUID = 4009839764353162256L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	/**
+	 * 顶级机构Id
+	 */
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	/**
+	 * 考试批次开始时间
+	 */
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date beginTime;
+
+	/**
+	 * 考试批次结束时间
+	 */
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date endTime;
+
+	/**
+	 * 考试名称
+	 */
+	@Column(nullable = false)
+	private String name;
+
+	/**
+	 * 考试编码
+	 */
+	@Column(nullable = false)
+	private String code;
+
+	/**
+	 * 考试类型
+	 */
+	@Column(nullable = false)
+	@Enumerated(EnumType.STRING)
+	private ExamType examType;
+
+	/**
+	 * 考试时长
+	 */
+	private Integer duration;
+
+	@Column(nullable = false)
+	private Boolean enable;
+
+	/**
+	 * 考试备注
+	 */
+	private String remark;
+
+	/**
+	 * 考试次数
+	 */
+	private Long examTimes;
+
+	/**
+	 * 考生锁定(为true时,不能更新考生)
+	 */
+	private Boolean examStudentLocked;
+
+	/**
+	 * 是否禁止考试
+	 */
+	private Boolean examLimit;
+
+	/**
+	 * 开启特殊设置
+	 */
+	@Column(nullable = false)
+	private Boolean specialSettingsEnabled;
+
+	/**
+	 * 特殊设置类型
+	 */
+	@Column(nullable = true)
+	@Enumerated(EnumType.STRING)
+	private ExamSpecialSettingsType specialSettingsType;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public ExamType getExamType() {
+		return examType;
+	}
+
+	public void setExamType(ExamType examType) {
+		this.examType = examType;
+	}
+
+	public Integer getDuration() {
+		return duration;
+	}
+
+	public void setDuration(Integer duration) {
+		this.duration = duration;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	public Long getExamTimes() {
+		return examTimes;
+	}
+
+	public void setExamTimes(Long examTimes) {
+		this.examTimes = examTimes;
+	}
+
+	public Boolean getExamStudentLocked() {
+		return examStudentLocked;
+	}
+
+	public void setExamStudentLocked(Boolean examStudentLocked) {
+		this.examStudentLocked = examStudentLocked;
+	}
+
+	public Boolean getExamLimit() {
+		return examLimit;
+	}
+
+	public void setExamLimit(Boolean examLimit) {
+		this.examLimit = examLimit;
+	}
+
+	public Boolean getSpecialSettingsEnabled() {
+		return specialSettingsEnabled;
+	}
+
+	public void setSpecialSettingsEnabled(Boolean specialSettingsEnabled) {
+		this.specialSettingsEnabled = specialSettingsEnabled;
+	}
+
+	public ExamSpecialSettingsType getSpecialSettingsType() {
+		return specialSettingsType;
+	}
+
+	public void setSpecialSettingsType(ExamSpecialSettingsType specialSettingsType) {
+		this.specialSettingsType = specialSettingsType;
+	}
+
+}

+ 84 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamOrgPropertyEntity.java

@@ -0,0 +1,84 @@
+package cn.com.qmth.examcloud.core.examwork.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.Index;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 考试--机构属性配置
+ *
+ * @author WANGWEI
+ * @date 2018年8月6日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_E_EXAM_ORG_PROP", indexes = {
+		@Index(name = "IDX_E_E_ORG_PROP_001001", columnList = "examId,orgId,keyId", unique = true)})
+public class ExamOrgPropertyEntity extends JpaEntity {
+
+	private static final long serialVersionUID = 4009839764353162256L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	@Column(nullable = false)
+	private Long examId;
+
+	@Column(nullable = false)
+	private Long orgId;
+
+	@Column(nullable = false)
+	private Long keyId;
+
+	@Lob
+	private String value;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
+
+	public Long getKeyId() {
+		return keyId;
+	}
+
+	public void setKeyId(Long keyId) {
+		this.keyId = keyId;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+
+}

+ 57 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamPaperTypeRelationEntity.java

@@ -0,0 +1,57 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年9月21日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_E_EXAM_PT_RELATION")
+@IdClass(ExamPaperTypeRelationPK.class)
+public class ExamPaperTypeRelationEntity extends JpaEntity {
+
+	private static final long serialVersionUID = -5192942623365129036L;
+
+	@Id
+	private Long examId;
+
+	@Id
+	private Long courseId;
+
+	@Id
+	private String paperType;
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getPaperType() {
+		return paperType;
+	}
+
+	public void setPaperType(String paperType) {
+		this.paperType = paperType;
+	}
+
+}

+ 46 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamPaperTypeRelationPK.java

@@ -0,0 +1,46 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import java.io.Serializable;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年9月21日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamPaperTypeRelationPK implements Serializable {
+
+	private static final long serialVersionUID = -5192942623365129036L;
+
+	private Long examId;
+
+	private Long courseId;
+
+	private String paperType;
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getPaperType() {
+		return paperType;
+	}
+
+	public void setPaperType(String paperType) {
+		this.paperType = paperType;
+	}
+
+}

+ 76 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamPropertyEntity.java

@@ -0,0 +1,76 @@
+package cn.com.qmth.examcloud.core.examwork.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.Index;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 考试属性配置
+ *
+ * @author WANGWEI
+ * @date 2018年8月6日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_E_EXAM_PROP", indexes = {
+		@Index(name = "IDX_E_EXAM_PROP_001001", columnList = "examId,keyId", unique = true)})
+public class ExamPropertyEntity extends JpaEntity {
+
+	private static final long serialVersionUID = 4009839764353162256L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	/**
+	 * 考试Id
+	 */
+	@Column(nullable = false)
+	private Long examId;
+
+	@Column(nullable = false)
+	private Long keyId;
+
+	@Lob
+	private String value;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getKeyId() {
+		return keyId;
+	}
+
+	public void setKeyId(Long keyId) {
+		this.keyId = keyId;
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public void setValue(String value) {
+		this.value = value;
+	}
+
+}

+ 97 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamSiteBatchEntity.java

@@ -0,0 +1,97 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+
+/**
+ * 考点场次
+ *
+ * @author WANGWEI
+ * @date 2019年10月18日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_EXAM_SITE_BATCH", indexes = {
+		@Index(name = "IDX_E_S_BATCH_001001", columnList = "examId,siteId", unique = false)})
+public class ExamSiteBatchEntity extends WithIdJpaEntity {
+
+	private static final long serialVersionUID = 7371957453011078961L;
+
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	/**
+	 * 考试ID
+	 */
+	@Column(nullable = false)
+	private Long examId;
+
+	/**
+	 * 考点ID
+	 */
+	@Column(nullable = false)
+	private Long siteId;
+
+	/**
+	 * 场次可以考试时间段开始时间
+	 */
+	@Column(nullable = false)
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date beginTime;
+
+	/**
+	 * 场次可以考试时间段结束时间
+	 */
+	@Column(nullable = false)
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date endTime;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getSiteId() {
+		return siteId;
+	}
+
+	public void setSiteId(Long siteId) {
+		this.siteId = siteId;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+}

+ 293 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamSpecialSettingsEntity.java

@@ -0,0 +1,293 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * 考试--特殊设置<br>
+ * orgId,courseId,studentId仅有一个可以不为空<br>
+ * 考试维度: orgId==null && courseId==null && studentId==null<br>
+ * 机构维度: orgId!=null && courseId==null && studentId==null<br>
+ * 学生维度: orgId==null && courseId==null && studentId!=null<br>
+ * 课程维度: orgId==null && courseId!=null && studentId ==null<br>
+ *
+ * @author WANGWEI
+ * @date 2018年5月16日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_E_EXAM_SPECIAL_SETTINGS", indexes = {
+		@Index(name = "IDX_E_E_S_S_001001", columnList = "examId,courseId,orgId,studentId", unique = true),
+		@Index(name = "IDX_E_E_S_S_001002", columnList = "examId,courseId", unique = false),
+		@Index(name = "IDX_E_E_S_S_001003", columnList = "examId,orgId", unique = false),
+		@Index(name = "IDX_E_E_S_S_001004", columnList = "examId,studentId", unique = false),
+		@Index(name = "IDX_E_E_S_S_001005", columnList = "rootOrgId,courseId", unique = false),
+		@Index(name = "IDX_E_E_S_S_001006", columnList = "rootOrgId,orgId", unique = false),
+		@Index(name = "IDX_E_E_S_S_001007", columnList = "rootOrgId,studentId", unique = false)})
+public class ExamSpecialSettingsEntity extends JpaEntity {
+
+	private static final long serialVersionUID = -3335725218626631530L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	/**
+	 * 顶级机构ID
+	 */
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	/**
+	 * 考试ID
+	 */
+	@Column(nullable = false)
+	private Long examId;
+
+	/**
+	 * 考试ID
+	 */
+	@Column(nullable = true)
+	private Long courseId;
+
+	/**
+	 * 机构ID
+	 */
+	@Column(nullable = true)
+	private Long orgId;
+
+	/**
+	 * 学生ID
+	 */
+	@Column(nullable = true)
+	private Long studentId;
+
+	/**
+	 * 考试类型
+	 */
+	@Column(nullable = false)
+	@Enumerated(EnumType.STRING)
+	private ExamType examType;
+
+	/**
+	 * 考试是否可用
+	 */
+	@Column(nullable = false)
+	private Boolean examEnable;
+
+	/**
+	 * 考试批次开始时间
+	 */
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date beginTime;
+
+	/**
+	 * 考试批次结束时间
+	 */
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date endTime;
+
+	/**
+	 * 是否禁止考试
+	 */
+	@Column(nullable = false)
+	private Boolean examLimit;
+
+	/**
+	 * 开启特殊设置
+	 */
+	@Column(nullable = false)
+	private Boolean specialSettingsEnabled;
+
+	/**
+	 * 特殊设置类型
+	 */
+	@Column(nullable = true)
+	@Enumerated(EnumType.STRING)
+	private ExamSpecialSettingsType specialSettingsType;
+
+	/**
+	 * 扩展属性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 getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
+
+	public Long getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	public ExamType getExamType() {
+		return examType;
+	}
+
+	public void setExamType(ExamType examType) {
+		this.examType = examType;
+	}
+
+	public Boolean getExamEnable() {
+		return examEnable;
+	}
+
+	public void setExamEnable(Boolean examEnable) {
+		this.examEnable = examEnable;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public Boolean getExamLimit() {
+		return examLimit;
+	}
+
+	public void setExamLimit(Boolean examLimit) {
+		this.examLimit = examLimit;
+	}
+
+	public Boolean getSpecialSettingsEnabled() {
+		return specialSettingsEnabled;
+	}
+
+	public void setSpecialSettingsEnabled(Boolean specialSettingsEnabled) {
+		this.specialSettingsEnabled = specialSettingsEnabled;
+	}
+
+	public ExamSpecialSettingsType getSpecialSettingsType() {
+		return specialSettingsType;
+	}
+
+	public void setSpecialSettingsType(ExamSpecialSettingsType specialSettingsType) {
+		this.specialSettingsType = specialSettingsType;
+	}
+
+	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;
+	}
+
+}

+ 358 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/ExamStudentEntity.java

@@ -0,0 +1,358 @@
+package cn.com.qmth.examcloud.core.examwork.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.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+@Entity
+@Table(name = "EC_E_EXAM_STUDENT", indexes = {
+		@Index(name = "IDX_E_E_S_001001", columnList = "studentId,examId,courseId", unique = true),
+		@Index(name = "IDX_E_E_S_001002", columnList = "rootOrgId,name", unique = false),
+		@Index(name = "IDX_E_E_S_001003", columnList = "rootOrgId,studentCode", unique = false),
+		@Index(name = "IDX_E_E_S_001004", columnList = "rootOrgId,identityNumber", unique = false),
+		@Index(name = "IDX_E_E_S_001005", columnList = "courseId", unique = false),
+		@Index(name = "IDX_E_E_S_001006", columnList = "studentId", unique = false)})
+public class ExamStudentEntity extends JpaEntity {
+
+	private static final long serialVersionUID = 757531976286006550L;
+
+	@Id
+	@GeneratedValue(strategy = GenerationType.IDENTITY)
+	private Long id;
+
+	/**
+	 * 考试
+	 */
+	@Column(nullable = false)
+	private Long examId;
+
+	/**
+	 * 学生用户id
+	 */
+	@Column(nullable = false)
+	private Long studentId;
+
+	/**
+	 * 身份证号
+	 */
+	@Column(nullable = false)
+	private String identityNumber;
+
+	/**
+	 * 学号
+	 */
+	private String studentCode;
+
+	/**
+	 * 考生姓名
+	 */
+	@Column(nullable = false)
+	private String name;
+
+	/**
+	 * 学校id
+	 */
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	/**
+	 * 学习中心id
+	 */
+	@Column(nullable = false)
+	private Long orgId;
+
+	/**
+	 * 学习中心编码
+	 */
+	@Column(nullable = false)
+	private String orgCode;
+
+	/**
+	 * 课程ID
+	 */
+	@Column(nullable = false)
+	private Long courseId;
+
+	/**
+	 * 课程名称
+	 */
+	@Column(nullable = false)
+	private String courseName;
+
+	/**
+	 * 课程ID
+	 */
+	@Column(nullable = false)
+	private String courseCode;
+
+	/**
+	 * 课程等级
+	 */
+	@Column(nullable = false)
+	private String courseLevel;
+
+	/**
+	 * 试卷类型
+	 */
+	private String paperType;
+
+	/**
+	 * 专业名称
+	 */
+	private String specialtyName;
+
+	/**
+	 * 年级
+	 */
+	private String grade;
+
+	/**
+	 * 备注
+	 */
+	private String remark;
+
+	/**
+	 * 考点
+	 */
+	private String examSite;
+
+	/**
+	 * 信息采集人
+	 */
+	private String infoCollector;
+
+	@Column(nullable = false)
+	private Boolean enable;
+
+	/**
+	 * 扩展属性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 getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	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 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 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 getGrade() {
+		return grade;
+	}
+
+	public void setGrade(String grade) {
+		this.grade = grade;
+	}
+
+	public String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	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 Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public String getCourseLevel() {
+		return courseLevel;
+	}
+
+	public void setCourseLevel(String courseLevel) {
+		this.courseLevel = courseLevel;
+	}
+
+	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 getOrgCode() {
+		return orgCode;
+	}
+
+	public void setOrgCode(String orgCode) {
+		this.orgCode = orgCode;
+	}
+
+	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;
+	}
+
+}

+ 118 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/NoticeEntity.java

@@ -0,0 +1,118 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Index;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+
+import org.springframework.format.annotation.DateTimeFormat;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+
+/**
+ * 公告
+ *
+ * @author WANGWEI
+ * @date 2019年6月27日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_E_NOTICE", indexes = {
+		@Index(name = "IDX_E_NOTICE_001001", columnList = "rootOrgId", unique = false)})
+public class NoticeEntity extends WithIdJpaEntity {
+
+	private static final long serialVersionUID = 7071809872436511009L;
+
+	/**
+	 * 学校id
+	 */
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	/**
+	 * 标题
+	 */
+	@Column(nullable = false, length = 100)
+	private String title;
+
+	/**
+	 * 内容
+	 */
+	@Lob
+	private String content;
+
+	/**
+	 * 发布者
+	 */
+	@Column(nullable = false, length = 100)
+	private String publisher;
+
+	/**
+	 * 发布时间
+	 */
+	@Column(nullable = true)
+	@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	private Date publishTime;
+
+	/**
+	 * 公告状态
+	 */
+	@Column(nullable = false)
+	@Enumerated(EnumType.STRING)
+	private NoticeStatus noticeStatus;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getContent() {
+		return content;
+	}
+
+	public void setContent(String content) {
+		this.content = content;
+	}
+
+	public String getPublisher() {
+		return publisher;
+	}
+
+	public void setPublisher(String publisher) {
+		this.publisher = publisher;
+	}
+
+	public Date getPublishTime() {
+		return publishTime;
+	}
+
+	public void setPublishTime(Date publishTime) {
+		this.publishTime = publishTime;
+	}
+
+	public NoticeStatus getNoticeStatus() {
+		return noticeStatus;
+	}
+
+	public void setNoticeStatus(NoticeStatus noticeStatus) {
+		this.noticeStatus = noticeStatus;
+	}
+
+}

+ 89 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/NoticeReceiverRuleEntity.java

@@ -0,0 +1,89 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+
+/**
+ * 公告接收规则
+ *
+ * @author WANGWEI
+ * @date 2019年6月27日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_E_NOTICE_RECEIVER_RULE", indexes = {
+		@Index(name = "IDX_E_N_RULE_001001", columnList = "rootOrgId", unique = false),
+		@Index(name = "IDX_E_N_RULE_001002", columnList = "noticeId", unique = false),
+		@Index(name = "IDX_E_N_RULE_001003", columnList = "noticeId,ruleType,ruleValue", unique = false)})
+public class NoticeReceiverRuleEntity extends WithIdJpaEntity {
+
+	private static final long serialVersionUID = 7071809872436511009L;
+
+	/**
+	 * 学校id
+	 */
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	/**
+	 * 公告ID
+	 */
+	@Column(nullable = false)
+	private Long noticeId;
+
+	/**
+	 * 规则类型
+	 */
+	@Column(nullable = false)
+	@Enumerated(EnumType.STRING)
+	private NoticeReceiverRuleType ruleType;
+
+	/**
+	 * 规则值<br>
+	 * ruleType=STUDENTS_OF_EXAM,ruleValue为examId<br>
+	 * ruleType=ALL_STUDENTS,ruleValue为null<br>
+	 * ruleType=COMMON_USERS_OF_ROEL,ruleValue为roleId<br>
+	 */
+	@Column(nullable = true, length = 100)
+	private String ruleValue;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getNoticeId() {
+		return noticeId;
+	}
+
+	public void setNoticeId(Long noticeId) {
+		this.noticeId = noticeId;
+	}
+
+	public NoticeReceiverRuleType getRuleType() {
+		return ruleType;
+	}
+
+	public void setRuleType(NoticeReceiverRuleType ruleType) {
+		this.ruleType = ruleType;
+	}
+
+	public String getRuleValue() {
+		return ruleValue;
+	}
+
+	public void setRuleValue(String ruleValue) {
+		this.ruleValue = ruleValue;
+	}
+
+}

+ 94 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/NoticeRulePublishProgressEntity.java

@@ -0,0 +1,94 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+
+/**
+ * 公告发布规则进度
+ * 
+ * @author lideyin
+ * @date 20100701
+ */
+@Entity
+@Table(name = "EC_E_NOTICE_RULE_PUBLISH_PROGRESS", indexes = {
+		@Index(name = "IDX_E_NOTICE_R_P_R_001001", columnList = "noticeId,noticeReceiverRuleType", unique = true)})
+public class NoticeRulePublishProgressEntity extends WithIdJpaEntity {
+
+	private static final long serialVersionUID = 7071809872436511009L;
+
+	/**
+	 * 公告id
+	 */
+	private Long noticeId;
+
+	/**
+	 * 规则类型
+	 */
+	@Column(nullable = false)
+	@Enumerated(EnumType.STRING)
+	private NoticeReceiverRuleType noticeReceiverRuleType;
+
+	/**
+	 * 学校id
+	 */
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	/**
+	 * 已发布的最大用户id
+	 */
+	private Long maxStudentId;
+
+	/**
+	 * 已发布的最大用户id
+	 */
+	private Long maxCommonUserId;
+
+	public Long getNoticeId() {
+		return noticeId;
+	}
+
+	public void setNoticeId(Long noticeId) {
+		this.noticeId = noticeId;
+	}
+
+	public NoticeReceiverRuleType getNoticeReceiverRuleType() {
+		return noticeReceiverRuleType;
+	}
+
+	public void setNoticeReceiverRuleType(NoticeReceiverRuleType noticeReceiverRuleType) {
+		this.noticeReceiverRuleType = noticeReceiverRuleType;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getMaxStudentId() {
+		return maxStudentId;
+	}
+
+	public void setMaxStudentId(Long maxStudentId) {
+		this.maxStudentId = maxStudentId;
+	}
+
+	public Long getMaxCommonUserId() {
+		return maxCommonUserId;
+	}
+
+	public void setMaxCommonUserId(Long maxCommonUserId) {
+		this.maxCommonUserId = maxCommonUserId;
+	}
+
+}

+ 72 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/SiteEntity.java

@@ -0,0 +1,72 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.WithIdAndStatusJpaEntity;
+
+/**
+ * 考点
+ *
+ * @author WANGWEI
+ * @date 2019年10月18日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_E_SITE", indexes = {
+		@Index(name = "IDX_E_SITE_001001", columnList = "rootOrgId,code", unique = true),
+		@Index(name = "IDX_E_SITE_001002", columnList = "rootOrgId,name", unique = false)})
+public class SiteEntity extends WithIdAndStatusJpaEntity {
+
+	private static final long serialVersionUID = -8373763465850491821L;
+
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	@Column(nullable = false)
+	private String code;
+
+	@Column(nullable = false)
+	private String name;
+
+	/**
+	 * 考点最大人数
+	 */
+	@Column(nullable = false)
+	private Long maxNumOfStudent;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Long getMaxNumOfStudent() {
+		return maxNumOfStudent;
+	}
+
+	public void setMaxNumOfStudent(Long maxNumOfStudent) {
+		this.maxNumOfStudent = maxNumOfStudent;
+	}
+
+}

+ 99 - 0
examcloud-core-examwork-dao/src/main/java/cn/com/qmth/examcloud/core/examwork/dao/entity/UserNoticeEntity.java

@@ -0,0 +1,99 @@
+package cn.com.qmth.examcloud.core.examwork.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
+
+/**
+ * 用户公告表
+ *
+ * @author WANGWEI
+ * @date 2019年6月27日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Entity
+@Table(name = "EC_E_USER_NOTICE", indexes = {
+		@Index(name = "IDX_E_U_NOTICE_001001", columnList = "noticeId", unique = false),
+		@Index(name = "IDX_E_U_NOTICE_001002", columnList = "noticeId,userType,userId", unique = true),
+		@Index(name = "IDX_E_U_NOTICE_001003", columnList = "userId", unique = false)})
+public class UserNoticeEntity extends WithIdJpaEntity {
+
+	private static final long serialVersionUID = 7071809872436511009L;
+
+	/**
+	 * 学校id
+	 */
+	@Column(nullable = false)
+	private Long rootOrgId;
+
+	/**
+	 * 公告ID
+	 */
+	@Column(nullable = false)
+	private Long noticeId;
+
+	/**
+	 * 用户类型
+	 */
+	@Column(nullable = false)
+	@Enumerated(EnumType.STRING)
+	private UserType userType;
+
+	/**
+	 * 用户ID
+	 */
+	@Column(nullable = false)
+	private Long userId;
+
+	/**
+	 * 是否已读
+	 */
+	@Column(nullable = false)
+	private Boolean hasRead;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getNoticeId() {
+		return noticeId;
+	}
+
+	public void setNoticeId(Long noticeId) {
+		this.noticeId = noticeId;
+	}
+
+	public UserType getUserType() {
+		return userType;
+	}
+
+	public void setUserType(UserType userType) {
+		this.userType = userType;
+	}
+
+	public Long getUserId() {
+		return userId;
+	}
+
+	public void setUserId(Long userId) {
+		this.userId = userId;
+	}
+
+	public Boolean getHasRead() {
+		return hasRead;
+	}
+
+	public void setHasRead(Boolean hasRead) {
+		this.hasRead = hasRead;
+	}
+}

+ 45 - 0
examcloud-core-examwork-service/pom.xml

@@ -0,0 +1,45 @@
+<?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.core.examwork</groupId>
+        <artifactId>examcloud-core-examwork</artifactId>
+        <version>2019-SNAPSHOT</version>
+    </parent>
+    <artifactId>examcloud-core-examwork-service</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.core.examwork</groupId>
+            <artifactId>examcloud-core-examwork-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-oe-admin-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-task-api-client</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+
+    </dependencies>
+
+</project>

+ 82 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/ExamService.java

@@ -0,0 +1,82 @@
+package cn.com.qmth.examcloud.core.examwork.service;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.api.commons.enums.CURD;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamInfo;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamSpecialSettingsInfo;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年8月17日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public interface ExamService {
+
+	/**
+	 * 保存考试
+	 *
+	 * @author WANGWEI
+	 * @param examInfo
+	 * @return
+	 */
+	ExamEntity saveExam(ExamInfo examInfo, CURD es);
+
+	/**
+	 * 保存考试机构设置
+	 *
+	 * @author WANGWEI
+	 * @param examSpecialInfo
+	 * @return
+	 */
+	ExamSpecialSettingsEntity saveExamSpecialSettings(ExamSpecialSettingsInfo examSpecialInfo);
+
+	/**
+	 * 查询考试机构属性
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @param orgId
+	 * @param key
+	 * @return
+	 */
+	String getExamOrgProperty(Long examId, Long orgId, String key);
+
+	/**
+	 * 查询考试学生属性
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @param studentId
+	 * @param key
+	 * @return
+	 */
+	String getExamStudentProperty(Long examId, Long studentId, String key);
+
+	/**
+	 * 导入学习中心设置
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @param file
+	 * @return
+	 */
+	List<Map<String, Object>> importExamOrgSettings(Long examId, File file);
+
+	/**
+	 * 查询考试属性
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @param key
+	 * @return
+	 */
+	String getExamProperty(Long examId, String key);
+
+}

+ 24 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/ExamStudentService.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.examcloud.core.examwork.service;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamStudentInfo;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年8月16日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public interface ExamStudentService {
+
+	public ExamStudentInfo saveExamStudent(ExamStudentInfo examStudentInfo);
+
+	public void deleteExamStudentsByExamId(Long examId);
+
+	public void deleteExamStudentsByStudentIds(List<Long> studentIds);
+
+	public void syncExamStudent(Long studentId);
+
+}

+ 104 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/NoticeService.java

@@ -0,0 +1,104 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:28:53.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.service;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.NoticeRulePublishProgressEntity;
+import cn.com.qmth.examcloud.core.examwork.service.bean.*;
+
+import java.util.List;
+
+/**
+ * 用户通知接口
+ */
+public interface NoticeService {
+
+	/**
+	 * 获取用户通知列表
+	 * 
+	 * @param query
+	 * @return
+	 */
+	List<UserNoticeInfo> getNoticeList(UserNoticeInfoQuery query);
+
+	/**
+	 * 更新用户的通知状态为已读
+	 * 
+	 * @param noticeId
+	 *            通知id(多个以逗号分隔)
+	 * @param userType
+	 *            用户类型
+	 * @param userId
+	 *            用户id
+	 * @return
+	 */
+	int updateNoticeReadStatus(String noticeId, UserType userType, Long userId);
+
+	/**
+	 * 分页获取通知列表
+	 * 
+	 * @param curPage
+	 * @param pageSize
+	 * @param infoQuery
+	 * @return
+	 */
+	PageInfo<NoticeInfo> getPagedNoticeList(Integer curPage, Integer pageSize,
+			NoticeInfoQuery infoQuery);
+
+	/**
+	 * 删除通知
+	 * 
+	 * @param rootOrgId
+	 * @param noticeIdList
+	 * @return
+	 */
+	int deleteNotice(Long rootOrgId, List<Long> noticeIdList);
+
+	/**
+	 * 添加通知
+	 * 
+	 * @param addNoticeInfo
+	 * @return
+	 */
+	int addNotice(AddNoticeInfo addNoticeInfo);
+
+	/**
+	 * 更新通知信息
+	 * 
+	 * @param info
+	 * @return
+	 */
+	NoticeRulePublishProgressEntity updateNotice(UpdateNoticeInfo info);
+
+	/**
+	 * 获取待处理的通知规则发布进度集合
+	 * @return
+	 */
+    List<NoticeRulePublishProgressEntity> getToBeDisposedNoticeRulePublishProgressList();
+
+	/**
+	 * 处理待发送的用户通知(自动服务调用)
+	 * @param startUserId
+	 * @return 下次请求的用户id
+	 */
+	Long disposePublishingUserNotice(Long startUserId,NoticeRulePublishProgressEntity rulePublishProcess);
+
+	/**
+	 * 处理过期的通知数据(自动服务调用)
+	 */
+	void disposeOverdueNotice();
+
+	/**
+	 * 更新通知状态
+	 * @param noticeId
+	 * @param noticeStatus
+	 */
+	void updateNoticeStatus(Long noticeId, NoticeStatus noticeStatus);
+}

+ 29 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/OnGoingExamService.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.core.examwork.service;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+
+/**
+ * 待考考试服务
+ *
+ * @author WANGWEI
+ * @date 2019年10月25日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public interface OnGoingExamService {
+
+	/**
+	 * 待考考试列表
+	 *
+	 * @author WANGWEI
+	 * @param rootOrgId
+	 * @param examType
+	 * @param orgId
+	 * @param studentId
+	 * @return
+	 */
+	List<ExamSpecialSettingsEntity> getOngoingExamList(Long rootOrgId, String examType, Long orgId,
+			Long studentId);
+
+}

+ 127 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/AddNoticeInfo.java

@@ -0,0 +1,127 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 添加通知信息实体
+ */
+public class AddNoticeInfo implements JsonSerializable {
+
+    private static final long serialVersionUID = -7739022488162924094L;
+
+    /**
+     * 组织机构id
+     */
+    private Long rootOrgId;
+    /**
+     * 用户id
+     */
+    private Long userId;
+    /**
+     * 标题
+     */
+    private String title;
+    /**
+     * 内容
+     */
+    private String content;
+
+    /**
+     * 规则类型
+     */
+    @Enumerated(EnumType.STRING)
+    private NoticeReceiverRuleType ruleType;
+
+    /**
+     * 公告状态
+     */
+    @Enumerated(EnumType.STRING)
+    @NotNull(message = "公告状态")
+    private NoticeStatus noticeStatus;
+    /**
+     * 发送对象
+     */
+    private String publishObjectId;
+    /**
+     * 发布者
+     */
+    private String publisher;
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getPublishObjectId() {
+        return publishObjectId;
+    }
+
+    public void setPublishObjectId(String publishObjectId) {
+        this.publishObjectId = publishObjectId;
+    }
+
+    public String getPublisher() {
+        return publisher;
+    }
+
+    public void setPublisher(String publisher) {
+        this.publisher = publisher;
+    }
+
+
+    public NoticeReceiverRuleType getRuleType() {
+        return ruleType;
+    }
+
+    public void setRuleType(NoticeReceiverRuleType ruleType) {
+        this.ruleType = ruleType;
+    }
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public NoticeStatus getNoticeStatus() {
+        return noticeStatus;
+    }
+
+    public void setNoticeStatus(NoticeStatus noticeStatus) {
+        this.noticeStatus = noticeStatus;
+    }
+}

+ 209 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/ExamInfo.java

@@ -0,0 +1,209 @@
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import java.util.Date;
+import java.util.Map;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.validation.constraints.NotNull;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年8月17日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamInfo implements JsonSerializable {
+
+	private static final long serialVersionUID = 4009839764353162256L;
+
+	/**
+	 * 顶级机构Id
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 考试批次开始时间
+	 */
+	private Date beginTime;
+
+	/**
+	 * 考试批次结束时间
+	 */
+	private Date endTime;
+
+	/**
+	 * 考试名称
+	 */
+	@NotNull
+	private String name;
+
+	/**
+	 * 考试编码
+	 */
+	private String code;
+
+	/**
+	 * 考试类型
+	 */
+	@Enumerated(EnumType.STRING)
+	private ExamType examType;
+
+	/**
+	 * 考试时长
+	 */
+	private Integer duration;
+
+	/**
+	 * 是否可用
+	 */
+	private Boolean enable;
+
+	/**
+	 * 考试备注
+	 */
+	private String remark;
+
+	/**
+	 * 考试次数
+	 */
+	private Long examTimes;
+
+	/**
+	 * 是否禁止考试
+	 */
+	private Boolean examLimit;
+
+	/**
+	 * 开启特殊设置
+	 */
+	private Boolean specialSettingsEnabled;
+
+	/**
+	 * 特殊设置类型
+	 */
+	private ExamSpecialSettingsType specialSettingsType;
+
+	/**
+	 * 考试属性
+	 */
+	private Map<String, String> properties;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public ExamType getExamType() {
+		return examType;
+	}
+
+	public void setExamType(ExamType examType) {
+		this.examType = examType;
+	}
+
+	public Integer getDuration() {
+		return duration;
+	}
+
+	public void setDuration(Integer duration) {
+		this.duration = duration;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	public Long getExamTimes() {
+		return examTimes;
+	}
+
+	public void setExamTimes(Long examTimes) {
+		this.examTimes = examTimes;
+	}
+
+	public Boolean getExamLimit() {
+		return examLimit;
+	}
+
+	public void setExamLimit(Boolean examLimit) {
+		this.examLimit = examLimit;
+	}
+
+	public Map<String, String> getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Map<String, String> properties) {
+		this.properties = properties;
+	}
+
+	public Boolean getSpecialSettingsEnabled() {
+		return specialSettingsEnabled;
+	}
+
+	public void setSpecialSettingsEnabled(Boolean specialSettingsEnabled) {
+		this.specialSettingsEnabled = specialSettingsEnabled;
+	}
+
+	public ExamSpecialSettingsType getSpecialSettingsType() {
+		return specialSettingsType;
+	}
+
+	public void setSpecialSettingsType(ExamSpecialSettingsType specialSettingsType) {
+		this.specialSettingsType = specialSettingsType;
+	}
+
+}

+ 143 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/ExamSpecialSettingsInfo.java

@@ -0,0 +1,143 @@
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import java.util.Date;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 考试特殊设置
+ *
+ * @author WANGWEI
+ * @date 2018年5月16日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamSpecialSettingsInfo implements JsonSerializable {
+
+	private static final long serialVersionUID = -3335725218626631530L;
+
+	private Long id;
+
+	/**
+	 * 考试ID
+	 */
+	private Long examId;
+
+	/**
+	 * 顶级机构ID
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 机构ID
+	 */
+	private Long orgId;
+
+	/**
+	 * 学生ID
+	 */
+	private Long studentId;
+
+	/**
+	 * 课程ID
+	 */
+	private Long courseId;
+
+	/**
+	 * 考试批次开始时间
+	 */
+	private Date beginTime;
+
+	/**
+	 * 考试批次结束时间
+	 */
+	private Date endTime;
+
+	/**
+	 * 是否禁止考试
+	 */
+	private Boolean examLimit;
+
+	private Map<String, String> properties;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public 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 Long getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public Date getBeginTime() {
+		return beginTime;
+	}
+
+	public void setBeginTime(Date beginTime) {
+		this.beginTime = beginTime;
+	}
+
+	public Date getEndTime() {
+		return endTime;
+	}
+
+	public void setEndTime(Date endTime) {
+		this.endTime = endTime;
+	}
+
+	public Boolean getExamLimit() {
+		return examLimit;
+	}
+
+	public void setExamLimit(Boolean examLimit) {
+		this.examLimit = examLimit;
+	}
+
+	public Map<String, String> getProperties() {
+		return properties;
+	}
+
+	public void setProperties(Map<String, String> properties) {
+		this.properties = properties;
+	}
+
+}

+ 401 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/ExamStudentInfo.java

@@ -0,0 +1,401 @@
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import java.util.Date;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年7月18日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class ExamStudentInfo {
+
+	private Long id;
+
+	/**
+	 * 顶级机构ID
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 考试ID
+	 */
+	private Long examId;
+
+	/**
+	 * 考试编码
+	 */
+	private String examCode;
+
+	/**
+	 * 考试名称
+	 */
+	private String examName;
+
+	/**
+	 * 学生ID
+	 */
+	private Long studentId;
+
+	/**
+	 * 机构ID
+	 */
+	private Long orgId;
+
+	/**
+	 * 机构名称
+	 */
+	private String orgName;
+
+	/**
+	 * 机构编码
+	 */
+	private String orgCode;
+
+	/**
+	 * 学生姓名
+	 */
+	private String studentName;
+
+	/**
+	 * 学生学号
+	 */
+	private String studentCode;
+
+	/**
+	 * 学生身份证号
+	 */
+	private String identityNumber;
+
+	/**
+	 * 考试课程名称
+	 */
+	private String courseName;
+
+	/**
+	 * 考试课程ID
+	 */
+	private Long courseId;
+
+	/**
+	 * 考试课程code
+	 */
+	private String courseCode;
+
+	/**
+	 * 考试课程level
+	 */
+	private String courseLevel;
+
+	/**
+	 * 是否可用
+	 */
+	private Boolean enable;
+
+	/**
+	 * 试卷类型
+	 */
+	private String paperType;
+
+	/**
+	 * 信息采集人
+	 */
+	private String infoCollector;
+
+	/**
+	 * 考点
+	 */
+	private String examSite;
+
+	/**
+	 * 专业名称
+	 */
+	private String specialtyName;
+
+	/**
+	 * 年级
+	 */
+	private String grade;
+
+	/**
+	 * 备注
+	 */
+	private String remark;
+
+	/**
+	 * 特殊考试批次开始时间
+	 */
+	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 getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public String getExamCode() {
+		return examCode;
+	}
+
+	public void setExamCode(String examCode) {
+		this.examCode = examCode;
+	}
+
+	public String getExamName() {
+		return examName;
+	}
+
+	public void setExamName(String examName) {
+		this.examName = examName;
+	}
+
+	public Long getStudentId() {
+		return studentId;
+	}
+
+	public void setStudentId(Long studentId) {
+		this.studentId = studentId;
+	}
+
+	public String getStudentName() {
+		return studentName;
+	}
+
+	public void setStudentName(String studentName) {
+		this.studentName = studentName;
+	}
+
+	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 String getCourseName() {
+		return courseName;
+	}
+
+	public void setCourseName(String courseName) {
+		this.courseName = courseName;
+	}
+
+	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 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 getInfoCollector() {
+		return infoCollector;
+	}
+
+	public void setInfoCollector(String infoCollector) {
+		this.infoCollector = infoCollector;
+	}
+
+	public String getExamSite() {
+		return examSite;
+	}
+
+	public void setExamSite(String examSite) {
+		this.examSite = examSite;
+	}
+
+	public String getSpecialtyName() {
+		return specialtyName;
+	}
+
+	public void setSpecialtyName(String specialtyName) {
+		this.specialtyName = specialtyName;
+	}
+
+	public String getGrade() {
+		return grade;
+	}
+
+	public void setGrade(String grade) {
+		this.grade = grade;
+	}
+
+	public String getRemark() {
+		return remark;
+	}
+
+	public void setRemark(String remark) {
+		this.remark = remark;
+	}
+
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
+
+	public String getOrgName() {
+		return orgName;
+	}
+
+	public void setOrgName(String orgName) {
+		this.orgName = orgName;
+	}
+
+	public String getOrgCode() {
+		return orgCode;
+	}
+
+	public void setOrgCode(String orgCode) {
+		this.orgCode = orgCode;
+	}
+
+	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;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	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;
+	}
+
+}

+ 48 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/GetLimitUserIdResp.java

@@ -0,0 +1,48 @@
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import java.util.List;
+
+/**
+ * @Description 获取指定数量用户id的响应类
+ * @Author THINKPAD
+ * @Date 2019/7/10 15:08
+ * @Version 1.0
+ */
+public class GetLimitUserIdResp {
+    /**
+     * id集合
+     */
+    List<Long> idList;
+    /**
+     * 集合中最大用户id
+     */
+    Long maxId;
+    /**
+     * 下一个id
+     */
+    Long nextId;
+
+    public List<Long> getIdList() {
+        return idList;
+    }
+
+    public void setIdList(List<Long> idList) {
+        this.idList = idList;
+    }
+
+    public Long getNextId() {
+        return nextId;
+    }
+
+    public void setNextId(Long nextId) {
+        this.nextId = nextId;
+    }
+
+    public Long getMaxId() {
+        return maxId;
+    }
+
+    public void setMaxId(Long maxId) {
+        this.maxId = maxId;
+    }
+}

+ 142 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/NoticeInfo.java

@@ -0,0 +1,142 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 考试分数信息
+ *
+ * @author: fengdesheng
+ * @since: 2018/8/31
+ */
+public class NoticeInfo implements JsonSerializable {
+
+	private static final long serialVersionUID = -7739022488162924094L;
+
+	/**
+	 * 公告id
+	 */
+	@ApiModelProperty("公告id")
+	private Long id;
+
+	/**
+	 * 标题
+	 */
+	@ApiModelProperty("标题")
+	private String title;
+
+	/**
+	 * 发送对象
+	 */
+	@ApiModelProperty("发送对象")
+	private java.util.List<Map<String, Object>> publishObject;
+
+	/**
+	 * 发布者
+	 */
+	@ApiModelProperty("发布者")
+	private String publisher;
+
+	/**
+	 * 发布时间
+	 */
+	@ApiModelProperty("发布时间")
+	private Date publishTime;
+
+	/**
+	 * 发送状态
+	 */
+	@ApiModelProperty("发送状态")
+	@Enumerated(EnumType.STRING)
+	private NoticeStatus publishStatus;
+
+	/**
+	 * 规则类型
+	 */
+	@Enumerated(EnumType.STRING)
+	private NoticeReceiverRuleType ruleType;
+
+	/**
+	 * 内容
+	 */
+	private String content;
+
+	public String getContent() {
+		return content;
+	}
+
+	public void setContent(String content) {
+		this.content = content;
+	}
+
+	public NoticeReceiverRuleType getRuleType() {
+		return ruleType;
+	}
+
+	public void setRuleType(NoticeReceiverRuleType ruleType) {
+		this.ruleType = ruleType;
+	}
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public List<Map<String, Object>> getPublishObject() {
+		return publishObject;
+	}
+
+	public void setPublishObject(List<Map<String, Object>> publishObject) {
+		this.publishObject = publishObject;
+	}
+
+	public String getPublisher() {
+		return publisher;
+	}
+
+	public void setPublisher(String publisher) {
+		this.publisher = publisher;
+	}
+
+	public Date getPublishTime() {
+		return publishTime;
+	}
+
+	public void setPublishTime(Date publishTime) {
+		this.publishTime = publishTime;
+	}
+
+	public NoticeStatus getPublishStatus() {
+		return publishStatus;
+	}
+
+	public void setPublishStatus(NoticeStatus publishStatus) {
+		this.publishStatus = publishStatus;
+	}
+}

+ 77 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/NoticeInfoQuery.java

@@ -0,0 +1,77 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:03.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+
+/**
+ * 公告查询类
+ */
+@ApiModel
+public class NoticeInfoQuery implements JsonSerializable {
+
+    private static final long serialVersionUID = -1962646957678995495L;
+
+    /**
+     * 学校id
+     */
+    private Long rootOrgId;
+    /**
+     * 用户id
+     */
+    private long userId;
+    /**
+     * 标题
+     */
+    @ApiModelProperty("标题")
+    private String title;
+    /**
+     * 发送状态
+     */
+    @ApiModelProperty("发送状态")
+    @Enumerated(EnumType.STRING)
+    private NoticeStatus publishStatus;
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(long userId) {
+        this.userId = userId;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public NoticeStatus getPublishStatus() {
+        return publishStatus;
+    }
+
+    public void setPublishStatus(NoticeStatus publishStatus) {
+        this.publishStatus = publishStatus;
+    }
+}

+ 137 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/UpdateNoticeInfo.java

@@ -0,0 +1,137 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.validation.constraints.NotNull;
+
+/**
+ * 添加通知信息实体
+ */
+public class UpdateNoticeInfo implements JsonSerializable {
+
+    private static final long serialVersionUID = -7739022488162924094L;
+
+    /**
+     * 通知id
+     */
+    private Long id;
+    /**
+     * 组织机构id
+     */
+    private Long rootOrgId;
+    /**
+     * 用户id
+     */
+    private Long userId;
+    /**
+     * 标题
+     */
+    private String title;
+    /**
+     * 内容
+     */
+    private String content;
+
+    /**
+     * 规则类型
+     */
+    private NoticeReceiverRuleType ruleType;
+    /**
+     * 发送对象
+     */
+    private String publishObjectId;
+    /**
+     * 发布者
+     */
+    private String publisher;
+
+    /**
+     * 公告状态
+     */
+    @Enumerated(EnumType.STRING)
+    @NotNull(message = "公告状态")
+    private NoticeStatus noticeStatus;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getPublishObjectId() {
+        return publishObjectId;
+    }
+
+    public void setPublishObjectId(String publishObjectId) {
+        this.publishObjectId = publishObjectId;
+    }
+
+    public String getPublisher() {
+        return publisher;
+    }
+
+    public void setPublisher(String publisher) {
+        this.publisher = publisher;
+    }
+
+    public NoticeReceiverRuleType getRuleType() {
+        return ruleType;
+    }
+
+    public void setRuleType(NoticeReceiverRuleType ruleType) {
+        this.ruleType = ruleType;
+    }
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public NoticeStatus getNoticeStatus() {
+        return noticeStatus;
+    }
+
+    public void setNoticeStatus(NoticeStatus noticeStatus) {
+        this.noticeStatus = noticeStatus;
+    }
+}

+ 99 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/UserNoticeInfo.java

@@ -0,0 +1,99 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:22.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.util.Date;
+
+/**
+ * 用户通知详情
+ */
+public class UserNoticeInfo implements JsonSerializable {
+
+    private static final long serialVersionUID = -7739022488162924094L;
+    /**
+     * 公告id
+     */
+    @ApiModelProperty("公告id")
+    private Long id;
+    /**
+     * 标题
+     */
+    @ApiModelProperty("标题")
+    private String title;
+    /**
+     * 内容
+     */
+    @ApiModelProperty("内容")
+    private String content;
+    /**
+     * 发布者
+     */
+    @ApiModelProperty("发布者")
+    private String publisher;
+    /**
+     * 发布时间
+     */
+    @ApiModelProperty("发布时间")
+    private Date publishTime;
+    /**
+     * 读取状态
+     */
+    @ApiModelProperty("读取状态")
+    private Boolean hasRead;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getPublisher() {
+        return publisher;
+    }
+
+    public void setPublisher(String publisher) {
+        this.publisher = publisher;
+    }
+
+    public Date getPublishTime() {
+        return publishTime;
+    }
+
+    public void setPublishTime(Date publishTime) {
+        this.publishTime = publishTime;
+    }
+
+    public Boolean getHasRead() {
+        return hasRead;
+    }
+
+    public void setHasRead(Boolean hasRead) {
+        this.hasRead = hasRead;
+    }
+}

+ 75 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/bean/UserNoticeInfoQuery.java

@@ -0,0 +1,75 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-08-31 09:35:03.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.examwork.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 公告查询类
+ */
+@ApiModel
+public class UserNoticeInfoQuery implements JsonSerializable {
+
+    private static final long serialVersionUID = -1962646957678995495L;
+    /**
+     * 学校id
+     */
+    @ApiModelProperty("学校id")
+    private Long rootOrgId;
+    /**
+     * 用户类型
+     */
+    @ApiModelProperty("用户类型")
+    private UserType userType;
+
+    /**
+     * 用户id
+     */
+    @ApiModelProperty("用户id")
+    private Long userId;
+    /**
+     * 是否已读(true,已读;false,未读)
+     */
+    @ApiModelProperty("是否已读")
+    private Boolean hasRead;
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public Boolean getHasRead() {
+        return hasRead;
+    }
+
+    public void setHasRead(Boolean hasRead) {
+        this.hasRead = hasRead;
+    }
+
+    public UserType getUserType() {
+        return userType;
+    }
+
+    public void setUserType(UserType userType) {
+        this.userType = userType;
+    }
+}

+ 60 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamOrgPropertyCache.java

@@ -0,0 +1,60 @@
+package cn.com.qmth.examcloud.core.examwork.service.cache;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnum;
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnumManager;
+import cn.com.qmth.examcloud.core.examwork.base.enums.ExamProperty;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamOrgPropertyRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamOrgPropertyEntity;
+import cn.com.qmth.examcloud.core.examwork.service.ExamService;
+import cn.com.qmth.examcloud.support.cache.bean.ExamOrgPropertyCacheBean;
+import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
+
+@Service
+public class ExamOrgPropertyCache extends RandomObjectRedisCache<ExamOrgPropertyCacheBean> {
+
+	@Autowired
+	ExamService examService;
+
+	@Autowired
+	ExamOrgPropertyRepo examOrgPropertyRepo;
+
+	@Override
+	public ExamOrgPropertyCacheBean loadFromResource(Object... keys) {
+		Long examId = (Long) keys[0];
+		Long orgId = (Long) keys[1];
+		String key = (String) keys[2];
+
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+		DynamicEnum de = manager.getByName(key);
+
+		ExamOrgPropertyCacheBean b = new ExamOrgPropertyCacheBean();
+		b.setExamId(examId);
+		b.setOrgId(orgId);
+		b.setKey(key);
+
+		ExamOrgPropertyEntity examOrgPropertyEntity = examOrgPropertyRepo
+				.findByExamIdAndOrgIdAndKeyId(examId, orgId, de.getId());
+		if (null != examOrgPropertyEntity) {
+			b.setValue(examOrgPropertyEntity.getValue());
+		} else {
+			b.setHasValue(false);
+		}
+
+		return b;
+	}
+
+	@Override
+	protected String getKeyPrefix() {
+		return "E_EXAM_ORG_PROP:";
+	}
+
+	@Override
+	protected int getTimeout() {
+		// 10s
+		return 10;
+	}
+
+}

+ 71 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamOrgSettingsCache.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.examcloud.core.examwork.service.cache;
+
+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.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamSpecialSettingsRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+import cn.com.qmth.examcloud.support.cache.bean.ExamOrgSettingsCacheBean;
+import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+
+@Service
+public class ExamOrgSettingsCache extends RandomObjectRedisCache<ExamOrgSettingsCacheBean> {
+
+	@Autowired
+	ExamRepo examRepo;
+
+	@Autowired
+	ExamSpecialSettingsRepo examSpecialSettingsRepo;
+
+	@Override
+	public ExamOrgSettingsCacheBean loadFromResource(Object... keys) {
+		Long examId = (Long) keys[0];
+		Long orgId = (Long) keys[1];
+
+		ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+
+		if (null == exam) {
+			throw new StatusException("002005", "考试不存在");
+		}
+
+		ExamOrgSettingsCacheBean bean = new ExamOrgSettingsCacheBean();
+
+		bean.setId(exam.getId());
+		bean.setExamType(exam.getExamType().name());
+		bean.setCode(exam.getCode());
+
+		ExamSpecialSettingsEntity examOrgEntity = examSpecialSettingsRepo
+				.findByExamIdAndOrgIdAndCourseIdIsNullAndStudentIdIsNull(exam.getId(), orgId);
+		if (null != examOrgEntity) {
+			if (null != examOrgEntity.getBeginTime()) {
+				bean.setBeginTime(examOrgEntity.getBeginTime());
+			}
+			if (null != examOrgEntity.getEndTime()) {
+				bean.setEndTime(examOrgEntity.getEndTime());
+			}
+			if (null != examOrgEntity.getExamLimit()) {
+				bean.setExamLimit(examOrgEntity.getExamLimit());
+			}
+		} else {
+			bean.setHasValue(false);
+		}
+
+		return bean;
+	}
+
+	@Override
+	protected String getKeyPrefix() {
+		return "E_EXAM_ORG_SETTINGS:";
+	}
+
+	@Override
+	protected int getTimeout() {
+		// 10s
+		return 10;
+	}
+
+}

+ 55 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamPropertyCache.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.examcloud.core.examwork.service.cache;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnum;
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnumManager;
+import cn.com.qmth.examcloud.core.examwork.base.enums.ExamProperty;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamPropertyRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPropertyEntity;
+import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
+import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
+
+@Service
+public class ExamPropertyCache extends RandomObjectRedisCache<ExamPropertyCacheBean> {
+
+	@Autowired
+	ExamPropertyRepo examPropertyRepo;
+
+	@Override
+	public ExamPropertyCacheBean loadFromResource(Object... keys) {
+		Long examId = (Long) keys[0];
+		String key = (String) keys[1];
+
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+		DynamicEnum de = manager.getByName(key);
+
+		ExamPropertyEntity examPropertyEntity = examPropertyRepo.findByExamIdAndKeyId(examId,
+				de.getId());
+
+		ExamPropertyCacheBean b = new ExamPropertyCacheBean();
+		b.setExamId(examId);
+		b.setKey(key);
+
+		if (null == examPropertyEntity) {
+			b.setHasValue(false);
+			return b;
+		}
+
+		b.setValue(examPropertyEntity.getValue());
+		return b;
+	}
+
+	@Override
+	protected String getKeyPrefix() {
+		return "E_EXAM_PROP:";
+	}
+
+	@Override
+	protected int getTimeout() {
+		// 10s
+		return 10;
+	}
+
+}

+ 64 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamSettingsCache.java

@@ -0,0 +1,64 @@
+package cn.com.qmth.examcloud.core.examwork.service.cache;
+
+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.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamSpecialSettingsRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
+import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+
+@Service
+public class ExamSettingsCache extends RandomObjectRedisCache<ExamSettingsCacheBean> {
+
+	@Autowired
+	ExamRepo examRepo;
+
+	@Autowired
+	ExamSpecialSettingsRepo examSpecialSettingsRepo;
+
+	@Override
+	public ExamSettingsCacheBean loadFromResource(Object... keys) {
+		Long examId = (Long) keys[0];
+
+		ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+
+		if (null == exam) {
+			throw new StatusException("002005", "考试不存在");
+		}
+
+		ExamSettingsCacheBean bean = new ExamSettingsCacheBean();
+
+		bean.setId(exam.getId());
+		bean.setBeginTime(exam.getBeginTime());
+		bean.setDuration(exam.getDuration());
+		bean.setEnable(exam.getEnable());
+		bean.setEndTime(exam.getEndTime());
+		bean.setExamTimes(exam.getExamTimes());
+		bean.setExamType(exam.getExamType().name());
+		bean.setName(exam.getName());
+		bean.setCode(exam.getCode());
+		bean.setRemark(exam.getRemark());
+		bean.setRootOrgId(exam.getRootOrgId());
+		bean.setExamLimit(exam.getExamLimit());
+		bean.setSpecialSettingsEnabled(exam.getSpecialSettingsEnabled());
+		bean.setSpecialSettingsType(exam.getSpecialSettingsType());
+
+		return bean;
+	}
+
+	@Override
+	protected String getKeyPrefix() {
+		return "E_EXAM:";
+	}
+
+	@Override
+	protected int getTimeout() {
+		// 10s
+		return 10;
+	}
+
+}

+ 41 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamStudentPropertyCache.java

@@ -0,0 +1,41 @@
+package cn.com.qmth.examcloud.core.examwork.service.cache;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.core.examwork.service.ExamService;
+import cn.com.qmth.examcloud.support.cache.bean.ExamStudentPropertyCacheBean;
+import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
+
+@Service
+public class ExamStudentPropertyCache extends RandomObjectRedisCache<ExamStudentPropertyCacheBean> {
+
+	@Autowired
+	ExamService examService;
+
+	@Override
+	public ExamStudentPropertyCacheBean loadFromResource(Object... keys) {
+		Long examId = (Long) keys[0];
+		Long studentId = (Long) keys[1];
+		String key = (String) keys[2];
+
+		ExamStudentPropertyCacheBean b = new ExamStudentPropertyCacheBean();
+		b.setExamId(examId);
+		b.setStudentId(studentId);
+		b.setKey(key);
+		b.setHasValue(false);
+		return b;
+	}
+
+	@Override
+	protected String getKeyPrefix() {
+		return "E_EXAM_STUDENT_PROP:";
+	}
+
+	@Override
+	protected int getTimeout() {
+		// 10s
+		return 10;
+	}
+
+}

+ 71 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/cache/ExamStudentSettingsCache.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.examcloud.core.examwork.service.cache;
+
+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.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamSpecialSettingsRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+import cn.com.qmth.examcloud.support.cache.bean.ExamStudentSettingsCacheBean;
+import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+
+@Service
+public class ExamStudentSettingsCache extends RandomObjectRedisCache<ExamStudentSettingsCacheBean> {
+
+	@Autowired
+	ExamRepo examRepo;
+
+	@Autowired
+	ExamSpecialSettingsRepo examSpecialSettingsRepo;
+
+	@Override
+	public ExamStudentSettingsCacheBean loadFromResource(Object... keys) {
+		Long examId = (Long) keys[0];
+		Long studentId = (Long) keys[1];
+
+		ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+
+		if (null == exam) {
+			throw new StatusException("002005", "考试不存在");
+		}
+
+		ExamStudentSettingsCacheBean bean = new ExamStudentSettingsCacheBean();
+
+		bean.setId(exam.getId());
+		bean.setExamType(exam.getExamType().name());
+		bean.setCode(exam.getCode());
+
+		ExamSpecialSettingsEntity examOrgEntity = examSpecialSettingsRepo
+				.findByExamIdAndStudentIdAndOrgIdIsNullAndCourseIdIsNull(exam.getId(), studentId);
+		if (null != examOrgEntity) {
+			if (null != examOrgEntity.getBeginTime()) {
+				bean.setBeginTime(examOrgEntity.getBeginTime());
+			}
+			if (null != examOrgEntity.getEndTime()) {
+				bean.setEndTime(examOrgEntity.getEndTime());
+			}
+			if (null != examOrgEntity.getExamLimit()) {
+				bean.setExamLimit(examOrgEntity.getExamLimit());
+			}
+		} else {
+			bean.setHasValue(false);
+		}
+
+		return bean;
+	}
+
+	@Override
+	protected String getKeyPrefix() {
+		return "E_EXAM_STUDENT_SETTINGS:";
+	}
+
+	@Override
+	protected int getTimeout() {
+		// 10s
+		return 10;
+	}
+
+}

+ 809 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/impl/ExamServiceImpl.java

@@ -0,0 +1,809 @@
+package cn.com.qmth.examcloud.core.examwork.service.impl;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import cn.com.qmth.examcloud.api.commons.enums.CURD;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnum;
+import cn.com.qmth.examcloud.commons.helpers.DynamicEnumManager;
+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.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.core.examwork.base.enums.ExamProperty;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamOrgPropertyRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamPropertyRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamSpecialSettingsRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamOrgPropertyEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPropertyEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+import cn.com.qmth.examcloud.core.examwork.service.ExamService;
+import cn.com.qmth.examcloud.core.examwork.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamInfo;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamSpecialSettingsInfo;
+import cn.com.qmth.examcloud.core.examwork.service.cache.ExamPropertyCache;
+import cn.com.qmth.examcloud.core.examwork.service.cache.ExamSettingsCache;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.StudentCacheBean;
+import cn.com.qmth.examcloud.support.privilege.PrivilegeDefine;
+import cn.com.qmth.examcloud.support.privilege.PrivilegeManager;
+import cn.com.qmth.examcloud.task.api.DataSyncCloudService;
+import cn.com.qmth.examcloud.task.api.request.SyncExamReq;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.helpers.SequenceLockHelper;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年8月17日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Service
+public class ExamServiceImpl implements ExamService {
+	private static Pattern pattern = Pattern.compile("\\s*");
+
+	@Autowired
+	ExamRepo examRepo;
+
+	@Autowired
+	ExamStudentService examStudentService;
+
+	@Autowired
+	ExamSpecialSettingsRepo examSpecialSettingsRepo;
+
+	@Autowired
+	OrgCloudService orgCloudService;
+
+	@Autowired
+	ExamPropertyRepo examPropertyRepo;
+
+	@Autowired
+	ExamOrgPropertyRepo examOrgPropertyRepo;
+
+	@Autowired
+	DataSyncCloudService dataSyncCloudService;
+
+	@Autowired
+	ExamSettingsCache examSettingsCache;
+
+	@Autowired
+	ExamPropertyCache examPropertyCache;
+
+	private static final String[] EXAM_ORG_SETTINGS_EXCEL_HEADER = new String[]{"学习中心ID", "学习中心代码",
+			"学习中心名称", "是否可以考试(是/否)", "开始考试时间 yyyy-MM-dd hh:mm:ss", "结束考试时间 yyyy-MM-dd hh:mm:ss"};
+
+	/*
+	 * 实现
+	 *
+	 * @author WANGWEI
+	 * 
+	 * @see
+	 * cn.com.qmth.examcloud.core.examwork.service.ExamService#saveExam(cn.com.
+	 * qmth.examcloud.core.examwork.service.bean.ExamInfo)
+	 */
+	@Override
+	public ExamEntity saveExam(ExamInfo examInfo, CURD curd) {
+
+		Long rootOrgId = examInfo.getRootOrgId();
+		String code = examInfo.getCode();
+		String name = examInfo.getName();
+		ExamType examType = examInfo.getExamType();
+
+		if (null == examType) {
+			throw new StatusException("001005", "考试类型不能为空");
+		}
+
+		if (StringUtils.isBlank(code)) {
+			throw new StatusException("001005", "考试编码不能为空");
+		}
+
+		if (null == rootOrgId) {
+			throw new StatusException("001005", "rootOrgId is null");
+		}
+
+		// 上锁
+		Object[] locker = new Object[]{"E_EXAM_SAVE_EXAM", rootOrgId, code};
+		SequenceLockHelper.getLock(locker);
+
+		GetOrgReq getOrgReq = new GetOrgReq();
+		getOrgReq.setOrgId(rootOrgId);
+		GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+		OrgBean rootOrg = getOrgResp.getOrg();
+
+		Map<String, String> properties = examInfo.getProperties();
+		Map<DynamicEnum, String> map = checkAndGetExamProperties(rootOrgId, properties);
+
+		ExamEntity exam = null;
+		CURD realStatus = null;
+
+		// 更新
+		if (curd.equals(CURD.UPDATE)) {
+			exam = examRepo.findByCodeAndRootOrgId(code, rootOrgId);
+			if (null == exam) {
+				throw new StatusException("002001", "code is wrong");
+			}
+			realStatus = CURD.UPDATE;
+		}
+		// 创建
+		else if (curd.equals(CURD.CREATION)) {
+			exam = new ExamEntity();
+			realStatus = CURD.CREATION;
+		}
+		// (根据考试编码)新增或创建
+		else if (curd.equals(CURD.CREATION_OR_UPDATE)) {
+			exam = examRepo.findByCodeAndRootOrgId(code, rootOrgId);
+			// 创建
+			if (null == exam) {
+				exam = new ExamEntity();
+				realStatus = CURD.CREATION;
+			}
+			// 更新
+			else {
+				realStatus = CURD.UPDATE;
+			}
+		}
+
+		if (realStatus.equals(CURD.CREATION)) {
+			if (null == examInfo.getBeginTime()) {
+				throw new StatusException("002006", "beginTime is null");
+			}
+			if (null == examInfo.getEndTime()) {
+				throw new StatusException("002006", "endTime is null");
+			}
+			if (StringUtils.isBlank(name)) {
+				throw new StatusException("002004", "name is blank");
+			}
+
+			ExamEntity examByCode = examRepo.findByCodeAndRootOrgId(code, rootOrgId);
+			if (null != examByCode) {
+				throw new StatusException("002005", "考试编码已存在");
+			}
+
+			ExamEntity examByName = examRepo.findByNameAndRootOrgId(name, rootOrgId);
+			if (null != examByName) {
+				throw new StatusException("002005", "考试名称已存在");
+			}
+
+			exam.setCode(code);
+			exam.setRootOrgId(rootOrgId);
+			exam.setExamType(examType);
+			exam.setEnable(true);
+			exam.setExamLimit(false);
+			exam.setSpecialSettingsEnabled(false);
+		} else if (realStatus.equals(CURD.UPDATE)) {
+			if (!exam.getRootOrgId().equals(rootOrgId)) {
+				throw new StatusException("002003", "rootOrgId is wrong");
+			}
+			if (!exam.getExamType().equals(examType)) {
+				throw new StatusException("002100", "examType is wrong");
+			}
+		}
+
+		if (null != examInfo.getSpecialSettingsEnabled()) {
+			exam.setSpecialSettingsEnabled(examInfo.getSpecialSettingsEnabled());
+		}
+		if (null != examInfo.getSpecialSettingsType()) {
+			exam.setSpecialSettingsType(examInfo.getSpecialSettingsType());
+		}
+		if (null != examInfo.getBeginTime()) {
+			exam.setBeginTime(examInfo.getBeginTime());
+		}
+		if (null != examInfo.getEndTime()) {
+			exam.setEndTime(examInfo.getEndTime());
+		}
+		if (null != examInfo.getDuration()) {
+			exam.setDuration(examInfo.getDuration());
+		}
+		if (null != examInfo.getEnable()) {
+			exam.setEnable(examInfo.getEnable());
+		}
+		if (null != examInfo.getExamTimes()) {
+			exam.setExamTimes(examInfo.getExamTimes());
+		}
+		if (null != examInfo.getExamLimit()) {
+			exam.setExamLimit(examInfo.getExamLimit());
+		}
+		if (StringUtils.isNotBlank(name)) {
+			exam.setName(name);
+		}
+		if (StringUtils.isNotBlank(examInfo.getRemark())) {
+			exam.setName(examInfo.getRemark());
+		}
+
+		// 数据订正代码
+		exam.setExamLimit(false);
+
+		ExamEntity saved = examRepo.saveAndFlush(exam);
+
+		for (Entry<DynamicEnum, String> entry : map.entrySet()) {
+			DynamicEnum de = entry.getKey();
+			String value = entry.getValue();
+			ExamPropertyEntity entity = examPropertyRepo.findByExamIdAndKeyId(saved.getId(),
+					de.getId());
+			if (null == entity) {
+				entity = new ExamPropertyEntity();
+				entity.setExamId(saved.getId());
+				entity.setKeyId(de.getId());
+			}
+			entity.setValue(value);
+
+			examPropertyRepo.save(entity);
+		}
+
+		// 考试设置混入特殊设置
+		ExamSpecialSettingsInfo examSpecialInfo = new ExamSpecialSettingsInfo();
+		examSpecialInfo.setBeginTime(saved.getBeginTime());
+		examSpecialInfo.setEndTime(saved.getEndTime());
+		examSpecialInfo.setExamId(saved.getId());
+		examSpecialInfo.setExamLimit(saved.getExamLimit());
+		examSpecialInfo.setRootOrgId(saved.getRootOrgId());
+		examSpecialInfo.setExamLimit(saved.getExamLimit());
+		this.saveExamSpecialSettings(examSpecialInfo);
+
+		examSpecialSettingsRepo.updateExamEnableByExamId(saved.getId(), saved.getEnable());
+		examSpecialSettingsRepo.updateSpecialSettingsEnabledByExamId(saved.getId(),
+				saved.getSpecialSettingsEnabled());
+		if (null == saved.getSpecialSettingsType()) {
+			examSpecialSettingsRepo.updateSpecialSettingsTypeByExamId(saved.getId(), null);
+		} else {
+			examSpecialSettingsRepo.updateSpecialSettingsTypeByExamId(saved.getId(),
+					saved.getSpecialSettingsType());
+		}
+
+		SyncExamReq req = new SyncExamReq();
+		req.setId(saved.getId());
+		req.setBeginTime(saved.getBeginTime());
+		req.setDuration(saved.getDuration());
+		req.setEnable(saved.getEnable());
+		req.setEndTime(saved.getEndTime());
+		req.setExamTimes(saved.getExamTimes());
+		req.setExamType(saved.getExamType().name());
+		req.setName(saved.getName());
+		req.setRemark(saved.getRemark());
+		req.setRootOrgId(saved.getRootOrgId());
+		req.setRootOrgName(rootOrg.getName());
+		req.setSyncType("update");
+		dataSyncCloudService.syncExam(req);
+
+		examSettingsCache.remove(saved.getId());
+
+		if (null != properties) {
+			for (String cur : properties.keySet()) {
+				examPropertyCache.remove(saved.getId(), cur);
+			}
+		}
+
+		return saved;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param rootOrgId
+	 * @param properties
+	 * @return
+	 */
+	private Map<DynamicEnum, String> checkAndGetExamProperties(Long rootOrgId,
+			Map<String, String> properties) {
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+
+		Map<DynamicEnum, String> map = Maps.newHashMap();
+		if (null == properties) {
+			return map;
+		}
+
+		for (Entry<String, String> entry : properties.entrySet()) {
+			String key = entry.getKey();
+			String value = entry.getValue();
+			DynamicEnum de;
+			try {
+				de = manager.getByName(key);
+			} catch (Exception e) {
+				throw new StatusException("001004", "考试属性错误. key=" + key);
+			}
+			if (!de.isLegal(value)) {
+				throw new StatusException("001200", "考试属性值非法. key=" + key);
+			}
+			map.put(de, value);
+		}
+
+		String beforeExamRemark = properties.get("BEFORE_EXAM_REMARK");
+
+		if (null != beforeExamRemark) {
+			Document doc = Jsoup.parse(beforeExamRemark);
+			Matcher m = pattern.matcher(doc.text());
+			String simpleText = m.replaceAll("").replaceAll(" ", "");
+			if (simpleText.length() > 800) {
+				throw new StatusException("001002", "考前说明内容不得超过800个字符");
+			}
+			long imgsize = getImgSizeByBase64String(beforeExamRemark);
+			if (imgsize > 150 * 1024) {
+				throw new StatusException("001002", "考前说明图片总大小不得超过150KB");
+			}
+		}
+
+		String afterExamRemark = properties.get("AFTER_EXAM_REMARK");
+		if (null != afterExamRemark) {
+			Document doc = Jsoup.parse(afterExamRemark);
+			Matcher m = pattern.matcher(doc.text());
+			String simpleText = m.replaceAll("").replaceAll(" ", "");
+			if (simpleText.length() > 800) {
+				throw new StatusException("001002", "考后说明内容不得超过800个字符");
+			}
+			long imgsize = getImgSizeByBase64String(afterExamRemark);
+			if (imgsize > 150 * 1024) {
+				throw new StatusException("001002", "考后说明图片总大小不得超过150KB");
+			}
+		}
+
+		String cheatingRemark = properties.get("CHEATING_REMARK");
+		if (null != cheatingRemark) {
+			Document doc = Jsoup.parse(cheatingRemark);
+			Matcher m = pattern.matcher(doc.text());
+			String simpleText = m.replaceAll("").replaceAll(" ", "");
+			if (simpleText.length() > 800) {
+				throw new StatusException("001002", "作弊说明内容不得超过800个字符");
+			}
+			long imgsize = getImgSizeByBase64String(cheatingRemark);
+			if (imgsize > 150 * 1024) {
+				throw new StatusException("001002", "作弊说明图片总大小不得超过150KB");
+			}
+		}
+
+		// 校验机构权限
+		// 人脸识别功能校验
+		String faceCheck = PrivilegeDefine.RootOrgFunctions.OnlineExamFunctions.FaceCheck.CODE;
+		Boolean hasFaceCheckFunction = PrivilegeManager.judge(rootOrgId, faceCheck);
+		String isFaceEnable = properties.get("IS_FACE_ENABLE");
+		if (!hasFaceCheckFunction) {
+			if (StringUtils.isNotBlank(isFaceEnable)
+					&& isFaceEnable.equalsIgnoreCase(Boolean.toString(true))) {
+				throw new StatusException("001002", "人脸识别功能未开放");
+			}
+		}
+
+		// 活体检测功能校验
+		String IdentificationOfLivingBody = PrivilegeDefine.RootOrgFunctions.OnlineExamFunctions.IdentificationOfLivingBody.CODE;
+		Boolean hasIdentificationOfLivingBodyFunction = PrivilegeManager.judge(rootOrgId,
+				IdentificationOfLivingBody);
+		String isFceVerify = properties.get("IS_FACE_VERIFY");
+		if (!hasIdentificationOfLivingBodyFunction) {
+			if (StringUtils.isNotBlank(isFceVerify)
+					&& isFceVerify.equalsIgnoreCase(Boolean.toString(true))) {
+				throw new StatusException("001002", "活体检测功能未开放");
+			}
+		}
+
+		return map;
+	}
+
+	/**
+	 * @param content
+	 *            内容
+	 * @return 内容中图片文件总大小
+	 */
+	private long getImgSizeByBase64String(String content) {
+		long size = 0;
+		if (StringUtils.isNoneEmpty(content)) {
+			Document doc = Jsoup.parse(content);
+			Elements imgs = doc.getElementsByTag("img");
+			if (imgs != null) {
+				for (Element img : imgs) {
+					String src = img.attr("src");
+					if (src.startsWith("data:")) {
+						String base64 = src.split(",")[1];
+						int equalIndex = base64.indexOf("=");
+						if (equalIndex > 0) {
+							base64 = base64.substring(0, equalIndex);
+						}
+						long fileSize = base64.length() - (base64.length() / 8) * 2;
+						size = size + fileSize;
+					}
+				}
+			}
+		}
+		return size;
+	}
+
+	/*
+	 * 实现
+	 *
+	 * @author WANGWEI
+	 * 
+	 * @see
+	 * cn.com.qmth.examcloud.core.examwork.service.ExamService#saveExamOrg(cn.
+	 * com.qmth.examcloud.core.examwork.service.bean.ExamOrgInfo)
+	 */
+	@Override
+	public ExamSpecialSettingsEntity saveExamSpecialSettings(
+			ExamSpecialSettingsInfo examSpecialInfo) {
+
+		ExamSpecialSettingsEntity examSpecialSettingsEntity = null;
+
+		Long rootOrgId = examSpecialInfo.getRootOrgId();
+		Long examId = examSpecialInfo.getExamId();
+		Date beginTime = examSpecialInfo.getBeginTime();
+		Date endTime = examSpecialInfo.getEndTime();
+		Long orgId = examSpecialInfo.getOrgId();
+		Long studentId = examSpecialInfo.getStudentId();
+		Long courseId = examSpecialInfo.getCourseId();
+
+		int trueNum = BooleanUtil.countTrue(null != orgId, null != courseId, null != studentId);
+		if (1 < trueNum) {
+			throw new StatusException("001502", "参数错误");
+		}
+
+		// 上锁
+		Object[] locker = new Object[]{"E_EXAM_SPECIAL_SETTINGS", examId, "ORG" + orgId,
+				"COURSE" + courseId, "Student" + studentId};
+		SequenceLockHelper.getLock(locker);
+
+		if (!new Boolean(null == beginTime).equals(new Boolean(null == endTime))) {
+			throw new StatusException("001101", "beginTime & endTime  wrong");
+		}
+
+		ExamEntity examEntity = GlobalHelper.getPresentEntity(examRepo, examId, ExamEntity.class);
+		GlobalHelper.uniformRootOrg(examEntity.getRootOrgId(), rootOrgId);
+
+		StudentCacheBean student = null;
+		if (null != studentId) {
+			student = CacheHelper.getStudent(studentId);
+			GlobalHelper.uniformRootOrg(student.getRootOrgId(), rootOrgId);
+		}
+
+		if (null == examSpecialInfo.getId()) {
+			// 机构特殊设置
+			if (null != orgId && null == courseId && null == studentId) {
+				examSpecialSettingsEntity = examSpecialSettingsRepo
+						.findByExamIdAndOrgIdAndCourseIdIsNullAndStudentIdIsNull(
+								examSpecialInfo.getExamId(), orgId);
+			}
+			// 学生特殊设置
+			else if (null == orgId && null == courseId && null != studentId) {
+				examSpecialSettingsEntity = examSpecialSettingsRepo
+						.findByExamIdAndStudentIdAndOrgIdIsNullAndCourseIdIsNull(
+								examSpecialInfo.getExamId(), studentId);
+			}
+			// 课程特殊设置
+			else if (null == orgId && null != courseId && null == studentId) {
+
+			}
+			// 考试设置
+			else if (null == orgId && null == courseId && null == studentId) {
+				examSpecialSettingsEntity = examSpecialSettingsRepo
+						.findAllByExamIdAndOrgIdIsNullAndCourseIdIsNullAndStudentIdIsNull(
+								examSpecialInfo.getExamId());
+			} else {
+				throw new StatusException("001501", "参数错误");
+			}
+
+			if (null == examSpecialSettingsEntity) {
+				examSpecialSettingsEntity = new ExamSpecialSettingsEntity();
+			}
+
+			examSpecialSettingsEntity.setRootOrgId(rootOrgId);
+			examSpecialSettingsEntity.setExamId(examId);
+			examSpecialSettingsEntity.setExamType(examEntity.getExamType());
+			examSpecialSettingsEntity.setExamEnable(examEntity.getEnable());
+			examSpecialSettingsEntity
+					.setSpecialSettingsEnabled(examEntity.getSpecialSettingsEnabled());
+			examSpecialSettingsEntity.setSpecialSettingsType(examEntity.getSpecialSettingsType());
+
+			examSpecialSettingsEntity.setOrgId(orgId);
+			examSpecialSettingsEntity.setCourseId(courseId);
+			examSpecialSettingsEntity.setStudentId(studentId);
+
+			// 机构特殊设置
+			if (null != orgId && null == courseId && null == studentId) {
+			}
+			// 学生特殊设置
+			else if (null == orgId && null == courseId && null != studentId) {
+				if (null != studentId) {
+					examSpecialSettingsEntity.setExt1(student.getIdentityNumber());
+				}
+			}
+			// 课程特殊设置
+			else if (null == orgId && null != courseId && null == studentId) {
+			}
+			// 考试设置
+			else if (null == orgId && null == courseId && null == studentId) {
+			}
+
+		} else {
+			examSpecialSettingsEntity = GlobalHelper.getEntity(examSpecialSettingsRepo,
+					examSpecialInfo.getId(), ExamSpecialSettingsEntity.class);
+			if (null == examSpecialSettingsEntity) {
+				throw new StatusException("001101", "id is wrong");
+			}
+		}
+
+		examSpecialSettingsEntity.setBeginTime(examSpecialInfo.getBeginTime());
+		examSpecialSettingsEntity.setEndTime(examSpecialInfo.getEndTime());
+
+		Boolean examLimit = examSpecialInfo.getExamLimit();
+		examLimit = null == examLimit ? false : examLimit;
+		examSpecialSettingsEntity.setExamLimit(examLimit);
+
+		ExamSpecialSettingsEntity saved = examSpecialSettingsRepo.save(examSpecialSettingsEntity);
+
+		Map<String, String> props = examSpecialInfo.getProperties();
+
+		if (null != props) {
+			Map<DynamicEnum, String> map = checkAndGetExamProperties(examEntity.getRootOrgId(),
+					props);
+
+			for (Entry<DynamicEnum, String> entry : map.entrySet()) {
+				DynamicEnum de = entry.getKey();
+				String value = entry.getValue();
+				if (StringUtils.isBlank(value)) {
+					value = null;
+				} else {
+					value = value.trim();
+				}
+
+				// 机构特殊设置
+				if (null != orgId && null == courseId && null == studentId) {
+					ExamOrgPropertyEntity entity = examOrgPropertyRepo.findByExamIdAndOrgIdAndKeyId(
+							saved.getExamId(), saved.getOrgId(), de.getId());
+					if (null == entity) {
+						entity = new ExamOrgPropertyEntity();
+						entity.setExamId(saved.getExamId());
+						entity.setOrgId(saved.getOrgId());
+						entity.setKeyId(de.getId());
+					}
+					entity.setValue(value);
+
+					examOrgPropertyRepo.save(entity);
+				}
+				// 学生特殊设置
+				else if (null == orgId && null == courseId && null != studentId) {
+
+				}
+				// 课程特殊设置
+				else if (null == orgId && null != courseId && null == studentId) {
+
+				}
+
+			}
+		}
+
+		return saved;
+	}
+
+	@Override
+	public String getExamOrgProperty(Long examId, Long orgId, String key) {
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+		DynamicEnum de = manager.getByName(key);
+
+		if (null != orgId) {
+			ExamOrgPropertyEntity examOrgPropertyEntity = examOrgPropertyRepo
+					.findByExamIdAndOrgIdAndKeyId(examId, orgId, de.getId());
+			if (null != examOrgPropertyEntity) {
+				return examOrgPropertyEntity.getValue();
+			}
+		}
+
+		ExamPropertyEntity examPropertyEntity = examPropertyRepo.findByExamIdAndKeyId(examId,
+				de.getId());
+		if (null == examPropertyEntity) {
+			return null;
+		}
+
+		return examPropertyEntity.getValue();
+	}
+
+	@Override
+	public String getExamStudentProperty(Long examId, Long studentId, String key) {
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+		DynamicEnum de = manager.getByName(key);
+
+		if (null != studentId) {
+			// 待实现
+		}
+
+		ExamPropertyEntity examPropertyEntity = examPropertyRepo.findByExamIdAndKeyId(examId,
+				de.getId());
+		if (null == examPropertyEntity) {
+			return null;
+		}
+
+		return examPropertyEntity.getValue();
+	}
+
+	@Override
+	public List<Map<String, Object>> importExamOrgSettings(Long examId, File file) {
+
+		ExamEntity examEntity = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		List<String[]> lineList = null;
+		try {
+			lineList = ExcelReader.readSheetBySax(PathUtil.getCanonicalPath(file), 1, 6);
+		} catch (Exception e) {
+			throw new StatusException("200110", "Excel 解析失败");
+		}
+
+		if (CollectionUtils.isEmpty(lineList)) {
+			throw new StatusException("200111", "Excel无内容");
+		}
+
+		if (10001 < lineList.size()) {
+			throw new StatusException("200112", "数据行数不能超过10000");
+		}
+
+		List<Map<String, Object>> failRecords = Collections
+				.synchronizedList(new ArrayList<Map<String, Object>>());
+		List<ExamSpecialSettingsInfo> specialSettingsList = Lists.newArrayList();
+
+		for (int i = 0; i < lineList.size(); i++) {
+			String[] line = lineList.get(i);
+			if (0 == i) {
+				if (headerError(line)) {
+					throw new StatusException("100111", "Excel表头错误");
+				}
+				continue;
+			}
+
+			boolean hasError = false;
+			StringBuilder msg = new StringBuilder();
+
+			ExamSpecialSettingsInfo examSpecialInfo = new ExamSpecialSettingsInfo();
+			examSpecialInfo.setRootOrgId(examEntity.getRootOrgId());
+			examSpecialInfo.setExamId(examId);
+
+			String orgId = trimAndNullIfBlank(line[0]);
+			if (StringUtils.isBlank(orgId)) {
+				msg.append("  学习中心ID不能为空");
+				hasError = true;
+			} else {
+				try {
+					long orgIdLong = Long.parseLong(orgId);
+
+					GetOrgReq getOrgReq = new GetOrgReq();
+					getOrgReq.setOrgId(orgIdLong);
+					GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+					OrgBean orgBean = getOrgResp.getOrg();
+
+					if (null == orgBean.getParentId()
+							|| !orgBean.getRootId().equals(examEntity.getRootOrgId())) {
+						msg.append("  学习中心ID非法");
+						hasError = true;
+					} else {
+						examSpecialInfo.setOrgId(orgIdLong);
+					}
+				} catch (NumberFormatException e) {
+					msg.append("  学习中心ID必须为整数");
+					hasError = true;
+				}
+			}
+
+			String examLimit = trimAndNullIfBlank(line[3]);
+			if (StringUtils.isNotBlank(examLimit)) {
+				if (examLimit.equals("是")) {
+					examSpecialInfo.setExamLimit(false);
+				} else if (examLimit.equals("否")) {
+					examSpecialInfo.setExamLimit(true);
+				} else {
+					msg.append("  是否可以开始考试必须为['是','否',空]");
+					hasError = true;
+				}
+			} else {
+				examSpecialInfo.setExamLimit(false);
+			}
+
+			String beginTime = trimAndNullIfBlank(line[4]);
+			if (StringUtils.isNotBlank(beginTime)) {
+				try {
+					Date beginDate = DateUtil.parse(beginTime, DatePatterns.CHINA_DEFAULT);
+					examSpecialInfo.setBeginTime(beginDate);
+				} catch (Exception e) {
+					msg.append("  开始考试时间格式错误. 正确格式为 " + DatePatterns.CHINA_DEFAULT);
+					hasError = true;
+				}
+			}
+			String endTime = trimAndNullIfBlank(line[5]);
+			if (StringUtils.isNotBlank(endTime)) {
+				try {
+					Date endDate = DateUtil.parse(endTime, DatePatterns.CHINA_DEFAULT);
+					examSpecialInfo.setEndTime(endDate);
+				} catch (Exception e) {
+					msg.append("  结束考试时间格式错误. 正确格式为 " + DatePatterns.CHINA_DEFAULT);
+					hasError = true;
+				}
+			}
+
+			if (hasError) {
+				failRecords.add(newError(i + 1, msg.toString()));
+			} else {
+				specialSettingsList.add(examSpecialInfo);
+			}
+
+		}
+
+		if (CollectionUtils.isNotEmpty(failRecords)) {
+			return failRecords;
+		}
+
+		for (ExamSpecialSettingsInfo cur : specialSettingsList) {
+			saveExamSpecialSettings(cur);
+		}
+
+		return failRecords;
+
+	}
+
+	private Map<String, Object> newError(int lineNum, String msg) {
+		Map<String, Object> map = Maps.newHashMap();
+		map.put("lineNum", lineNum);
+		map.put("msg", msg);
+		return map;
+	}
+
+	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 < EXAM_ORG_SETTINGS_EXCEL_HEADER.length; i++) {
+			if (null == header[i]) {
+				return true;
+			}
+			if (!EXAM_ORG_SETTINGS_EXCEL_HEADER[i].equals(header[i].trim())) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	@Override
+	public String getExamProperty(Long examId, String key) {
+		DynamicEnumManager manager = ExamProperty.getDynamicEnumManager();
+		DynamicEnum de = manager.getByName(key);
+
+		ExamPropertyEntity examPropertyEntity = examPropertyRepo.findByExamIdAndKeyId(examId,
+				de.getId());
+		if (null == examPropertyEntity) {
+			return null;
+		}
+
+		return examPropertyEntity.getValue();
+	}
+
+}

+ 507 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/impl/ExamStudentServiceImpl.java

@@ -0,0 +1,507 @@
+package cn.com.qmth.examcloud.core.examwork.service.impl;
+
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.DateUtil;
+import cn.com.qmth.examcloud.commons.util.DateUtil.DatePatterns;
+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.bean.StudentBean;
+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.GetStudentReq;
+import cn.com.qmth.examcloud.core.basic.api.request.SaveCourseReq;
+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.GetStudentResp;
+import cn.com.qmth.examcloud.core.basic.api.response.SaveCourseResp;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamCourseRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamPaperTypeRelationRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamStudentRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamCourseRelationEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamPaperTypeRelationEntity;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamStudentEntity;
+import cn.com.qmth.examcloud.core.examwork.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.examwork.service.bean.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordCloudService;
+import cn.com.qmth.examcloud.core.oe.admin.api.request.CheckExamIsStartedReq;
+import cn.com.qmth.examcloud.core.oe.admin.api.response.CheckExamIsStartedResp;
+import cn.com.qmth.examcloud.task.api.DataSyncCloudService;
+import cn.com.qmth.examcloud.task.api.request.SyncExamStudentReq;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+
+/**
+ * 考试学生服务类 Created by songyue on 17/1/14.
+ */
+@Service
+public class ExamStudentServiceImpl implements ExamStudentService {
+
+	@Autowired
+	ExamStudentRepo examStudentRepo;
+
+	@Autowired
+	ExamRepo examRepo;
+
+	@Autowired
+	OrgCloudService orgCloudService;
+
+	@Autowired
+	ExamStudentServiceImpl examStudentService;
+
+	@Autowired
+	StudentCloudService studentCloudService;
+
+	@Autowired
+	CourseCloudService courseCloudService;
+
+	@Autowired
+	ExamCourseRelationRepo examCourseRelationRepo;
+
+	@Autowired
+	DataSyncCloudService dataSyncCloudService;
+
+	@Autowired
+	ExamRecordCloudService examRecordCloudService;
+
+	@Autowired
+	ExamPaperTypeRelationRepo examPaperTypeRelationRepo;
+
+	/**
+	 * 是否强制删除考生
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	private boolean forceDeleteExamStudent() {
+		boolean b = PropertyHolder.getBoolean("$deleteExamStudent.force", false);
+		return b;
+	}
+
+	/**
+	 * 是否开考
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 * @param StudentId
+	 * @param courseId
+	 * @return
+	 */
+	private boolean isStarted(Long examId, Long StudentId, Long courseId) {
+		CheckExamIsStartedReq checkExamIsStartedReq = new CheckExamIsStartedReq();
+		checkExamIsStartedReq.setExamId(examId);
+		checkExamIsStartedReq.setCourseId(courseId);
+		checkExamIsStartedReq.setStudentId(StudentId);
+		CheckExamIsStartedResp checkExamIsStartedResp = examRecordCloudService
+				.checkExamIsStarted(checkExamIsStartedReq);
+		return checkExamIsStartedResp.getIsStarted();
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param ids
+	 */
+	@Override
+	public void deleteExamStudentsByStudentIds(List<Long> ids) {
+		List<ExamStudentEntity> examStudents = examStudentRepo.findByIdIn(ids);
+
+		for (ExamStudentEntity examStudent : examStudents) {
+			// 网考判断
+			if ((!forceDeleteExamStudent()) && isStarted(examStudent.getExamId(), null, null)) {
+				throw new StatusException("150112", examStudent.getName() + "已开始考试,不能删除");
+			}
+			examStudentRepo.delete(examStudent);
+
+			List<ExamStudentEntity> top2 = examStudentRepo.findTop2ByExamIdAndCourseId(
+					examStudent.getExamId(), examStudent.getCourseId());
+			if (1 > top2.size()) {
+				examCourseRelationRepo.deleteByExamIdAndCourseId(examStudent.getExamId(),
+						examStudent.getCourseId());
+			}
+
+			top2 = examStudentRepo.findTop2ByExamIdAndCourseIdAndPaperType(examStudent.getExamId(),
+					examStudent.getCourseId(), examStudent.getPaperType());
+			if (1 > top2.size()) {
+				examPaperTypeRelationRepo.deleteByExamIdAndCourseIdAndPaperType(
+						examStudent.getExamId(), examStudent.getCourseId(),
+						examStudent.getPaperType());
+			}
+		}
+
+		for (ExamStudentEntity cur : examStudents) {
+			// 同步操作
+			SyncExamStudentReq req = new SyncExamStudentReq();
+			req.setSyncType("delete");
+			req.setId(cur.getId());
+			req.setCourseId(cur.getId());
+			req.setCourseCode(cur.getCourseCode());
+			req.setCourseName(cur.getCourseName());
+			req.setExamId(cur.getId());
+			req.setStudentId(cur.getStudentId());
+			req.setStudentName(cur.getName());
+			req.setIdentityNumber(cur.getIdentityNumber());
+			req.setStudentCode(cur.getStudentCode());
+			dataSyncCloudService.syncExamStudent(req);
+		}
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param examId
+	 */
+	@Override
+	public void deleteExamStudentsByExamId(Long examId) {
+		ExamEntity exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+		// 已经开考
+		if ((!forceDeleteExamStudent()) && isStarted(exam.getId(), null, null)) {
+			throw new StatusException("150113", "该考试已开始,不能删除");
+		}
+
+		ExamStudentEntity entity = new ExamStudentEntity();
+		entity.setExamId(examId);
+		Example<ExamStudentEntity> example = Example.of(entity);
+		long count = examStudentRepo.count(example);
+		if (1000 < count) {
+			throw new StatusException("150116", "考生数量超过1000,无法删除");
+		}
+
+		List<ExamStudentEntity> list = examStudentRepo.findAll(example);
+
+		List<Long> IdList = list.stream().map(ExamStudentEntity::getId)
+				.collect(Collectors.toList());
+
+		deleteExamStudentsByStudentIds(IdList);
+	}
+
+	/*
+	 * 实现
+	 *
+	 * @author WANGWEI
+	 * 
+	 * @see cn.com.qmth.examcloud.core.examwork.service.ExamStudentService#
+	 * saveExamStudent(cn.com.qmth.examcloud.core.examwork.service.bean.
+	 * ExamStudentInfo)
+	 */
+	@Override
+	public ExamStudentInfo saveExamStudent(ExamStudentInfo examStudentInfo) {
+		String paperType = examStudentInfo.getPaperType();
+		String studentCode = examStudentInfo.getStudentCode();
+
+		if (StringUtils.isBlank(paperType)) {
+			paperType = "X";
+		} else if (!paperType.matches("[A-Z]")) {
+			throw new StatusException("100020", "试卷类型必须为一个大写的英文字母");
+		}
+
+		Long rootOrgId = examStudentInfo.getRootOrgId();
+		GetOrgReq getOrgReq = new GetOrgReq();
+		getOrgReq.setOrgId(rootOrgId);
+		GetOrgResp getOrgResp = orgCloudService.getOrg(getOrgReq);
+		OrgBean rootOrg = getOrgResp.getOrg();
+		if (null != rootOrg.getParentId()) {
+			throw new StatusException("100001", "rootOrgId is wrong");
+		}
+
+		ExamEntity exam = null;
+		Long examId = examStudentInfo.getExamId();
+		String examName = examStudentInfo.getExamName();
+		String examCode = examStudentInfo.getExamCode();
+
+		if (null != examId) {
+			exam = GlobalHelper.getEntity(examRepo, examId, ExamEntity.class);
+			if (null == exam) {
+				throw new StatusException("100020", "examId is wrong");
+			}
+		} else if (StringUtils.isNotBlank(examCode)) {
+			exam = examRepo.findByCodeAndRootOrgId(examCode, rootOrgId);
+			if (null == exam) {
+				throw new StatusException("1000050", "考试不存在");
+			}
+		} else if (StringUtils.isNotBlank(examName)) {
+			exam = examRepo.findByNameAndRootOrgId(examName, rootOrgId);
+			if (null == exam) {
+				throw new StatusException("100005", "考试不存在");
+			}
+		} else {
+			throw new StatusException("100002", "examId,examCode,examName cannot be all empty");
+		}
+
+		String identityNumber = examStudentInfo.getIdentityNumber();
+
+		if (StringUtils.isBlank(identityNumber)) {
+			throw new StatusException("100003", "identityNumber is null");
+		}
+		GetStudentReq getStudentReq = new GetStudentReq();
+		getStudentReq.setRootOrgId(rootOrgId);
+		getStudentReq.setIdentityNumber(identityNumber);
+		GetStudentResp getStudentResp = studentCloudService.getStudent(getStudentReq);
+
+		StudentBean studentInfo = getStudentResp.getStudentInfo();
+
+		if (StringUtils.isNotBlank(studentCode)) {
+			List<String> studentCodeList = studentInfo.getStudentCodeList();
+			boolean containIgnoreCase = false;
+			for (String cur : studentCodeList) {
+				if (cur.equalsIgnoreCase(studentCode)) {
+					containIgnoreCase = true;
+					studentCode = cur;
+					break;
+				}
+			}
+			if (!containIgnoreCase) {
+				throw new StatusException("100063", "学号未绑定到身份证");
+			}
+		} else {
+			studentCode = null;
+		}
+
+		String courseCode = examStudentInfo.getCourseCode();
+		String courseName = examStudentInfo.getCourseName();
+		String courseLevel = examStudentInfo.getCourseLevel();
+
+		SaveCourseReq saveCourseReq = new SaveCourseReq();
+		saveCourseReq.setCourseId(examStudentInfo.getCourseId());
+		saveCourseReq.setCourseCode(courseCode);
+		saveCourseReq.setCourseName(courseName);
+		saveCourseReq.setCourseLevel(courseLevel);
+		saveCourseReq.setRootOrgId(rootOrgId);
+
+		SaveCourseResp saveCourseResp = courseCloudService.saveCourse(saveCourseReq);
+		CourseBean courseBean = saveCourseResp.getCourseBean();
+
+		ExamStudentEntity examStudent = examStudentRepo.findByExamIdAndStudentIdAndCourseId(
+				exam.getId(), studentInfo.getId(), courseBean.getId());
+
+		if (null == examStudent) {
+			examStudent = new ExamStudentEntity();
+			examStudent.setEnable(true);
+		} else {
+			if (!paperType.equals(examStudent.getPaperType())) {
+				List<ExamStudentEntity> top2 = examStudentRepo
+						.findTop2ByExamIdAndCourseIdAndPaperType(examStudent.getExamId(),
+								examStudent.getCourseId(), examStudent.getPaperType());
+				if (2 > top2.size()) {
+					examPaperTypeRelationRepo.deleteByExamIdAndCourseIdAndPaperType(
+							examStudent.getExamId(), examStudent.getCourseId(),
+							examStudent.getPaperType());
+				}
+			}
+		}
+
+		examStudent.setInfoCollector(examStudentInfo.getInfoCollector());
+		examStudent.setName(studentInfo.getName());
+		examStudent.setRootOrgId(rootOrgId);
+
+		examStudent.setCourseId(courseBean.getId());
+		examStudent.setCourseName(courseBean.getName());
+		examStudent.setCourseCode(courseBean.getCode());
+		examStudent.setCourseLevel(courseBean.getLevel());
+
+		examStudent.setExamId(exam.getId());
+		examStudent.setIdentityNumber(studentInfo.getIdentityNumber());
+		examStudent.setStudentCode(studentCode);
+		examStudent.setPaperType(paperType);
+		examStudent.setStudentId(studentInfo.getId());
+		examStudent.setOrgId(studentInfo.getOrgId());
+		examStudent.setOrgCode(studentInfo.getOrgCode());
+		examStudent.setGrade(examStudentInfo.getGrade());
+		examStudent.setSpecialtyName(examStudentInfo.getSpecialtyName());
+		examStudent.setExamSite(examStudentInfo.getExamSite());
+		examStudent.setRemark(examStudentInfo.getRemark());
+
+		examStudent.setExt1(examStudentInfo.getExt1());
+		examStudent.setExt2(examStudentInfo.getExt2());
+		examStudent.setExt3(examStudentInfo.getExt3());
+		examStudent.setExt4(examStudentInfo.getExt4());
+		examStudent.setExt5(examStudentInfo.getExt5());
+
+		Date specialBeginTime = examStudentInfo.getSpecialBeginTime();
+		Date specialEndTime = examStudentInfo.getSpecialEndTime();
+
+		if (!new Boolean(null == specialBeginTime).equals(new Boolean(null == specialEndTime))) {
+			throw new StatusException("210101", "specialBeginTime & specialEndTime  wrong");
+		}
+
+		// 临时代码
+		if (null != specialBeginTime) {
+			examStudent.setExt4(DateUtil.format(specialBeginTime, DatePatterns.CHINA_DEFAULT));
+		} else {
+			examStudent.setExt4(null);
+		}
+		if (null != specialEndTime) {
+			examStudent.setExt5(DateUtil.format(specialEndTime, DatePatterns.CHINA_DEFAULT));
+		} else {
+			examStudent.setExt5(null);
+		}
+
+		Boolean enable = examStudentInfo.getEnable();
+
+		if (null != enable) {
+			examStudent.setEnable(enable);
+		}
+
+		ExamStudentEntity saved = examStudentRepo.save(examStudent);
+
+		ExamCourseRelationEntity relation = new ExamCourseRelationEntity();
+		relation.setExamId(saved.getExamId());
+		relation.setCourseId(saved.getCourseId());
+		relation.setCourseLevel(saved.getCourseLevel());
+		relation.setCourseCode(courseBean.getCode());
+		relation.setCourseName(saved.getCourseName());
+		relation.setCourseEnable(courseBean.getEnable());
+		examCourseRelationRepo.save(relation);
+
+		ExamPaperTypeRelationEntity pt = new ExamPaperTypeRelationEntity();
+		pt.setCourseId(saved.getCourseId());
+		pt.setExamId(saved.getExamId());
+		pt.setPaperType(saved.getPaperType());
+		examPaperTypeRelationRepo.save(pt);
+
+		// 同步操作
+		SyncExamStudentReq req = new SyncExamStudentReq();
+		req.setSyncType("update");
+		req.setId(saved.getId());
+
+		req.setEnable(saved.getEnable());
+
+		req.setCourseId(courseBean.getId());
+		req.setCourseCode(courseBean.getCode());
+		req.setCourseLevel(courseBean.getLevel());
+		req.setCourseName(courseBean.getName());
+
+		req.setExamId(exam.getId());
+		req.setExamName(exam.getName());
+
+		req.setRootOrgId(saved.getRootOrgId());
+		req.setStudentId(studentInfo.getId());
+		req.setStudentName(studentInfo.getName());
+		req.setIdentityNumber(saved.getIdentityNumber());
+		req.setStudentCode(saved.getStudentCode());
+		req.setOrgId(studentInfo.getOrgId());
+		req.setOrgName(studentInfo.getOrgName());
+		req.setOrgCode(studentInfo.getOrgCode());
+
+		req.setGrade(saved.getGrade());
+		req.setSpecialtyName(saved.getSpecialtyName());
+		req.setPaperType(saved.getPaperType());
+		req.setRemark(saved.getRemark());
+		req.setInfoCollector(saved.getInfoCollector());
+		req.setExamSite(saved.getExamSite());
+
+		req.setExt1(saved.getExt1());
+		req.setExt2(saved.getExt2());
+		req.setExt3(saved.getExt3());
+		req.setExt4(saved.getExt4());
+		req.setExt5(saved.getExt5());
+
+		dataSyncCloudService.syncExamStudent(req);
+
+		ExamStudentInfo ret = new ExamStudentInfo();
+		ret.setId(saved.getId());
+
+		ret.setCourseId(courseBean.getId());
+		ret.setCourseCode(courseBean.getCode());
+		ret.setCourseLevel(courseBean.getLevel());
+		ret.setCourseName(courseBean.getName());
+
+		ret.setExamId(exam.getId());
+		ret.setExamName(exam.getName());
+
+		ret.setRootOrgId(saved.getRootOrgId());
+		ret.setStudentName(studentInfo.getName());
+		ret.setIdentityNumber(saved.getIdentityNumber());
+		ret.setStudentCode(saved.getStudentCode());
+		ret.setOrgId(studentInfo.getOrgId());
+		ret.setOrgName(studentInfo.getOrgName());
+		ret.setOrgCode(studentInfo.getOrgCode());
+		ret.setStudentId(studentInfo.getId());
+
+		ret.setPaperType(saved.getPaperType());
+		ret.setRemark(saved.getRemark());
+		ret.setInfoCollector(saved.getInfoCollector());
+		ret.setExamSite(saved.getExamSite());
+
+		return ret;
+	}
+
+	@Override
+	public void syncExamStudent(Long studentId) {
+
+		ExamStudentEntity saved = GlobalHelper.getPresentEntity(examStudentRepo, studentId,
+				ExamStudentEntity.class);
+
+		GetCourseReq gcReq = new GetCourseReq();
+		gcReq.setRootOrgId(saved.getRootOrgId());
+		gcReq.setCode(saved.getCourseCode());
+		gcReq.setId(saved.getCourseId());
+		GetCourseResp gcResp = courseCloudService.getCourse(gcReq);
+		CourseBean courseBean = gcResp.getCourseBean();
+
+		GetOrgReq goReq = new GetOrgReq();
+		goReq.setRootOrgId(saved.getRootOrgId());
+		goReq.setOrgId(saved.getOrgId());
+		goReq.setOrgCode(saved.getOrgCode());
+		GetOrgResp goResp = orgCloudService.getOrg(goReq);
+		OrgBean orgBean = goResp.getOrg();
+
+		GetStudentReq getStudentReq = new GetStudentReq();
+		getStudentReq.setRootOrgId(saved.getRootOrgId());
+		getStudentReq.setIdentityNumber(saved.getIdentityNumber());
+		GetStudentResp getStudentResp = studentCloudService.getStudent(getStudentReq);
+		StudentBean studentInfo = getStudentResp.getStudentInfo();
+
+		ExamEntity exam = GlobalHelper.getEntity(examRepo, saved.getExamId(), ExamEntity.class);
+
+		// 同步操作
+		SyncExamStudentReq req = new SyncExamStudentReq();
+		req.setSyncType("update");
+		req.setId(saved.getId());
+
+		req.setEnable(saved.getEnable());
+
+		req.setCourseId(courseBean.getId());
+		req.setCourseCode(courseBean.getCode());
+		req.setCourseLevel(courseBean.getLevel());
+		req.setCourseName(courseBean.getName());
+
+		req.setExamId(exam.getId());
+		req.setExamName(exam.getName());
+
+		req.setRootOrgId(saved.getRootOrgId());
+		req.setStudentId(studentInfo.getId());
+		req.setStudentName(studentInfo.getName());
+		req.setIdentityNumber(studentInfo.getIdentityNumber());
+		req.setStudentCode(saved.getStudentCode());
+		req.setOrgId(saved.getOrgId());
+		req.setOrgName(orgBean.getName());
+		req.setOrgCode(saved.getOrgCode());
+
+		req.setGrade(saved.getGrade());
+		req.setSpecialtyName(saved.getSpecialtyName());
+		req.setPaperType(saved.getPaperType());
+		req.setRemark(saved.getRemark());
+		req.setInfoCollector(saved.getInfoCollector());
+		req.setExamSite(saved.getExamSite());
+
+		dataSyncCloudService.syncExamStudent(req);
+	}
+
+}

+ 706 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/impl/NoticeServiceImpl.java

@@ -0,0 +1,706 @@
+package cn.com.qmth.examcloud.core.examwork.service.impl;
+
+import cn.com.qmth.examcloud.api.commons.enums.NoticeReceiverRuleType;
+import cn.com.qmth.examcloud.api.commons.enums.NoticeStatus;
+import cn.com.qmth.examcloud.api.commons.exchange.PageInfo;
+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.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.DBUtil;
+import cn.com.qmth.examcloud.core.basic.api.UserCloudService;
+import cn.com.qmth.examcloud.core.basic.api.request.GetAllUsersByRoleReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetAllUsersByRoleResp;
+import cn.com.qmth.examcloud.core.examwork.dao.*;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.*;
+import cn.com.qmth.examcloud.core.examwork.service.NoticeService;
+import cn.com.qmth.examcloud.core.examwork.service.bean.*;
+import cn.com.qmth.examcloud.marking.api.MarkWorkCloudService;
+import cn.com.qmth.examcloud.marking.api.bean.MarkWorkMainBean;
+import cn.com.qmth.examcloud.marking.api.request.GetMarkWorkMainByIdsReq;
+import cn.com.qmth.examcloud.marking.api.request.GetMarkersByWorkIdsReq;
+import cn.com.qmth.examcloud.marking.api.response.GetMarkWorkMainByIdsResp;
+import cn.com.qmth.examcloud.marking.api.response.GetMarkersByWorkIdsResp;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import com.mysql.cj.util.StringUtils;
+import org.apache.commons.lang.time.DateUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.criteria.Predicate;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @Description 通知实现类
+ * @Author lideyin
+ * @Date 2019/7/2 17:41
+ * @Version 1.0
+ */
+@Service("noticeService")
+public class NoticeServiceImpl implements NoticeService {
+    @Autowired
+    MarkWorkCloudService markWorkCloudService;
+    @Autowired
+    ExamStudentRepo examStudentRepo;
+    @Autowired
+    UserCloudService userCloudService;
+    @Autowired
+    private NoticeRepo noticeRepo;
+    @Autowired
+    private UserNoticeRepo userNoticeRepo;
+    @Autowired
+    private NoticeRulePublishProgressRepo noticeRulePublishProgressRepo;
+    @Autowired
+    private NoticeReceiverRuleRepo noticeReceiverRuleRepo;
+    @Autowired
+    private ExamRepo examRepo;
+
+    @Override
+    public List<UserNoticeInfo> getNoticeList(UserNoticeInfoQuery query) {
+        List<UserNoticeInfo> resultList = new ArrayList<>();
+        List<UserNoticeEntity> userNoticeList;
+        if (query.getHasRead() != null) {
+            userNoticeList = userNoticeRepo
+                    .findByRootOrgIdAndUserTypeAndUserIdAndHasReadOrderByCreationTimeDesc(
+                            query.getRootOrgId(), query.getUserType(), query.getUserId(),
+                            query.getHasRead());
+        } else {
+            userNoticeList = userNoticeRepo
+                    .findByRootOrgIdAndUserTypeAndUserIdOrderByCreationTimeDesc(
+                            query.getRootOrgId(), query.getUserType(), query.getUserId());
+        }
+        if (null != userNoticeList && !userNoticeList.isEmpty()) {
+            List<Long> noticeIdList = userNoticeList.stream().map(UserNoticeEntity::getNoticeId)
+                    .collect(Collectors.toList());
+            List<NoticeEntity> noticeList = noticeRepo.findByIdIn(noticeIdList);
+            for (UserNoticeEntity un : userNoticeList) {
+                NoticeEntity noticeEntity = getNoticeById(noticeList, un.getNoticeId());
+                if (noticeEntity == null) {
+                    throw new StatusException("501005", "找不到id为:" + un.getNoticeId() + "的通知");
+                }
+                UserNoticeInfo info = new UserNoticeInfo();
+                info.setId(noticeEntity.getId());
+                info.setTitle(noticeEntity.getTitle());
+                info.setContent(noticeEntity.getContent());
+                info.setPublisher(noticeEntity.getPublisher());
+                info.setPublishTime(noticeEntity.getPublishTime());
+                info.setHasRead(un.getHasRead());
+                resultList.add(info);
+            }
+        }
+        return resultList;
+    }
+
+    @Override
+    public int updateNoticeReadStatus(String noticeId, UserType userType, Long userId) {
+        List<Long> noticeIdList;
+        if (noticeId.contains(",")) {
+            noticeIdList = Arrays.asList(noticeId.split(",")).stream().map(p -> Long.parseLong(p))
+                    .collect(Collectors.toList());
+        } else {
+            noticeIdList = Arrays.asList(noticeId).stream().map(p -> Long.parseLong(p))
+                    .collect(Collectors.toList());
+        }
+        return userNoticeRepo.updateNoticeReadStatus(noticeIdList, userType.toString(), userId);
+    }
+
+    @Override
+    public PageInfo<NoticeInfo> getPagedNoticeList(Integer curPage, Integer pageSize,
+                                                   NoticeInfoQuery infoQuery) {
+        List<NoticeInfo> resultList = new ArrayList<>();
+        Long rootOrgId = infoQuery.getRootOrgId();
+        Specification<NoticeEntity> specification = (root, query, cb) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+            if (!StringUtils.isNullOrEmpty(infoQuery.getTitle())) {
+                predicates.add(cb.like(root.get("title"),
+                        DBUtil.toSqlSearchPattern(infoQuery.getTitle())));
+            }
+            if (null != infoQuery.getPublishStatus()) {
+                predicates.add(cb.equal(root.get("noticeStatus"), infoQuery.getPublishStatus()));
+            }
+
+            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+        PageRequest pageRequest = PageRequest.of(curPage, pageSize,
+                new Sort(Sort.Direction.DESC, "creationTime"));
+        Page<NoticeEntity> pagedNoticeEntityList = noticeRepo.findAll(specification, pageRequest);
+        List<Long> noticeIdList = pagedNoticeEntityList.stream().map(p -> p.getId())
+                .collect(Collectors.toList());
+        List<NoticeReceiverRuleEntity> ruleList = getReceiverRuleList(rootOrgId,
+                noticeIdList.toArray(new Long[noticeIdList.size()]));
+
+        for (NoticeEntity ne : pagedNoticeEntityList) {
+            NoticeReceiverRuleType ruleType = getNoticeReceiverRuleType(ne.getId(), ruleList);
+            NoticeInfo ni = new NoticeInfo();
+            ni.setId(ne.getId());
+            ni.setPublisher(ne.getPublisher());
+            ni.setPublishTime(ne.getPublishTime());
+            ni.setTitle(ne.getTitle());
+            ni.setContent(ne.getContent());
+            ni.setPublishObject(getPublishObject(rootOrgId, ruleType,ne.getId(), ruleList));
+            ni.setPublishStatus(ne.getNoticeStatus());
+            ni.setRuleType(ruleType);
+            resultList.add(ni);
+        }
+        return new PageInfo<>(pagedNoticeEntityList, resultList);
+
+    }
+
+    @Transactional
+    @Override
+    public int deleteNotice(Long rootOrgId, List<Long> noticeIdList) {
+        int result = 0;
+        // 删除通知进度相关信息
+        result += noticeRulePublishProgressRepo.deleteByRootOrgIdAndNoticeIdIn(rootOrgId,
+                noticeIdList);
+        // 删除通知规则相关信息
+        result += noticeReceiverRuleRepo.deleteByRootOrgIdAndNoticeIdIn(rootOrgId, noticeIdList);
+        // 删除通知相关信息
+        result += noticeRepo.deleteByIdIn(noticeIdList);
+        return result;
+    }
+
+    @Transactional
+    @Override
+    public int addNotice(AddNoticeInfo addNoticeInfo) {
+        // 保存公告基本信息
+        NoticeEntity noticeEntity = getNoticeEntityFrom(addNoticeInfo);
+        NoticeEntity savedNotice = noticeRepo.save(noticeEntity);
+
+        Long noticeId = savedNotice.getId();
+
+        // 保存公告接收规则
+        List<NoticeReceiverRuleEntity> ruleList = getNoticeReceiverRuleEntityListFrom(addNoticeInfo,
+                noticeId);
+        noticeReceiverRuleRepo.saveAll(ruleList);
+
+        // 保存公告发布进度
+        NoticeRulePublishProgressEntity publishScheduleEntity = getNoticeRulePublishProgressEntityFrom(
+                addNoticeInfo, noticeId);
+        noticeRulePublishProgressRepo.save(publishScheduleEntity);
+
+        return noticeId > 0 ? 1 : 0;
+
+    }
+
+    @Transactional
+    @Override
+    public NoticeRulePublishProgressEntity updateNotice(UpdateNoticeInfo info) {
+        Long rootOrgId = info.getRootOrgId();
+
+        // 校验通知状态,正在发送的通知不允许修改
+        NoticeEntity originalNotice = GlobalHelper.getEntity(noticeRepo, info.getId(),
+                NoticeEntity.class);
+        if (originalNotice == null) {
+            throw new StatusException("501006", "找不到通知id为:" + info.getId() + "的数据");
+        }
+        if (originalNotice.getNoticeStatus() != NoticeStatus.DRAFT) {
+            throw new StatusException("501008", "发布中或已发布的通知不允许修改");
+        }
+
+        // 更新通知表
+
+        originalNotice.setTitle(info.getTitle());
+        originalNotice.setPublisher(info.getPublisher());
+        originalNotice.setNoticeStatus(info.getNoticeStatus());
+        if (info.getNoticeStatus() == NoticeStatus.TO_BE_PUBLISHED) {
+            originalNotice.setPublishTime(new Date());
+        }
+        originalNotice.setContent(info.getContent());
+        originalNotice.setUpdateTime(new Date());
+        noticeRepo.save(originalNotice);
+
+        List<Long> noticeIdList = Collections.singletonList(info.getId());
+
+        // 更新公告接收规则实体
+        noticeReceiverRuleRepo.deleteByRootOrgIdAndNoticeIdIn(rootOrgId, noticeIdList);
+        List<NoticeReceiverRuleEntity> ruleList = getNoticeReceiverRuleEntityListFrom(info);
+        noticeReceiverRuleRepo.saveAll(ruleList);
+
+        // 更新公告发布进度实体
+        noticeRulePublishProgressRepo.deleteByRootOrgIdAndNoticeIdIn(rootOrgId, noticeIdList);
+        NoticeRulePublishProgressEntity publishScheduleEntity = getNoticeRulePublishProgressEntityFrom(
+                info);
+        NoticeRulePublishProgressEntity saved = noticeRulePublishProgressRepo
+                .save(publishScheduleEntity);
+
+        return saved;
+    }
+
+    @Override
+    public List<NoticeRulePublishProgressEntity> getToBeDisposedNoticeRulePublishProgressList() {
+        return noticeRulePublishProgressRepo.findUnpublishedNoticeProcessList();
+    }
+
+    @Override
+    public Long disposePublishingUserNotice(Long startUserId,
+                                            NoticeRulePublishProgressEntity ruleProgress) {
+
+        // 发布通知每次处理的用户id数量 // TODO: 2019/7/10 需要将参数放到配置文件
+        int rowNumber = PropertyHolder.getInt("notice.dispose.userId.size", 20);
+        Long rootOrgId = ruleProgress.getRootOrgId();
+        Long noticeId = ruleProgress.getNoticeId();
+        NoticeReceiverRuleType ruleType = ruleProgress.getNoticeReceiverRuleType();
+
+        List<NoticeReceiverRuleEntity> currentRuleList = getReceiverRuleList(rootOrgId, noticeId);
+
+        //获取指定数量的用户id
+        GetLimitUserIdResp getLimitUserIdResp = getSpecifiedUserIdList(rootOrgId, rowNumber,
+                startUserId, ruleType, currentRuleList);
+
+        // 满足条件的用户id集合(可能为空)
+        List<Long> limitStudentIdList = getLimitUserIdResp.getIdList();
+
+        //如果没有取到数据,则直接返回
+        if (null == limitStudentIdList || limitStudentIdList.size()==0){
+            return getLimitUserIdResp.getNextId();
+        }
+
+        //保存用户通知关系表数据,并更新通知的发布进度
+        saveUserNoticeAndUpdateRulePublishProgress(rootOrgId, noticeId, ruleType, ruleProgress,
+                limitStudentIdList, getLimitUserIdResp.getMaxId());
+
+        return getLimitUserIdResp.getNextId();
+
+    }
+
+    @Override
+    public void disposeOverdueNotice() {
+        // 通知过期年限阈值// TODO: 2019/7/10
+        int overdueYearThreshold = PropertyHolder.getInt("notice.dispose.overdue.year", 1);
+        Date now = new Date();
+        Date lastYear = DateUtils.addYears(now, -overdueYearThreshold);
+        List<NoticeEntity> overdueNoticeList = noticeRepo.findByCreationTimeBefore(lastYear);
+        if (overdueNoticeList != null && !overdueNoticeList.isEmpty()) {
+            for (NoticeEntity notice : overdueNoticeList) {
+                deleteAllRelatedNotice(notice.getId());
+            }
+        }
+
+    }
+
+    @Override
+    public void updateNoticeStatus(Long noticeId, NoticeStatus noticeStatus) {
+        noticeRepo.updateNoticeStatus(noticeId, noticeStatus.toString());
+    }
+
+    /**
+     * 删除所有相关的通知数据
+     *
+     * @param noticeId
+     */
+    @Transactional
+    public void deleteAllRelatedNotice(Long noticeId) {
+        userNoticeRepo.deleteByNoticeId(noticeId);
+        noticeReceiverRuleRepo.deleteByNoticeId(noticeId);
+        noticeRulePublishProgressRepo.deleteByNoticeId(noticeId);
+        noticeRepo.deleteById(noticeId);
+    }
+
+    /**
+     * 更新发布进度
+     *
+     * @param publishSchedule
+     * @param maxUserId
+     */
+    @Transactional
+    public void saveUserNoticeAndUpdateRulePublishProgress(Long rootOrgId, Long noticeId,
+                                                           NoticeReceiverRuleType ruleType, NoticeRulePublishProgressEntity rulePublishProgress,
+                                                           List<Long> limitStudentIdList, Long maxUserId) {
+        // 保存并更新发布状态为发布完成
+        List<UserNoticeEntity> userNoticeList = new ArrayList<>();
+        for (Long userId : limitStudentIdList) {
+            //构建查询条件,判断当前用户是否已发过当前通知
+            UserNoticeEntity query = new UserNoticeEntity();
+            query.setUserId(userId);
+            query.setNoticeId(noticeId);
+            UserType userType = (ruleType == NoticeReceiverRuleType.STUDENTS_OF_EXAM
+                    || ruleType == NoticeReceiverRuleType.ALL_STUDENTS_OF_ROOT_ORG)
+                    ? UserType.STUDENT
+                    : UserType.COMMON;
+            query.setUserType(userType);
+            Example<UserNoticeEntity> queryExample = Example.of(query);
+
+            //如果用户未发过当前通知,则添加相当通知数据,否则跳过
+            if (!userNoticeRepo.exists(queryExample)){
+                UserNoticeEntity userNotice = initUserNoticeEntity(rootOrgId, userId, noticeId,
+                        userType);
+                userNoticeList.add(userNotice);
+            }
+        }
+        userNoticeRepo.saveAll(userNoticeList);
+
+        //更新通知通知进度的最大用户id
+        rulePublishProgress.setMaxStudentId(maxUserId);
+        noticeRulePublishProgressRepo.save(rulePublishProgress);
+    }
+
+    /**
+     * 获取指定数量的考试记录id
+     *
+     * @param rootOrgId   组织机构id
+     * @param rowNumber   获取的行数
+     * @param startUserId 起始用户id
+     * @param ruleType    通知对象规则类型
+     * @param ruleList    当前规则类型下对应的规则明细
+     * @return 返回用户id集合, 和下一次请求id
+     */
+    private GetLimitUserIdResp getSpecifiedUserIdList(Long rootOrgId, int rowNumber,
+                                                      Long startUserId, NoticeReceiverRuleType ruleType,
+                                                      List<NoticeReceiverRuleEntity> ruleList) {
+        GetLimitUserIdResp resultResp = new GetLimitUserIdResp();
+        
+        switch (ruleType) {
+            case TEACHER_OF_MARK_WORK:
+                return getGetLimitUserIdByMarkWork(rootOrgId, rowNumber, startUserId, ruleList);
+            case COMMON_USERS_OF_ROLE:
+                return getGetLimitUserIdByRole(rootOrgId, startUserId);
+            case STUDENTS_OF_EXAM:
+                return getLimitUserIdByExamStudent(rootOrgId, startUserId, rowNumber, ruleList);
+            case ALL_STUDENTS_OF_ROOT_ORG:
+                return getLimitUserIdByAllStudent(rootOrgId, rowNumber, startUserId);
+                default:
+                    resultResp.setNextId(0L);
+                    resultResp.setIdList(null);
+                    return resultResp;
+        }
+    }
+
+    private GetLimitUserIdResp getGetLimitUserIdByMarkWork(Long rootOrgId, int rowNumber,
+                                                           Long startUserId, List<NoticeReceiverRuleEntity> ruleList) {
+        GetLimitUserIdResp resultResp = new GetLimitUserIdResp();
+        // 获取当前规则下所有问卷工作id
+        List<Long> markWorkIdList = ruleList.stream().map(p -> Long.parseLong(p.getRuleValue()))
+                .collect(Collectors.toList());
+        GetMarkersByWorkIdsReq markWorkerReq = new GetMarkersByWorkIdsReq();
+        markWorkerReq.setRootOrgId(rootOrgId);
+        markWorkerReq.setWorkIds(markWorkIdList);
+        markWorkerReq.setStarId(startUserId);
+        markWorkerReq.setSize(rowNumber);
+        // FIXME: 2019/7/11
+        GetMarkersByWorkIdsResp markWorkerResp = markWorkCloudService
+                .getMarkersByWorkIds(markWorkerReq);
+        List<Long> limitUserIdList = markWorkerResp.getMarkers();
+        if (markWorkerResp.getMarkers() != null && !markWorkerResp.getMarkers().isEmpty()) {
+            resultResp.setMaxId(Collections.max(limitUserIdList));
+        }
+        resultResp.setNextId(markWorkerResp.getNextId());
+        resultResp.setIdList(markWorkerResp.getMarkers());
+        return resultResp;
+    }
+
+    private GetLimitUserIdResp getGetLimitUserIdByRole(Long rootOrgId, Long startUserId) {
+        GetLimitUserIdResp resultResp = new GetLimitUserIdResp();
+        List<Long> limitUserIdList = new ArrayList<>();
+        long nextUserId;// 当前需求只有考试中心
+        GetAllUsersByRoleReq getLcUserReq = new GetAllUsersByRoleReq();
+        getLcUserReq.setRootOrgId(rootOrgId);
+        getLcUserReq.setRoleCode(RoleMeta.LC_USER.toString());
+        getLcUserReq.setStart(startUserId);
+        GetAllUsersByRoleResp getLcUserResp = userCloudService.getAllUsersByRole(getLcUserReq);
+        nextUserId = getLcUserResp.getNext();
+        if (getLcUserResp.getUserBeanList() != null && !getLcUserResp.getUserBeanList().isEmpty()) {
+            limitUserIdList = getLcUserResp.getUserBeanList().stream().map(p -> p.getUserId())
+                    .collect(Collectors.toList());
+            resultResp.setMaxId(Collections.max(limitUserIdList));
+        }
+        resultResp.setNextId(nextUserId);
+        resultResp.setIdList(limitUserIdList);
+        return resultResp;
+    }
+
+    private GetLimitUserIdResp getLimitUserIdByAllStudent(Long rootOrgId, int rowNumber,
+                                                          Long startUserId) {
+        GetLimitUserIdResp resultResp = new GetLimitUserIdResp();
+        long nextId = startUserId;
+        Long maxUserId = null;
+        List<Long> limitUserIdList = examStudentRepo.findLimitStudentIdList(rootOrgId, startUserId,
+                rowNumber);
+        if (limitUserIdList != null && !limitUserIdList.isEmpty()) {
+            maxUserId = Collections.max(limitUserIdList);
+            nextId = maxUserId;
+            nextId++;
+        }
+
+        resultResp.setNextId(nextId);
+        resultResp.setMaxId(maxUserId);
+        resultResp.setIdList(limitUserIdList);
+        return resultResp;
+    }
+
+    private GetLimitUserIdResp getLimitUserIdByExamStudent(Long rootOrgId, Long startUserId,
+                                                           int rowNumber, List<NoticeReceiverRuleEntity> ruleList) {
+        GetLimitUserIdResp resultResp = new GetLimitUserIdResp();
+        // 获取当前规则下所有的考试批次id
+        List<Long> examIdList = ruleList.stream().map(p -> Long.parseLong(p.getRuleValue()))
+                .collect(Collectors.toList());
+        long nextId = startUserId;
+        Long maxUserId = null;
+        List<Long> limitUserIdList = examStudentRepo.findByExamIdLimitStudentIdList(rootOrgId,
+                examIdList, startUserId, rowNumber);
+        if (limitUserIdList != null && !limitUserIdList.isEmpty()) {
+            maxUserId = Collections.max(limitUserIdList);
+            nextId = maxUserId;
+            nextId++;
+        }
+        resultResp.setMaxId(maxUserId);
+        resultResp.setNextId(nextId);
+        resultResp.setIdList(limitUserIdList);
+        return resultResp;
+    }
+
+    private UserNoticeEntity initUserNoticeEntity(Long rootOrgId, Long userId, Long noticeId,
+                                                  UserType userType) {
+        UserNoticeEntity userNotice = new UserNoticeEntity();
+        userNotice.setRootOrgId(rootOrgId);
+        userNotice.setHasRead(false);
+        userNotice.setNoticeId(noticeId);
+        userNotice.setUserType(userType);
+        userNotice.setUserId(userId);
+        return userNotice;
+    }
+
+    /**
+     * 获取发布对象
+     *
+     * @param rootOrgId
+     * @param ruleType
+     * @param noticeId
+     * @return
+     */
+    private List<Map<String, Object>> getPublishObject(Long rootOrgId,
+                                                       NoticeReceiverRuleType ruleType,Long noticeId, List<NoticeReceiverRuleEntity> ruleList) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        Map<String, Object> objectMap;
+        List<NoticeReceiverRuleEntity> currentRuleList;
+        switch (ruleType) {
+            case STUDENTS_OF_EXAM:
+                currentRuleList = ruleList.stream().filter(p -> rootOrgId.equals(p.getRootOrgId()) &&
+                        noticeId.equals(p.getNoticeId()) && p.getRuleType() == ruleType)
+                        .collect(Collectors.toList());
+                return getExamStudentObject(currentRuleList);
+            case ALL_STUDENTS_OF_ROOT_ORG:
+                objectMap = new HashMap<>();
+                objectMap.put("id", 0);
+                objectMap.put("name", "所有学生");
+                objectMap.put("ruleType", "ALL_STUDENTS_OF_ROOT_ORG");
+                resultList.add(objectMap);
+                return resultList;
+            case COMMON_USERS_OF_ROLE:
+                objectMap = new HashMap<>();
+                objectMap.put("id", 0);
+                objectMap.put("name", "所有学习中心用户");
+                objectMap.put("ruleType", "COMMON_USERS_OF_ROLE");
+                resultList.add(objectMap);
+                return resultList;
+            case TEACHER_OF_MARK_WORK:
+                currentRuleList = ruleList.stream().filter(p -> rootOrgId.equals(p.getRootOrgId()) &&
+                        noticeId.equals(p.getNoticeId()) && p.getRuleType() == ruleType)
+                        .collect(Collectors.toList());
+                return getMarkTeacherObject(rootOrgId, currentRuleList);
+        }
+        return resultList;
+    }
+
+    /**
+     * 组装学生发布对象
+     *
+     * @param ruleList
+     * @return
+     */
+    private List<Map<String, Object>> getExamStudentObject(
+            List<NoticeReceiverRuleEntity> ruleList) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        // 考试批次id
+        List<Long> examIdList = ruleList.stream().map(p -> Long.parseLong(p.getRuleValue()))
+                .collect(Collectors.toList());
+        List<ExamEntity> examList = examRepo.findByIdIn(examIdList);
+        for (ExamEntity e : examList) {
+            Map<String, Object> map = new HashMap<>();
+            map.put("id", e.getId());
+            map.put("name", "学生-" + e.getName());
+            map.put("ruleType", "STUDENTS_OF_EXAM");
+            resultList.add(map);
+        }
+        return resultList;
+    }
+
+    /**
+     * 组装阅卷老师发布对象
+     *
+     * @param rootOrgId
+     * @param ruleList
+     * @return
+     */
+    private List<Map<String, Object>> getMarkTeacherObject(Long rootOrgId,
+                                                           List<NoticeReceiverRuleEntity> ruleList) {
+        List<Map<String, Object>> resultList = new ArrayList<>();
+        List<Long> markWorkIdList = ruleList.stream().map(p -> Long.parseLong(p.getRuleValue()))
+                .collect(Collectors.toList());
+        GetMarkWorkMainByIdsReq req = new GetMarkWorkMainByIdsReq();
+        req.setRootOrgId(rootOrgId);
+        req.setWorkIds(markWorkIdList);
+
+        GetMarkWorkMainByIdsResp markWorkResp = markWorkCloudService.getMarkWorkMainByIds(req);
+        List<MarkWorkMainBean> markWorkList = markWorkResp.getList();
+        for (MarkWorkMainBean mw : markWorkList) {
+            Map<String, Object> map = new HashMap<>();
+            map.put("id", mw.getId());
+            map.put("name", "老师-" + mw.getName());
+            map.put("ruleType", "TEACHER_OF_MARK_WORK");
+            resultList.add(map);
+        }
+        return resultList;
+    }
+
+    /**
+     * 根据通知id获取通知
+     *
+     * @param noticeList
+     * @param noticeId
+     * @return
+     */
+    private NoticeEntity getNoticeById(List<NoticeEntity> noticeList, Long noticeId) {
+        Optional<NoticeEntity> optional = noticeList.stream()
+                .filter(p -> p.getId().equals(noticeId)).findFirst();
+        if (optional.isPresent()) {
+            return optional.get();
+        } else {
+            return null;
+        }
+    }
+
+    private NoticeRulePublishProgressEntity getNoticeRulePublishProgressEntityFrom(
+            AddNoticeInfo addNoticeInfo, Long noticeId) {
+        NoticeRulePublishProgressEntity progressEntity = new NoticeRulePublishProgressEntity();
+        progressEntity.setRootOrgId(addNoticeInfo.getRootOrgId());
+        progressEntity.setNoticeId(noticeId);
+        progressEntity.setNoticeReceiverRuleType(addNoticeInfo.getRuleType());
+        return progressEntity;
+    }
+
+    private NoticeEntity getNoticeEntityFrom(AddNoticeInfo addNoticeInfo) {
+        NoticeEntity noticeEntity = new NoticeEntity();
+        noticeEntity.setRootOrgId(addNoticeInfo.getRootOrgId());
+        noticeEntity.setTitle(addNoticeInfo.getTitle());
+        noticeEntity.setContent(addNoticeInfo.getContent());
+        noticeEntity.setNoticeStatus(addNoticeInfo.getNoticeStatus());
+        noticeEntity.setPublisher(addNoticeInfo.getPublisher());
+        // 只有为立即发布的才有发布时间
+        if (addNoticeInfo.getNoticeStatus() == NoticeStatus.TO_BE_PUBLISHED) {
+            noticeEntity.setPublishTime(new Date());
+        }
+        return noticeEntity;
+    }
+
+    private List<NoticeReceiverRuleEntity> getNoticeReceiverRuleEntityListFrom(
+            AddNoticeInfo addNoticeInfo, Long noticeId) {
+        List<NoticeReceiverRuleEntity> ruleList = new ArrayList<>();
+        // 如果发送对象规则类型为所有学生或学习中心不需要赋值
+        if (addNoticeInfo.getRuleType() == NoticeReceiverRuleType.ALL_STUDENTS_OF_ROOT_ORG
+                || addNoticeInfo.getRuleType() == NoticeReceiverRuleType.COMMON_USERS_OF_ROLE) {
+            NoticeReceiverRuleEntity ruleEntity = new NoticeReceiverRuleEntity();
+            ruleEntity.setRootOrgId(addNoticeInfo.getRootOrgId());
+            ruleEntity.setNoticeId(noticeId);
+            ruleEntity.setRuleType(addNoticeInfo.getRuleType());
+            ruleEntity.setRuleValue(null);
+            ruleList.add(ruleEntity);
+        } else {
+            String publishObjectIds = getStandardIds(addNoticeInfo.getPublishObjectId(), ",");
+            String[] publishObjectIdArr = publishObjectIds.split(",");
+            for (String publishObjectId : publishObjectIdArr) {
+                NoticeReceiverRuleEntity ruleEntity = new NoticeReceiverRuleEntity();
+                ruleEntity.setRootOrgId(addNoticeInfo.getRootOrgId());
+                ruleEntity.setNoticeId(noticeId);
+                ruleEntity.setRuleType(addNoticeInfo.getRuleType());
+                ruleEntity.setRuleValue(publishObjectId);
+                ruleList.add(ruleEntity);
+            }
+        }
+
+        return ruleList;
+    }
+
+    /**
+     * 获取标准的id字符串,去掉最后一个字符
+     *
+     * @param strIds    id字符串
+     * @param separator 分隔符
+     * @return
+     */
+    private String getStandardIds(String strIds, String separator) {
+        if (!StringUtils.isNullOrEmpty(strIds)) {
+            if (strIds.lastIndexOf(separator) == strIds.length() - 1) {
+                return strIds.substring(0, strIds.lastIndexOf(separator));
+            }
+        }
+        return strIds;
+    }
+
+    private NoticeRulePublishProgressEntity getNoticeRulePublishProgressEntityFrom(
+            UpdateNoticeInfo info) {
+        NoticeRulePublishProgressEntity publishScheduleEntity = new NoticeRulePublishProgressEntity();
+        publishScheduleEntity.setRootOrgId(info.getRootOrgId());
+        publishScheduleEntity.setNoticeId(info.getId());
+        publishScheduleEntity.setNoticeReceiverRuleType(info.getRuleType());
+        return publishScheduleEntity;
+    }
+
+    private List<NoticeReceiverRuleEntity> getNoticeReceiverRuleEntityListFrom(
+            UpdateNoticeInfo info) {
+        List<NoticeReceiverRuleEntity> ruleList = new ArrayList<>();
+        // 如果发送对象规则类型为所有学生或学习中心不需要赋值
+        if (info.getRuleType() == NoticeReceiverRuleType.ALL_STUDENTS_OF_ROOT_ORG
+                || info.getRuleType() == NoticeReceiverRuleType.COMMON_USERS_OF_ROLE) {
+            NoticeReceiverRuleEntity ruleEntity = new NoticeReceiverRuleEntity();
+            ruleEntity.setRootOrgId(info.getRootOrgId());
+            ruleEntity.setNoticeId(info.getId());
+            ruleEntity.setRuleType(info.getRuleType());
+            ruleEntity.setRuleValue(null);
+            ruleList.add(ruleEntity);
+        } else {
+            String publishObjectIds = getStandardIds(info.getPublishObjectId(), ",");
+            String[] publishObjectIdArr = publishObjectIds.split(",");
+            for (String publishObjectId : publishObjectIdArr) {
+                NoticeReceiverRuleEntity ruleEntity = new NoticeReceiverRuleEntity();
+                ruleEntity.setRootOrgId(info.getRootOrgId());
+                ruleEntity.setNoticeId(info.getId());
+                ruleEntity.setRuleType(info.getRuleType());
+                ruleEntity.setRuleValue(publishObjectId);
+                ruleList.add(ruleEntity);
+            }
+        }
+
+        return ruleList;
+    }
+
+    private List<NoticeReceiverRuleEntity> getReceiverRuleList(Long rootOrgId, Long... noticeId) {
+        List<NoticeReceiverRuleEntity> ruleList = noticeReceiverRuleRepo
+                .findByRootOrgIdAndNoticeIdIn(rootOrgId, Arrays.asList(noticeId));
+        if (ruleList == null) {
+            throw new StatusException("501009", "找不到通知id为:" + noticeId + "的通知对象信息");
+        }
+        return ruleList;
+    }
+
+    private NoticeReceiverRuleType getNoticeReceiverRuleType(Long noticeId,
+                                                             List<NoticeReceiverRuleEntity> ruleList) {
+        Optional<NoticeReceiverRuleType> first = ruleList.stream()
+                .filter(p -> p.getNoticeId().equals(noticeId)).map(p -> p.getRuleType())
+                .findFirst();
+        if (first.isPresent()) {
+            return first.get();
+        } else {
+            throw new StatusException("501012", "找不到通知id为:" + noticeId + "的通知规则类型");
+        }
+    }
+
+}

+ 206 - 0
examcloud-core-examwork-service/src/main/java/cn/com/qmth/examcloud/core/examwork/service/impl/OnGoingExamServiceImpl.java

@@ -0,0 +1,206 @@
+package cn.com.qmth.examcloud.core.examwork.service.impl;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.criteria.Predicate;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+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.enums.ExamSpecialSettingsType;
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.examwork.dao.ExamSpecialSettingsRepo;
+import cn.com.qmth.examcloud.core.examwork.dao.entity.ExamSpecialSettingsEntity;
+import cn.com.qmth.examcloud.core.examwork.service.OnGoingExamService;
+
+/**
+ * 待考考试服务
+ *
+ * @author WANGWEI
+ * @date 2019年10月25日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+@Service
+public class OnGoingExamServiceImpl implements OnGoingExamService {
+
+	@Autowired
+	ExamSpecialSettingsRepo examSpecialSettingsRepo;
+
+	@Override
+	public List<ExamSpecialSettingsEntity> getOngoingExamList(Long rootOrgId, String examType,
+			Long orgId, Long studentId) {
+
+		if (null == rootOrgId) {
+			throw new StatusException("008001", "rootOrgId is wrong");
+		}
+
+		ExamType et = null;
+		if (StringUtils.isNotBlank(examType)) {
+			try {
+				et = ExamType.valueOf(examType);
+			} catch (Exception e) {
+				throw new StatusException("008002", "examType is wrong");
+			}
+		}
+
+		if (null == orgId) {
+			throw new StatusException("008003", "orgId is wrong");
+		}
+
+		if (null == studentId) {
+			throw new StatusException("008004", "studentId is wrong");
+		}
+
+		List<ExamSpecialSettingsEntity> byExam = getByExam(rootOrgId, et);
+		List<ExamSpecialSettingsEntity> byOrg = getByOrg(rootOrgId, et, orgId);
+		List<ExamSpecialSettingsEntity> byStudentId = getByStudentId(rootOrgId, et, studentId);
+
+		List<ExamSpecialSettingsEntity> examList = Lists.newArrayListWithCapacity(0);
+		examList.addAll(byOrg);
+		examList.addAll(byStudentId);
+
+		for (ExamSpecialSettingsEntity cur : byExam) {
+			if (!cur.getSpecialSettingsEnabled()) {
+				examList.add(cur);
+			}
+			ExamSpecialSettingsType specialSettingsType = cur.getSpecialSettingsType();
+			if (null == specialSettingsType) {
+				examList.add(cur);
+			} else if (specialSettingsType.equals(ExamSpecialSettingsType.ORG_BASED)) {
+				ExamSpecialSettingsEntity specialSettings = examSpecialSettingsRepo
+						.findByExamIdAndOrgIdAndCourseIdIsNullAndStudentIdIsNull(cur.getExamId(),
+								orgId);
+				if (null == specialSettings) {
+					examList.add(cur);
+				} else if (null == specialSettings.getBeginTime()
+						&& null == specialSettings.getEndTime()) {
+					examList.add(cur);
+				}
+			} else if (specialSettingsType.equals(ExamSpecialSettingsType.STUDENT_BASED)) {
+				ExamSpecialSettingsEntity specialSettings = examSpecialSettingsRepo
+						.findByExamIdAndStudentIdAndOrgIdIsNullAndCourseIdIsNull(cur.getExamId(),
+								studentId);
+				if (null == specialSettings) {
+					examList.add(cur);
+				} else if (null == specialSettings.getBeginTime()
+						&& null == specialSettings.getEndTime()) {
+					examList.add(cur);
+				}
+			}
+		}
+
+		examList.sort((a, b) -> a.getBeginTime().after(b.getBeginTime()) ? 1 : -1);
+
+		return examList;
+	}
+
+	/**
+	 * 考试设置
+	 *
+	 * @author WANGWEI
+	 * @param rootOrgId
+	 * @param type
+	 * @return
+	 */
+	private List<ExamSpecialSettingsEntity> getByExam(Long rootOrgId, ExamType type) {
+		Specification<ExamSpecialSettingsEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+			predicates.add(cb.greaterThan(root.get("endTime"), new Date()));
+			predicates.add(cb.equal(root.get("examType"), type));
+			predicates.add(cb.equal(root.get("examEnable"), true));
+
+			predicates.add(cb.isNull(root.get("courseId")));
+			predicates.add(cb.isNull(root.get("orgId")));
+			predicates.add(cb.isNull(root.get("studentId")));
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		PageRequest pageRequest = PageRequest.of(0, 100, new Sort(Direction.ASC, "beginTime"));
+
+		List<ExamSpecialSettingsEntity> entityList = examSpecialSettingsRepo
+				.findAll(specification, pageRequest).getContent();
+		return entityList;
+	}
+
+	/**
+	 * 机构特殊设置
+	 *
+	 * @author WANGWEI
+	 * @param rootOrgId
+	 * @param type
+	 * @param orgId
+	 * @return
+	 */
+	private List<ExamSpecialSettingsEntity> getByOrg(Long rootOrgId, ExamType type, Long orgId) {
+		Specification<ExamSpecialSettingsEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+			predicates.add(cb.greaterThan(root.get("endTime"), new Date()));
+			predicates.add(cb.equal(root.get("examType"), type));
+			predicates.add(cb.equal(root.get("examEnable"), true));
+
+			predicates.add(cb.isNull(root.get("courseId")));
+			predicates.add(cb.isNull(root.get("studentId")));
+			predicates.add(cb.equal(root.get("orgId"), orgId));
+			predicates.add(cb.equal(root.get("specialSettingsEnabled"), true));
+			predicates.add(
+					cb.equal(root.get("specialSettingsType"), ExamSpecialSettingsType.ORG_BASED));
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		PageRequest pageRequest = PageRequest.of(0, 100, new Sort(Direction.ASC, "beginTime"));
+
+		List<ExamSpecialSettingsEntity> entityList = examSpecialSettingsRepo
+				.findAll(specification, pageRequest).getContent();
+		return entityList;
+	}
+
+	/**
+	 * 学生特殊设置
+	 *
+	 * @author WANGWEI
+	 * @param rootOrgId
+	 * @param type
+	 * @param studentId
+	 * @return
+	 */
+	private List<ExamSpecialSettingsEntity> getByStudentId(Long rootOrgId, ExamType type,
+			Long studentId) {
+		Specification<ExamSpecialSettingsEntity> specification = (root, query, cb) -> {
+			List<Predicate> predicates = new ArrayList<>();
+			predicates.add(cb.equal(root.get("rootOrgId"), rootOrgId));
+			predicates.add(cb.greaterThan(root.get("endTime"), new Date()));
+			predicates.add(cb.equal(root.get("examType"), type));
+			predicates.add(cb.equal(root.get("examEnable"), true));
+
+			predicates.add(cb.isNull(root.get("courseId")));
+			predicates.add(cb.isNull(root.get("orgId")));
+			predicates.add(cb.equal(root.get("studentId"), studentId));
+			predicates.add(cb.equal(root.get("specialSettingsEnabled"), true));
+			predicates.add(cb.equal(root.get("specialSettingsType"),
+					ExamSpecialSettingsType.STUDENT_BASED));
+
+			return cb.and(predicates.toArray(new Predicate[predicates.size()]));
+		};
+
+		PageRequest pageRequest = PageRequest.of(0, 100, new Sort(Direction.ASC, "beginTime"));
+
+		List<ExamSpecialSettingsEntity> entityList = examSpecialSettingsRepo
+				.findAll(specification, pageRequest).getContent();
+		return entityList;
+	}
+
+}

+ 16 - 0
examcloud-core-examwork-starter/.springBeans

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beansProjectDescription>
+	<version>1</version>
+	<pluginVersion><![CDATA[3.9.4.201804120850-RELEASE]]></pluginVersion>
+	<configSuffixes>
+		<configSuffix><![CDATA[xml]]></configSuffix>
+	</configSuffixes>
+	<enableImports><![CDATA[false]]></enableImports>
+	<configs>
+		<config>java:cn.com.qmth.examcloud.core.examwork.starter.CoreExamWorkApplication</config>
+	</configs>
+	<autoconfigs>
+	</autoconfigs>
+	<configSets>
+	</configSets>
+</beansProjectDescription>

+ 30 - 0
examcloud-core-examwork-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>

+ 68 - 0
examcloud-core-examwork-starter/pom.xml

@@ -0,0 +1,68 @@
+<?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.core.examwork</groupId>
+		<artifactId>examcloud-core-examwork</artifactId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+
+	<artifactId>examcloud-core-examwork-starter</artifactId>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.core.examwork</groupId>
+			<artifactId>examcloud-core-examwork-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.core.examwork.starter.CoreExamWorkApp</mainClass>
+							<addClasspath>true</addClasspath>
+							<classpathPrefix>./</classpathPrefix>
+						</manifest>
+						<manifestEntries>
+							<Class-Path>../config/</Class-Path>
+						</manifestEntries>
+					</archive>
+					<excludes>
+						<exclude>templates/*</exclude>
+						<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-core-examwork</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>

+ 1 - 0
examcloud-core-examwork-starter/shell/start.args

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

+ 38 - 0
examcloud-core-examwork-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-core-examwork-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-core-examwork-starter/shell/start.vmoptions

@@ -0,0 +1 @@
+-server -Xms2g -Xmx2g

+ 18 - 0
examcloud-core-examwork-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-core-examwork-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-core-examwork-starter/shell/version

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

+ 80 - 0
examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/CoreExamWorkApp.java

@@ -0,0 +1,80 @@
+package cn.com.qmth.examcloud.core.examwork.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.core.examwork.base.enums.ExamProperty;
+import cn.com.qmth.examcloud.core.examwork.dao.UniqueRuleHolder;
+import cn.com.qmth.examcloud.web.bootstrap.AppBootstrap;
+import cn.com.qmth.examcloud.web.jpa.DataIntegrityViolationTransverter;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+
+@SpringBootApplication
+@Configuration
+@EnableJpaAuditing
+@EnableTransactionManagement
+@EnableEurekaClient
+@EnableDiscoveryClient
+@ComponentScan(basePackages = {"cn.com.qmth"})
+@EntityScan(basePackages = {"cn.com.qmth"})
+@EnableJpaRepositories(basePackages = {"cn.com.qmth"})
+@EnableAutoConfiguration(exclude = {MultipartAutoConfiguration.class})
+public class CoreExamWorkApp {
+
+	static {
+		String runtimeLevel = System.getProperty("log.commonLevel");
+		if (null == runtimeLevel) {
+			System.setProperty("log.commonLevel", "DEBUG");
+		}
+		System.setProperty("hibernate.dialect.storage_engine", "innodb");
+
+		DataIntegrityViolationTransverter.setUniqueRules(UniqueRuleHolder.getUniqueRuleList());
+		ExamProperty.init();
+	}
+
+	/**
+	 * main 启动类
+	 * 
+	 * @author WANGWEI
+	 * @param args
+	 * @throws Exception
+	 */
+	public static void main(String[] args) throws Exception {
+		AppBootstrap.run(CoreExamWorkApp.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-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/Tester.java

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

+ 135 - 0
examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/config/ExamCloudResourceManager.java

@@ -0,0 +1,135 @@
+package cn.com.qmth.examcloud.core.examwork.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 http://www.qmth.com.cn/ 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);
+		if (null == appCacheBean) {
+			return null;
+		}
+		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;
+	}
+
+}

+ 53 - 0
examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/config/ExamCloudWebMvcConfigurer.java

@@ -0,0 +1,53 @@
+package cn.com.qmth.examcloud.core.examwork.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.ApiFlowLimitedInterceptor;
+import cn.com.qmth.examcloud.web.interceptor.ApiStatisticInterceptor;
+import cn.com.qmth.examcloud.web.interceptor.FirstInterceptor;
+import cn.com.qmth.examcloud.web.interceptor.SeqlockInterceptor;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.security.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 http://www.qmth.com.cn/ 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 ApiFlowLimitedInterceptor()).addPathPatterns("/**");
+		registry.addInterceptor(new RpcInterceptor(resourceManager)).addPathPatterns("/**");
+
+		RequestPermissionInterceptor permissionInterceptor = new RequestPermissionInterceptor(
+				resourceManager, redisClient);
+		registry.addInterceptor(permissionInterceptor).addPathPatterns("/**");
+		registry.addInterceptor(new SeqlockInterceptor(redisClient)).addPathPatterns("/**");
+		registry.addInterceptor(new ApiStatisticInterceptor()).addPathPatterns("/**");
+	}
+
+	@Override
+	public void addCorsMappings(CorsRegistry registry) {
+		registry.addMapping("/**").allowedOrigins("*").allowCredentials(false).allowedMethods("*")
+				.maxAge(3600);
+	}
+
+}

+ 33 - 0
examcloud-core-examwork-starter/src/main/java/cn/com/qmth/examcloud/core/examwork/starter/config/Swagger2.java

@@ -0,0 +1,33 @@
+package cn.com.qmth.examcloud.core.examwork.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.core.examwork.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();
+	}
+
+}

+ 8 - 0
examcloud-core-examwork-starter/src/main/resources/application.properties

@@ -0,0 +1,8 @@
+spring.profiles.active=dev
+
+examcloud.startup.startupCode=8001
+examcloud.startup.configCenterHost=127.0.0.1
+examcloud.startup.configCenterPort=9999
+examcloud.startup.appCode=E
+
+

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

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

+ 0 - 0
examcloud-core-examwork-starter/src/main/resources/config.properties


+ 261 - 0
examcloud-core-examwork-starter/src/main/resources/exam-properties.xml

@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<enums>
+	<enum>
+		<id>1</id>
+		<name>SCORE_PUBLISHING</name>
+		<desc>发布成绩</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>2</id>
+		<name>IS_ENTRANCE_EXAM</name>
+		<desc>是否入学考试</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>3</id>
+		<name>FREEZE_TIME</name>
+		<desc>交卷冻结时间</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>4</id>
+		<name>EXAM_RECONNECT_TIME</name>
+		<desc>断点续考时间(秒)</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>5</id>
+		<name>BEFORE_EXAM_REMARK</name>
+		<desc>考前说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>6</id>
+		<name>AFTER_EXAM_REMARK</name>
+		<desc>考后说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>7</id>
+		<name>SHOW_CHEATING_REMARK</name>
+		<desc>是否展示作弊</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>8</id>
+		<name>CHEATING_REMARK</name>
+		<desc>作弊说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>9</id>
+		<name>PRACTICE_TYPE</name>
+		<desc>练习模式</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>10</id>
+		<name>SINGLE_EDIT</name>
+		<desc>单选题补充说明是否可填</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>11</id>
+		<name>MUTIPLE_EDIT</name>
+		<desc>多选题补充说明是否可填</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>12</id>
+		<name>BOOL_EDIT</name>
+		<desc>判断题补充说明是否可填</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>13</id>
+		<name>FILL_BLANK_EDIT</name>
+		<desc>填空题补充说明是否可填</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>14</id>
+		<name>SINGLE_ANSWER_REMARK</name>
+		<desc>单选题补充说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>15</id>
+		<name>MUTIPLE_ANSWER_REMARK</name>
+		<desc>多选题补充说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>16</id>
+		<name>BOOL_ANSWER_REMARK</name>
+		<desc>判断题补充说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>17</id>
+		<name>FILL_BLANK_REMARK</name>
+		<desc>填空题补充说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>18</id>
+		<name>TEXT_ANSWER_REMARK</name>
+		<desc>问答题补充说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>19</id>
+		<name>NESTED_ANSWER_REMARK</name>
+		<desc>套题补充说明</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>20</id>
+		<name>IS_FACE_ENABLE</name>
+		<desc>是否启用人脸识别</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>21</id>
+		<name>IS_FACE_CHECK</name>
+		<desc>进入考试是否验证人脸识别(强制、非强制)</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>22</id>
+		<name>SNAPSHOT_INTERVAL</name>
+		<desc>抓拍间隔</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>23</id>
+		<name>WARN_THRESHOLD</name>
+		<desc>预警阈值</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>24</id>
+		<name>MARKING_TYPE</name>
+		<desc>阅卷方式</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>25</id>
+		<name>IS_FACE_VERIFY</name>
+		<desc>是否开启人脸活体检测</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>26</id>
+		<name>FACE_VERIFY_START_MINUTE</name>
+		<desc>活体检测开始分钟数</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>27</id>
+		<name>FACE_VERIFY_END_MINUTE</name>
+		<desc>活体检测结束分钟数</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>28</id>
+		<name>IP_LIMIT</name>
+		<desc>是否IP限制</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>29</id>
+		<name>IP_ADDRESSES</name>
+		<desc>IP白名单</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>30</id>
+		<name>IS_OBJ_SCORE_VIEW</name>
+		<desc>是否显示客观题成绩</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>31</id>
+		<name>CAN_UPLOAD_ATTACHMENT</name>
+		<desc>是否允许上传附件(离线考试)</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>32</id>
+		<name>LIVING_WARN_THRESHOLD</name>
+		<desc>真实性预警阀值</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>33</id>
+		<name>PUSH_SCORE</name>
+		<desc>是否推送成绩给学校</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>34</id>
+		<name>OFFLINE_UPLOAD_FILE_TYPE</name>
+		<desc>离线考试上传文件类型限制</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>35</id>
+		<name>MARKING_TASK_BUILDED</name>
+		<desc>阅卷是否生成评卷任务</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>36</id>
+		<name>MAX_INTERRUPT_NUM</name>
+		<desc>断点续考次数限制</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>37</id>
+		<name>IS_STRANGER_ENABLE</name>
+		<desc>是否启用陌生人检测</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>38</id>
+		<name>CHECK_ENVIRONMENT</name>
+		<desc>是否开启环境监察</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>39</id>
+		<name>WEIXIN_ANSWER_ENABLED</name>
+		<desc>开放微信小程序作答</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>40</id>
+		<name>ADD_FACE_VERIFY_OUT_FREEZE_TIME</name>
+		<desc>冻结时间外添加人脸活体检测</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>41</id>
+		<name>OUT_FREEZE_TIME_FACE_VERIFY_START_MINUTE</name>
+		<desc>冻结时间外活体检测开始分钟数</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>42</id>
+		<name>OUT_FREEZE_TIME_FACE_VERIFY_END_MINUTE</name>
+		<desc>冻结时间外活体检测结束分钟数</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>43</id>
+		<name>LIMITED_IF_NO_SPECIAL_SETTINGS</name>
+		<desc>无特殊设置时禁止考试</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+</enums>

+ 1 - 0
examcloud-core-examwork-starter/src/main/resources/limited.properties

@@ -0,0 +1 @@
+  

+ 67 - 0
examcloud-core-examwork-starter/src/main/resources/log4j2.xml

@@ -0,0 +1,67 @@
+<?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_APPENDER" fileName="./logs/debug/debug.log"
+			filePattern="./logs/debug/debug-%d{yyyy.MM.dd.HH}-%i.log">
+			<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}| %level | %X{TRACE_ID} - %X{CALLER} | %m | %l%n" />
+			<Policies>
+				<TimeBasedTriggeringPolicy interval="1" />
+				<SizeBasedTriggeringPolicy size="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>
+	</Appenders>
+
+	<Loggers>
+		<Logger name="cn.com.qmth" level="${commonLevel}" additivity="false">
+			<AppenderRef ref="DEBUG_APPENDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Logger name="INTERFACE_LOGGER" level="INFO" additivity="false">
+			<AppenderRef ref="INTERFACE_APPENDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Logger name="cn.com.qmth.examcloud.web.actuator" level="ERROR" />
+		<Logger name="org.hibernate.SQL" level="${commonLevel}" />
+
+		<Root level="INFO">
+			<AppenderRef ref="Console" />
+			<AppenderRef ref="DEBUG_APPENDER" />
+		</Root>
+	</Loggers>
+
+</Configuration>

+ 15 - 0
examcloud-core-examwork-starter/src/main/resources/security.properties

@@ -0,0 +1,15 @@
+[s][${$rmp.ctr.examwork}/exam][queryByNameLike][GET]=true
+[s][${$rmp.ctr.examwork}/exam][{examId}][GET]=true
+[s][${$rmp.ctr.examwork}/exam][ipLimit/{examId}][GET]=true
+[s][${$rmp.ctr.examwork}/exam][getExamPropertyFromCacheByStudentSession/{examId}/{keys}][GET]=true
+[s][${$rmp.ctr.examwork}/exam][getExamSettingsFromCacheByStudentSession/{examId}][GET]=true
+[s][${$rmp.ctr.examwork}/exam][weixinAnswerEnabled/{examId}][GET]=true
+[s][${$rmp.ctr.examwork}/exam][faceCheckEnabled/{examId}][GET]=true
+[s][${$rmp.ctr.examwork}/exam][identificationOfLivingEnabled/{examId}][GET]=true
+
+[s][${$rmp.ctr.examwork}/exam_student][specialtyNameList][GET]=true
+[s][${$rmp.ctr.examwork}/exam_student][courseLevelList][GET]=true
+
+[s][${$rmp.ctr.examwork}/notice][getUserNoticeList][GET]=true
+[s][${$rmp.ctr.examwork}/notice][updateNoticeReadStatus][POST]=true
+

BIN
examcloud-core-examwork-starter/src/main/resources/templates/studentImportTemplate.xlsx


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно