deason 5 lat temu
rodzic
commit
5f7e1bbda1
100 zmienionych plików z 8142 dodań i 3 usunięć
  1. 12 0
      .gitignore
  2. 0 3
      README.md
  3. 22 0
      examcloud-api-commons/.gitignore
  4. 57 0
      examcloud-api-commons/pom.xml
  5. 12 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/CloudService.java
  6. 14 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/EnterpriseService.java
  7. 14 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/BasicDataType.java
  8. 44 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/BooleanSelect.java
  9. 37 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/CURD.java
  10. 50 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/CourseLevel.java
  11. 27 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/ExamSpecialSettingsType.java
  12. 39 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/ExamType.java
  13. 8 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/Gender.java
  14. 32 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/NoticeReceiverRuleType.java
  15. 30 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/NoticeStatus.java
  16. 32 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/PrivilegeGroupType.java
  17. 13 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/BaseRequest.java
  18. 29 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/BaseResponse.java
  19. 23 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/EnterpriseRequest.java
  20. 23 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/EnterpriseResponse.java
  21. 13 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/ExchangeBean.java
  22. 58 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/FormFilePart.java
  23. 29 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/FormRequest.java
  24. 16 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/JsonSerializable.java
  25. 136 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/PageInfo.java
  26. 66 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/AccessApp.java
  27. 64 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/Role.java
  28. 187 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/User.java
  29. 59 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/UserType.java
  30. 81 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/WebSocketSession.java
  31. 49 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/enums/RoleMeta.java
  32. 21 0
      examcloud-commons/.gitignore
  33. 240 0
      examcloud-commons/pom.xml
  34. 50 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/exception/ExamCloudRuntimeException.java
  35. 80 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/exception/StatusException.java
  36. 14 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/BasicDataType.java
  37. 63 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/BlackHolePrintStreamBuilder.java
  38. 79 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/Counter.java
  39. 109 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/DynamicEnum.java
  40. 145 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/DynamicEnumManager.java
  41. 134 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/FileChangeWatchdog.java
  42. 58 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/FormFilePart.java
  43. 44 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/JsonHttpResponseHolder.java
  44. 52 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/KeyValuePair.java
  45. 46 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/ObjectHolder.java
  46. 98 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/XStreamBuilder.java
  47. 205 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/concurrency/simple/ConcurrentTask.java
  48. 7 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/concurrency/simple/Worker.java
  49. 27 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/concurrency/simple/WorkerController.java
  50. 43 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/Counter.java
  51. 59 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/Node.java
  52. 35 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/NodeExecuter.java
  53. 246 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/SimpleNode.java
  54. 40 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/Storer.java
  55. 34 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/TaskContext.java
  56. 165 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/poi/ExcelReader.java
  57. 137 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/poi/ExcelWriter.java
  58. 204 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/poi/XlsxHandler.java
  59. 110 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/ExamCloudLog.java
  60. 78 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/ExamCloudLogFactory.java
  61. 70 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/NoLoggingImpl.java
  62. 67 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/Resources.java
  63. 111 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/SLF4JImpl.java
  64. 137 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/AES.java
  65. 89 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/AsciiUtil.java
  66. 54 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/BooleanUtil.java
  67. 116 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ByteUtil.java
  68. 41 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/Calculator.java
  69. 12 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/CollectionUtil.java
  70. 271 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/DBUtil.java
  71. 210 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/DateUtil.java
  72. 36 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/FileUtil.java
  73. 63 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/FreeMarkerUtil.java
  74. 176 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/HttpClientUtil.java
  75. 115 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/HttpMethod.java
  76. 105 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/IOUtil.java
  77. 171 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/JsonUtil.java
  78. 59 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/MD5.java
  79. 46 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/MapUtil.java
  80. 310 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/OKHttpUtil.java
  81. 69 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ObjectUtil.java
  82. 180 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/PathUtil.java
  83. 245 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/PropertiesUtil.java
  84. 103 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/RegExpUtil.java
  85. 67 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ResourceLoader.java
  86. 43 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/SHA256.java
  87. 124 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/SecurityUtil.java
  88. 390 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/StringUtil.java
  89. 121 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ThreadLocalUtil.java
  90. 20 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/UUID.java
  91. 160 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/UrlUtil.java
  92. 126 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/Util.java
  93. 147 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ZipUtil.java
  94. 34 0
      examcloud-commons/src/test/java/cn/com/qmth/examcloud/test/pipeline/FileReaderExecuter.java
  95. 18 0
      examcloud-commons/src/test/java/cn/com/qmth/examcloud/test/pipeline/PrintExecuter.java
  96. 33 0
      examcloud-commons/src/test/java/cn/com/qmth/examcloud/test/pipeline/Test.java
  97. 42 0
      examcloud-commons/src/test/resources/log4j2.xml
  98. 12 0
      examcloud-config-center-starter/.gitignore
  99. 32 0
      examcloud-config-center-starter/pom.xml
  100. 18 0
      examcloud-config-center-starter/shell/jenkins.sh

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+*.class
+.project
+.classpath
+.settings
+target/
+.idea/
+*.iml
+*.log
+*.class
+*.jar
+*.war
+*.ear

+ 0 - 3
README.md

@@ -1,3 +0,0 @@
-# examcloud-components
-
-云平台-公共组件

+ 22 - 0
examcloud-api-commons/.gitignore

@@ -0,0 +1,22 @@
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.idea/
+*.iml
+
+.project
+.classpath
+.settings
+target/
+.idea/
+*.iml
+*test/
+# Package Files #
+*.jar
+

+ 57 - 0
examcloud-api-commons/pom.xml

@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud</groupId>
+		<artifactId>examcloud-parent</artifactId>
+		<version>2019</version>
+	</parent>
+	<artifactId>examcloud-api-commons</artifactId>
+	<version>2019-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-jenkins-build</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>io.swagger</groupId>
+			<artifactId>swagger-annotations</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.validation</groupId>
+			<artifactId>validation-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.hibernate.validator</groupId>
+			<artifactId>hibernate-validator</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.data</groupId>
+			<artifactId>spring-data-commons</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>*</groupId>
+					<artifactId>*</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-web</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>*</groupId>
+					<artifactId>*</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+	</dependencies>
+
+</project>

+ 12 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/CloudService.java

@@ -0,0 +1,12 @@
+package cn.com.qmth.examcloud.api.commons;
+
+import java.io.Serializable;
+
+/**
+ * 云服务顶级接口
+ *
+ * @author WANGWEI
+ */
+public interface CloudService extends Serializable {
+
+}

+ 14 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/EnterpriseService.java

@@ -0,0 +1,14 @@
+package cn.com.qmth.examcloud.api.commons;
+
+import java.io.Serializable;
+
+/**
+ * 对外服务接口
+ *
+ * @author WANGWEI
+ * @date 2018年6月29日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public interface EnterpriseService extends Serializable {
+
+}

+ 14 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/BasicDataType.java

@@ -0,0 +1,14 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 基础数据类型
+ *
+ * @author WANGWEI
+ * @date 2019年2月14日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public enum BasicDataType {
+
+	STRING, INTEGER, BOOLEAN, LONG, DATE;
+
+}

+ 44 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/BooleanSelect.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 页面boolean选择
+ *
+ * @author WANGWEI
+ * @date 2018年12月24日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum BooleanSelect {
+
+	/**
+	 * 未指定
+	 */
+	UNDEFINED,
+
+	/**
+	 * true
+	 */
+	TRUE,
+
+	/**
+	 * false
+	 */
+	FALSE;
+
+	/**
+	 * 获取boolean
+	 *
+	 * @author WANGWEI
+	 * @param value
+	 * @return
+	 */
+	public Boolean getBoolean() {
+		if (UNDEFINED.equals(this)) {
+			return null;
+		} else if (TRUE.equals(this)) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+}

+ 37 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/CURD.java

@@ -0,0 +1,37 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 数据库操作
+ *
+ * @author WANGWEI
+ * @date 2018年11月7日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum CURD {
+
+	/**
+	 * 创建
+	 */
+	CREATION,
+
+	/**
+	 * 更新
+	 */
+	UPDATE,
+
+	/**
+	 * 创建或更新
+	 */
+	CREATION_OR_UPDATE,
+
+	/**
+	 * 获取
+	 */
+	RETRIEVE,
+
+	/**
+	 * 删除
+	 */
+	DELETE
+
+}

+ 50 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/CourseLevel.java

@@ -0,0 +1,50 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 重新定义
+ *
+ * @author WANGWEI
+ * @date 2018年7月10日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum CourseLevel {
+
+	ZSB("专升本"),
+
+	GQZ("高起专"),
+
+	GQB("高起本"),
+
+	ALL("不限");
+
+	private String name;
+
+	private CourseLevel(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return this.name;
+	}
+
+	/**
+	 * 混合查询
+	 *
+	 * @author WANGWEI
+	 * @param name
+	 * @return
+	 */
+	public static CourseLevel getCourseLevel(String name) {
+		for (CourseLevel cur : CourseLevel.values()) {
+			if (name.equals(cur.getName())) {
+				return cur;
+			}
+			if (name.equals(cur.name())) {
+				return cur;
+			}
+		}
+
+		throw new RuntimeException("level name is wong");
+	}
+
+}

+ 27 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/ExamSpecialSettingsType.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 考试特殊设置类型
+ *
+ * @author WANGWEI
+ * @date 2019年10月23日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum ExamSpecialSettingsType {
+
+	/**
+	 * 机构维度
+	 */
+	ORG_BASED,
+
+	/**
+	 * 学生维度
+	 */
+	STUDENT_BASED,
+
+	/**
+	 * 课程维度
+	 */
+	COURSE_BASED
+
+}

+ 39 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/ExamType.java

@@ -0,0 +1,39 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 
+ * @Description: 考试类型
+ * @author ting.yin
+ * @date 2017年1月5日
+ */
+public enum ExamType {
+	/**
+	 * 传统
+	 */
+	TRADITION,
+
+	/**
+	 * 网考
+	 */
+	ONLINE,
+
+	/**
+	 * 练习
+	 */
+	PRACTICE,
+
+	/**
+	 * 离线
+	 */
+	OFFLINE,
+
+	/**
+	 * 分布式印刷考试
+	 */
+	PRINT_EXAM,
+
+	/**
+	 * 在线作业
+	 */
+	ONLINE_HOMEWORK
+}

+ 8 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/Gender.java

@@ -0,0 +1,8 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * Created by songyue on 17/2/22.
+ */
+public enum Gender {
+    MAN,WOMAN
+}

+ 32 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/NoticeReceiverRuleType.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 公告接受规则类型
+ *
+ * @author WANGWEI
+ * @date 2019年6月27日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum NoticeReceiverRuleType {
+
+	/**
+	 * 归属考试的考生
+	 */
+	STUDENTS_OF_EXAM,
+
+	/**
+	 * 顶级机构下所有学生
+	 */
+	ALL_STUDENTS_OF_ROOT_ORG,
+
+	/**
+	 * 拥有角色的用户
+	 */
+	COMMON_USERS_OF_ROLE,
+
+	/**
+	 * 阅卷工作的老师
+	 */
+	TEACHER_OF_MARK_WORK
+
+}

+ 30 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/NoticeStatus.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 公告状态
+ *
+ * @author WANGWEI
+ * @date 2019年6月27日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum NoticeStatus {
+
+	/**
+	 * 草稿
+	 */
+	DRAFT,
+
+	/**
+	 * 待发布
+	 */
+	TO_BE_PUBLISHED,
+	/**
+	 * 发布中
+	 */
+	PUBLISHING,
+	/**
+	 * 已发布
+	 */
+	PUBLISHED
+
+}

+ 32 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/PrivilegeGroupType.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 权限组类型
+ *
+ * @author WANGWEI
+ * @date 2020年2月14日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum PrivilegeGroupType {
+
+	/**
+	 * 菜单
+	 */
+	ADMIN_MENU,
+
+	/**
+	 * 功能
+	 */
+	FUNCTION,
+
+	/**
+	 * 数据权限
+	 */
+	DATA_ACCESS,
+
+	/**
+	 * 学生端菜单
+	 */
+	STUDENT_CLIENT_MENU
+
+}

+ 13 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/BaseRequest.java

@@ -0,0 +1,13 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+/**
+ * 请求体基类
+ * 
+ * @author WANGWEI
+ *
+ */
+public abstract class BaseRequest extends ExchangeBean {
+
+	private static final long serialVersionUID = 6465330136225230063L;
+
+}

+ 29 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/BaseResponse.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 响应体基类
+ * 
+ * @author WANGWEI
+ *
+ */
+public abstract class BaseResponse extends ExchangeBean {
+
+	private static final long serialVersionUID = 1755304211766414171L;
+
+	/**
+	 * 耗时(毫秒)
+	 */
+	@ApiModelProperty(value = "耗时(毫秒)", example = "500", required = true)
+	private Long cost;
+
+	public Long getCost() {
+		return cost;
+	}
+
+	public void setCost(Long cost) {
+		this.cost = cost;
+	}
+
+}

+ 23 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/EnterpriseRequest.java

@@ -0,0 +1,23 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class EnterpriseRequest extends BaseRequest {
+
+	private static final long serialVersionUID = -7805477355173448066L;
+
+	@Deprecated
+	@ApiModelProperty(value = "废弃字段", example = "", required = false)
+	private String des;
+
+	@Deprecated
+	public String getDes() {
+		return des;
+	}
+
+	@Deprecated
+	public void setDes(String des) {
+		this.des = des;
+	}
+
+}

+ 23 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/EnterpriseResponse.java

@@ -0,0 +1,23 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class EnterpriseResponse extends BaseResponse {
+
+	private static final long serialVersionUID = 1317937489317321732L;
+
+	@Deprecated
+	@ApiModelProperty(value = "废弃字段", example = "", required = false)
+	private String des;
+
+	@Deprecated
+	public String getDes() {
+		return des;
+	}
+
+	@Deprecated
+	public void setDes(String des) {
+		this.des = des;
+	}
+
+}

+ 13 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/ExchangeBean.java

@@ -0,0 +1,13 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+/**
+ * bean 基类
+ * 
+ * @author WANGWEI
+ *
+ */
+public abstract class ExchangeBean implements JsonSerializable {
+
+	private static final long serialVersionUID = 3913250969569367810L;
+
+}

+ 58 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/FormFilePart.java

@@ -0,0 +1,58 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+import java.io.File;
+
+/**
+ * 表单文件参数
+ *
+ * @author WANGWEI
+ * @date 2019年5月9日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class FormFilePart {
+
+	private String paramName;
+
+	private String filename;
+
+	private File file;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param paramName
+	 * @param filename
+	 * @param file
+	 */
+	public FormFilePart(String paramName, String filename, File file) {
+		super();
+		this.paramName = paramName;
+		this.filename = filename;
+		this.file = file;
+	}
+
+	public String getParamName() {
+		return paramName;
+	}
+
+	public void setParamName(String paramName) {
+		this.paramName = paramName;
+	}
+
+	public String getFilename() {
+		return filename;
+	}
+
+	public void setFilename(String filename) {
+		this.filename = filename;
+	}
+
+	public File getFile() {
+		return file;
+	}
+
+	public void setFile(File file) {
+		this.file = file;
+	}
+
+}

+ 29 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/FormRequest.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+import java.util.List;
+
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 表单请求
+ *
+ * @author WANGWEI
+ * @date 2019年5月7日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class FormRequest extends BaseRequest {
+
+	private static final long serialVersionUID = 1201394717322230917L;
+
+	@ApiModelProperty(value = "文件参数集合", required = false)
+	private transient List<FormFilePart> formFilePartList;
+
+	public List<FormFilePart> getFormFilePartList() {
+		return formFilePartList;
+	}
+
+	public void setFormFilePartList(List<FormFilePart> formFilePartList) {
+		this.formFilePartList = formFilePartList;
+	}
+
+}

+ 16 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/JsonSerializable.java

@@ -0,0 +1,16 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+import java.io.Serializable;
+
+/**
+ * 可序列化为JSON<br>
+ * <p>
+ * 严重警告: 此接口为标识接口,禁止添加属性和方法. by wangwei
+ * </p>
+ * 
+ * @author WANGWEI
+ *
+ */
+public interface JsonSerializable extends Serializable {
+
+}

+ 136 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/exchange/PageInfo.java

@@ -0,0 +1,136 @@
+package cn.com.qmth.examcloud.api.commons.exchange;
+
+import java.io.Serializable;
+import java.util.List;
+
+import org.springframework.data.domain.Page;
+
+/**
+ * 分页
+ *
+ * @author WANGWEI
+ * @date 2018年6月25日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ * @param <T>
+ */
+public class PageInfo<T> implements Serializable {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 当前页码
+	 */
+	private long index = 1;
+
+	/**
+	 * 每页的数量限制
+	 */
+	private long limit = 10;
+
+	/**
+	 * 当前页的数量
+	 */
+	private long size;
+
+	/**
+	 * 总记录数
+	 */
+	private long total;
+
+	/**
+	 * 总页数
+	 */
+	private long pages;
+
+	/**
+	 * 结果集
+	 */
+	private List<T> list;
+
+	/**
+	 * 构造函数
+	 *
+	 */
+	public PageInfo() {
+
+	}
+
+	/**
+	 * 构造函数
+	 *
+	 * @param page
+	 */
+	public PageInfo(Page<T> page) {
+		this.total = page.getTotalElements();
+		this.pages = page.getTotalPages();
+		this.index = page.getNumber();
+		this.size = page.getNumberOfElements();
+		this.limit = page.getSize();
+
+		this.list = page.getContent();
+	}
+
+	/**
+	 * 构造函数
+	 *
+	 * @param page
+	 * @param list
+	 */
+	public PageInfo(Page<?> page, List<T> list) {
+		this.total = page.getTotalElements();
+		this.pages = page.getTotalPages();
+		this.index = page.getNumber();
+		this.size = page.getNumberOfElements();
+		this.limit = page.getSize();
+
+		this.list = list;
+	}
+
+	public long getIndex() {
+		return index;
+	}
+
+	public void setIndex(long index) {
+		this.index = index;
+	}
+
+	public long getLimit() {
+		return limit;
+	}
+
+	public void setLimit(long limit) {
+		this.limit = limit;
+	}
+
+	public long getSize() {
+		return size;
+	}
+
+	public void setSize(long size) {
+		this.size = size;
+	}
+
+	public long getTotal() {
+		return total;
+	}
+
+	public void setTotal(long total) {
+		this.total = total;
+	}
+
+	public long getPages() {
+		return pages;
+	}
+
+	public void setPages(long pages) {
+		this.pages = pages;
+	}
+
+	public List<T> getList() {
+		return list;
+	}
+
+	public void setList(List<T> list) {
+		this.list = list;
+	}
+
+}

+ 66 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/AccessApp.java

@@ -0,0 +1,66 @@
+package cn.com.qmth.examcloud.api.commons.security.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 接入的app
+ *
+ * @author WANGWEI
+ * @date 2019年1月28日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class AccessApp implements JsonSerializable {
+
+	private static final long serialVersionUID = 9193428318438074444L;
+
+	private Long appId;
+
+	private String appName;
+
+	private String appCode;
+
+	private String secretKey;
+
+	private Long timeRange;
+
+	public Long getAppId() {
+		return appId;
+	}
+
+	public void setAppId(Long appId) {
+		this.appId = appId;
+	}
+
+	public String getAppName() {
+		return appName;
+	}
+
+	public void setAppName(String appName) {
+		this.appName = appName;
+	}
+
+	public String getAppCode() {
+		return appCode;
+	}
+
+	public void setAppCode(String appCode) {
+		this.appCode = appCode;
+	}
+
+	public String getSecretKey() {
+		return secretKey;
+	}
+
+	public void setSecretKey(String secretKey) {
+		this.secretKey = secretKey;
+	}
+
+	public Long getTimeRange() {
+		return timeRange;
+	}
+
+	public void setTimeRange(Long timeRange) {
+		this.timeRange = timeRange;
+	}
+
+}

+ 64 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/Role.java

@@ -0,0 +1,64 @@
+package cn.com.qmth.examcloud.api.commons.security.bean;
+
+/**
+ * 角色
+ *
+ * @author WANGWEI
+ * @date 2018年5月23日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class Role {
+
+	private Long roleId;
+
+	private String roleCode;
+
+	private String roleName;
+
+	/**
+	 * 构造函数
+	 *
+	 */
+	public Role() {
+		super();
+	}
+
+	/**
+	 * 构造函数
+	 *
+	 * @param roleId
+	 * @param roleCode
+	 * @param roleName
+	 */
+	public Role(Long roleId, String roleCode, String roleName) {
+		super();
+		this.roleId = roleId;
+		this.roleCode = roleCode;
+		this.roleName = roleName;
+	}
+
+	public Long getRoleId() {
+		return roleId;
+	}
+
+	public void setRoleId(Long roleId) {
+		this.roleId = roleId;
+	}
+
+	public String getRoleCode() {
+		return roleCode;
+	}
+
+	public void setRoleCode(String roleCode) {
+		this.roleCode = roleCode;
+	}
+
+	public String getRoleName() {
+		return roleName;
+	}
+
+	public void setRoleName(String roleName) {
+		this.roleName = roleName;
+	}
+
+}

+ 187 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/User.java

@@ -0,0 +1,187 @@
+package cn.com.qmth.examcloud.api.commons.security.bean;
+
+import java.util.Date;
+import java.util.List;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 用户
+ *
+ * @author WANGWEI
+ * @date 2018年5月22日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class User implements JsonSerializable {
+
+	private static final long serialVersionUID = 8766713125414955078L;
+
+	/**
+	 * 全局唯一用户标识符
+	 */
+	private String key;
+
+	/**
+	 * 用户类型
+	 */
+	private UserType userType;
+
+	/**
+	 * 用户ID(包含普通用户ID,学生用户ID)
+	 */
+	private Long userId;
+
+	/**
+	 * 显示名
+	 */
+	private String displayName;
+
+	/**
+	 * 顶级机构ID
+	 */
+	private Long rootOrgId;
+
+	/**
+	 * 顶级机构名称
+	 */
+	private String rootOrgName;
+
+	/**
+	 * 顶级机构域名
+	 */
+	private String rootOrgDomain;
+
+	/**
+	 * 创建时间
+	 */
+	private Date creationTime;
+
+	/**
+	 * 角色集合
+	 */
+	private List<Role> roleList;
+
+	/**
+	 * 客户端IP
+	 */
+	private String clientIp;
+
+	/**
+	 * 鉴权token
+	 */
+	private String token;
+
+	/**
+	 * 会话失效时长
+	 */
+	private Integer sessionTimeout;
+
+	/**
+	 * 构建key
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public String buildKey() {
+		this.key = new StringBuilder().append("U_").append(userType.getCode()).append("_")
+				.append(rootOrgId).append("_").append(userId).toString();
+		return this.key;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	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 String getDisplayName() {
+		return displayName;
+	}
+
+	public void setDisplayName(String displayName) {
+		this.displayName = displayName;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public String getRootOrgName() {
+		return rootOrgName;
+	}
+
+	public void setRootOrgName(String rootOrgName) {
+		this.rootOrgName = rootOrgName;
+	}
+
+	public String getRootOrgDomain() {
+		return rootOrgDomain;
+	}
+
+	public void setRootOrgDomain(String rootOrgDomain) {
+		this.rootOrgDomain = rootOrgDomain;
+	}
+
+	public Date getCreationTime() {
+		return creationTime;
+	}
+
+	public void setCreationTime(Date creationTime) {
+		this.creationTime = creationTime;
+	}
+
+	public List<Role> getRoleList() {
+		return roleList;
+	}
+
+	public void setRoleList(List<Role> roleList) {
+		this.roleList = roleList;
+	}
+
+	public String getClientIp() {
+		return clientIp;
+	}
+
+	public void setClientIp(String clientIp) {
+		this.clientIp = clientIp;
+	}
+
+	public String getToken() {
+		return token;
+	}
+
+	public void setToken(String token) {
+		this.token = token;
+	}
+
+	public Integer getSessionTimeout() {
+		return sessionTimeout;
+	}
+
+	public void setSessionTimeout(Integer sessionTimeout) {
+		this.sessionTimeout = sessionTimeout;
+	}
+
+}

+ 59 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/UserType.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.examcloud.api.commons.security.bean;
+
+/**
+ * 用户类型
+ *
+ * @author WANGWEI
+ * @date 2018年5月25日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum UserType {
+
+	/**
+	 * 学生
+	 */
+	STUDENT("S", "学生"),
+
+	/**
+	 * 常规用户
+	 */
+	COMMON("C", "常规用户");
+
+	// ===========================================================================
+
+	/**
+	 * 码
+	 */
+	private String code;
+
+	/**
+	 * 描述
+	 */
+	private String desc;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param code
+	 * @param desc
+	 */
+	private UserType(String code, String desc) {
+		this.code = code;
+		this.desc = desc;
+	}
+
+	/**
+	 * @return the code
+	 */
+	public String getCode() {
+		return code;
+	}
+
+	/**
+	 * @return the desc
+	 */
+	public String getDesc() {
+		return desc;
+	}
+
+}

+ 81 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/WebSocketSession.java

@@ -0,0 +1,81 @@
+package cn.com.qmth.examcloud.api.commons.security.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * WebSocket Session
+ *
+ * @author WANGWEI
+ * @date 2019年11月22日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class WebSocketSession implements JsonSerializable {
+
+	private static final long serialVersionUID = 6657113738382557514L;
+
+	/**
+	 * session ID
+	 */
+	private String sessionId;
+
+	/**
+	 * 全局唯一用户标识符
+	 */
+	private String key;
+
+	/**
+	 * 鉴权token
+	 */
+	private String token;
+
+	/**
+	 * 应用名称
+	 */
+	private String appName;
+
+	/**
+	 * 会话失效时长
+	 */
+	private Integer sessionTimeout;
+
+	public String getSessionId() {
+		return sessionId;
+	}
+
+	public void setSessionId(String sessionId) {
+		this.sessionId = sessionId;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	public String getToken() {
+		return token;
+	}
+
+	public void setToken(String token) {
+		this.token = token;
+	}
+
+	public String getAppName() {
+		return appName;
+	}
+
+	public void setAppName(String appName) {
+		this.appName = appName;
+	}
+
+	public Integer getSessionTimeout() {
+		return sessionTimeout;
+	}
+
+	public void setSessionTimeout(Integer sessionTimeout) {
+		this.sessionTimeout = sessionTimeout;
+	}
+
+}

+ 49 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/enums/RoleMeta.java

@@ -0,0 +1,49 @@
+package cn.com.qmth.examcloud.api.commons.security.enums;
+
+/**
+ * Created by songyue on 17/2/24.
+ */
+public enum RoleMeta {
+
+	SUPER_ADMIN("超级管理员"),
+
+	ORG_ADMIN("机构管理员"),
+
+	LC_USER("学习中心用户"),
+
+	MARKING_ADMIN("阅卷管理员"),
+
+	MARKER("评卷员"),
+
+	QUESTION_ADMIN("题库管理员"),
+
+	OE_ADMIN("网考管理员"),
+
+	PRINT_SUPPLIER("印刷供应商"),
+
+	PRINT_SCHOOL_LEADER("印刷学校管理员"),
+
+	PRINT_SUPER_LEADER("印刷总负责人"),
+
+	PRINT_PROJECT_LEADER("项目经理"),
+
+	PRINT_LOCALE_LEADER("印刷现场负责人"),
+
+	PRINT_SALE_LEADER("销售负责人"),
+
+	PRINT_FINANCE_LEADER("财务负责人");
+
+	/**
+	 * 角色名
+	 */
+	private String name;
+
+	RoleMeta(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+}

+ 21 - 0
examcloud-commons/.gitignore

@@ -0,0 +1,21 @@
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.idea/
+*.iml
+
+.project
+.classpath
+.settings
+target/
+.idea/
+*.iml
+# Package Files #
+*.jar
+

+ 240 - 0
examcloud-commons/pom.xml

@@ -0,0 +1,240 @@
+<?xml version="1.0"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud</groupId>
+		<artifactId>examcloud-parent</artifactId>
+		<version>2019</version>
+	</parent>
+	<artifactId>examcloud-commons</artifactId>
+	<version>2019-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-jenkins-build</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>jdk.tools</groupId>
+			<artifactId>jdk.tools</artifactId>
+			<version>${java.version}</version>
+			<scope>system</scope>
+			<systemPath>${JAVA_HOME}/lib/tools.jar</systemPath>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.springframework.boot</groupId>
+					<artifactId>spring-boot-starter-logging</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-test</artifactId>
+			<scope>compile</scope>
+			<exclusions>
+				<exclusion>
+					<groupId>org.springframework.boot</groupId>
+					<artifactId>spring-boot-starter</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-log4j2</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>jcl-over-slf4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-1.2-api</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.fasterxml.jackson.dataformat</groupId>
+			<artifactId>jackson-dataformat-yaml</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>commons-lang</groupId>
+			<artifactId>commons-lang</artifactId>
+			<version>2.6</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-beanutils</groupId>
+			<artifactId>commons-beanutils</artifactId>
+			<version>1.9.3</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.5</version>
+		</dependency>
+		<dependency>
+			<groupId>com.thoughtworks.xstream</groupId>
+			<artifactId>xstream</artifactId>
+			<version>1.4.11</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-net</groupId>
+			<artifactId>commons-net</artifactId>
+			<version>3.4</version>
+		</dependency>
+		<dependency>
+			<groupId>com.mchange</groupId>
+			<artifactId>c3p0</artifactId>
+			<version>0.9.5.2</version>
+		</dependency>
+		<dependency>
+			<groupId>com.jcraft</groupId>
+			<artifactId>jsch</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-collections</groupId>
+			<artifactId>commons-collections</artifactId>
+			<version>3.2.2</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-compress</artifactId>
+			<version>1.12</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpclient</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpmime</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>dom4j</groupId>
+			<artifactId>dom4j</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.json-lib</groupId>
+			<artifactId>json-lib</artifactId>
+			<version>2.4</version>
+			<classifier>jdk15</classifier>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi</artifactId>
+			<version>3.17</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi-ooxml</artifactId>
+			<version>3.17</version>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>redis.clients</groupId>
+			<artifactId>jedis</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-pool2</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-fileupload</groupId>
+			<artifactId>commons-fileupload</artifactId>
+			<version>1.3.3</version>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-annotations</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.fasterxml.jackson.core</groupId>
+			<artifactId>jackson-databind</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+			<version>1.2.62</version>
+		</dependency>
+		<dependency>
+			<groupId>org.freemarker</groupId>
+			<artifactId>freemarker</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.googlecode.aviator</groupId>
+			<artifactId>aviator</artifactId>
+			<version>2.3.3</version>
+		</dependency>
+		<dependency>
+			<groupId>org.quartz-scheduler</groupId>
+			<artifactId>quartz</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>mysql</groupId>
+			<artifactId>mysql-connector-java</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.squareup.okhttp3</groupId>
+			<artifactId>okhttp</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-mail</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jsoup</groupId>
+			<artifactId>jsoup</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.googlecode.aviator</groupId>
+			<artifactId>aviator</artifactId>
+			<version>4.2.0</version>
+			<exclusions>
+				<exclusion>
+					<groupId>junit</groupId>
+					<artifactId>junit</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>commons-beanutils</groupId>
+					<artifactId>commons-beanutils</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>org.springframework</groupId>
+					<artifactId>spring-context</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+	</dependencies>
+
+</project>

+ 50 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/exception/ExamCloudRuntimeException.java

@@ -0,0 +1,50 @@
+package cn.com.qmth.examcloud.commons.exception;
+
+/**
+ * 通用异常类<br>
+ *
+ * @author WANG
+ */
+public class ExamCloudRuntimeException extends RuntimeException {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 7992796744711512927L;
+
+	/**
+	 * 构造函数
+	 */
+	public ExamCloudRuntimeException() {
+		super();
+	}
+
+	/**
+	 * 构造函数
+	 */
+	public ExamCloudRuntimeException(String message) {
+		super(message);
+	}
+
+	/**
+	 * 构造函数
+	 */
+	public ExamCloudRuntimeException(String message, Throwable cause) {
+		super(message, cause);
+	}
+
+	/**
+	 * 构造函数
+	 */
+	public ExamCloudRuntimeException(Throwable cause) {
+		super(cause);
+	}
+
+	/**
+	 * 构造函数
+	 */
+	protected ExamCloudRuntimeException(String message, Throwable cause, boolean enableSuppression,
+			boolean writableStackTrace) {
+		super(message, cause, enableSuppression, writableStackTrace);
+	}
+
+}

+ 80 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/exception/StatusException.java

@@ -0,0 +1,80 @@
+package cn.com.qmth.examcloud.commons.exception;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+
+/**
+ * 状态异常类<br>
+ *
+ * @author WANG
+ */
+public class StatusException extends RuntimeException {
+	private static final long serialVersionUID = 5003047488500388819L;
+
+	/**
+	 * 追踪ID
+	 */
+	private String traceId;
+
+	/**
+	 * 状态码
+	 */
+	private String code;
+
+	/**
+	 * 状态描述
+	 */
+	private String desc;
+
+	/**
+	 * 构造函数
+	 */
+	public StatusException(String code, String desc) {
+		super("[code: " + code + "; desc: " + desc + "]");
+		this.code = code;
+		this.desc = desc;
+	}
+
+	/**
+	 * 构造函数
+	 */
+	public StatusException(String code, String desc, Throwable cause) {
+		super("[code: " + code + "; desc: " + desc + "]", cause);
+		this.code = code;
+		this.desc = desc;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public String getDesc() {
+		return desc;
+	}
+
+	public String getTraceId() {
+		return traceId;
+	}
+
+	public void setTraceId(String traceId) {
+		this.traceId = traceId;
+	}
+
+	/**
+	 * @return
+	 */
+	public String toJson() {
+		Map<String, Object> map = new HashMap<String, Object>();
+		map.put("code", code);
+		map.put("desc", desc);
+		return JsonUtil.toJson(map);
+	}
+
+	@Override
+	public String toString() {
+		return toJson();
+	}
+
+}

+ 14 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/BasicDataType.java

@@ -0,0 +1,14 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+/**
+ * 基础数据类型
+ *
+ * @author WANGWEI
+ * @date 2019年2月14日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public enum BasicDataType {
+
+	STRING, INTEGER, BOOLEAN, LONG;
+
+}

+ 63 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/BlackHolePrintStreamBuilder.java

@@ -0,0 +1,63 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+/**
+ * 黑洞(PrintStream)构建器
+ *
+ * @author WANGWEI
+ * @date 2019年3月28日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class BlackHolePrintStreamBuilder {
+
+	/**
+	 * 创建BlackHolePrintStream
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static PrintStream build() {
+		return new BlackHolePrintStream(new ByteArrayOutputStream());
+	}
+
+	/**
+	 * 黑洞(PrintStream)
+	 *
+	 * @author WANGWEI
+	 * @date 2019年3月28日
+	 * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+	 */
+	private static class BlackHolePrintStream extends PrintStream {
+
+		@Override
+		public void close() {
+			super.close();
+		}
+
+		ByteArrayOutputStream outputStream;
+
+		/**
+		 * 私有构造函数
+		 *
+		 * @param out
+		 */
+		private BlackHolePrintStream(ByteArrayOutputStream outputStream) {
+			super(outputStream);
+			this.outputStream = outputStream;
+		}
+
+		@Override
+		public void println(String x) {
+			outputStream.reset();
+		}
+
+		@Override
+		public void print(String x) {
+			outputStream.reset();
+		}
+
+	}
+
+}

+ 79 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/Counter.java

@@ -0,0 +1,79 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 计数器
+ *
+ * @author WANGWEI
+ * @date 2018年1月26日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class Counter {
+
+	private AtomicLong counter;
+
+	/**
+	 * 最小值
+	 */
+	private long min;
+
+	/**
+	 * 最大值
+	 */
+	private long max;
+
+	/**
+	 * 循环计数回合数
+	 */
+	private int bout = 0;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param min
+	 * @param max
+	 */
+	public Counter(long min, long max) {
+		this.min = min;
+		this.max = max;
+		bout();
+	}
+
+	/**
+	 * 初始化循环
+	 *
+	 * @author WANGWEI
+	 */
+	private void bout() {
+		int cur = bout;
+		synchronized (this) {
+			if (cur < bout) {
+				return;
+			}
+			counter = new AtomicLong(min);
+			bout++;
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public long next() {
+		long next = counter.getAndIncrement();
+
+		if (next > max) {
+			bout();
+			return next();
+		}
+
+		return next;
+	}
+
+	public long get() {
+		return counter.get();
+	}
+}

+ 109 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/DynamicEnum.java

@@ -0,0 +1,109 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import java.io.Serializable;
+
+import org.apache.commons.lang3.StringUtils;
+
+import cn.com.qmth.examcloud.commons.util.StringUtil;
+
+/**
+ * 动态枚举
+ *
+ * @author WANGWEI
+ * @date 2018年8月23日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public final class DynamicEnum implements Serializable {
+
+	private static final long serialVersionUID = -694709140246715214L;
+
+	/**
+	 * ID
+	 */
+	private Long id;
+
+	/**
+	 * name
+	 */
+	private String name;
+
+	/**
+	 * 描述
+	 */
+	private String desc;
+
+	/**
+	 * 值类型
+	 */
+	private String valueType;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public String getDesc() {
+		return desc;
+	}
+
+	public void setDesc(String desc) {
+		this.desc = desc;
+	}
+
+	public String getValueType() {
+		return valueType;
+	}
+
+	public void setValueType(String valueType) {
+		this.valueType = valueType;
+	}
+
+	/**
+	 * 是否合法
+	 *
+	 * @author WANGWEI
+	 * @param value
+	 * @return
+	 */
+	public Boolean isLegal(String value) {
+		if (StringUtils.isNotBlank(value) && null != valueType) {
+			BasicDataType dataType = BasicDataType.valueOf(valueType);
+			if (dataType.equals(BasicDataType.BOOLEAN)) {
+				try {
+					StringUtil.toBoolean(value);
+					return true;
+				} catch (Exception e) {
+					return false;
+				}
+			} else if (dataType.equals(BasicDataType.INTEGER)) {
+				try {
+					StringUtil.toInteger(value);
+					return true;
+				} catch (Exception e) {
+					return false;
+				}
+			} else if (dataType.equals(BasicDataType.LONG)) {
+				try {
+					StringUtil.toLong(value);
+					return true;
+				} catch (Exception e) {
+					return false;
+				}
+			}
+		}
+
+		return true;
+	}
+
+}

+ 145 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/DynamicEnumManager.java

@@ -0,0 +1,145 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.google.common.collect.Maps;
+import com.thoughtworks.xstream.XStream;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.util.PathUtil;
+
+/**
+ * 动态属性管理器
+ *
+ * @author WANGWEI
+ * @date 2018年8月23日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class DynamicEnumManager {
+
+	private List<DynamicEnum> enums;
+
+	private Map<String, DynamicEnum> nameIndex;
+
+	private Map<Long, DynamicEnum> idIndex;
+
+	/**
+	 * 构造函数
+	 *
+	 */
+	private DynamicEnumManager() {
+		super();
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param xmlResourcePath
+	 * @return
+	 */
+	public static DynamicEnumManager newInstance(String xmlResourcePath) {
+		DynamicEnumManager manager = new DynamicEnumManager();
+
+		String resoucePath = PathUtil.getResoucePath(xmlResourcePath);
+		File file = new File(resoucePath);
+
+		XStream xStream = XStreamBuilder.newInstance().build();
+		xStream.allowTypes(new Class[]{DynamicEnum.class, List.class});
+		xStream.alias("enums", List.class);
+		xStream.alias("enum", DynamicEnum.class);
+
+		@SuppressWarnings("unchecked")
+		List<DynamicEnum> list = (List<DynamicEnum>) xStream.fromXML(file);
+		manager.enums = list;
+
+		manager.nameIndex = Maps.newHashMap();
+		manager.idIndex = Maps.newHashMap();
+
+		for (DynamicEnum cur : manager.enums) {
+			String valueTpye = cur.getValueType();
+			if (StringUtils.isBlank(valueTpye)) {
+				cur.setValueType(null);
+			} else {
+				try {
+					BasicDataType dataType = BasicDataType.valueOf(valueTpye);
+					cur.setValueType(dataType.name());
+				} catch (Exception e) {
+					throw new RuntimeException("valueTpye is wrong. valueTpye=" + valueTpye);
+				}
+
+			}
+			cur.setName(cur.getName().trim());
+			cur.setDesc(cur.getDesc().trim());
+			manager.nameIndex.put(cur.getName(), cur);
+			manager.idIndex.put(cur.getId(), cur);
+		}
+
+		return manager;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param name
+	 * @return
+	 */
+	public DynamicEnum getByName(String name) {
+		DynamicEnum dynamicEnum = nameIndex.get(name);
+		if (null != dynamicEnum) {
+			return dynamicEnum;
+		}
+		throw new ExamCloudRuntimeException("动态枚举不存在. name=" + name);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param name
+	 * @return
+	 */
+	public Long getIdByName(String name) {
+		DynamicEnum dynamicEnum = nameIndex.get(name);
+		if (null != dynamicEnum) {
+			return dynamicEnum.getId();
+		}
+		throw new ExamCloudRuntimeException("动态枚举不存在. name=" + name);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param id
+	 * @return
+	 */
+	public DynamicEnum getById(Long id) {
+		DynamicEnum dynamicEnum = idIndex.get(id);
+		if (null != dynamicEnum) {
+			return dynamicEnum;
+		}
+		throw new ExamCloudRuntimeException("动态枚举不存在. id=" + id);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param id
+	 * @return
+	 */
+	public String getNameById(Long id) {
+		DynamicEnum dynamicEnum = idIndex.get(id);
+		if (null != dynamicEnum) {
+			return dynamicEnum.getName();
+		}
+		throw new ExamCloudRuntimeException("动态枚举不存在. id=" + id);
+	}
+
+}

+ 134 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/FileChangeWatchdog.java

@@ -0,0 +1,134 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import java.io.File;
+
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+
+/**
+ * 文件变更监视器
+ *
+ * @author WANGWEI
+ */
+public abstract class FileChangeWatchdog extends Thread {
+	/**
+	 * 属性注释
+	 */
+	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(FileChangeWatchdog.class);
+
+	/**
+	 * 属性注释
+	 */
+	static final public long DEFAULT_DELAY = 60000;
+
+	/**
+	 * 属性注释
+	 */
+	protected String path;
+
+	/**
+	 * 属性注释
+	 */
+	protected long delay = DEFAULT_DELAY;
+
+	/**
+	 * 属性注释
+	 */
+	protected File file;
+
+	/**
+	 * 属性注释
+	 */
+	protected long lastModif = 0;
+
+	/**
+	 * 属性注释
+	 */
+	protected boolean warnedAlready = false;
+
+	/**
+	 * 属性注释
+	 */
+	protected boolean interrupted = false;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param path
+	 */
+	protected FileChangeWatchdog(String path) {
+		super("FileChangeWatchdog");
+		LOG.info("new a FileChangeWatchdog. path=" + path);
+		this.path = path;
+		file = new File(path);
+		setDaemon(true);
+		checkAndConfigure();
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param delay
+	 */
+	public void setDelay(long delay) {
+		this.delay = delay;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 */
+	abstract protected void doOnChange();
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 */
+	protected void checkAndConfigure() {
+		boolean fileExists;
+		try {
+			fileExists = file.exists();
+		} catch (SecurityException e) {
+			LOG.warn("Was not allowed to read check file existance, file:[" + path + "].");
+			interrupted = true;
+			return;
+		}
+
+		if (fileExists) {
+			long l = file.lastModified();
+
+			if (l > lastModif) {
+				lastModif = l;
+				doOnChange();
+				warnedAlready = false;
+			}
+		} else {
+			if (!warnedAlready) {
+				LOG.debug("[" + path + "] does not exist.");
+				warnedAlready = true;
+			}
+		}
+	}
+
+	/*
+	 * 实现
+	 *
+	 * @author WANGWEI
+	 * 
+	 * @see java.lang.Thread#run()
+	 */
+	@Override
+	public void run() {
+		while (!interrupted) {
+			try {
+				Thread.sleep(delay);
+			} catch (InterruptedException e) {
+				LOG.error("unexpected interruption.", e);
+			}
+			checkAndConfigure();
+		}
+	}
+}

+ 58 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/FormFilePart.java

@@ -0,0 +1,58 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import java.io.File;
+
+/**
+ * 表单文件参数
+ *
+ * @author WANGWEI
+ * @date 2019年5月9日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class FormFilePart {
+
+	private String paramName;
+
+	private String filename;
+
+	private File file;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param paramName
+	 * @param filename
+	 * @param file
+	 */
+	public FormFilePart(String paramName, String filename, File file) {
+		super();
+		this.paramName = paramName;
+		this.filename = filename;
+		this.file = file;
+	}
+
+	public String getParamName() {
+		return paramName;
+	}
+
+	public void setParamName(String paramName) {
+		this.paramName = paramName;
+	}
+
+	public String getFilename() {
+		return filename;
+	}
+
+	public void setFilename(String filename) {
+		this.filename = filename;
+	}
+
+	public File getFile() {
+		return file;
+	}
+
+	public void setFile(File file) {
+		this.file = file;
+	}
+
+}

+ 44 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/JsonHttpResponseHolder.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * http json 响应
+ *
+ * @author WANGWEI
+ * @date 2019年9月16日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class JsonHttpResponseHolder {
+
+	private int statusCode;
+
+	private JSONObject respBody;
+
+	public JsonHttpResponseHolder() {
+		super();
+	}
+
+	public JsonHttpResponseHolder(int statusCode, JSONObject respBody) {
+		super();
+		this.statusCode = statusCode;
+		this.respBody = respBody;
+	}
+
+	public int getStatusCode() {
+		return statusCode;
+	}
+
+	public void setStatusCode(int statusCode) {
+		this.statusCode = statusCode;
+	}
+
+	public JSONObject getRespBody() {
+		return respBody;
+	}
+
+	public void setRespBody(JSONObject respBody) {
+		this.respBody = respBody;
+	}
+
+}

+ 52 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/KeyValuePair.java

@@ -0,0 +1,52 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import java.io.Serializable;
+
+import com.alibaba.fastjson.JSONObject;
+
+/**
+ * 键值对
+ * 
+ * @author WANGWEI
+ *
+ * @param <V>
+ */
+public class KeyValuePair<K, V> implements Serializable {
+
+	private static final long serialVersionUID = 5140216132754119367L;
+
+	private K key;
+
+	private V value;
+
+	public KeyValuePair(K key, V value) {
+		super();
+		this.key = key;
+		this.value = value;
+
+	}
+
+	public JSONObject toJsonObject(String keyAlias, String valueAlias) {
+		JSONObject obj = new JSONObject();
+		obj.put(keyAlias, key);
+		obj.put(valueAlias, value);
+		return obj;
+	}
+
+	public K getKey() {
+		return key;
+	}
+
+	public void setKey(K key) {
+		this.key = key;
+	}
+
+	public V getValue() {
+		return value;
+	}
+
+	public void setValue(V value) {
+		this.value = value;
+	}
+
+}

+ 46 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/ObjectHolder.java

@@ -0,0 +1,46 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+/**
+ * 基本类型对象化
+ *
+ * @author WANGWEI
+ * @date 2019年1月16日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ * @param <T>
+ */
+public class ObjectHolder<T> {
+
+	private T t;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param t
+	 */
+	public ObjectHolder(T t) {
+		super();
+		this.t = t;
+	}
+
+	public T get() {
+		return t;
+	}
+
+	public void set(T t) {
+		this.t = t;
+	}
+
+	public boolean isNull() {
+		return null == t;
+	}
+
+	@Override
+	public String toString() {
+		if (isNull()) {
+			return super.toString();
+		} else {
+			return t.toString();
+		}
+	}
+
+}

+ 98 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/XStreamBuilder.java

@@ -0,0 +1,98 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import java.util.TimeZone;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.basic.DateConverter;
+import com.thoughtworks.xstream.converters.basic.DoubleConverter;
+import com.thoughtworks.xstream.converters.basic.FloatConverter;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ */
+public class XStreamBuilder {
+
+	/**
+	 * 属性注释
+	 */
+	private XStream xs;
+
+	/**
+	 * 构造函数
+	 *
+	 */
+	private XStreamBuilder() {
+		this.xs = new XStream();
+		XStream.setupDefaultSecurity(this.xs);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static XStreamBuilder newInstance() {
+		XStreamBuilder builder = new XStreamBuilder();
+		return builder;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param defaultFormat
+	 * @return
+	 */
+	public XStreamBuilder registerDateConverter(final String defaultFormat) {
+		xs.registerConverter(new DateConverter(defaultFormat, null, TimeZone.getTimeZone("GMT+8")));
+		return this;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public XStreamBuilder registerNormalNumberConverter() {
+		xs.registerConverter(new NormalDoubleConverter());
+		xs.registerConverter(new NormalFloatConverter());
+		return this;
+	}
+
+	/**
+	 * 类注释
+	 *
+	 * @author WANGWEI
+	 */
+	private class NormalDoubleConverter extends DoubleConverter {
+		public String toString(Object obj) {
+			return String.valueOf(obj);
+		}
+	}
+
+	/**
+	 * 类注释
+	 *
+	 * @author WANGWEI
+	 */
+	private class NormalFloatConverter extends FloatConverter {
+		public String toString(Object obj) {
+			return String.valueOf(obj);
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public XStream build() {
+		return xs;
+	}
+
+}

+ 205 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/concurrency/simple/ConcurrentTask.java

@@ -0,0 +1,205 @@
+package cn.com.qmth.examcloud.commons.helpers.concurrency.simple;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.Util;
+
+/**
+ * 并发任务
+ *
+ * @author WANGWEI
+ * @date 2019年6月19日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class ConcurrentTask<T> {
+
+	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(ConcurrentTask.class);
+
+	private BlockingQueue<T> queue = new LinkedBlockingQueue<T>(10000);
+
+	private BlockingQueue<Integer> workerMessages = new LinkedBlockingQueue<Integer>(10000);
+
+	private final WorkerController workerController = new WorkerController();
+
+	/**
+	 * 最大线程数
+	 */
+	private int maxActiveThreadSize = 5;
+
+	/**
+	 * 不死线程数
+	 */
+	private int minThreadSize = 2;
+
+	private ThreadPoolExecutor threadPoolExecutor;
+
+	/**
+	 * 处理者
+	 */
+	private Worker<T> worker;
+
+	/**
+	 * 巡检周期
+	 */
+	private int inspectionPeriod = 30;
+
+	/**
+	 * 任务名称
+	 */
+	private String taskName;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param taskName
+	 */
+	public ConcurrentTask(String taskName) {
+		super();
+		this.taskName = taskName;
+	}
+
+	/**
+	 * 添加处理元素
+	 *
+	 * @author WANGWEI
+	 * @param e
+	 * @return
+	 */
+	public boolean offerElement(T e) {
+		boolean offer = queue.offer(e);
+		if (LOG.isDebugEnabled()) {
+			LOG.debug("offerElement. result=" + offer + ";element=" + e.toString());
+		}
+		return offer;
+	}
+
+	/**
+	 * 启动任务
+	 *
+	 * @author WANGWEI
+	 */
+	public void start() {
+		threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(maxActiveThreadSize);
+
+		// 创建不死线程
+		for (int i = 0; i < minThreadSize; i++) {
+			addWorkerThread(true);
+		}
+
+		// 巡检线程
+		Thread inspectionThread = new Thread() {
+
+			@Override
+			public void run() {
+				Util.sleep(inspectionPeriod);
+
+				while (true) {
+
+					int size = queue.size();
+					int activeCount = threadPoolExecutor.getActiveCount();
+					int warnCount = workerController.getWarnCount();
+
+					if (LOG.isInfoEnabled()) {
+						LOG.info("taskName [" + taskName + "]. activeCount = " + activeCount
+								+ "; warnCount = " + warnCount);
+					}
+
+					// 巡检周期内(因并发超出限制导致的)警告数量未超过100时,增加一个worker
+					if (warnCount <= 100) {
+						if (100 < size && maxActiveThreadSize > activeCount) {
+							addWorkerThread(false);
+						}
+					} else {
+						// 巡检周期内(因并发超出限制导致的)警告数量超过100时,减少一个worker
+						workerMessages.offer(warnCount);
+					}
+
+					workerController.resetWarnCount();
+					Util.sleep(inspectionPeriod);
+				}
+
+			};
+
+		};
+
+		inspectionThread.setDaemon(true);
+		inspectionThread.start();
+	}
+
+	/**
+	 * 添加处理线程
+	 *
+	 * @author WANGWEI
+	 * @param immortal
+	 */
+	private void addWorkerThread(final boolean immortal) {
+		LOG.info("create a new worker. immortal=" + immortal);
+		Thread thread = new Thread() {
+
+			long nullTimes = 0;
+
+			@Override
+			public void run() {
+				while (true) {
+					T el = queue.poll();
+					if (null == el) {
+						nullTimes++;
+						if (10 <= nullTimes) {
+							if (immortal) {
+								Util.sleep(2);
+								continue;
+							} else {
+								LOG.info("no element.worker exist.");
+								break;
+							}
+						} else {
+							Util.sleep(2);
+							continue;
+						}
+					} else {
+						nullTimes = 0;
+					}
+
+					try {
+						if (LOG.isDebugEnabled()) {
+							LOG.debug("process. element=" + el.toString());
+						}
+						worker.process(workerController, el);
+					} catch (Exception e) {
+						LOG.error("unexpected exception", e);
+					}
+
+					// 非不死线程在抢到终止消息时,结束线程
+					if (!immortal) {
+						Integer warnCount = workerMessages.poll();
+						if (null != warnCount) {
+							LOG.info("worker exist. warnCount=" + warnCount);
+							break;
+						}
+					}
+
+				}
+			}
+		};
+
+		threadPoolExecutor.execute(thread);
+	}
+
+	public void setMaxActiveThreadSize(int maxActiveThreadSize) {
+		this.maxActiveThreadSize = maxActiveThreadSize;
+	}
+
+	public void setMinThreadSize(int minThreadSize) {
+		this.minThreadSize = minThreadSize;
+	}
+
+	public void setWorker(Worker<T> worker) {
+		this.worker = worker;
+	}
+
+}

+ 7 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/concurrency/simple/Worker.java

@@ -0,0 +1,7 @@
+package cn.com.qmth.examcloud.commons.helpers.concurrency.simple;
+
+public interface Worker<T> {
+
+	void process(final WorkerController controller, T element);
+
+}

+ 27 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/concurrency/simple/WorkerController.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.examcloud.commons.helpers.concurrency.simple;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+
+public class WorkerController {
+
+	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(WorkerController.class);
+
+	private AtomicInteger warnCount = new AtomicInteger(0);
+
+	public void addConcurrencyWarn() {
+		LOG.info("warn count ++");
+		this.warnCount.incrementAndGet();
+	}
+
+	public int getWarnCount() {
+		return this.warnCount.get();
+	}
+
+	public void resetWarnCount() {
+		warnCount.set(0);
+	}
+
+}

+ 43 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/Counter.java

@@ -0,0 +1,43 @@
+package cn.com.qmth.examcloud.commons.helpers.pipeline;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 输出计数器
+ *
+ * @author WANGWEI
+ * @date 2019年12月12日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class Counter {
+
+	private AtomicLong total = new AtomicLong(0);
+
+	private AtomicLong successAmount = new AtomicLong(0);
+
+	private AtomicLong failureAmount = new AtomicLong(0);
+
+	public long getTotal() {
+		return total.get();
+	}
+
+	public long getSuccessAmount() {
+		return successAmount.get();
+	}
+
+	public long getFailureAmount() {
+		return failureAmount.get();
+	}
+
+	public long incrementTotal() {
+		return this.total.incrementAndGet();
+	}
+
+	public long incrementSuccessAmount() {
+		return this.successAmount.incrementAndGet();
+	}
+
+	public long incrementFailureAmount() {
+		return this.failureAmount.incrementAndGet();
+	}
+}

+ 59 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/Node.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.examcloud.commons.helpers.pipeline;
+
+/**
+ * 节点
+ *
+ * @author WANGWEI
+ * @date 2019年12月12日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public interface Node<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
+
+	/**
+	 * 启动
+	 *
+	 * @author WANGWEI
+	 */
+	void start();
+
+	/**
+	 * 设置下级节点
+	 *
+	 * @author WANGWEI
+	 * @param subNode
+	 */
+	void setLowerNode(Node<KEYOUT, VALUEOUT, ?, ?> lowerNode);
+
+	/**
+	 * 获取下级节点
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	Node<KEYOUT, VALUEOUT, ?, ?> getLowerNode();
+
+	/**
+	 * 获取存储器
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	Storer<KEYIN, VALUEIN> getStorer();
+
+	/**
+	 * 设置是否是首节点
+	 *
+	 * @author WANGWEI
+	 * @param first
+	 */
+	void setFirst(boolean first);
+
+	/**
+	 * 设置循环间隔
+	 *
+	 * @author WANGWEI
+	 * @param sleep
+	 */
+	void setSleep(int sleep);
+
+}

+ 35 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/NodeExecuter.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.commons.helpers.pipeline;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.commons.helpers.KeyValuePair;
+import cn.com.qmth.examcloud.commons.helpers.ObjectHolder;
+
+/**
+ * 节点执行器
+ *
+ * @author WANGWEI
+ * @date 2019年12月12日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ * @param <KEYIN>
+ * @param <VALUEIN>
+ * @param <KEYOUT>
+ * @param <VALUEOUT>
+ */
+public interface NodeExecuter<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
+
+	/**
+	 * 执行
+	 *
+	 * @author WANGWEI
+	 * @param key
+	 * @param value
+	 * @param outList
+	 * @param removable
+	 * @param context
+	 * @throws Exception
+	 */
+	void execute(KEYIN key, VALUEIN value, final List<KeyValuePair<KEYOUT, VALUEOUT>> outList,
+			final ObjectHolder<Boolean> removable, TaskContext context) throws Exception;
+
+}

+ 246 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/SimpleNode.java

@@ -0,0 +1,246 @@
+package cn.com.qmth.examcloud.commons.helpers.pipeline;
+
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.ThreadContext;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.examcloud.commons.helpers.KeyValuePair;
+import cn.com.qmth.examcloud.commons.helpers.ObjectHolder;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.ThreadLocalUtil;
+import cn.com.qmth.examcloud.commons.util.Util;
+
+/**
+ * 简单节点
+ *
+ * @author WANGWEI
+ * @date 2019年12月12日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class SimpleNode<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
+		implements
+			Node<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
+
+	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(SimpleNode.class);
+
+	private String nodeName;
+
+	private Storer<KEYIN, VALUEIN> storer = new Storer<KEYIN, VALUEIN>();
+
+	private Counter counter = new Counter();
+
+	private TaskContext context;
+
+	private NodeExecuter<KEYIN, VALUEIN, KEYOUT, VALUEOUT> executer;
+
+	private SimpleNode<KEYIN, VALUEIN, KEYOUT, VALUEOUT> myself;
+
+	private Node<KEYOUT, VALUEOUT, ?, ?> lowerNode;
+
+	private Storer<KEYOUT, VALUEOUT> lowerStorer;
+
+	private boolean first;
+
+	private int sleep = 10;
+
+	private String traceId = ThreadLocalUtil.getTraceId();
+
+	/**
+	 * 构造函数
+	 *
+	 * @param nodeName
+	 * @param nodeExecuter
+	 * @param context
+	 */
+	public SimpleNode(String nodeName, NodeExecuter<KEYIN, VALUEIN, KEYOUT, VALUEOUT> executer,
+			TaskContext context) {
+		super();
+		this.nodeName = nodeName;
+		this.executer = executer;
+		this.context = context;
+		this.myself = this;
+	}
+
+	/**
+	 * 启动
+	 *
+	 * @author WANGWEI
+	 */
+	@Override
+	public void start() {
+		this.startNodeExecuter();
+
+		this.startLogThread();
+	}
+
+	/**
+	 * 创建节点执行器
+	 *
+	 * @author WANGWEI
+	 */
+	private void startNodeExecuter() {
+		new Thread(() -> {
+
+			while (true) {
+				traceId = ThreadLocalUtil.getTraceId();
+				// 设置log4j线程上下文
+				ThreadContext.put("TRACE_ID", traceId);
+
+				LOG.info(new StringBuilder("[" + nodeName + "]. ").append("start ... ...")
+						.toString());
+				counter = new Counter();
+				if (isFirst()) {
+					execute(null, null);
+				} else {
+					Set<Entry<KEYIN, VALUEIN>> entrySet = getStorer().getEntrySet();
+
+					for (Entry<KEYIN, VALUEIN> entry : entrySet) {
+						execute(entry.getKey(), entry.getValue());
+					}
+				}
+
+				try {
+					Util.sleep(getSleep());
+				} catch (Exception e) {
+					LOG.error("sleep Exception.", e);
+				}
+
+				// 清理log4j线程上下文
+				ThreadContext.clearAll();
+			}
+
+		}).start();
+	}
+
+	private void execute(KEYIN key, VALUEIN value) {
+		long s = System.currentTimeMillis();
+		try {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug(new StringBuilder("[" + nodeName + "]. ").append("handle entry. key=")
+						.append(null == key ? null : key.toString()).append("; value=")
+						.append(null == value ? null : value.toString()).toString());
+			}
+			counter.incrementTotal();
+			List<KeyValuePair<KEYOUT, VALUEOUT>> outList = Lists.newLinkedList();
+			ObjectHolder<Boolean> removable = new ObjectHolder<Boolean>(true);
+			executer.execute(key, value, outList, removable, context);
+
+			if (null != outList && null != getLowerStorer()) {
+				for (KeyValuePair<KEYOUT, VALUEOUT> pair : outList) {
+					getLowerStorer().putElement(pair.getKey(), pair.getValue());
+				}
+			}
+
+			if (null != removable.get() && removable.get()) {
+				if (null != key) {
+					getStorer().remove(key);
+				}
+			}
+
+			counter.incrementSuccessAmount();
+
+			if (LOG.isDebugEnabled()) {
+				LOG.debug(new StringBuilder("[" + nodeName + "]. ")
+						.append("handle entry successfully. key=")
+						.append(null == key ? null : key.toString()).append("; value=")
+						.append(null == value ? null : value.toString()).toString());
+			}
+		} catch (Exception e) {
+			counter.incrementFailureAmount();
+			if (LOG.isErrorEnabled()) {
+				LOG.error(new StringBuilder("[" + nodeName + "]. ")
+						.append("fail to handle entry. key=")
+						.append(null == key ? null : key.toString()).append("; value=")
+						.append(null == value ? null : value.toString()).toString(), e);
+			}
+		} finally {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug(new StringBuilder("[" + nodeName + "]. ").append("cost ")
+						.append(System.currentTimeMillis() - s).append("ms.").toString());
+			}
+		}
+	}
+
+	/**
+	 * 启动日志线程
+	 *
+	 * @author WANGWEI
+	 */
+	private void startLogThread() {
+		new Thread(() -> {
+			while (true) {
+				// 设置log4j线程上下文
+				ThreadContext.put("TRACE_ID", traceId);
+
+				Util.sleep(TimeUnit.SECONDS, 2);
+				if (LOG.isInfoEnabled()) {
+					LOG.info(new StringBuilder("[" + nodeName + "]. ").append("status: ")
+							.append(myself.getNodeStatusInfo()).toString());
+				}
+
+				// 清理log4j线程上下文
+				ThreadContext.clearAll();
+			}
+
+		}).start();
+	}
+
+	private String getNodeStatusInfo() {
+		long total = 0;
+		long successAmount = 0;
+		long failureAmount = 0;
+		Counter c = this.counter;
+		if (null != c) {
+			total = c.getTotal();
+			successAmount = c.getSuccessAmount();
+			failureAmount = c.getFailureAmount();
+		}
+		return new StringBuilder("Total=").append(total).append(",SuccessAmount=")
+				.append(successAmount).append(",FailureAmount=").append(failureAmount).toString();
+	}
+
+	@Override
+	public void setLowerNode(Node<KEYOUT, VALUEOUT, ?, ?> lowerNode) {
+		this.lowerNode = lowerNode;
+		lowerStorer = this.lowerNode.getStorer();
+	}
+
+	@Override
+	public Node<KEYOUT, VALUEOUT, ?, ?> getLowerNode() {
+		return this.lowerNode;
+	}
+
+	@Override
+	public Storer<KEYIN, VALUEIN> getStorer() {
+		return this.storer;
+	}
+
+	public Storer<KEYOUT, VALUEOUT> getLowerStorer() {
+		return lowerStorer;
+	}
+
+	public boolean isFirst() {
+		return first;
+	}
+
+	@Override
+	public void setFirst(boolean first) {
+		this.first = first;
+	}
+
+	public int getSleep() {
+		return sleep;
+	}
+
+	@Override
+	public void setSleep(int sleep) {
+		this.sleep = sleep;
+	}
+
+}

+ 40 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/Storer.java

@@ -0,0 +1,40 @@
+package cn.com.qmth.examcloud.commons.helpers.pipeline;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import com.google.common.collect.Maps;
+
+/**
+ * 节点存储器
+ *
+ * @author WANGWEI
+ * @date 2019年12月12日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class Storer<KEY, VALUE> {
+
+	private Map<KEY, VALUE> MAP = Maps.newConcurrentMap();
+
+	private Map<KEY, VALUE> BUFFER_MAP = Maps.newConcurrentMap();
+
+	public synchronized Set<Entry<KEY, VALUE>> getEntrySet() {
+		MAP.putAll(BUFFER_MAP);
+		BUFFER_MAP.clear();
+		return MAP.entrySet();
+	}
+
+	public synchronized void putElement(KEY key, VALUE value) {
+		BUFFER_MAP.put(key, value);
+	}
+
+	public synchronized boolean isEmpty() {
+		return MAP.isEmpty();
+	}
+
+	public synchronized void remove(KEY key) {
+		MAP.remove(key);
+	}
+
+}

+ 34 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/TaskContext.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.commons.helpers.pipeline;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 任务上下文(任务参数)
+ *
+ * @author WANGWEI
+ * @date 2019年12月12日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class TaskContext implements Serializable {
+	private static final long serialVersionUID = 4979254175922604943L;
+
+	private final Map<String, Object> props = new ConcurrentHashMap<String, Object>();
+
+	public String get(String name) {
+		return get(name, String.class);
+	}
+
+	public <T> T get(String name, Class<T> t) {
+		Object value = props.get(name);
+		@SuppressWarnings("unchecked")
+		T ret = (T) value;
+		return ret;
+	}
+
+	public void put(String name, Object bean) {
+		props.put(name, bean);
+	}
+
+}

+ 165 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/poi/ExcelReader.java

@@ -0,0 +1,165 @@
+package cn.com.qmth.examcloud.commons.helpers.poi;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import com.google.common.collect.Lists;
+
+/**
+ * Excel解析器
+ *
+ * @author WANGWEI
+ * @date 2018年3月30日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class ExcelReader {
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param sheet
+	 * @param columSize
+	 * @return
+	 */
+	public static List<String[]> readSheet(Sheet sheet, int columSize) {
+		List<String[]> list = Lists.newArrayList();
+		int firstRowNum = sheet.getFirstRowNum();
+		int lastRowNum = sheet.getLastRowNum();
+		for (int rowNum = firstRowNum; rowNum <= lastRowNum; rowNum++) {
+			Row row = sheet.getRow(rowNum);
+			if (row == null) {
+				continue;
+			}
+			String[] cells = new String[columSize];
+
+			for (int index = 0; index < columSize; index++) {
+				Cell cell = row.getCell(index);
+				cells[index] = getCellValue(cell);
+			}
+
+			list.add(cells);
+		}
+
+		return list;
+	}
+
+	/**
+	 * 获取sheet
+	 *
+	 * @author WANGWEI
+	 * @param workbook
+	 * @return
+	 */
+	public static List<Sheet> getSheet(Workbook workbook) {
+
+		List<Sheet> sheets = Lists.newArrayList();
+		for (int sheetNum = 0; sheetNum < workbook.getNumberOfSheets(); sheetNum++) {
+			Sheet sheet = workbook.getSheetAt(sheetNum);
+			sheets.add(sheet);
+		}
+		return sheets;
+	}
+
+	/**
+	 * 获取 Workbook 对象
+	 *
+	 * @author WANGWEI
+	 * @param file
+	 */
+	public static Workbook getWorkBook(File file) {
+
+		Workbook workbook = null;
+		try {
+			// 2007
+			workbook = new XSSFWorkbook(new FileInputStream(file));
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+
+		return workbook;
+	}
+
+	/**
+	 * 获取单元格的字符串值
+	 *
+	 * @author WANGWEI
+	 * @param cell
+	 * @return
+	 */
+	public static String getCellValue(Cell cell) {
+		String value = "";
+		if (null == cell) {
+			return value;
+		}
+		cell.setCellType(CellType.STRING);
+		switch (cell.getCellTypeEnum()) {
+			case _NONE :
+				break;
+			case STRING :
+				value = cell.getStringCellValue();
+				break;
+			case NUMERIC :
+				// 待完善
+				value = String.valueOf(cell.getNumericCellValue());
+				break;
+			case BOOLEAN :
+				value = String.valueOf(cell.getBooleanCellValue());
+				break;
+			case BLANK :
+				value = "";
+				break;
+			default :
+				value = cell.toString();
+		}
+		return value.trim();
+	}
+
+	/**
+	 * 关闭资源
+	 *
+	 * @author WANGWEI
+	 * @param workbook
+	 */
+	public static void close(Workbook workbook) {
+		IOUtils.closeQuietly(workbook);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param filePath
+	 * @param sheetId
+	 * @param columnSize
+	 * @return
+	 */
+	public static List<String[]> readSheetBySax(String filePath, int sheetId, int columnSize) {
+		List<String[]> list = Lists.newArrayList();
+
+		XlsxHandler xlsxHandler = new XlsxHandler(columnSize) {
+			@Override
+			public void optRows(int sheetIndex, int curRow, String[] row) {
+				list.add(row);
+			}
+		};
+
+		try {
+			xlsxHandler.processOneSheet(filePath, sheetId);
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+
+		return list;
+	}
+
+}

+ 137 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/poi/ExcelWriter.java

@@ -0,0 +1,137 @@
+package cn.com.qmth.examcloud.commons.helpers.poi;
+
+import java.awt.Color;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFCellStyle;
+import org.apache.poi.xssf.usermodel.XSSFColor;
+import org.apache.poi.xssf.usermodel.XSSFDataFormat;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+
+import cn.com.qmth.examcloud.commons.util.DateUtil.DatePatterns;
+
+/**
+ * Excel 生成器
+ *
+ * @author WANGWEI
+ * @date 2018年9月7日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class ExcelWriter {
+
+	/**
+	 * 写入excel文件
+	 *
+	 * @author WANGWEI
+	 * @param tableHeader
+	 * @param datas
+	 * @param file
+	 */
+	public static void write(String[] tableHeader, Class<?>[] types, List<Object[]> datas,
+			File file) {
+		// 2007
+		XSSFWorkbook workbook = null;
+		FileOutputStream out = null;
+		try {
+			// 2007
+			workbook = new XSSFWorkbook();
+
+			XSSFSheet sheet = workbook.createSheet();
+
+			XSSFCellStyle style = workbook.createCellStyle();
+			style.setFillForegroundColor(new XSSFColor(new Color(227, 239, 217)));
+			style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+
+			XSSFRow firstRow = sheet.createRow(0);
+			for (int i = 0; i < tableHeader.length; i++) {
+				XSSFCell cell = firstRow.createCell(i);
+				cell.setCellStyle(style);
+				cell.setCellType(CellType.STRING);
+				cell.setCellValue(tableHeader[i]);
+			}
+
+			for (int k = 0; k < datas.size(); k++) {
+				XSSFRow nextRow = sheet.createRow(k + 1);
+				Object[] rowDatas = datas.get(k);
+
+				for (int j = 0; j < types.length; j++) {
+					Class<?> type = types[j];
+					Object value = rowDatas[j];
+
+					XSSFCell cell = nextRow.createCell(j);
+
+					if (type.equals(String.class)) {
+						cell.setCellType(CellType.STRING);
+						if (null != value) {
+							cell.setCellValue((String) value);
+						}
+					} else if (type.equals(Long.class)) {
+						cell.setCellType(CellType.NUMERIC);
+						if (null != value) {
+							cell.setCellValue((Long) value);
+						}
+					} else if (type.equals(Integer.class)) {
+						cell.setCellType(CellType.NUMERIC);
+						if (null != value) {
+							cell.setCellValue((Integer) value);
+						}
+					} else if (type.equals(Short.class)) {
+						cell.setCellType(CellType.NUMERIC);
+						if (null != value) {
+							cell.setCellValue((Short) value);
+						}
+					} else if (type.equals(Float.class)) {
+						cell.setCellType(CellType.NUMERIC);
+						if (null != value) {
+							cell.setCellValue((Float) value);
+						}
+					} else if (type.equals(Double.class)) {
+						cell.setCellType(CellType.NUMERIC);
+						if (null != value) {
+							cell.setCellValue((Double) value);
+						}
+					} else if (type.equals(Date.class)) {
+						CellStyle cs = workbook.createCellStyle();
+						XSSFDataFormat format = workbook.createDataFormat();
+						cs.setDataFormat(format.getFormat(DatePatterns.CHINA_DEFAULT));
+						cell.setCellStyle(cs);
+						cell.setCellType(CellType.NUMERIC);
+						if (null != value) {
+							cell.setCellValue((Date) value);
+						}
+					} else if (type.equals(Boolean.class)) {
+						cell.setCellType(CellType.BOOLEAN);
+						if (null != value) {
+							cell.setCellValue((Boolean) value);
+						}
+					} else {
+						IOUtils.closeQuietly(workbook);
+						throw new RuntimeException("cell type is wrong");
+					}
+				}
+			}
+
+			out = FileUtils.openOutputStream(file);
+			workbook.write(out);
+		} catch (RuntimeException e) {
+			throw e;
+		} catch (Throwable e) {
+			throw new RuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(out);
+			IOUtils.closeQuietly(workbook);
+		}
+	}
+
+}

+ 204 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/poi/XlsxHandler.java

@@ -0,0 +1,204 @@
+package cn.com.qmth.examcloud.commons.helpers.poi;
+
+import java.io.InputStream;
+import java.util.Iterator;
+
+import org.apache.commons.compress.utils.IOUtils;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.xssf.eventusermodel.XSSFReader;
+import org.apache.poi.xssf.model.SharedStringsTable;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * excel xlsx 文件解析
+ *
+ * @author WANGWEI
+ * @date 2018年8月1日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public abstract class XlsxHandler extends DefaultHandler {
+
+	private String[] row;
+
+	private SharedStringsTable sst;
+
+	private String cellValue;
+
+	private boolean isString;
+
+	private int sheetIndex = -1;
+
+	private int curRow = 0;
+
+	private int curColumnIndex = 0;
+
+	private int columnSize = 0;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param columnSize
+	 */
+	public XlsxHandler(int columnSize) {
+		super();
+		this.columnSize = columnSize;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param sheetIndex
+	 * @param curRow
+	 * @param row
+	 */
+	public abstract void optRows(int sheetIndex, int curRow, String[] row);
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param filePath
+	 * @param sheetId
+	 * @throws Exception
+	 */
+	public void processOneSheet(String filePath, int sheetId) throws Exception {
+		OPCPackage pkg = null;
+		InputStream is = null;
+		try {
+			pkg = OPCPackage.open(filePath);
+			XSSFReader r = new XSSFReader(pkg);
+			SharedStringsTable sst = r.getSharedStringsTable();
+
+			XMLReader parser = XMLReaderFactory.createXMLReader();
+			this.sst = sst;
+			parser.setContentHandler(this);
+			is = r.getSheet("rId" + sheetId);
+			sheetIndex++;
+			InputSource sheetSource = new InputSource(is);
+			parser.parse(sheetSource);
+		} finally {
+			IOUtils.closeQuietly(is);
+			IOUtils.closeQuietly(pkg);
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param filePath
+	 * @throws Exception
+	 */
+	public void process(String filePath) throws Exception {
+		OPCPackage pkg = null;
+		try {
+			pkg = OPCPackage.open(filePath);
+			XSSFReader r = new XSSFReader(pkg);
+			SharedStringsTable sst = r.getSharedStringsTable();
+
+			XMLReader parser = XMLReaderFactory.createXMLReader();
+			this.sst = sst;
+			parser.setContentHandler(this);
+
+			Iterator<InputStream> sheets = r.getSheetsData();
+			while (sheets.hasNext()) {
+				curRow = 0;
+				sheetIndex++;
+				InputStream sheet = null;
+				try {
+					sheet = sheets.next();
+					InputSource sheetSource = new InputSource(sheet);
+					parser.parse(sheetSource);
+				} finally {
+					IOUtils.closeQuietly(sheet);
+				}
+			}
+		} finally {
+			IOUtils.closeQuietly(pkg);
+		}
+	}
+
+	@Override
+	public void startElement(String uri, String localName, String name, Attributes attributes)
+			throws SAXException {
+		if (name.equals("c")) {
+			String cellType = attributes.getValue("t");
+			String x = attributes.getValue("r");
+			curColumnIndex = this.getColunmIndex(x);
+			if (cellType != null && cellType.equals("s")) {
+				isString = true;
+			} else {
+				isString = false;
+			}
+		}
+		cellValue = "";
+	}
+
+	@Override
+	public void characters(char[] ch, int start, int length) throws SAXException {
+		cellValue += new String(ch, start, length);
+	}
+
+	@Override
+	public void endElement(String uri, String localName, String name) throws SAXException {
+		if (isString) {
+			try {
+				int idx = Integer.parseInt(cellValue);
+				cellValue = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
+			} catch (Exception e) {
+
+			}
+		}
+
+		if (null == row) {
+			row = new String[columnSize];
+		}
+
+		if (name.equals("v")) {
+			String value = cellValue.trim();
+			if (curColumnIndex - 1 < columnSize) {
+				row[curColumnIndex - 1] = value;
+			}
+		} else if (name.equals("row")) {
+			optRows(sheetIndex, curRow, row);
+			row = null;
+			curRow++;
+			curColumnIndex = 0;
+		}
+
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param x
+	 * @return
+	 */
+	private int getColunmIndex(String x) {
+		x = x.replaceAll("[^A-Z]", "");
+		byte[] bytes = x.getBytes();
+		int len = bytes.length;
+		float num = 0;
+		for (int i = 0; i < len; i++) {
+			num += (bytes[i] - 'A' + 1) * Math.pow(26, len - i - 1);
+		}
+		return (int) num;
+	}
+
+	public int getColumnSize() {
+		return columnSize;
+	}
+
+	public void setColumnSize(int columnSize) {
+		this.columnSize = columnSize;
+	}
+
+}

+ 110 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/ExamCloudLog.java

@@ -0,0 +1,110 @@
+package cn.com.qmth.examcloud.commons.logging;
+
+/**
+ * 通用日志写入器
+ * 
+ * @author WANGWEI
+ */
+public interface ExamCloudLog {
+
+	/**
+	 * Is the logger instance enabled for the DEBUG level?
+	 *
+	 * @return True if this Logger is enabled for the DEBUG level, false otherwise.
+	 */
+	public boolean isDebugEnabled();
+
+	/**
+	 * Log a message at the DEBUG level.
+	 *
+	 * @param msg
+	 *            the message string to be logged
+	 */
+	public void debug(String msg);
+
+	/**
+	 * Log an exception (throwable) at the DEBUG level with an accompanying message.
+	 *
+	 * @param msg
+	 *            the message accompanying the exception
+	 * @param t
+	 *            the exception (throwable) to log
+	 */
+	public void debug(String msg, Throwable t);
+
+	/**
+	 * Is the logger instance enabled for the INFO level?
+	 *
+	 * @return True if this Logger is enabled for the INFO level, false otherwise.
+	 */
+	public boolean isInfoEnabled();
+
+	/**
+	 * Log a message at the INFO level.
+	 *
+	 * @param msg
+	 *            the message string to be logged
+	 */
+	public void info(String msg);
+
+	/**
+	 * Log an exception (throwable) at the INFO level with an accompanying message.
+	 *
+	 * @param msg
+	 *            the message accompanying the exception
+	 * @param t
+	 *            the exception (throwable) to log
+	 */
+	public void info(String msg, Throwable t);
+
+	/**
+	 * Is the logger instance enabled for the WARN level?
+	 *
+	 * @return True if this Logger is enabled for the WARN level, false otherwise.
+	 */
+	public boolean isWarnEnabled();
+
+	/**
+	 * Log a message at the WARN level.
+	 *
+	 * @param msg
+	 *            the message string to be logged
+	 */
+	public void warn(String msg);
+
+	/**
+	 * Log an exception (throwable) at the WARN level with an accompanying message.
+	 *
+	 * @param msg
+	 *            the message accompanying the exception
+	 * @param t
+	 *            the exception (throwable) to log
+	 */
+	public void warn(String msg, Throwable t);
+
+	/**
+	 * Is the logger instance enabled for the ERROR level?
+	 *
+	 * @return True if this Logger is enabled for the ERROR level, false otherwise.
+	 */
+	public boolean isErrorEnabled();
+
+	/**
+	 * Log a message at the ERROR level.
+	 *
+	 * @param msg
+	 *            the message string to be logged
+	 */
+	public void error(String msg);
+
+	/**
+	 * Log an exception (throwable) at the ERROR level with an accompanying message.
+	 *
+	 * @param msg
+	 *            the message accompanying the exception
+	 * @param t
+	 *            the exception (throwable) to log
+	 */
+	public void error(String msg, Throwable t);
+
+}

+ 78 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/ExamCloudLogFactory.java

@@ -0,0 +1,78 @@
+package cn.com.qmth.examcloud.commons.logging;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * 通用日志写入器工厂
+ * 
+ * @author WANGWEI
+ */
+public class ExamCloudLogFactory {
+
+	private static Constructor<?> logConstructor;
+
+	static {
+		tryImplementation("org.slf4j.Logger", SLF4JImpl.class.getName());
+
+		if (logConstructor == null) {
+			try {
+				logConstructor = NoLoggingImpl.class.getConstructor(String.class);
+			} catch (Exception e) {
+				throw new IllegalStateException(e.getMessage(), e);
+			}
+		}
+	}
+
+	/**
+	 * @param testClassName
+	 * @param implClassName
+	 */
+	private static void tryImplementation(String testClassName, String implClassName) {
+		if (logConstructor != null) {
+			return;
+		}
+
+		try {
+			Resources.classForName(testClassName);
+			Class<?> implClass = Resources.classForName(implClassName);
+			logConstructor = implClass.getConstructor(new Class[] { String.class });
+
+			Class<?> declareClass = logConstructor.getDeclaringClass();
+			if (!ExamCloudLog.class.isAssignableFrom(declareClass)) {
+				logConstructor = null;
+			}
+
+			try {
+				if (null != logConstructor) {
+					logConstructor.newInstance(ExamCloudLogFactory.class.getName());
+				}
+			} catch (Throwable t) {
+				logConstructor = null;
+			}
+
+		} catch (Throwable t) {
+			// skip
+		}
+	}
+
+	/**
+	 * @param clazz
+	 * @return
+	 */
+	public static ExamCloudLog getLog(Class<?> clazz) {
+		return getLog(clazz.getName());
+	}
+
+	/**
+	 * @param loggerName
+	 * @return
+	 */
+	public static ExamCloudLog getLog(String loggerName) {
+		try {
+			return (ExamCloudLog) logConstructor.newInstance(loggerName);
+		} catch (Throwable t) {
+			throw new RuntimeException("Error creating logger for logger '" + loggerName + "'.  Cause: " + t, t);
+		}
+	}
+
+}

+ 70 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/NoLoggingImpl.java

@@ -0,0 +1,70 @@
+package cn.com.qmth.examcloud.commons.logging;
+
+/**
+ * no logging
+ * 
+ * @author WANGWEI
+ */
+public class NoLoggingImpl implements ExamCloudLog {
+
+	/**
+	 * 构造函数
+	 * 
+	 * @param loggerName
+	 */
+	public NoLoggingImpl(String loggerName) {
+	}
+
+	@Override
+	public boolean isDebugEnabled() {
+		return false;
+	}
+
+	@Override
+	public void debug(String msg) {
+	}
+
+	@Override
+	public void debug(String msg, Throwable t) {
+	}
+
+	@Override
+	public boolean isInfoEnabled() {
+		return false;
+	}
+
+	@Override
+	public void info(String msg) {
+	}
+
+	@Override
+	public void info(String msg, Throwable t) {
+	}
+
+	@Override
+	public boolean isWarnEnabled() {
+		return false;
+	}
+
+	@Override
+	public void warn(String msg) {
+	}
+
+	@Override
+	public void warn(String msg, Throwable t) {
+	}
+
+	@Override
+	public boolean isErrorEnabled() {
+		return false;
+	}
+
+	@Override
+	public void error(String msg) {
+	}
+
+	@Override
+	public void error(String msg, Throwable t) {
+	}
+
+}

+ 67 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/Resources.java

@@ -0,0 +1,67 @@
+package cn.com.qmth.examcloud.commons.logging;
+
+/**
+ * A class to simplify access to resources through the classloader.
+ *
+ * @author WANGWEI
+ */
+public final class Resources extends Object {
+
+	private static ClassLoader defaultClassLoader;
+
+	/**
+	 * 构造函数
+	 */
+	private Resources() {
+	}
+
+	/**
+	 * Returns the default classloader (may be null).
+	 * 
+	 * @return The default classloader
+	 */
+	public static ClassLoader getDefaultClassLoader() {
+		return defaultClassLoader;
+	}
+
+	/**
+	 * Sets the default classloader
+	 * 
+	 * @param defaultClassLoader
+	 *            - the new default ClassLoader
+	 */
+	public static void setDefaultClassLoader(ClassLoader defaultClassLoader) {
+		Resources.defaultClassLoader = defaultClassLoader;
+	}
+
+	/**
+	 * Loads a class
+	 * 
+	 * @param className
+	 *            - the class to load
+	 * @return The loaded class
+	 * @throws ClassNotFoundException
+	 *             If the class cannot be found (duh!)
+	 */
+	public static Class<?> classForName(String className) throws ClassNotFoundException {
+		Class<?> clazz = null;
+		try {
+			clazz = getClassLoader().loadClass(className);
+		} catch (Exception e) {
+			// Ignore.
+		}
+		if (clazz == null) {
+			clazz = Class.forName(className);
+		}
+		return clazz;
+	}
+
+	private static ClassLoader getClassLoader() {
+		if (defaultClassLoader != null) {
+			return defaultClassLoader;
+		} else {
+			return Thread.currentThread().getContextClassLoader();
+		}
+	}
+
+}

+ 111 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/logging/SLF4JImpl.java

@@ -0,0 +1,111 @@
+package cn.com.qmth.examcloud.commons.logging;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.spi.LocationAwareLogger;
+
+/**
+ * slf4j
+ * 
+ * @author WANGWEI
+ */
+public class SLF4JImpl implements ExamCloudLog {
+
+	/**
+	 * 属性注释
+	 */
+	private static final String callerFQCN = SLF4JImpl.class.getName();
+
+	/**
+	 * 属性注释
+	 */
+	private static final Logger testLogger = LoggerFactory.getLogger(SLF4JImpl.class);
+
+	/**
+	 * 属性注释
+	 */
+	private LocationAwareLogger log;
+
+	static {
+		if (!(testLogger instanceof LocationAwareLogger)) {
+			throw new UnsupportedOperationException(testLogger.getClass() + " is not a suitable logger");
+		}
+	}
+
+	/**
+	 * 构造函数
+	 * 
+	 * @param log
+	 */
+	public SLF4JImpl(LocationAwareLogger log) {
+		this.log = log;
+	}
+
+	/**
+	 * 构造函数
+	 */
+	public SLF4JImpl(String loggerName) {
+		this.log = (LocationAwareLogger) LoggerFactory.getLogger(loggerName);
+	}
+
+	@Override
+	public boolean isDebugEnabled() {
+		return log.isDebugEnabled();
+	}
+
+	@Override
+	public void debug(String msg) {
+		log.log(null, callerFQCN, LocationAwareLogger.DEBUG_INT, msg, null, null);
+	}
+
+	@Override
+	public void debug(String msg, Throwable e) {
+		log.log(null, callerFQCN, LocationAwareLogger.ERROR_INT, msg, null, e);
+	}
+
+	@Override
+	public boolean isInfoEnabled() {
+		return log.isInfoEnabled();
+	}
+
+	@Override
+	public void info(String msg) {
+		log.log(null, callerFQCN, LocationAwareLogger.INFO_INT, msg, null, null);
+	}
+
+	@Override
+	public void info(String msg, Throwable t) {
+		log.log(null, callerFQCN, LocationAwareLogger.INFO_INT, msg, null, t);
+	}
+
+	@Override
+	public boolean isWarnEnabled() {
+		return log.isWarnEnabled();
+	}
+
+	@Override
+	public void warn(String msg) {
+		log.log(null, callerFQCN, LocationAwareLogger.WARN_INT, msg, null, null);
+	}
+
+	@Override
+	public void warn(String msg, Throwable t) {
+		log.log(null, callerFQCN, LocationAwareLogger.WARN_INT, msg, null, t);
+	}
+
+	@Override
+	public boolean isErrorEnabled() {
+		return log.isErrorEnabled();
+	}
+
+	@Override
+	public void error(String msg) {
+		log.log(null, callerFQCN, LocationAwareLogger.ERROR_INT, msg, null, null);
+	}
+
+	@Override
+	public void error(String msg, Throwable t) {
+		log.log(null, callerFQCN, LocationAwareLogger.ERROR_INT, msg, null, t);
+	}
+
+}

+ 137 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/AES.java

@@ -0,0 +1,137 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.security.GeneralSecurityException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.lang3.StringUtils;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+
+/**
+ * AES算法<br>
+ * CBC(密码块链)模式<br>
+ * 警告:每次加解密都要创建一个{@link AES}
+ *
+ * @author WANGWEI
+ * @date 2018年11月21日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class AES {
+
+	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(AES.class);
+
+	private static final String KEY_ALGORITHM = "AES";
+
+	private static final String CIPHER_ALGORITHM_CBC = "AES/CBC/PKCS5Padding";
+
+	private static final String DEFAULT_KEY = "7&*5690)%#6#)7!-9*52@*#^&*$%";
+
+	private Cipher encryptCipher = null;
+
+	private Cipher decryptCipher = null;
+
+	public static void main(String[] args) {
+		String s = "";
+		System.out.println(new AES().decrypt(s));
+	}
+
+	/**
+	 * 默认构造方法
+	 * 
+	 */
+	public AES() {
+		this(DEFAULT_KEY);
+	}
+
+	/**
+	 * 指定密钥构造方法
+	 * 
+	 * @param keyStr
+	 *            指定的密钥
+	 */
+	public AES(String keyStr) {
+		try {
+			SecretKey secretKey = getSecretKey(keyStr);
+			keyStr = StringUtils.rightPad(keyStr, 30, (char) 48);
+			final byte[] iv = keyStr.substring(2, 18).getBytes();
+
+			encryptCipher = Cipher.getInstance(CIPHER_ALGORITHM_CBC);
+			decryptCipher = Cipher.getInstance(CIPHER_ALGORITHM_CBC);
+
+			encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
+			decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
+		} catch (GeneralSecurityException e) {
+			LOG.error("fail to new instance of AES.", e);
+		}
+	}
+
+	/**
+	 * 加密
+	 * 
+	 * @param bytes
+	 * @return
+	 */
+	public String encrypt(byte[] bytes) {
+		try {
+			byte[] enc = encryptCipher.doFinal(bytes);
+			return Hex.encodeHexString(enc);
+		} catch (IllegalBlockSizeException e) {
+			throw new ExamCloudRuntimeException(e);
+		} catch (BadPaddingException e) {
+			throw new ExamCloudRuntimeException(e);
+		}
+	}
+
+	/**
+	 * 加密
+	 * 
+	 * @param str
+	 * @return
+	 */
+	public String encrypt(String str) {
+		return encrypt(str.getBytes());
+	}
+
+	/**
+	 * 解密
+	 * 
+	 * @param str
+	 * @return
+	 */
+	public String decrypt(String str) {
+		try {
+			byte[] dec = decryptCipher.doFinal(Hex.decodeHex(str));
+			return new String(dec);
+		} catch (IllegalBlockSizeException e) {
+			throw new ExamCloudRuntimeException(e);
+		} catch (BadPaddingException e) {
+			throw new ExamCloudRuntimeException(e);
+		} catch (DecoderException e) {
+			throw new ExamCloudRuntimeException(e);
+		}
+	}
+
+	/**
+	 * @param key
+	 * @return
+	 */
+	private SecretKey getSecretKey(String key) {
+		byte[] bytes = key.getBytes();
+		byte[] target = new byte[16];
+
+		int length = bytes.length;
+		System.arraycopy(bytes, 0, target, 0, length < 16 ? length : 16);
+		return new SecretKeySpec(target, KEY_ALGORITHM);
+	}
+
+}

+ 89 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/AsciiUtil.java

@@ -0,0 +1,89 @@
+package cn.com.qmth.examcloud.commons.util;
+
+/**
+ * Ascii转换
+ *
+ * @author WANGWEI
+ * @date 2018年12月3日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class AsciiUtil {
+
+	private static String PREFIX = "\\u";
+
+	/**
+	 * native to ASCII
+	 *
+	 * @author WANGWEI
+	 * @param str
+	 * @return
+	 */
+	public static String native2Ascii(String str) {
+		char[] chars = str.toCharArray();
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < chars.length; i++) {
+			sb.append(char2Ascii(chars[i]));
+		}
+		return sb.toString();
+	}
+
+	private static String char2Ascii(char c) {
+		if (c > 255) {
+			StringBuilder sb = new StringBuilder();
+			sb.append(PREFIX);
+			int code = (c >> 8);
+			String tmp = Integer.toHexString(code);
+			if (tmp.length() == 1) {
+				sb.append("0");
+			}
+			sb.append(tmp);
+			code = (c & 0xFF);
+			tmp = Integer.toHexString(code);
+			if (tmp.length() == 1) {
+				sb.append("0");
+			}
+			sb.append(tmp);
+			return sb.toString();
+		} else {
+			return Character.toString(c);
+		}
+	}
+
+	/**
+	 * ASCII to native
+	 *
+	 * @author WANGWEI
+	 * @param str
+	 * @return
+	 */
+	public static String ascii2Native(String str) {
+		StringBuilder sb = new StringBuilder();
+		int begin = 0;
+		int index = str.indexOf(PREFIX);
+		while (index != -1) {
+			sb.append(str.substring(begin, index));
+			sb.append(ascii2Char(str.substring(index, index + 6)));
+			begin = index + 6;
+			index = str.indexOf(PREFIX, begin);
+		}
+		sb.append(str.substring(begin));
+		return sb.toString();
+	}
+
+	private static char ascii2Char(String str) {
+		if (str.length() != 6) {
+			throw new IllegalArgumentException(
+					"Ascii string of a native character must be 6 character.");
+		}
+		if (!PREFIX.equals(str.substring(0, 2))) {
+			throw new IllegalArgumentException(
+					"Ascii string of a native character must start with \"\\u\".");
+		}
+		String tmp = str.substring(2, 4);
+		int code = Integer.parseInt(tmp, 16) << 8;
+		tmp = str.substring(4, 6);
+		code += Integer.parseInt(tmp, 16);
+		return (char) code;
+	}
+
+}

+ 54 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/BooleanUtil.java

@@ -0,0 +1,54 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import org.apache.commons.lang3.BooleanUtils;
+
+/**
+ * boolean 工具
+ *
+ * @author WANGWEI
+ * @date 2019年10月21日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class BooleanUtil {
+
+	/**
+	 * 统计true的数量
+	 *
+	 * @author WANGWEI
+	 * @param values
+	 * @return
+	 */
+	public static int countTrue(boolean... values) {
+		if (null == values) {
+			return 0;
+		}
+		int count = 0;
+		for (boolean b : values) {
+			if (BooleanUtils.isTrue(b)) {
+				count++;
+			}
+		}
+		return count;
+	}
+
+	/**
+	 * 统计false的数量
+	 *
+	 * @author WANGWEI
+	 * @param values
+	 * @return
+	 */
+	public static int countFalse(boolean... values) {
+		if (null == values) {
+			return 0;
+		}
+		int count = 0;
+		for (boolean b : values) {
+			if (BooleanUtils.isFalse(b)) {
+				count++;
+			}
+		}
+		return count;
+	}
+
+}

+ 116 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ByteUtil.java

@@ -0,0 +1,116 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+/**
+ * 字节转换工具
+ *
+ * @author WANGWEI
+ * @date 2018年4月27日
+ */
+public class ByteUtil {
+	public final static short UNSIGNED_MAX_VALUE = (Byte.MAX_VALUE * 2) + 1;
+
+	private ByteUtil() {
+	}
+
+	public static int unsignedPromote(byte b) {
+		return b & 0xff;
+	}
+
+	public static String toHexAscii(byte b) {
+		StringWriter sw = new StringWriter(2);
+		addHexAscii(b, sw);
+		return sw.toString();
+	}
+
+	public static String toLowercaseHexAscii(byte b) {
+		StringWriter sw = new StringWriter(2);
+		addLowercaseHexAscii(b, sw);
+		return sw.toString();
+	}
+
+	public static String toHexAscii(byte[] bytes) {
+		int len = bytes.length;
+		StringWriter sw = new StringWriter(len * 2);
+		for (int i = 0; i < len; ++i)
+			addHexAscii(bytes[i], sw);
+		return sw.toString();
+	}
+
+	public static String toLowercaseHexAscii(byte[] bytes) {
+		int len = bytes.length;
+		StringWriter sw = new StringWriter(len * 2);
+		for (int i = 0; i < len; ++i)
+			addLowercaseHexAscii(bytes[i], sw);
+		return sw.toString();
+	}
+
+	public static byte[] fromHexAscii(String s) throws NumberFormatException {
+		try {
+			int len = s.length();
+			if ((len % 2) != 0)
+				throw new NumberFormatException("Hex ascii must be exactly two digits per byte.");
+
+			int out_len = len / 2;
+			byte[] out = new byte[out_len];
+			int i = 0;
+			StringReader sr = new StringReader(s);
+			while (i < out_len) {
+				int val = (16 * fromHexDigit(sr.read())) + fromHexDigit(sr.read());
+				out[i++] = (byte) val;
+			}
+			return out;
+		} catch (IOException e) {
+			throw new InternalError("IOException reading from StringReader?!?!");
+		}
+	}
+
+	static void addHexAscii(byte b, StringWriter sw) {
+		int ub = unsignedPromote(b);
+		int h1 = ub / 16;
+		int h2 = ub % 16;
+		sw.write(toHexDigit(h1));
+		sw.write(toHexDigit(h2));
+	}
+
+	static void addLowercaseHexAscii(byte b, StringWriter sw) {
+		int ub = unsignedPromote(b);
+		int h1 = ub / 16;
+		int h2 = ub % 16;
+		sw.write(toLowercaseHexDigit(h1));
+		sw.write(toLowercaseHexDigit(h2));
+	}
+
+	private static int fromHexDigit(int c) throws NumberFormatException {
+		if (c >= 0x30 && c < 0x3A)
+			return c - 0x30;
+		else if (c >= 0x41 && c < 0x47)
+			return c - 0x37;
+		else if (c >= 0x61 && c < 0x67)
+			return c - 0x57;
+		else
+			throw new NumberFormatException('\'' + c + "' is not a valid hexadecimal digit.");
+	}
+
+	private static char toHexDigit(int h) {
+		char out;
+		if (h <= 9)
+			out = (char) (h + 0x30);
+		else
+			out = (char) (h + 0x37);
+		return out;
+	}
+
+	private static char toLowercaseHexDigit(int h) {
+		char out;
+		if (h <= 9)
+			out = (char) (h + 0x30);
+		else
+			out = (char) (h + 0x57);
+		return out;
+	}
+
+}

+ 41 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/Calculator.java

@@ -0,0 +1,41 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.math.BigDecimal;
+
+/**
+ * 计算器
+ *
+ * @author WANGWEI
+ * @date 2019年7月30日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class Calculator {
+
+	public static double add(double v1, double v2) {
+		BigDecimal b1 = new BigDecimal(Double.toString(v1));
+		BigDecimal b2 = new BigDecimal(Double.toString(v2));
+		return b1.add(b2).doubleValue();
+
+	}
+
+	public static double subtract(double v1, double v2) {
+		BigDecimal b1 = new BigDecimal(Double.toString(v1));
+		BigDecimal b2 = new BigDecimal(Double.toString(v2));
+		return b1.subtract(b2).doubleValue();
+
+	}
+
+	public static double multiply(double v1, double v2) {
+		BigDecimal b1 = new BigDecimal(v1);
+		BigDecimal b2 = new BigDecimal(v2);
+		return b1.multiply(b2).doubleValue();
+
+	}
+
+	public static double divide(double v1, double v2, int len) {
+		BigDecimal b1 = new BigDecimal(v1);
+		BigDecimal b2 = new BigDecimal(v2);
+		return b1.divide(b2, len, BigDecimal.ROUND_HALF_UP).doubleValue();
+	}
+
+}

+ 12 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/CollectionUtil.java

@@ -0,0 +1,12 @@
+package cn.com.qmth.examcloud.commons.util;
+
+/**
+ * 集合工具
+ *
+ * @author WANGWEI
+ * @date 2019年5月15日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class CollectionUtil {
+
+}

+ 271 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/DBUtil.java

@@ -0,0 +1,271 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.lang3.StringUtils;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ */
+public class DBUtil {
+
+	/**
+	 * 属性注释
+	 */
+	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(DBUtil.class);
+
+	/**
+	 * 属性注释
+	 */
+	private static final Object LOCK = new Object();
+
+	/**
+	 * 属性注释
+	 */
+	private static Map<String, DataSource> dataSources = new ConcurrentHashMap<String, DataSource>();
+
+	/**
+	 * 初始化数据源(C3P0)
+	 *
+	 * @author WANGWEI
+	 * @param dataSourceName
+	 * @return
+	 */
+	public static DataSource initDataSource(final String dataSourceName) {
+		Class<?> c = null;
+		try {
+			c = Class.forName("com.alibaba.druid.pool.DruidDataSource");
+		} catch (ClassNotFoundException e) {
+			// ignore
+		}
+
+		if (null == c) {
+			try {
+				c = Class.forName("com.mchange.v2.c3p0.ComboPooledDataSource");
+			} catch (ClassNotFoundException e) {
+				// ignore
+			}
+		}
+
+		if (null == c) {
+			throw new ExamCloudRuntimeException("no class about dataSource implementation.");
+		}
+
+		@SuppressWarnings("unchecked")
+		Class<? extends DataSource> z = (Class<? extends DataSource>) c;
+
+		return initDataSource(z, dataSourceName);
+	}
+
+	/**
+	 * 初始化数据源
+	 *
+	 * @author WANGWEI
+	 * 
+	 * @param implementation
+	 *            数据源实现类
+	 * @param dataSourceName
+	 *            数据源名称
+	 * @return
+	 */
+	public static DataSource initDataSource(Class<? extends DataSource> implementation,
+			final String dataSourceName) {
+
+		String dbDriver = PropertiesUtil.getString(dataSourceName + ".driver");
+		String url = PropertiesUtil.getString(dataSourceName + ".url");
+		String userName = PropertiesUtil.getString(dataSourceName + ".username");
+		String password = PropertiesUtil.getString(dataSourceName + ".password");
+		int initialPoolSize = PropertiesUtil.getInt(dataSourceName + ".initialPoolSize", 1);
+		int minPoolSize = PropertiesUtil.getInt(dataSourceName + ".minPoolSize", 1);
+		int maxPoolSize = PropertiesUtil.getInt(dataSourceName + ".maxPoolSize", 10);
+
+		DataSource dataSource = null;
+		String simpleName = implementation.getSimpleName();
+		try {
+			// C3P0
+			if (simpleName.equals("ComboPooledDataSource")) {
+				DataSource c3po = implementation.newInstance();
+				dataSource = c3po;
+				implementation.getMethod("setDriverClass", String.class).invoke(c3po, dbDriver);
+				implementation.getMethod("setJdbcUrl", String.class).invoke(c3po, url);
+				implementation.getMethod("setUser", String.class).invoke(c3po, userName);
+				implementation.getMethod("setPassword", String.class).invoke(c3po, password);
+				implementation.getMethod("setInitialPoolSize", int.class).invoke(c3po,
+						initialPoolSize);
+				implementation.getMethod("setMinPoolSize", int.class).invoke(c3po, minPoolSize);
+				implementation.getMethod("setMaxPoolSize", int.class).invoke(c3po, maxPoolSize);
+				implementation.getMethod("setPreferredTestQuery", String.class).invoke(c3po,
+						"select 1 from dual");
+			}
+			// druid
+			else if (simpleName.equals("DruidDataSource")) {
+				DataSource druid = implementation.newInstance();
+				dataSource = druid;
+				implementation.getMethod("setDriverClassName", String.class).invoke(druid,
+						dbDriver);
+				implementation.getMethod("setUrl", String.class).invoke(druid, url);
+				implementation.getMethod("setUsername", String.class).invoke(druid, userName);
+				implementation.getMethod("setPassword", String.class).invoke(druid, password);
+				implementation.getMethod("setInitialSize", int.class).invoke(druid,
+						initialPoolSize);
+				implementation.getMethod("setMaxActive", int.class).invoke(druid, maxPoolSize);
+
+				implementation.getMethod("setValidationQuery", String.class).invoke(druid,
+						"select 1 from dual");
+			}
+
+		} catch (Exception e) {
+			LOG.error("[JDBC] Fail to init dataSource. dataSourceName=" + dataSourceName, e);
+			throw new RuntimeException(e);
+		}
+
+		Connection conn = null;
+		try {
+			conn = dataSource.getConnection();
+		} catch (Exception e) {
+			LOG.error("[JDBC] Fail to get connection. dataSourceName=" + dataSourceName, e);
+			throw new RuntimeException(e);
+		} finally {
+			close(conn);
+		}
+
+		return dataSource;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param dataSourceName
+	 * @return
+	 */
+	public static DataSource getDataSource(String dataSourceName) {
+		DataSource dataSource = dataSources.get(dataSourceName);
+		if (null == dataSource) {
+			synchronized (LOCK) {
+				dataSource = dataSources.get(dataSourceName);
+				if (null == dataSource) {
+					dataSource = initDataSource(dataSourceName);
+					dataSources.put(dataSourceName, dataSource);
+				}
+			}
+		}
+		return dataSource;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param dataSourceName
+	 * @return
+	 */
+	public static Connection getConnection(String dataSourceName) {
+		Connection conn = null;
+		try {
+			conn = getDataSource(dataSourceName).getConnection();
+		} catch (SQLException e) {
+			LOG.error("[JDBC] Fail to get connection.", e);
+		}
+
+		return conn;
+	}
+
+	/**
+	 * @param conn
+	 * @param stmt
+	 * @param res
+	 */
+	public static void close(Connection conn, Statement statement, ResultSet rs) {
+		close(rs);
+		close(conn, statement);
+	}
+
+	/**
+	 * @param conn
+	 * @param statement
+	 */
+	public static void close(Connection conn, Statement statement) {
+		close(statement);
+		close(conn);
+	}
+
+	/**
+	 * @param stmt
+	 * @param res
+	 */
+	public static void close(Statement statement, ResultSet rs) {
+		close(statement);
+		close(rs);
+	}
+
+	/**
+	 * @param conn
+	 */
+	public static void close(Connection conn) {
+		if (conn != null) {
+			try {
+				conn.close();
+			} catch (SQLException e) {
+				LOG.error("[JDBC] Fail to close Connection.", e);
+			}
+		}
+	}
+
+	/**
+	 * @param statement
+	 */
+	public static void close(Statement statement) {
+		if (statement != null) {
+			try {
+				statement.close();
+			} catch (SQLException e) {
+				LOG.error("[JDBC] Fail to close Statement.", e);
+			}
+		}
+	}
+
+	/**
+	 * @param rs
+	 */
+	public static void close(ResultSet rs) {
+		if (rs != null) {
+			try {
+				rs.close();
+			} catch (SQLException e) {
+				LOG.error("[JDBC] Fail to close ResultSet.", e);
+			}
+		}
+	}
+
+	/**
+	 * 转换为数据库模糊查询匹配模式
+	 *
+	 * @author WANGWEI
+	 * @param column
+	 * @return
+	 */
+	public static String toSqlSearchPattern(String column) {
+		if (StringUtils.isBlank(column)) {
+			return "%";
+		} else {
+			column = column.trim().replaceAll("\\s", "%");
+			column = "%" + column + "%";
+			return column;
+		}
+	}
+
+}

+ 210 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/DateUtil.java

@@ -0,0 +1,210 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.math.BigDecimal;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+
+/**
+ * 日期工具
+ * 
+ * @author WANGWEI
+ */
+public class DateUtil {
+
+	/**
+	 * patterns describing the date and time format
+	 *
+	 * @author WANGWEI
+	 */
+	public interface DatePatterns {
+
+		public static final String DEFAULT = "yyyyMMddHHmmss";
+
+		public static final String YYYY = "yyyy";
+
+		public static final String YYYYMM = "yyyyMM";
+
+		public static final String YYYYMMDD = "yyyyMMdd";
+
+		public static final String YYYYMMDDHH = "yyyyMMddHH";
+
+		public static final String YYYYMMDDHHMM = "yyyyMMddHHmm";
+
+		public static final String CHINA_DEFAULT = "yyyy-MM-dd HH:mm:ss";
+
+		public static final String YYYY_MM = "yyyy-MM";
+
+		public static final String YYYY_MM_DD = "yyyy-MM-dd";
+
+		public static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm";
+
+		public static final String YYYY_MM_DD_HH_MM_SS_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
+	}
+
+	/**
+	 * get now date.
+	 * 
+	 * @param pattern
+	 * @return
+	 */
+	public static String now(String pattern) {
+		return format(new Date(), pattern);
+	}
+
+	/**
+	 * get now china date.
+	 * 
+	 * @return
+	 */
+	public static String chinaNow() {
+		return format(new Date(), DatePatterns.CHINA_DEFAULT);
+	}
+
+	/**
+	 * format date.
+	 * 
+	 * @param date
+	 * @param pattern
+	 * @return
+	 */
+	public static String format(Date date, String pattern) {
+		try {
+			SimpleDateFormat df = new SimpleDateFormat(pattern);
+			return df.format(date);
+		} catch (Exception e) {
+			throw new ExamCloudRuntimeException(e);
+		}
+	}
+
+	/**
+	 * format now date.
+	 *
+	 * @author WANGWEI
+	 * @param pattern
+	 * @return
+	 */
+	public static String formatNow(String pattern) {
+		try {
+			SimpleDateFormat df = new SimpleDateFormat(pattern);
+			return df.format(new Date());
+		} catch (Exception e) {
+			throw new ExamCloudRuntimeException(e);
+		}
+	}
+
+	/**
+	 * parse date.
+	 * 
+	 * @param source
+	 * @param pattern
+	 * @return
+	 */
+	public static Date parse(String source, String pattern) {
+		SimpleDateFormat df = new SimpleDateFormat(pattern);
+		try {
+			return df.parse(source);
+		} catch (ParseException e) {
+			throw new ExamCloudRuntimeException(e);
+		}
+	}
+
+	/**
+	 * parse date randomly.
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static Date parseRandomly(String s) {
+		if (s.matches("\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}")) {
+			return parse(s, "yyyy/MM/dd HH:mm:ss");
+		} else if (s.matches("\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{1,2}")) {
+			return parse(s, "yyyy/MM/dd HH:mm");
+		} else if (s.matches("\\d{4}/\\d{1,2}/\\d{1,2}")) {
+			return parse(s, "yyyy/MM/dd");
+		} else if (s.matches("\\d{4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}")) {
+			return parse(s, "yyyy-MM-dd HH:mm:ss");
+		} else if (s.matches("\\d{4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}")) {
+			return parse(s, "yyyy-MM-dd HH:mm");
+		} else if (s.matches("\\d{4}-\\d{1,2}-\\d{1,2}")) {
+			return parse(s, "yyyy-MM-dd");
+		} else {
+			throw new ExamCloudRuntimeException("unsupported date string.");
+		}
+	}
+
+	/**
+	 * 解析excel日期
+	 *
+	 * @author WANGWEI
+	 * @param number
+	 * @return
+	 */
+	public static Date parseExcel(String number) {
+
+		BigDecimal bd = new BigDecimal(number);
+		int days = bd.intValue();
+		int mills = (int) Math.round(bd.subtract(new BigDecimal(days)).doubleValue() * 24 * 3600);
+
+		Calendar c = Calendar.getInstance();
+		c.set(1900, 0, 1);
+		c.add(Calendar.DATE, days - 2);
+		int hour = mills / 3600;
+		int minute = (mills - hour * 3600) / 60;
+		int second = mills - hour * 3600 - minute * 60;
+		c.set(Calendar.HOUR_OF_DAY, hour);
+		c.set(Calendar.MINUTE, minute);
+		c.set(Calendar.SECOND, second);
+
+		Date date = c.getTime();
+
+		return date;
+	}
+
+	/**
+	 * 是否同一天
+	 *
+	 * @author WANGWEI
+	 * @param date1
+	 * @param date2
+	 * @return
+	 */
+	public static boolean isSameDay(Date date1, Date date2) {
+		if (null == date1) {
+			throw new ExamCloudRuntimeException("first argument must not be null");
+		}
+		if (null == date2) {
+			throw new ExamCloudRuntimeException("second argument must not be null");
+		}
+
+		Calendar cal1 = Calendar.getInstance();
+		cal1.setTime(date1);
+		Calendar cal2 = Calendar.getInstance();
+		cal2.setTime(date2);
+		return isSameDay(cal1, cal2);
+	}
+
+	/**
+	 * 是否同一天
+	 *
+	 * @author WANGWEI
+	 * @param calendar1
+	 * @param calendar2
+	 * @return
+	 */
+	public static boolean isSameDay(Calendar calendar1, Calendar calendar2) {
+		if (null == calendar1) {
+			throw new ExamCloudRuntimeException("first argument must not be null");
+		}
+		if (null == calendar2) {
+			throw new ExamCloudRuntimeException("second argument must not be null");
+		}
+		return calendar1.get(0) == calendar2.get(0) && calendar1.get(1) == calendar2.get(1)
+				&& calendar1.get(6) == calendar2.get(6);
+	}
+
+}

+ 36 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/FileUtil.java

@@ -0,0 +1,36 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * 文件工具
+ *
+ * @author WANGWEI
+ * @date 2019年12月3日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class FileUtil {
+
+	/**
+	 * 排序
+	 *
+	 * @author WANGWEI
+	 * @param files
+	 */
+	public static void sortByLastUpdateTime(File... files) {
+		Arrays.sort(files, new Comparator<File>() {
+			public int compare(File f1, File f2) {
+				long diff = f1.lastModified() - f2.lastModified();
+				if (diff > 0)
+					return 1;
+				else if (diff == 0)
+					return 0;
+				else
+					return -1;
+			}
+		});
+	}
+
+}

+ 63 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/FreeMarkerUtil.java

@@ -0,0 +1,63 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Locale;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+
+/**
+ * FreeMarker 工具
+ *
+ * @author WANGWEI
+ * @date 2018年11月21日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class FreeMarkerUtil {
+
+	private static final String ENCODING = "UTF-8";
+
+	private static Configuration configuration;
+
+	static {
+		configuration = new Configuration(Configuration.VERSION_2_3_23);
+		configuration.setEncoding(Locale.US, ENCODING);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param template
+	 * @param dataModel
+	 * @return
+	 */
+	public static String process(String template, Object dataModel) {
+		return process("$$$$$", template, dataModel);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param name
+	 * @param template
+	 * @param dataModel
+	 * @return
+	 */
+	public static String process(String name, String template, Object dataModel) {
+		StringWriter result = null;
+		try {
+			result = new StringWriter();
+			Template t = new Template(name, new StringReader(template), configuration);
+			t.process(dataModel, result);
+
+			return result.toString();
+		} catch (Exception e) {
+			throw new ExamCloudRuntimeException(e);
+		}
+	}
+
+}

+ 176 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/HttpClientUtil.java

@@ -0,0 +1,176 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.config.CookieSpecs;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.util.EntityUtils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+
+import cn.com.qmth.examcloud.commons.helpers.JsonHttpResponseHolder;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+
+/**
+ * httpclient
+ *
+ * @author WANGWEI
+ * @date 2019年9月17日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class HttpClientUtil {
+
+	private static final ExamCloudLog LOGGER = ExamCloudLogFactory.getLog("HTTP_CLIENT_LOGGER");
+
+	private static CloseableHttpClient httpclient;
+
+	private static RequestConfig requestConfig;
+
+	static {
+		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(60,
+				TimeUnit.SECONDS);
+		cm.setValidateAfterInactivity(1000);
+		cm.setMaxTotal(8000);
+		cm.setDefaultMaxPerRoute(200);
+
+		requestConfig = RequestConfig.custom().setConnectionRequestTimeout(500)
+				.setSocketTimeout(10000).setConnectTimeout(10000)
+				.setCookieSpec(CookieSpecs.IGNORE_COOKIES).build();
+
+		httpclient = HttpClients.custom().setConnectionManager(cm).disableAutomaticRetries()
+				.setDefaultRequestConfig(requestConfig).build();
+
+	}
+
+	public static CloseableHttpClient getHttpClient() {
+		return httpclient;
+	}
+
+	public static HttpPost buildHttpPost(String url) {
+		HttpPost httpPost = new HttpPost(url);
+		httpPost.setConfig(requestConfig);
+
+		return httpPost;
+	}
+
+	public static HttpPut buildHttpPut(String url) {
+		HttpPut httpPut = new HttpPut(url);
+		httpPut.setConfig(requestConfig);
+
+		return httpPut;
+	}
+
+	public static HttpGet buildHttpGet(String url) {
+		HttpGet httpGet = new HttpGet(url);
+		httpGet.setConfig(requestConfig);
+
+		return httpGet;
+	}
+
+	public static JsonHttpResponseHolder execute(final HttpUriRequest request) {
+
+		CloseableHttpResponse response = null;
+		JsonHttpResponseHolder responseHolder = null;
+		long s = System.currentTimeMillis();
+		try {
+
+			response = httpclient.execute(request);
+			int statusCode = response.getStatusLine().getStatusCode();
+			String entityStr = EntityUtils.toString(response.getEntity(), "UTF-8");
+			JSONObject obj = JSON.parseObject(entityStr);
+			responseHolder = new JsonHttpResponseHolder(statusCode, obj);
+
+			if (HttpStatus.SC_OK != responseHolder.getStatusCode()) {
+				LOGGER.error("[HTTP-FAIL]. statusCode=" + statusCode + "; responseEntity="
+						+ entityStr + "; uri=" + request.getURI());
+			} else {
+				if (LOGGER.isDebugEnabled()) {
+					LOGGER.debug("[HTTP-OK]. statusCode=" + statusCode + "; responseEntity="
+							+ entityStr + "; uri=" + request.getURI());
+				}
+			}
+
+		} catch (Exception e) {
+			LOGGER.error("[HTTP-ERROR]. uri=" + request.getURI(), e);
+		} finally {
+			IOUtils.closeQuietly(response);
+			LOGGER.debug("[HTTP-COST]. cost = " + (System.currentTimeMillis() - s) + " ms; uri="
+					+ request.getURI());
+		}
+		return responseHolder;
+	}
+
+	/**
+	 * 关闭流
+	 *
+	 * @author WANGWEI
+	 * @param resp
+	 */
+	public static void close(CloseableHttpResponse resp) {
+		if (null != resp) {
+			try {
+				EntityUtils.consumeQuietly(resp.getEntity());
+				resp.close();
+			} catch (Exception e) {
+				LOGGER.error("fail to close http response stream.", e);
+			}
+		}
+	}
+
+	/**
+	 * GET 请求
+	 *
+	 * @author WANGWEI
+	 * @param url
+	 * @return
+	 */
+	public static byte[] get(String url) {
+		return get(url, 1048576L);
+	}
+
+	/**
+	 * GET 请求
+	 *
+	 * @author WANGWEI
+	 * @param url
+	 * @param maxByteSize
+	 * @return
+	 */
+	public static byte[] get(String url, long maxByteSize) {
+
+		CloseableHttpClient httpclient = HttpClientBuilder.create().build();
+
+		HttpGet get = new HttpGet(url);
+		get.setConfig(RequestConfig.custom().setConnectTimeout(10000).build());
+
+		CloseableHttpResponse response = null;
+		try {
+			response = httpclient.execute(get);
+			InputStream in = response.getEntity().getContent();
+
+			byte[] byteArray = IOUtil.toLimitedByteArray(in, 1048576);
+			return byteArray;
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			close(response);
+			IOUtils.closeQuietly(httpclient);
+		}
+
+	}
+
+}

+ 115 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/HttpMethod.java

@@ -0,0 +1,115 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * HTTP method
+ *
+ * @author WANGWEI
+ * @date 2019年4月10日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class HttpMethod {
+
+	public static final HttpMethod OPTIONS = new HttpMethod("OPTIONS");
+
+	public static final HttpMethod GET = new HttpMethod("GET");
+
+	public static final HttpMethod HEAD = new HttpMethod("HEAD");
+
+	public static final HttpMethod POST = new HttpMethod("POST");
+
+	public static final HttpMethod PUT = new HttpMethod("PUT");
+
+	public static final HttpMethod PATCH = new HttpMethod("PATCH");
+
+	public static final HttpMethod DELETE = new HttpMethod("DELETE");
+
+	public static final HttpMethod TRACE = new HttpMethod("TRACE");
+
+	public static final HttpMethod CONNECT = new HttpMethod("CONNECT");
+
+	private static final Map<String, HttpMethod> METHOD_MAP = new HashMap<String, HttpMethod>();
+
+	static {
+		METHOD_MAP.put(OPTIONS.toString(), OPTIONS);
+		METHOD_MAP.put(GET.toString(), GET);
+		METHOD_MAP.put(HEAD.toString(), HEAD);
+		METHOD_MAP.put(POST.toString(), POST);
+		METHOD_MAP.put(PUT.toString(), PUT);
+		METHOD_MAP.put(PATCH.toString(), PATCH);
+		METHOD_MAP.put(DELETE.toString(), DELETE);
+		METHOD_MAP.put(TRACE.toString(), TRACE);
+		METHOD_MAP.put(CONNECT.toString(), CONNECT);
+	}
+
+	private final String name;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param name
+	 */
+	private HttpMethod(String name) {
+		if (name == null) {
+			throw new NullPointerException("name");
+		}
+
+		name = name.trim();
+		if (name.length() == 0) {
+			throw new IllegalArgumentException("empty name");
+		}
+
+		for (int i = 0; i < name.length(); i++) {
+			if (Character.isISOControl(name.charAt(i)) || Character.isWhitespace(name.charAt(i))) {
+				throw new IllegalArgumentException("invalid character in name");
+			}
+		}
+
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public static HttpMethod valueOf(String name) {
+		if (name == null) {
+			throw new NullPointerException("name");
+		}
+
+		name = name.trim();
+		if (name.length() == 0) {
+			throw new IllegalArgumentException("empty name");
+		}
+
+		HttpMethod result = METHOD_MAP.get(name);
+		if (result != null) {
+			return result;
+		} else {
+			throw new IllegalArgumentException("undefined name");
+		}
+	}
+
+	@Override
+	public int hashCode() {
+		return getName().hashCode();
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if (!(o instanceof HttpMethod)) {
+			return false;
+		}
+
+		HttpMethod that = (HttpMethod) o;
+		return getName().equals(that.getName());
+	}
+
+	@Override
+	public String toString() {
+		return getName();
+	}
+
+}

+ 105 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/IOUtil.java

@@ -0,0 +1,105 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+/**
+ * IO
+ *
+ * @author WANGWEI
+ * @date 2018年6月29日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class IOUtil {
+
+	/**
+	 * 文件转字节数组
+	 *
+	 * @author WANGWEI
+	 * @param file
+	 * @return
+	 */
+	public static byte[] toByteArray(File file) {
+		FileInputStream is = null;
+		try {
+			is = new FileInputStream(file);
+			byte[] byteArray = IOUtils.toByteArray(is);
+			return byteArray;
+		} catch (FileNotFoundException e) {
+			throw new RuntimeException(e);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(is);
+		}
+	}
+
+	/**
+	 * 字节数组转文件
+	 *
+	 * @author WANGWEI
+	 * @param bfile
+	 * @param filePath
+	 */
+	public static void toFile(byte[] bfile, String filePath) {
+		BufferedOutputStream bos = null;
+		FileOutputStream fos = null;
+		try {
+			File file = new File(filePath);
+			FileUtils.forceMkdir(file.getParentFile());
+			fos = new FileOutputStream(file);
+			bos = new BufferedOutputStream(fos);
+			bos.write(bfile);
+		} catch (Exception e) {
+			throw new RuntimeException("", e);
+		} finally {
+			IOUtils.closeQuietly(bos);
+			IOUtils.closeQuietly(fos);
+		}
+	}
+
+	/**
+	 * 输入流转换为限制大小的字节
+	 *
+	 * @author WANGWEI
+	 * @param in
+	 * @param maxByteSize
+	 * @return
+	 */
+	public static byte[] toLimitedByteArray(InputStream in, long maxByteSize) {
+		ByteArrayOutputStream out = null;
+		int bufferSize = 1024 * 4;
+		int totalSize = 0;
+		byte[] buffer = new byte[bufferSize];
+		try {
+			out = new ByteArrayOutputStream();
+			int n = 0;
+			while ((n = in.read(buffer)) != -1) {
+				out.write(buffer, 0, n);
+				totalSize += n;
+
+				if (maxByteSize < totalSize) {
+					throw new RuntimeException("input stream exceed the limit.");
+				}
+			}
+			return out.toByteArray();
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		} catch (RuntimeException e) {
+			throw e;
+		} finally {
+			IOUtils.closeQuietly(out);
+			IOUtils.closeQuietly(in);
+		}
+	}
+
+}

+ 171 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/JsonUtil.java

@@ -0,0 +1,171 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import com.google.common.collect.Maps;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+import cn.com.qmth.examcloud.commons.util.DateUtil.DatePatterns;
+
+/**
+ * json tool
+ *
+ * @author WANGWEI
+ * @param <T>
+ * @date 2019年1月16日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class JsonUtil {
+
+	/**
+	 * to json
+	 *
+	 * @author WANGWEI
+	 * @param obj
+	 * @return
+	 */
+	public static String toJson(Object obj) {
+		GsonBuilder builder = new GsonBuilder().disableHtmlEscaping().serializeNulls()
+				.setDateFormat(DatePatterns.CHINA_DEFAULT);
+		return builder.create().toJson(obj);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param obj
+	 * @param excludeFields
+	 * @return
+	 */
+	public static String toJson(Object obj, String... excludeFields) {
+		GsonBuilder builder = new GsonBuilder().disableHtmlEscaping().serializeNulls()
+				.setDateFormat(DatePatterns.CHINA_DEFAULT);
+
+		builder.setExclusionStrategies(new ExclusionStrategy() {
+			@Override
+			public boolean shouldSkipField(FieldAttributes f) {
+				for (String field : excludeFields) {
+					if (f.getName().matches(field)) {
+						return true;
+					}
+				}
+
+				return false;
+			}
+
+			@Override
+			public boolean shouldSkipClass(Class<?> clazz) {
+				return false;
+			}
+		});
+
+		return builder.create().toJson(obj);
+	}
+
+	/**
+	 * to pretty json
+	 *
+	 * @author WANGWEI
+	 * @param obj
+	 * @return
+	 */
+	public static String toPrettyJson(Object obj) {
+		GsonBuilder builder = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting()
+				.serializeNulls().setDateFormat(DatePatterns.CHINA_DEFAULT);
+		return builder.create().toJson(obj);
+	}
+
+	/**
+	 * 格式化json
+	 *
+	 * @author WANGWEI
+	 * @param json
+	 * @return
+	 */
+	public static String format(String json) {
+		if (null == json) {
+			return json;
+		}
+		JsonElement jsonEl = JsonParser.parseString(json);
+		return toPrettyJson(jsonEl);
+	}
+
+	/**
+	 * json转对象
+	 *
+	 * @author WANGWEI
+	 * @param json
+	 * @param c
+	 * @return
+	 */
+	public static <T> T fromJson(String json, Class<T> c) {
+		Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting()
+				.setDateFormat(DatePatterns.CHINA_DEFAULT).create();
+		return gson.fromJson(json, c);
+	}
+
+	/**
+	 * json数组转list
+	 *
+	 * @author WANGWEI
+	 * @param json
+	 * @param elementType
+	 * @return
+	 */
+	public static <T> List<T> fromJsonArray(String json, Class<T[]> elementType) {
+		Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting()
+				.setDateFormat(DatePatterns.CHINA_DEFAULT).create();
+		T[] array = gson.fromJson(json, elementType);
+		return Arrays.asList(array);
+	}
+
+	/**
+	 * simple json to map
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static Map<String, String> json2Map(String s) {
+		JsonElement jsonElement = JsonParser.parseString(s);
+		JsonObject jsonObject = jsonElement.getAsJsonObject();
+		Set<Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
+
+		Map<String, String> map = Maps.newHashMap();
+		for (Entry<String, JsonElement> entry : entrySet) {
+			JsonElement e = entry.getValue();
+			if (!e.isJsonNull()) {
+				String v = e.getAsString();
+				map.put(entry.getKey(), v);
+			}
+		}
+		return map;
+	}
+
+	/**
+	 * json转对象
+	 *
+	 * @author WANGWEI
+	 * @param json
+	 * @param type
+	 * @return
+	 */
+	public static <T> T fromJson(String json, Type type) {
+		Gson gson = new GsonBuilder().disableHtmlEscaping()
+				.setDateFormat(DatePatterns.CHINA_DEFAULT).create();
+		return gson.fromJson(json, type);
+	}
+
+}

+ 59 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/MD5.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.IOUtils;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ */
+public class MD5 {
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param str
+	 * @return
+	 */
+	public static String encrypt32(String str) {
+		return DigestUtils.md5Hex(str);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param str
+	 * @return
+	 */
+	public static String encrypt16(String str) {
+		return DigestUtils.md5Hex(str).substring(8, 24);
+	}
+
+	/**
+	 * 文件md5
+	 *
+	 * @author WANGWEI
+	 * @param file
+	 * @return
+	 */
+	public static String md5Hex(File file) {
+		FileInputStream in = null;
+		try {
+			in = new FileInputStream(file);
+			return DigestUtils.md5Hex(in);
+		} catch (Exception e) {
+			throw new ExamCloudRuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(in);
+		}
+	}
+
+}

+ 46 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/MapUtil.java

@@ -0,0 +1,46 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * map 工具
+ *
+ * @author WANGWEI
+ * @date 2019年5月15日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class MapUtil {
+
+	/**
+	 * map按key排序
+	 *
+	 * @author WANGWEI
+	 * @param map
+	 * @param asc
+	 *            升序
+	 * @return
+	 */
+	public static <T> Map<String, T> sortMapByKey(Map<String, T> map, boolean asc) {
+		if (map == null) {
+			return null;
+		}
+		Map<String, T> sortMap = new TreeMap<String, T>(new Comparator<String>() {
+
+			@Override
+			public int compare(String o1, String o2) {
+
+				if (asc) {
+					return ((String) o1).compareTo((String) o2);
+				} else {
+					return ((String) o2).compareTo((String) o1);
+				}
+			}
+		});
+
+		sortMap.putAll(map);
+		return sortMap;
+	}
+
+}

+ 310 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/OKHttpUtil.java

@@ -0,0 +1,310 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.collections.CollectionUtils;
+
+import cn.com.qmth.examcloud.commons.helpers.FormFilePart;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import okhttp3.FormBody;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Request.Builder;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+/**
+ * OKHttp
+ *
+ * @author WANGWEI
+ * @date 2018年9月6日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class OKHttpUtil {
+
+	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(OKHttpUtil.class);
+
+	public static final class MediaTypes {
+
+		public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
+	}
+
+	/**
+	 * 请求体构建器
+	 *
+	 * @author WANGWEI
+	 * @date 2019年4月10日
+	 * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+	 */
+	public static interface RequestBodyBuilder {
+		RequestBody build();
+	}
+
+	/**
+	 * json请求体构建器
+	 *
+	 * @author WANGWEI
+	 * @date 2019年4月10日
+	 * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+	 */
+	public static final class JsonBodyBuilder implements RequestBodyBuilder {
+
+		private String json;
+
+		public JsonBodyBuilder(String json) {
+			super();
+			this.json = json;
+		}
+
+		@Override
+		public RequestBody build() {
+			return RequestBody.create(MediaTypes.JSON, json);
+		}
+
+		@Override
+		public String toString() {
+			return json;
+		}
+	}
+
+	private static OkHttpClient okHttpClient;
+
+	static {
+		okHttpClient = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS)
+				.readTimeout(20, TimeUnit.SECONDS).build();
+	}
+
+	public static OkHttpClient getOkHttpClient() {
+		return okHttpClient;
+	}
+
+	/**
+	 * 发送请求 (带json请求体)
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @param jsonBody
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers,
+			String jsonBody) {
+		return call(httpMethod, url, headers, new JsonBodyBuilder(jsonBody));
+	}
+
+	/**
+	 * 发送请求 (带请求体)
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @param requestBodyBuilder
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers,
+			RequestBodyBuilder requestBodyBuilder) {
+
+		LOG.info("[okhttp3] new call: " + httpMethod + " " + url);
+		LOG.info("[okhttp3] headers: " + JsonUtil.toJson(headers));
+		LOG.info("[okhttp3] body: " + requestBodyBuilder);
+
+		Builder builder = null;
+		if (httpMethod.equals(HttpMethod.GET)) {
+			builder = new Request.Builder().url(url).get();
+		} else if (httpMethod.equals(HttpMethod.POST)) {
+			builder = new Request.Builder().url(url).post(requestBodyBuilder.build());
+		} else if (httpMethod.equals(HttpMethod.PUT)) {
+			builder = new Request.Builder().url(url).put(requestBodyBuilder.build());
+		} else if (httpMethod.equals(HttpMethod.DELETE)) {
+			builder = new Request.Builder().url(url).delete(requestBodyBuilder.build());
+		}
+
+		if (null != headers && 0 != headers.size()) {
+			for (Entry<String, String> entry : headers.entrySet()) {
+				builder.addHeader(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Request request = builder.build();
+
+		Response response = null;
+		try {
+			response = okHttpClient.newCall(request).execute();
+			return response;
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 发送请求
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url) {
+		return call(httpMethod, url, null);
+	}
+
+	/**
+	 * 发送请求
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers) {
+
+		LOG.info("[okhttp3] new call: " + httpMethod + " " + url);
+		LOG.info("[okhttp3] headers: " + JsonUtil.toJson(headers));
+
+		Builder builder = null;
+		if (httpMethod.equals(HttpMethod.GET)) {
+			builder = new Request.Builder().url(url).get();
+		} else if (httpMethod.equals(HttpMethod.POST)) {
+			builder = new Request.Builder().url(url).post(new FormBody.Builder().build());
+		} else if (httpMethod.equals(HttpMethod.PUT)) {
+			builder = new Request.Builder().url(url).put(new FormBody.Builder().build());
+		} else if (httpMethod.equals(HttpMethod.DELETE)) {
+			builder = new Request.Builder().url(url).delete(new FormBody.Builder().build());
+		}
+
+		if (null != headers && 0 != headers.size()) {
+			for (Entry<String, String> entry : headers.entrySet()) {
+				builder.addHeader(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Request request = builder.build();
+
+		Response response = null;
+		try {
+			response = okHttpClient.newCall(request).execute();
+			return response;
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 发送请求 (表单)
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @param params
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers,
+			Map<String, String> params) {
+
+		LOG.info("[okhttp3] new call: " + httpMethod + " " + url);
+		LOG.info("[okhttp3] headers: " + JsonUtil.toJson(headers));
+		LOG.info("[okhttp3] params: " + JsonUtil.toJson(params));
+
+		Builder builder = null;
+		if (httpMethod.equals(HttpMethod.GET)) {
+			url = UrlUtil.joinParams(url, params);
+			builder = new Request.Builder().url(url).get();
+		} else {
+			okhttp3.FormBody.Builder formBody = new FormBody.Builder();
+			if (null != params && 0 != params.size()) {
+				for (Entry<String, String> entry : params.entrySet()) {
+					formBody.add(entry.getKey(), entry.getValue());
+				}
+			}
+			if (httpMethod.equals(HttpMethod.POST)) {
+				builder = new Request.Builder().url(url).post(formBody.build());
+			} else if (httpMethod.equals(HttpMethod.PUT)) {
+				builder = new Request.Builder().url(url).put(formBody.build());
+			} else if (httpMethod.equals(HttpMethod.DELETE)) {
+				builder = new Request.Builder().url(url).delete(formBody.build());
+			}
+		}
+
+		if (null != headers && 0 != headers.size()) {
+			for (Entry<String, String> entry : headers.entrySet()) {
+				builder.addHeader(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Request request = builder.build();
+
+		Response response = null;
+		try {
+			response = okHttpClient.newCall(request).execute();
+			return response;
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 发送请求 (包含文件表单)
+	 *
+	 * @author WANGWEI
+	 * @param httpMethod
+	 * @param url
+	 * @param headers
+	 * @param params
+	 * @param formFilePartList
+	 * @return
+	 */
+	public static Response call(HttpMethod httpMethod, String url, Map<String, String> headers,
+			Map<String, String> params, List<FormFilePart> formFilePartList) {
+
+		LOG.info("[okhttp3] new call: " + httpMethod + " " + url);
+		LOG.info("[okhttp3] headers: " + JsonUtil.toJson(headers));
+		LOG.info("[okhttp3] params: " + JsonUtil.toJson(params));
+
+		okhttp3.MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder()
+				.setType(MultipartBody.ALTERNATIVE);
+
+		if (null != params) {
+			for (Entry<String, String> entry : params.entrySet()) {
+				multipartBodyBuilder.addFormDataPart(entry.getKey(), entry.getValue());
+			}
+		}
+
+		if (CollectionUtils.isNotEmpty(formFilePartList)) {
+			MediaType type = MediaType.parse("application/octet-stream");
+			for (FormFilePart part : formFilePartList) {
+				RequestBody fileBody = RequestBody.create(type, part.getFile());
+				multipartBodyBuilder.addFormDataPart(part.getParamName(), part.getFilename(),
+						fileBody);
+			}
+		}
+
+		Builder builder = new Request.Builder().url(url).post(multipartBodyBuilder.build());
+		if (null != headers && 0 != headers.size()) {
+			for (Entry<String, String> entry : headers.entrySet()) {
+				builder.addHeader(entry.getKey(), entry.getValue());
+			}
+		}
+
+		Request request = builder.build();
+
+		Response response = null;
+		try {
+			response = okHttpClient.newCall(request).execute();
+			return response;
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}

+ 69 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ObjectUtil.java

@@ -0,0 +1,69 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * 对象工具
+ *
+ * @author WANGWEI
+ */
+public final class ObjectUtil {
+
+	/**
+	 * 判断对象或对象数组中每一个对象是否为空: 对象为null,字符序列长度为0,集合类、Map为empty
+	 *
+	 * @author WANGWEI
+	 * @param obj
+	 * @return
+	 */
+	public static boolean isNullOrEmpty(Object obj) {
+		if (obj == null)
+			return true;
+
+		if (obj instanceof CharSequence)
+			return ((CharSequence) obj).length() == 0;
+
+		if (obj instanceof Collection)
+			return ((Collection<?>) obj).isEmpty();
+
+		if (obj instanceof Map)
+			return ((Map<?, ?>) obj).isEmpty();
+
+		if (obj instanceof Object[]) {
+			Object[] object = (Object[]) obj;
+			if (object.length == 0) {
+				return true;
+			}
+			boolean empty = true;
+			for (int i = 0; i < object.length; i++) {
+				if (!isNullOrEmpty(object[i])) {
+					empty = false;
+					break;
+				}
+			}
+			return empty;
+		}
+		return false;
+	}
+
+	/**
+	 * 判断一个类是否为基本数据类型
+	 *
+	 * @author WANGWEI
+	 * @param clazz
+	 * @return
+	 */
+	public static boolean isBaseDataType(Class<?> clazz) {
+		Boolean isBaseType = (clazz.equals(String.class) || clazz.equals(Integer.class) || clazz.equals(Byte.class)
+				|| clazz.equals(Long.class) || clazz.equals(Double.class) || clazz.equals(Float.class)
+				|| clazz.equals(Character.class) || clazz.equals(Short.class) || clazz.equals(BigDecimal.class)
+				|| clazz.equals(BigInteger.class) || clazz.equals(Boolean.class) || clazz.equals(Date.class)
+				|| clazz.isPrimitive());
+		return isBaseType;
+	}
+
+}

+ 180 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/PathUtil.java

@@ -0,0 +1,180 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLDecoder;
+
+/**
+ * 路径工具
+ *
+ * @author WANGWEI
+ */
+public class PathUtil {
+
+	/**
+	 * 获取标准路径
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 * @return
+	 */
+	public static String getCanonicalPath(String path) {
+		path = path.replaceAll("\\\\+", "/");
+		path = path.replaceAll("/+", "/");
+		return path;
+	}
+
+	/**
+	 * 以"/"开头
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 * @return
+	 */
+	public static String startsWithSeparator(String path) {
+		path = getCanonicalPath(path);
+		if (path.startsWith("/")) {
+			return path;
+		}
+		return "/" + path;
+	}
+
+	/**
+	 * 不以"/"开头
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 * @return
+	 */
+	public static String startsWithoutSeparator(String path) {
+		path = getCanonicalPath(path);
+		if (path.startsWith("/")) {
+			return path.substring(1);
+		}
+		return path;
+	}
+
+	/**
+	 * 以"/"结尾
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 * @return
+	 */
+	public static String endsWithSeparator(String path) {
+		path = getCanonicalPath(path);
+		if (path.endsWith("/")) {
+			return path;
+		}
+		return path + "/";
+	}
+
+	/**
+	 * 不以"/"结尾
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 * @return
+	 */
+	public static String endsWithoutSeparator(String path) {
+		path = getCanonicalPath(path);
+		if (path.endsWith("/")) {
+			return path.substring(0, path.length() - 1);
+		}
+		return path;
+	}
+
+	/**
+	 * 获取路径
+	 *
+	 * @author WANGWEI
+	 * @param file
+	 * @return
+	 */
+	public static String getCanonicalPath(File file) {
+		try {
+			return file.getCanonicalPath();
+		} catch (IOException e) {
+			throw new RuntimeException("Fail to get canonical path.", e);
+		}
+
+	}
+
+	/**
+	 * 获取当前路径
+	 *
+	 * @author WANGWEI
+	 * @return
+	 * @throws IOException
+	 */
+	public static String currentPath() {
+		File directory = new File(". ");
+		return getCanonicalPath(directory);
+	}
+
+	/**
+	 * 获取资源路径
+	 *
+	 * @author WANGWEI
+	 * @param resourceName
+	 * @return
+	 */
+	public static String getResoucePath(String resourceName) {
+		try {
+			ClassLoader classLoader = PathUtil.class.getClassLoader();
+
+			URL url = classLoader.getResource(resourceName);
+			if (null != url) {
+				String path = URLDecoder.decode(url.getPath(), "UTF-8");
+				return path;
+			} else {
+				return null;
+			}
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 获取windows盘符
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 * @return
+	 */
+	public static String getDrive(String path) {
+		if (path.matches("[a-zA-Z]:[\\\\/].*")) {
+			return path.substring(0, 2);
+		} else {
+			throw new RuntimeException("Path is not a windows path.");
+		}
+	}
+
+	/**
+	 * 拼接路径
+	 *
+	 * @author WANGWEI
+	 * @param fragments
+	 * @return
+	 */
+	public static String joinUrl(String... fragments) {
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < fragments.length; i++) {
+			String cur = fragments[i].trim();
+			if (cur.endsWith("/")) {
+				cur = cur.substring(0, cur.length() - 1);
+			}
+			if (0 == i) {
+				sb.append(cur);
+			} else if (cur.startsWith("/")) {
+				sb.append(cur);
+			} else {
+				sb.append("/").append(cur);
+			}
+		}
+
+		return sb.toString();
+	}
+
+}

+ 245 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/PropertiesUtil.java

@@ -0,0 +1,245 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+
+import com.google.common.collect.Sets;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ * @date 2018年10月8日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class PropertiesUtil {
+
+	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(PropertiesUtil.class);
+
+	private static final Properties PROPS = new Properties();
+
+	private static final Set<String> PROP_FILES = Sets.newConcurrentHashSet();
+
+	/**
+	 * 构造函数
+	 *
+	 */
+	private PropertiesUtil() {
+	}
+
+	/**
+	 * 设置属性
+	 *
+	 * @author WANGWEI
+	 * @param key
+	 * @param value
+	 */
+	public static void setProperty(String key, String value) {
+		PROPS.setProperty(key, value);
+	}
+
+	/**
+	 * 加载配置文件
+	 *
+	 * @author WANGWEI
+	 * @param resourceName
+	 */
+	public static void loadFromResource(String resourceName) {
+		loadFromPath(PathUtil.getResoucePath(resourceName));
+	}
+
+	/**
+	 * 加载配置文件
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 */
+	public static synchronized void loadFromPath(String path) {
+		if (StringUtils.isBlank(path)) {
+			return;
+		}
+		try {
+			File file = new File(path);
+
+			if (PROP_FILES.contains(file.getCanonicalPath())) {
+				return;
+			}
+
+			loadFromFile(file, PROPS);
+
+			PROP_FILES.add(file.getCanonicalPath());
+		} catch (Exception e) {
+			LOG.error("Fail to load and watch file [" + path + "].", e);
+		}
+	}
+
+	/**
+	 * @param resourceName
+	 * @param props
+	 */
+	public static void loadFromResource(String resourceName, Properties props) {
+		if (null != PropertiesUtil.class.getClassLoader()) {
+			InputStream in = PropertiesUtil.class.getClassLoader()
+					.getResourceAsStream(resourceName);
+
+			loadFromStream(in, props);
+		} else {
+			throw new ExamCloudRuntimeException("fail to get class loader");
+		}
+	}
+
+	/**
+	 * @param file
+	 * @param props
+	 * @throws IOException
+	 */
+	public static void loadFromFile(File file, Properties props) {
+		if (null == file) {
+			LOG.error("file is null.");
+			return;
+		}
+		if (!file.isFile()) {
+			LOG.error("file is not a normal file.");
+			return;
+		}
+
+		String path = PathUtil.getCanonicalPath(file);
+		LOG.info("Loading properties from file [" + path + "]");
+
+		try {
+			loadFromStream(new FileInputStream(file), props);
+		} catch (Exception e) {
+			throw new ExamCloudRuntimeException(e);
+		}
+	}
+
+	/**
+	 * @param is
+	 * @param props
+	 */
+	public static void loadFromStream(InputStream is, Properties props) {
+		BufferedReader reader = null;
+		try {
+			reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+			Properties temp = new Properties();
+			temp.load(reader);
+			for (Entry<Object, Object> entry : temp.entrySet()) {
+				Object key = entry.getKey();
+				Object value = entry.getValue();
+				if (key instanceof String) {
+					key = ((String) key).trim();
+				}
+				if (value instanceof String) {
+					value = ((String) value).trim();
+				}
+				props.put(key, value);
+			}
+
+		} catch (IOException e) {
+			throw new ExamCloudRuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(reader);
+			IOUtils.closeQuietly(is);
+		}
+	}
+
+	/**
+	 * @param key
+	 * @return
+	 */
+	public static String getString(String key) {
+		String value = PROPS.getProperty(key);
+		if (StringUtils.isNotBlank(value)) {
+			return value.trim();
+		} else {
+			if (LOG.isDebugEnabled()) {
+				LOG.debug("No  property key named [" + key + "] is configured.");
+			}
+			return null;
+		}
+	}
+
+	/**
+	 * @param key
+	 * @param defaultValue
+	 * @return
+	 */
+	public static String getString(String key, String defaultValue) {
+		String value = getString(key);
+		if (null != value) {
+			return value;
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * @param key
+	 * @param defaultValue
+	 * @return
+	 */
+	public static int getInt(String key, int defaultValue) {
+		String value = getString(key);
+		if (null != value) {
+			try {
+				return Integer.parseInt(value);
+			} catch (NumberFormatException e) {
+				PROPS.setProperty(key, String.valueOf(defaultValue));
+				return defaultValue;
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * @param key
+	 * @param defaultValue
+	 * @return
+	 */
+	public static long getLong(String key, long defaultValue) {
+		String value = getString(key);
+		if (null != value) {
+			try {
+				return Long.parseLong(value);
+			} catch (NumberFormatException e) {
+				return defaultValue;
+			}
+		}
+		return defaultValue;
+	}
+
+	/**
+	 * 获取boolean
+	 *
+	 * @author WANGWEI
+	 * @param key
+	 * @param defaultVale
+	 * @return
+	 */
+	public static boolean getBoolean(String key, boolean defaultVale) {
+		String value = getString(key);
+		if (null == value) {
+			return defaultVale;
+		}
+		if (value.equals("true")) {
+			return true;
+		} else if (value.equals("false")) {
+			return false;
+		} else {
+			return defaultVale;
+		}
+	}
+}

+ 103 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/RegExpUtil.java

@@ -0,0 +1,103 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 正则表达式处理工具
+ *
+ * @author WANGWEI
+ * @date 2018年5月24日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class RegExpUtil {
+	/**
+	 * 特殊字符
+	 */
+	private static final char[] SPECIFIC_CHAR_ARRAY = new char[]{'\\', '$', '(', ')', '*', '+', '.',
+			'[', ']', '?', '^', '{', '}', '|', ','};
+
+	/**
+	 * 转义.
+	 *
+	 * @author WANGWEI
+	 * @param input
+	 * @return
+	 */
+	public static String escape(String input) {
+		// '\'必须第一个转义
+		for (char c : SPECIFIC_CHAR_ARRAY) {
+			input = StringUtils.replace(input, String.valueOf(c), "\\" + c);
+		}
+
+		input = input.replaceAll("\\s+", "\\\\s+");
+
+		return input;
+	}
+
+	/**
+	 * find一次,取出匹配的字符串.<br>
+	 * <功能详细描述>
+	 * 
+	 * @param target
+	 * @param regex
+	 * @return
+	 * @see [类、类#方法、类#成员]
+	 */
+	public static String find(String target, String regex) {
+		return find(target, regex, 0);
+	}
+
+	/**
+	 * find一次,取出匹配的字符串的第index组子串.
+	 * 
+	 * @param target
+	 * @param regex
+	 * @param index
+	 * @return
+	 * @see [类、类#方法、类#成员]
+	 */
+	public static String find(String target, String regex, Integer index) {
+		Pattern p = Pattern.compile(regex);
+		Matcher m = p.matcher(target);
+		return m.find() ? m.group(index) : null;
+	}
+
+	/**
+	 * 取出所有匹配的字符串<br>
+	 * <功能详细描述>
+	 * 
+	 * @param target
+	 * @param regex
+	 * @return
+	 * @see [类、类#方法、类#成员]
+	 */
+	public static List<String> findAll(String target, String regex) {
+		return findAll(target, regex, 0);
+	}
+
+	/**
+	 * 取出所有匹配的字符串的第index组子串.<br>
+	 * <功能详细描述>
+	 * 
+	 * @param target
+	 * @param regex
+	 * @param index
+	 * @return
+	 * @see [类、类#方法、类#成员]
+	 */
+	public static List<String> findAll(String target, String regex, Integer index) {
+		Pattern p = Pattern.compile(regex);
+		Matcher m = p.matcher(target);
+		List<String> list = new ArrayList<String>();
+		while (m.find()) {
+			list.add(m.group(index));
+		}
+		return list;
+	}
+
+}

+ 67 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ResourceLoader.java

@@ -0,0 +1,67 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.apache.commons.io.IOUtils;
+
+/**
+ * 资源加载器
+ *
+ * @author WANGWEI
+ */
+public class ResourceLoader {
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param resourceName
+	 * @return
+	 */
+	public static String getResource(String resourceName) {
+		InputStream is = null;
+		try {
+			is = ResourceLoader.class.getClassLoader().getResourceAsStream(resourceName);
+			if (null == is) {
+				throw new RuntimeException(
+						"Resource could not be found. resource name is '" + resourceName + "'.");
+			}
+			return IOUtils.toString(is, "UTF-8");
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(is);
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param resourceName
+	 * @return
+	 */
+	public static String getResourceWithoutBlank(String resourceName) {
+		InputStream is = null;
+		BufferedReader br = null;
+		try {
+			is = ResourceLoader.class.getClassLoader().getResourceAsStream(resourceName);
+			br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+			StringBuilder sb = new StringBuilder();
+			String line = null;
+			while (null != (line = br.readLine())) {
+				sb.append(line.trim());
+			}
+
+			return sb.toString();
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(br);
+			IOUtils.closeQuietly(is);
+		}
+	}
+}

+ 43 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/SHA256.java

@@ -0,0 +1,43 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * SHA256加密
+ * 
+ * @author WANGWEI
+ *
+ */
+public class SHA256 {
+
+	/**
+	 * main
+	 *
+	 * @author WANGWEI
+	 * @param args
+	 */
+	public static void main(String[] args) {
+		String s = "";
+		System.out.println(ByteUtil.toHexAscii(SHA256.encode(s)));
+	}
+
+	/**
+	 * 加密
+	 *
+	 * @author WANGWEI
+	 * @param str
+	 * @return
+	 */
+	public static byte[] encode(String str) {
+		MessageDigest messageDigest;
+		try {
+			messageDigest = MessageDigest.getInstance("SHA-256");
+		} catch (NoSuchAlgorithmException e) {
+			throw new RuntimeException(e);
+		}
+		messageDigest.update(str.getBytes());
+		return messageDigest.digest();
+	}
+
+}

+ 124 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/SecurityUtil.java

@@ -0,0 +1,124 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+/**
+ * 安全工具
+ *
+ * @author WANGWEI
+ * @date 2018年12月4日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class SecurityUtil {
+
+	private static final String RANDOM = "9451utb@91&vrmio!90iu!89F2QN1V ALAII!K33I*DFA^sA";
+
+	private static final String ENCRYPTED_HEAD = "$encrypted";
+
+	/**
+	 * main
+	 *
+	 * @author WANGWEI
+	 * @param args
+	 */
+	public static void main(String[] args) {
+		String s = "";
+		String secretKey = "5220";
+		System.out.println(encrypt(s, secretKey));
+	}
+
+	/**
+	 * 加密
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @param secretKey
+	 */
+	public static String encrypt(String s, String secretKey) {
+		AES aes = new AES(secretKey + RANDOM);
+		String encrypted = aes.encrypt(s);
+		return encrypted;
+	}
+
+	/**
+	 * 解密
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @param secretKey
+	 */
+	public static String decrypt(String s, String secretKey) {
+		AES aes = new AES(secretKey + RANDOM);
+		String decrypted = aes.decrypt(s);
+		return decrypted;
+	}
+
+	/**
+	 * 解密
+	 *
+	 * @author WANGWEI
+	 * @param props
+	 * @param secretKey
+	 */
+	public static void decrypt(Properties props, String secretKey) {
+		AES aes = new AES(secretKey + RANDOM);
+		Map<String, String> map = Maps.newHashMap();
+		Set<String> set = Sets.newHashSet();
+		for (Entry<Object, Object> entry : props.entrySet()) {
+			Object key = entry.getKey();
+			Object value = entry.getValue();
+			if (key instanceof String && value instanceof String) {
+				if (((String) key).startsWith(ENCRYPTED_HEAD)) {
+					String decrypted = aes.decrypt((String) value);
+					map.put(((String) key).substring(ENCRYPTED_HEAD.length() + 1), decrypted);
+					set.add((String) key);
+				}
+			}
+		}
+
+		for (String s : set) {
+			props.remove(s);
+		}
+
+		for (Entry<String, String> entry : map.entrySet()) {
+			props.put(entry.getKey(), entry.getValue());
+		}
+	}
+
+	/**
+	 * 解密
+	 *
+	 * @author WANGWEI
+	 * @param props
+	 * @param secretKey
+	 */
+	public static void decrypt(Map<String, String> props, String secretKey) {
+		AES aes = new AES(secretKey + RANDOM);
+		Map<String, String> map = Maps.newHashMap();
+		Set<String> set = Sets.newHashSet();
+		for (Entry<String, String> entry : props.entrySet()) {
+			String key = entry.getKey();
+			String value = entry.getValue();
+			if (key.startsWith(ENCRYPTED_HEAD)) {
+				String decrypted = aes.decrypt((String) value);
+				map.put(key.substring(ENCRYPTED_HEAD.length() + 1), decrypted);
+				set.add(key);
+			}
+		}
+
+		for (String s : set) {
+			props.remove(s);
+		}
+
+		for (Entry<String, String> entry : map.entrySet()) {
+			props.put(entry.getKey(), entry.getValue());
+		}
+	}
+
+}

+ 390 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/StringUtil.java

@@ -0,0 +1,390 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+
+/**
+ *
+ * @author WANGWEI
+ */
+public class StringUtil {
+
+	/**
+	 * 是否是ACSII码组成的字符串
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static boolean isAscString(String s) {
+		char[] arr = s.toCharArray();
+		for (char c : arr) {
+			if (c < 0 || c > 127) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * Example: subString("12345","1","4")=23
+	 * 
+	 * @param src
+	 * @param start
+	 * @param to
+	 * @return
+	 */
+	public static Integer subStringToInteger(String src, String start, String to) {
+		return stringToInteger(subString(src, start, to));
+	}
+
+	/**
+	 * Example: subString("abcd","a","c")="b"
+	 * 
+	 * @param src
+	 * @param start
+	 *            null while start from index=0
+	 * @param to
+	 *            null while to index=src.length
+	 * @return
+	 */
+	public static String subString(String src, String start, String to) {
+		int indexFrom = start == null ? 0 : src.indexOf(start);
+		int indexTo = to == null ? src.length() : src.indexOf(to);
+		if (indexFrom < 0 || indexTo < 0 || indexFrom > indexTo) {
+			return null;
+		}
+
+		if (null != start) {
+			indexFrom += start.length();
+		}
+
+		return src.substring(indexFrom, indexTo);
+
+	}
+
+	/**
+	 * Example: subString("abcdc","a","c",true)="bcd"
+	 * 
+	 * @param src
+	 * @param start
+	 *            null while start from index=0
+	 * @param to
+	 *            null while to index=src.length
+	 * @param toLast
+	 *            true while to index=src.lastIndexOf(to)
+	 * @return
+	 */
+	public static String subString(String src, String start, String to, boolean toLast) {
+		if (!toLast) {
+			return subString(src, start, to);
+		}
+		int indexFrom = start == null ? 0 : src.indexOf(start);
+		int indexTo = to == null ? src.length() : src.lastIndexOf(to);
+		if (indexFrom < 0 || indexTo < 0 || indexFrom > indexTo) {
+			return null;
+		}
+
+		if (null != start) {
+			indexFrom += start.length();
+		}
+
+		return src.substring(indexFrom, indexTo);
+
+	}
+
+	/**
+	 * @param in
+	 * @return
+	 */
+	public static Integer stringToInteger(String in) {
+		if (in == null) {
+			return null;
+		}
+		in = in.trim();
+		if (in.length() == 0) {
+			return null;
+		}
+
+		try {
+			return Integer.parseInt(in);
+		} catch (NumberFormatException e) {
+			throw new ExamCloudRuntimeException(e);
+		}
+	}
+
+	/**
+	 * @param a
+	 * @param b
+	 * @return
+	 */
+	public static boolean equals(String a, String b) {
+		if (a == null) {
+			return b == null;
+		}
+		return a.equals(b);
+	}
+
+	/**
+	 * @param a
+	 * @param b
+	 * @return
+	 */
+	public static boolean equalsIgnoreCase(String a, String b) {
+		if (a == null) {
+			return b == null;
+		}
+		return a.equalsIgnoreCase(b);
+	}
+
+	/**
+	 * @param str
+	 * @return
+	 */
+	public static boolean isNumber(String str) {
+		if (str.length() == 0) {
+			return false;
+		}
+		int sz = str.length();
+		boolean hasExp = false;
+		boolean hasDecPoint = false;
+		boolean allowSigns = false;
+		boolean foundDigit = false;
+		int start = (str.charAt(0) == '-') ? 1 : 0;
+		if (sz > start + 1) {
+			if (str.charAt(start) == '0' && str.charAt(start + 1) == 'x') {
+				int i = start + 2;
+				if (i == sz) {
+					return false;
+				}
+				for (; i < str.length(); i++) {
+					char ch = str.charAt(i);
+					if ((ch < '0' || ch > '9') && (ch < 'a' || ch > 'f')
+							&& (ch < 'A' || ch > 'F')) {
+						return false;
+					}
+				}
+				return true;
+			}
+		}
+		sz--;
+		int i = start;
+		while (i < sz || (i < sz + 1 && allowSigns && !foundDigit)) {
+			char ch = str.charAt(i);
+			if (ch >= '0' && ch <= '9') {
+				foundDigit = true;
+				allowSigns = false;
+
+			} else if (ch == '.') {
+				if (hasDecPoint || hasExp) {
+					return false;
+				}
+				hasDecPoint = true;
+			} else if (ch == 'e' || ch == 'E') {
+				if (hasExp) {
+					return false;
+				}
+				if (!foundDigit) {
+					return false;
+				}
+				hasExp = true;
+				allowSigns = true;
+			} else if (ch == '+' || ch == '-') {
+				if (!allowSigns) {
+					return false;
+				}
+				allowSigns = false;
+				foundDigit = false;
+			} else {
+				return false;
+			}
+			i++;
+		}
+		if (i < str.length()) {
+			char ch = str.charAt(i);
+
+			if (ch >= '0' && ch <= '9') {
+				return true;
+			}
+			if (ch == 'e' || ch == 'E') {
+				return false;
+			}
+			if (!allowSigns && (ch == 'd' || ch == 'D' || ch == 'f' || ch == 'F')) {
+				return foundDigit;
+			}
+			if (ch == 'l' || ch == 'L') {
+				return foundDigit && !hasExp;
+			}
+			return false;
+		}
+
+		return !allowSigns && foundDigit;
+	}
+
+	/**
+	 * 拼接
+	 * 
+	 * @param elements
+	 * @return
+	 */
+	public static String join(Object... elements) {
+		StringBuilder sb = new StringBuilder();
+		for (Object e : elements) {
+			if (e == null) {
+				e = "null";
+			}
+			sb.append(e.toString());
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 拼接
+	 * 
+	 * @param elements
+	 * @return
+	 */
+	public static String join(List<Object> elements) {
+		StringBuilder sb = new StringBuilder();
+		for (Object e : elements) {
+			if (e == null) {
+				e = "null";
+			}
+			sb.append(e.toString());
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 拼接
+	 * 
+	 * @param sep
+	 * @param elements
+	 * @return
+	 */
+	public static String joinWithSep(String sep, Object... elements) {
+		StringBuilder sb = new StringBuilder();
+		boolean first = true;
+		for (Object e : elements) {
+			if (e == null) {
+				e = "null";
+			}
+			if (first) {
+				sb.append(e.toString());
+				first = false;
+			} else {
+				sb.append(sep).append(e.toString());
+			}
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 拼接
+	 * 
+	 * @param sep
+	 * @param elements
+	 * @return
+	 */
+	public static String joinWithSep(String sep, List<Object> elements) {
+		StringBuilder sb = new StringBuilder();
+		boolean first = true;
+		for (Object e : elements) {
+			if (e == null) {
+				e = "null";
+			}
+			if (first) {
+				sb.append(e.toString());
+				first = false;
+			} else {
+				sb.append(sep).append(e.toString());
+			}
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 转换成Boolean
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static Boolean toBoolean(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		s = s.trim();
+		if (s.equals("true")) {
+			return true;
+		} else if (s.equals("false")) {
+			return false;
+		} else {
+			throw new ExamCloudRuntimeException("[" + s + "] must be  \"true\\\" or \\\"false\\\"");
+		}
+	}
+
+	/**
+	 * 转换成Integer
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static Integer toInteger(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		s = s.trim();
+		int i = Integer.parseInt(s);
+		return i;
+	}
+
+	/**
+	 * 转换成Long
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static Long toLong(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		s = s.trim();
+		Long l = Long.parseLong(s);
+		return l;
+	}
+
+	/**
+	 * 字符串是否为"true"
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static boolean isTrue(String s) {
+		if (null == s) {
+			return false;
+		}
+		return s.equalsIgnoreCase("true");
+	}
+
+	/**
+	 * 字符串是否为"false"
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static boolean isFalse(String s) {
+		if (null == s) {
+			return false;
+		}
+		return s.equalsIgnoreCase("false");
+	}
+
+}

+ 121 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ThreadLocalUtil.java

@@ -0,0 +1,121 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.commons.lang.StringUtils;
+
+import com.google.common.collect.Maps;
+
+/**
+ * 线程本地化工具
+ *
+ * @author WANGWEI
+ */
+public class ThreadLocalUtil {
+	/**
+	 * trace ID
+	 */
+	private static final ThreadLocal<String> LOCAL_TRACE_ID = new ThreadLocal<String>() {
+		@Override
+		public String initialValue() {
+			return null;
+		}
+	};
+
+	/**
+	 * map
+	 */
+	private static final ThreadLocal<Map<String, Object>> LOCAL_TRACE_MAP = new ThreadLocal<Map<String, Object>>() {
+		@Override
+		public Map<String, Object> initialValue() {
+			return null;
+		}
+	};
+
+	/**
+	 * 重新初始化
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static String next() {
+		LOCAL_TRACE_MAP.set(null);
+		String traceID = UUID.randomUUID().toString().replace("-", "");
+		setTraceId(traceID);
+		return traceID;
+	}
+
+	/**
+	 * 获取 trace ID
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static String getTraceId() {
+		String traceID = LOCAL_TRACE_ID.get();
+		if (traceID == null) {
+			traceID = next();
+		} else {
+			return traceID;
+		}
+
+		return traceID;
+	}
+
+	/**
+	 * 设置 trace ID
+	 *
+	 * @author WANGWEI
+	 * @param traceId
+	 */
+	public static void setTraceId(String traceId) {
+		if (!(StringUtils.isNotBlank(traceId)))
+			return;
+		LOCAL_TRACE_ID.set(traceId);
+	}
+
+	/**
+	 * 设置属性
+	 *
+	 * @author WANGWEI
+	 * @param key
+	 * @param value
+	 */
+	public static void set(String key, Object value) {
+		Map<String, Object> map = LOCAL_TRACE_MAP.get();
+
+		if (null == map) {
+			map = Maps.newHashMap();
+			LOCAL_TRACE_MAP.set(map);
+		}
+		map.put(key, value);
+	}
+
+	/**
+	 * 获取属性
+	 *
+	 * @author WANGWEI
+	 * @param key
+	 * @return
+	 */
+	public static Object get(String key) {
+		Map<String, Object> map = LOCAL_TRACE_MAP.get();
+		if (null == map) {
+			return null;
+		}
+		return map.get(key);
+	}
+
+	/**
+	 * 清理属性
+	 *
+	 * @author WANGWEI
+	 */
+	public static void clearAll() {
+		Map<String, Object> map = LOCAL_TRACE_MAP.get();
+		if (null != map) {
+			map.clear();
+		}
+	}
+}

+ 20 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/UUID.java

@@ -0,0 +1,20 @@
+package cn.com.qmth.examcloud.commons.util;
+
+/**
+ * UUID
+ *
+ * @author WANGWEI
+ * @date 2018年2月27日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class UUID {
+	/**
+	 * UUID
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static String randomUUID() {
+		return java.util.UUID.randomUUID().toString().replaceAll("-", "");
+	}
+}

+ 160 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/UrlUtil.java

@@ -0,0 +1,160 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * url 处理工具
+ *
+ * @author WANGWEI
+ */
+public class UrlUtil {
+	/**
+	 * 获取url参数
+	 *
+	 * @author WANG WEI
+	 *
+	 * @param str
+	 * @return
+	 */
+	public static Map<String, String> getUrlParams(String url) {
+		Map<String, String> params = new HashMap<String, String>();
+
+		try {
+			int indexOf = url.lastIndexOf('?');
+
+			if (0 < indexOf) {
+				url = url.substring(indexOf + 1);
+			}
+			String[] pairs = url.trim().split("&");
+			for (String pair : pairs) {
+				String[] kv = pair.split("=");
+				if (2 != kv.length) {
+					continue;
+				}
+				params.put(kv[0], URLDecoder.decode(kv[1], "UTF-8"));
+			}
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+
+		return params;
+	}
+
+	/**
+	 * 编码
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static String encode(String s) {
+		return encode(s, "UTF-8");
+	}
+
+	/**
+	 * 编码
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @param enc
+	 * @return
+	 */
+	public static String encode(String s, String enc) {
+		try {
+			return URLEncoder.encode(s, enc).replaceAll("\\+", "%20");
+		} catch (UnsupportedEncodingException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 解码
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static String decode(String s) {
+		return decode(s, "UTF-8");
+	}
+
+	/**
+	 * 解码
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @param enc
+	 * @return
+	 */
+	public static String decode(String s, String enc) {
+		try {
+			return URLDecoder.decode(s, enc);
+		} catch (UnsupportedEncodingException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * url拼接参数
+	 *
+	 * @author WANGWEI
+	 * @param url
+	 * @param params
+	 * @return
+	 */
+	public static String joinParams(String url, Map<String, String> params) {
+		Map<String, String> urlParams = getUrlParams(url);
+		urlParams.putAll(params);
+
+		int indexOf = url.lastIndexOf('?');
+
+		if (0 < indexOf) {
+			url = url.substring(0, indexOf);
+		}
+
+		StringBuilder sb = new StringBuilder();
+		sb.append(url).append("?");
+		boolean hasParams = false;
+		for (Entry<String, String> entry : urlParams.entrySet()) {
+			sb.append(entry.getKey()).append("=").append(encode(entry.getValue())).append("&");
+			hasParams = true;
+		}
+		if (hasParams) {
+			sb.deleteCharAt(sb.length() - 1);
+		}
+
+		return sb.toString();
+	}
+
+	/**
+	 * 拼接url片段
+	 *
+	 * @author WANGWEI
+	 * @param fragments
+	 * @return
+	 */
+	public static String joinUrl(String... fragments) {
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < fragments.length; i++) {
+			String cur = fragments[i].trim();
+			if (cur.endsWith("/")) {
+				cur = cur.substring(0, cur.length() - 1);
+			}
+			if (0 == i) {
+				sb.append(cur);
+			} else if (cur.startsWith("/")) {
+				sb.append(cur);
+			} else {
+				sb.append("/").append(cur);
+			}
+		}
+
+		return sb.toString();
+	}
+
+}

+ 126 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/Util.java

@@ -0,0 +1,126 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.poi.util.IOUtils;
+
+/**
+ * 工具集
+ *
+ * @author WANGWEI
+ * @date 2018年12月6日
+ * @Copyright (c) 2018-2020 http://www.qmth.com.cn/ All Rights Reserved.
+ */
+public class Util {
+
+	/**
+	 * @param elements
+	 * @return
+	 */
+	public static List<String> buildList(String... elements) {
+		return Arrays.asList(elements);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param timeout
+	 */
+	public static void sleep(int timeout) {
+		sleep(TimeUnit.SECONDS, timeout);
+	}
+
+	/**
+	 * 
+	 * @param timeUnit
+	 * @param timeout
+	 */
+	public static void sleep(TimeUnit timeUnit, int timeout) {
+		try {
+			timeUnit.sleep(timeout);
+		} catch (InterruptedException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	/**
+	 * 获取堆栈信息
+	 *
+	 * @author WANGWEI
+	 * @param e
+	 * @return
+	 */
+	public static String getStackTrace(Exception e) {
+		PrintWriter pw = null;
+		StringWriter sw = null;
+		try {
+			sw = new StringWriter();
+			pw = new PrintWriter(sw);
+			e.printStackTrace(pw);
+			return sw.toString();
+		} catch (Exception ex) {
+			throw new RuntimeException(ex);
+		} finally {
+			IOUtils.closeQuietly(pw);
+			IOUtils.closeQuietly(sw);
+		}
+	}
+
+	/**
+	 * 所有元素 equals
+	 *
+	 * @author WANGWEI
+	 * @param elements
+	 * @return
+	 */
+	public static boolean equals(Object... elements) {
+		List<?> list = Arrays.asList(elements);
+		return equals(list);
+	}
+
+	/**
+	 * 所有元素 equals
+	 *
+	 * @author WANGWEI
+	 * @param elements
+	 * @return
+	 */
+	public static boolean equals(List<?> elements) {
+		Object firstElement = null;
+		boolean isFirst = true;
+		for (Object cur : elements) {
+
+			if (isFirst) {
+				firstElement = cur;
+				isFirst = false;
+			} else {
+				try {
+					if (!firstElement.equals(cur)) {
+						return false;
+					}
+				} catch (Exception e) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	/**
+	 * 数组转List
+	 *
+	 * @author WANGWEI
+	 * @param t
+	 * @return
+	 */
+	@SafeVarargs
+	public static <T> List<T> toList(T... t) {
+		return Arrays.asList(t);
+	}
+
+}

+ 147 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/ZipUtil.java

@@ -0,0 +1,147 @@
+package cn.com.qmth.examcloud.commons.util;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.zip.Zip64Mode;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.io.FileUtils;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ */
+public class ZipUtil {
+
+	/**
+	 * 压缩
+	 * 
+	 * @param dir
+	 *            压缩的文件夹
+	 * @param zipFile
+	 *            压缩文件
+	 */
+	public static void zip(File dir, File zipFile) {
+		List<File> files = getAllFiles(dir);
+		if (CollectionUtils.isEmpty(files)) {
+			return;
+		}
+
+		ZipArchiveOutputStream zipOut = null;
+
+		try {
+			zipOut = new ZipArchiveOutputStream(zipFile);
+			zipOut.setUseZip64(Zip64Mode.AsNeeded);
+
+			for (File file : files) {
+				if (file.isDirectory()) {
+					continue;
+				}
+
+				String filePath = file.getCanonicalPath()
+						.replace(dir.getCanonicalPath() + File.separator, "");
+
+				ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(file, filePath);
+				zipOut.putArchiveEntry(zipArchiveEntry);
+
+				InputStream in = null;
+				try {
+					in = new BufferedInputStream(new FileInputStream(file));
+					IOUtils.copy(in, zipOut);
+					zipOut.closeArchiveEntry();
+				} finally {
+					IOUtils.closeQuietly(in);
+				}
+			}
+
+			zipOut.finish();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(zipOut);
+		}
+
+	}
+
+	/**
+	 * @param dir
+	 * @return
+	 */
+	private static List<File> getAllFiles(File dir) {
+		List<File> ret = new ArrayList<File>();
+		File[] files = dir.listFiles();
+		for (File f : files) {
+			if (f.isDirectory()) {
+				ret.addAll(getAllFiles(f));
+			}
+			ret.add(f);
+		}
+		return ret;
+	}
+
+	/**
+	 * 解压
+	 * 
+	 * @param file
+	 *            解压的文件
+	 * @param dir
+	 *            解压的文件夹
+	 */
+	public static void unzip(File file, File dir) {
+		try {
+			FileUtils.forceMkdir(dir);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+
+		if (!file.exists()) {
+			throw new RuntimeException("file is not existing.");
+		}
+
+		ZipArchiveInputStream zipIn = null;
+
+		try {
+			zipIn = new ZipArchiveInputStream(new FileInputStream(file));
+			ArchiveEntry entry = null;
+
+			while (null != (entry = zipIn.getNextEntry())) {
+				String entryName = entry.getName();
+				String entryPath = dir.getCanonicalPath() + File.separator + entryName;
+
+				OutputStream out = null;
+				try {
+					File entryFile = new File(entryPath);
+					if (entryName.endsWith("/")) {
+						entryFile.mkdirs();
+					} else {
+						out = new FileOutputStream(entryFile);
+						IOUtils.copy(zipIn, out);
+					}
+				} finally {
+					IOUtils.closeQuietly(out);
+				}
+
+			}
+
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(zipIn);
+		}
+
+	}
+
+}

+ 34 - 0
examcloud-commons/src/test/java/cn/com/qmth/examcloud/test/pipeline/FileReaderExecuter.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.test.pipeline;
+
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.RandomUtils;
+
+import cn.com.qmth.examcloud.commons.helpers.KeyValuePair;
+import cn.com.qmth.examcloud.commons.helpers.ObjectHolder;
+import cn.com.qmth.examcloud.commons.helpers.pipeline.NodeExecuter;
+import cn.com.qmth.examcloud.commons.helpers.pipeline.TaskContext;
+
+public class FileReaderExecuter implements NodeExecuter<String, String, String, String> {
+
+	@Override
+	public void execute(String key, String value, List<KeyValuePair<String, String>> outList,
+			ObjectHolder<Boolean> removable, TaskContext context) throws Exception {
+
+		String path = (String) context.get("path");
+
+		List<String> readLines = FileUtils.readLines(new File(path), "UTF-8");
+
+		readLines.forEach(e -> {
+			outList.add(new KeyValuePair<String, String>(Objects.toString((RandomUtils.nextLong())),
+					e));
+		});
+
+		removable.set(false);
+		outList.clear();
+	}
+
+}

+ 18 - 0
examcloud-commons/src/test/java/cn/com/qmth/examcloud/test/pipeline/PrintExecuter.java

@@ -0,0 +1,18 @@
+package cn.com.qmth.examcloud.test.pipeline;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.commons.helpers.KeyValuePair;
+import cn.com.qmth.examcloud.commons.helpers.ObjectHolder;
+import cn.com.qmth.examcloud.commons.helpers.pipeline.NodeExecuter;
+import cn.com.qmth.examcloud.commons.helpers.pipeline.TaskContext;
+
+public class PrintExecuter implements NodeExecuter<String, String, String, String> {
+
+	@Override
+	public void execute(String key, String value, List<KeyValuePair<String, String>> outList,
+			ObjectHolder<Boolean> removable, TaskContext context) throws Exception {
+		System.out.println("Print: " + key + " -> " + value);
+	}
+
+}

+ 33 - 0
examcloud-commons/src/test/java/cn/com/qmth/examcloud/test/pipeline/Test.java

@@ -0,0 +1,33 @@
+package cn.com.qmth.examcloud.test.pipeline;
+
+import cn.com.qmth.examcloud.commons.helpers.pipeline.Node;
+import cn.com.qmth.examcloud.commons.helpers.pipeline.SimpleNode;
+import cn.com.qmth.examcloud.commons.helpers.pipeline.TaskContext;
+
+public class Test {
+
+	public static void main(String[] args) {
+		TaskContext context = new TaskContext();
+		context.put("path", "D:/Temp/test.txt");
+
+		FileReaderExecuter fileReaderExecuter = new FileReaderExecuter();
+		PrintExecuter printExecuter = new PrintExecuter();
+
+		Node<String, String, String, String> node1 = new SimpleNode<String, String, String, String>(
+				"reader", fileReaderExecuter, context);
+
+		Node<String, String, String, String> node2 = new SimpleNode<String, String, String, String>(
+				"print", printExecuter, context);
+
+		node1.setLowerNode(node2);
+
+		node1.setFirst(true);
+		node1.setSleep(10);
+
+		node2.setSleep(5);
+
+		node1.start();
+		node2.start();
+	}
+
+}

+ 42 - 0
examcloud-commons/src/test/resources/log4j2.xml

@@ -0,0 +1,42 @@
+<?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} | %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} | %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>
+	</Appenders>
+
+	<Loggers>
+		<Logger name="cn.tianji.heaven" level="DEBUG" additivity="false">
+			<AppenderRef ref="DEBUG_APPENDER" />
+			<AppenderRef ref="Console" />
+		</Logger>
+
+		<Root level="INFO">
+			<AppenderRef ref="Console" />
+			<AppenderRef ref="DEBUG_APPENDER" />
+		</Root>
+	</Loggers>
+
+</Configuration>

+ 12 - 0
examcloud-config-center-starter/.gitignore

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

+ 32 - 0
examcloud-config-center-starter/pom.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>cn.com.qmth.examcloud</groupId>
+		<artifactId>examcloud-parent</artifactId>
+		<version>2019</version>
+	</parent>
+	<artifactId>examcloud-config-center-starter</artifactId>
+	<version>2019-SNAPSHOT</version>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-web</artifactId>
+			<version>${examcloud.version}</version>
+			<exclusions>
+				<exclusion>
+					<groupId>org.springframework.cloud</groupId>
+					<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>org.jline</groupId>
+			<artifactId>jline</artifactId>
+			<version>3.12.1</version>
+		</dependency>
+	</dependencies>
+
+</project>

+ 18 - 0
examcloud-config-center-starter/shell/jenkins.sh

@@ -0,0 +1,18 @@
+#!/bin/bash
+pwd
+
+rm -rf  ~/project/examcloud/examcloud-config-center-distribution.zip
+rm -rf  ~/project/examcloud/examcloud-config-center/lib
+
+cp target/examcloud-config-center-distribution.zip ~/project/examcloud/
+
+cd  ~/project/examcloud/
+unzip -o examcloud-config-center-distribution.zip
+
+cd examcloud-config-center
+echo "--spring.profiles.active=test" > start.args
+echo "-server -Xms128m -Xmx128m  -XX:-UseGCOverheadLimit" > start.vmoptions
+
+
+
+

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików