wangwei 6 éve
commit
6f647d7912
43 módosított fájl, 4498 hozzáadás és 0 törlés
  1. 22 0
      .gitignore
  2. 127 0
      pom.xml
  3. 50 0
      src/main/java/cn/com/qmth/examcloud/commons/base/exception/ExamCloudRuntimeException.java
  4. 80 0
      src/main/java/cn/com/qmth/examcloud/commons/base/exception/StatusException.java
  5. 79 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/Counter.java
  6. 14 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/DataType.java
  7. 84 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/DynamicEnum.java
  8. 131 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/DynamicEnumManager.java
  9. 134 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/FileChangeWatchdog.java
  10. 52 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/KeyValuePair.java
  11. 38 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/ObjectHolder.java
  12. 98 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/XStreamBuilder.java
  13. 165 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/poi/ExcelReader.java
  14. 137 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/poi/ExcelWriter.java
  15. 198 0
      src/main/java/cn/com/qmth/examcloud/commons/base/helpers/poi/XlsxHandler.java
  16. 110 0
      src/main/java/cn/com/qmth/examcloud/commons/base/logging/ExamCloudLog.java
  17. 78 0
      src/main/java/cn/com/qmth/examcloud/commons/base/logging/ExamCloudLogFactory.java
  18. 70 0
      src/main/java/cn/com/qmth/examcloud/commons/base/logging/NoLoggingImpl.java
  19. 67 0
      src/main/java/cn/com/qmth/examcloud/commons/base/logging/Resources.java
  20. 111 0
      src/main/java/cn/com/qmth/examcloud/commons/base/logging/SLF4JImpl.java
  21. 166 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/AES.java
  22. 89 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/AsciiUtil.java
  23. 116 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/ByteUtil.java
  24. 202 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/DBUtil.java
  25. 94 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/DateUtil.java
  26. 63 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/FreeMarkerUtil.java
  27. 59 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/HttpClientPool.java
  28. 108 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/HttpClientUtil.java
  29. 105 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/IOUtil.java
  30. 70 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/JsonUtil.java
  31. 69 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/ObjectUtil.java
  32. 95 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/PathUtil.java
  33. 266 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/PropertiesUtil.java
  34. 103 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/RegExpUtil.java
  35. 67 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/ResourceLoader.java
  36. 43 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/SHA256.java
  37. 124 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/SecurityUtil.java
  38. 337 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/StringUtil.java
  39. 109 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/ThreadLocalUtil.java
  40. 20 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/UUID.java
  41. 127 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/UrlUtil.java
  42. 74 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/Util.java
  43. 147 0
      src/main/java/cn/com/qmth/examcloud/commons/base/util/ZipUtil.java

+ 22 - 0
.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
+

+ 127 - 0
pom.xml

@@ -0,0 +1,127 @@
+<?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>
+
+	<dependencies>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-lang3</artifactId>
+			<version>${commons-lang3.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>redis.clients</groupId>
+			<artifactId>jedis</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.data</groupId>
+			<artifactId>spring-data-redis</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.gson</groupId>
+			<artifactId>gson</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>fastjson</artifactId>
+			<version>${fastjson.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.poi</groupId>
+			<artifactId>poi-ooxml</artifactId>
+			<version>${poi.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>2.5</version>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-aop</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-aspects</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-web</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework</groupId>
+			<artifactId>spring-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-fileupload</groupId>
+			<artifactId>commons-fileupload</artifactId>
+			<version>1.3.3</version>
+			<exclusions>
+				<exclusion>
+					<groupId>commons-io</groupId>
+					<artifactId>commons-io</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-collections</groupId>
+			<artifactId>commons-collections</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-fileupload</groupId>
+			<artifactId>commons-fileupload</artifactId>
+			<version>1.3.2</version>
+		</dependency>
+		<dependency>
+			<groupId>com.esotericsoftware</groupId>
+			<artifactId>reflectasm</artifactId>
+			<version>1.11.3</version>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpclient</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.commons</groupId>
+			<artifactId>commons-compress</artifactId>
+			<version>1.12</version>
+		</dependency>
+		<dependency>
+			<groupId>com.mchange</groupId>
+			<artifactId>c3p0</artifactId>
+			<version>0.9.5.2</version>
+		</dependency>
+		<dependency>
+			<groupId>com.thoughtworks.xstream</groupId>
+			<artifactId>xstream</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.freemarker</groupId>
+			<artifactId>freemarker</artifactId>
+		</dependency>
+	</dependencies>
+
+</project>

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

@@ -0,0 +1,50 @@
+package cn.com.qmth.examcloud.commons.base.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
src/main/java/cn/com/qmth/examcloud/commons/base/exception/StatusException.java

@@ -0,0 +1,80 @@
+package cn.com.qmth.examcloud.commons.base.exception;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.commons.base.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();
+	}
+
+}

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

@@ -0,0 +1,79 @@
+package cn.com.qmth.examcloud.commons.base.helpers;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 计数器
+ *
+ * @author WANGWEI
+ * @date 2018年1月26日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] 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();
+	}
+}

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

@@ -0,0 +1,14 @@
+package cn.com.qmth.examcloud.commons.base.helpers;
+
+/**
+ * 数据类型
+ *
+ * @author WANGWEI
+ * @date 2018年12月3日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public enum DataType {
+
+	STRING, LONG, INTEGER, DATE, BOOLEAN;
+
+}

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

@@ -0,0 +1,84 @@
+package cn.com.qmth.examcloud.commons.base.helpers;
+
+import java.io.Serializable;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 动态枚举
+ *
+ * @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 pattern;
+
+	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 getPattern() {
+		return pattern;
+	}
+
+	public void setPattern(String pattern) {
+		this.pattern = pattern;
+	}
+
+	/**
+	 * 是否合法
+	 *
+	 * @author WANGWEI
+	 * @param value
+	 * @return
+	 */
+	public Boolean isLegal(String value) {
+		if (StringUtils.isNotBlank(value) && StringUtils.isNotBlank(pattern)) {
+			return value.matches(pattern);
+		}
+		return true;
+	}
+
+}

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

@@ -0,0 +1,131 @@
+package cn.com.qmth.examcloud.commons.base.helpers;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+import com.thoughtworks.xstream.XStream;
+
+import cn.com.qmth.examcloud.commons.base.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.base.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) {
+			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
src/main/java/cn/com/qmth/examcloud/commons/base/helpers/FileChangeWatchdog.java

@@ -0,0 +1,134 @@
+package cn.com.qmth.examcloud.commons.base.helpers;
+
+import java.io.File;
+
+import cn.com.qmth.examcloud.commons.base.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.base.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();
+		}
+	}
+}

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

@@ -0,0 +1,52 @@
+package cn.com.qmth.examcloud.commons.base.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;
+	}
+
+}

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

@@ -0,0 +1,38 @@
+package cn.com.qmth.examcloud.commons.base.helpers;
+
+/**
+ * 基本类型对象化
+ *
+ * @author WANGWEI
+ * @param <T>
+ */
+public class ObjectHolder<T> {
+	private T t;
+
+	/**
+	 * 构造函数
+	 *
+	 * @param t
+	 */
+	public ObjectHolder(T t) {
+		super();
+		this.t = t;
+	}
+
+	public T getT() {
+		return t;
+	}
+
+	public void setT(T t) {
+		this.t = t;
+	}
+
+	public boolean isNull() {
+		return null == t;
+	}
+
+	public T get() {
+		return t;
+	}
+
+}

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

@@ -0,0 +1,98 @@
+package cn.com.qmth.examcloud.commons.base.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;
+	}
+
+}

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

@@ -0,0 +1,165 @@
+package cn.com.qmth.examcloud.commons.base.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 WANGWEI [QQ:522080330] 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
src/main/java/cn/com/qmth/examcloud/commons/base/helpers/poi/ExcelWriter.java

@@ -0,0 +1,137 @@
+package cn.com.qmth.examcloud.commons.base.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.base.util.DateUtil.DatePatterns;
+
+/**
+ * Excel 生产者
+ *
+ * @author WANGWEI
+ * @date 2018年9月7日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] 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.ISO));
+						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);
+		}
+	}
+
+}

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

@@ -0,0 +1,198 @@
+package cn.com.qmth.examcloud.commons.base.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 = OPCPackage.open(filePath);
+		XSSFReader r = new XSSFReader(pkg);
+		SharedStringsTable sst = r.getSharedStringsTable();
+
+		XMLReader parser = XMLReaderFactory.createXMLReader();
+		this.sst = sst;
+		parser.setContentHandler(this);
+
+		InputStream is = null;
+		try {
+			is = r.getSheet("rId" + sheetId);
+			sheetIndex++;
+			InputSource sheetSource = new InputSource(is);
+			parser.parse(sheetSource);
+		} finally {
+			IOUtils.closeQuietly(is);
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param filePath
+	 * @throws Exception
+	 */
+	public void process(String filePath) throws Exception {
+		OPCPackage 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);
+			}
+		}
+	}
+
+	@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
src/main/java/cn/com/qmth/examcloud/commons/base/logging/ExamCloudLog.java

@@ -0,0 +1,110 @@
+package cn.com.qmth.examcloud.commons.base.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
src/main/java/cn/com/qmth/examcloud/commons/base/logging/ExamCloudLogFactory.java

@@ -0,0 +1,78 @@
+package cn.com.qmth.examcloud.commons.base.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
src/main/java/cn/com/qmth/examcloud/commons/base/logging/NoLoggingImpl.java

@@ -0,0 +1,70 @@
+package cn.com.qmth.examcloud.commons.base.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
src/main/java/cn/com/qmth/examcloud/commons/base/logging/Resources.java

@@ -0,0 +1,67 @@
+package cn.com.qmth.examcloud.commons.base.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
src/main/java/cn/com/qmth/examcloud/commons/base/logging/SLF4JImpl.java

@@ -0,0 +1,111 @@
+package cn.com.qmth.examcloud.commons.base.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);
+	}
+
+}

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

@@ -0,0 +1,166 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.security.GeneralSecurityException;
+import java.util.Locale;
+
+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.lang3.StringUtils;
+
+import cn.com.qmth.examcloud.commons.base.exception.ExamCloudRuntimeException;
+
+/**
+ * AES算法<br>
+ * CBC(密码块链)模式
+ *
+ * @author WANGWEI
+ * @date 2018年11月21日
+ * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
+ */
+public class AES {
+	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) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * 加密
+	 * 
+	 * @param bytes
+	 * @return
+	 */
+	public String encrypt(byte[] bytes) {
+		try {
+			byte[] enc = encryptCipher.doFinal(bytes);
+			return bytes2HexString(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(hexString2Bytes(str));
+			return new String(dec);
+		} catch (IllegalBlockSizeException e) {
+			throw new ExamCloudRuntimeException(e);
+		} catch (BadPaddingException 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);
+	}
+
+	/**
+	 * @param hexString
+	 * @return
+	 */
+	private byte[] hexString2Bytes(String hexString) {
+		hexString = hexString.toUpperCase(Locale.US);
+		int length = hexString.length() / 2;
+		char[] hexChars = hexString.toCharArray();
+		byte[] bytes = new byte[length];
+
+		String str = "0123456789ABCDEF";
+
+		for (int i = 0; i < length; i++) {
+			int pos = i * 2;
+			bytes[i] = (byte) (str.indexOf(hexChars[pos]) << 4 | str.indexOf(hexChars[pos + 1]));
+
+		}
+		return bytes;
+	}
+
+	/**
+	 * @param bytes
+	 * @return
+	 */
+	private String bytes2HexString(byte[] bytes) {
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < bytes.length; i++) {
+			String hex = Integer.toHexString(bytes[i] & 0xFF);
+			if (hex.length() == 1) {
+				hex = '0' + hex;
+			}
+
+			sb.append(hex);
+		}
+
+		return sb.toString();
+	}
+
+}

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

@@ -0,0 +1,89 @@
+package cn.com.qmth.examcloud.commons.base.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;
+	}
+
+}

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

@@ -0,0 +1,116 @@
+package cn.com.qmth.examcloud.commons.base.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;
+	}
+
+}

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

@@ -0,0 +1,202 @@
+package cn.com.qmth.examcloud.commons.base.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 com.mchange.v2.c3p0.ComboPooledDataSource;
+import com.mchange.v2.c3p0.DataSources;
+
+import cn.com.qmth.examcloud.commons.base.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.base.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>();
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param dataSourceName
+	 * @return
+	 */
+	public static DataSource initDataSource(final String dataSourceName) {
+		final ComboPooledDataSource dataSource = new ComboPooledDataSource();
+		Connection conn = null;
+		try {
+			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 minPoolSize = PropertiesUtil.getInt(dataSourceName + ".minPoolSize", 0);
+			int maxPoolSize = PropertiesUtil.getInt(dataSourceName + ".maxPoolSize", 10);
+
+			dataSource.setDriverClass(dbDriver);
+			dataSource.setJdbcUrl(url);
+			dataSource.setUser(userName);
+			dataSource.setPassword(password);
+			dataSource.setMinPoolSize(minPoolSize);
+			dataSource.setMaxPoolSize(maxPoolSize);
+			dataSource.setInitialPoolSize(minPoolSize);
+			dataSource.setPreferredTestQuery("select 1 from dual");
+			dataSource.setTestConnectionOnCheckin(true);
+			dataSource.setAcquireRetryAttempts(1);
+			dataSource.setAcquireRetryDelay(1000);
+			dataSource.setMaxIdleTime(60);
+			dataSource.setCheckoutTimeout(2000);
+			dataSource.setAcquireIncrement(1);
+			dataSource.setBreakAfterAcquireFailure(true);
+
+			conn = dataSource.getConnection();
+
+			Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+				public void run() {
+					try {
+						DataSources.destroy(dataSource);
+					} catch (SQLException e) {
+						LOG.error("[JDBC] Fail to destroy dataSource. dataSourceName="
+								+ dataSourceName, e);
+					}
+				}
+			}));
+		} catch (Exception e) {
+			LOG.error("[JDBC] Fail to init dataSource. 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);
+			}
+		}
+	}
+
+}

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

@@ -0,0 +1,94 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 日期工具
+ * 
+ * @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 ISO = "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 getNow(String pattern) {
+		return format(new Date(), pattern);
+	}
+
+	/**
+	 * get now ISO date.
+	 * 
+	 * @return
+	 */
+	public static String getNowISO() {
+		return format(new Date(), DatePatterns.ISO);
+	}
+
+	/**
+	 * 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 RuntimeException(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 RuntimeException(e);
+		}
+	}
+
+}

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

@@ -0,0 +1,63 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Locale;
+
+import cn.com.qmth.examcloud.commons.base.exception.ExamCloudRuntimeException;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+
+/**
+ * FreeMarker 工具
+ *
+ * @author WANGWEI
+ * @date 2018年11月21日
+ * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] 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);
+		}
+	}
+
+}

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

@@ -0,0 +1,59 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.SSLContext;
+
+import org.apache.http.config.Registry;
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ */
+public class HttpClientPool
+{
+
+	private static PoolingHttpClientConnectionManager cm = null;
+
+	static
+	{
+		LayeredConnectionSocketFactory sslsf = null;
+		try
+		{
+			sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
+		}
+		catch (NoSuchAlgorithmException e)
+		{
+			e.printStackTrace();
+		}
+
+		Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
+				.<ConnectionSocketFactory> create().register("https", sslsf)
+				.register("http", new PlainConnectionSocketFactory()).build();
+		cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
+		cm.setMaxTotal(200);
+		cm.setDefaultMaxPerRoute(20);
+	}
+
+	/**
+	 * get http client.
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static CloseableHttpClient getHttpClient()
+	{
+		CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
+		return httpClient;
+	}
+
+}

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

@@ -0,0 +1,108 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import org.apache.commons.io.IOUtils;
+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.impl.client.CloseableHttpClient;
+import org.apache.http.util.EntityUtils;
+
+import cn.com.qmth.examcloud.commons.base.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.base.logging.ExamCloudLogFactory;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ */
+public class HttpClientUtil {
+	private static final ExamCloudLog LOGGER = ExamCloudLogFactory.getLog(HttpClientUtil.class);
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param url
+	 * @param data
+	 * @param timeout
+	 * @return
+	 */
+	public static String post(String url, String data, int timeout) {
+		StringBuilder responseBuilder = null;
+		BufferedReader reader = null;
+		OutputStreamWriter wr = null;
+
+		URL u = null;
+		try {
+			u = new URL(url);
+			HttpURLConnection conn = (HttpURLConnection) u.openConnection();
+
+			conn.setDoOutput(true);
+			conn.setDoInput(true);
+
+			conn.setRequestMethod("POST");
+
+			if (null != data) {
+				conn.setConnectTimeout(timeout);
+				wr = new OutputStreamWriter(conn.getOutputStream());
+
+				wr.write(data);
+				wr.flush();
+			}
+
+			reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
+			responseBuilder = new StringBuilder();
+			String line = null;
+			while ((line = reader.readLine()) != null) {
+				responseBuilder.append(line).append("\n");
+			}
+			return responseBuilder.toString();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		} finally {
+			IOUtils.closeQuietly(wr);
+			IOUtils.closeQuietly(reader);
+		}
+	}
+
+	/**
+	 * 关闭流
+	 *
+	 * @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);
+			}
+		}
+	}
+
+	public static byte[] get(String url) {
+		CloseableHttpClient httpclient = HttpClientPool.getHttpClient();
+		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);
+		}
+	}
+
+}

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

@@ -0,0 +1,105 @@
+package cn.com.qmth.examcloud.commons.base.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 WANGWEI [QQ:522080330] 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);
+		}
+	}
+
+}

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

@@ -0,0 +1,70 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.lang.reflect.Type;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import cn.com.qmth.examcloud.commons.base.util.DateUtil.DatePatterns;
+
+/**
+ * 类注释
+ *
+ * @author WANGWEI
+ */
+public class JsonUtil {
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param obj
+	 * @return
+	 */
+	public static String toJson(Object obj) {
+		Gson gson = new GsonBuilder().disableHtmlEscaping().setDateFormat(DatePatterns.ISO)
+				.create();
+		return gson.toJson(obj);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param obj
+	 * @return
+	 */
+	public static String toPrettyJson(Object obj) {
+		Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting()
+				.setDateFormat(DatePatterns.ISO).create();
+		return gson.toJson(obj);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param json
+	 * @param c
+	 * @return
+	 */
+	public static <T> T fromJson(String json, Class<T> c) {
+		Gson gson = new GsonBuilder().disableHtmlEscaping().setDateFormat(DatePatterns.ISO)
+				.create();
+		return gson.fromJson(json, c);
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param json
+	 * @param type
+	 * @return
+	 */
+	public static <T> T fromJson(String json, Type type) {
+		Gson gson = new GsonBuilder().disableHtmlEscaping().setDateFormat(DatePatterns.ISO)
+				.create();
+		return gson.fromJson(json, type);
+	}
+
+}

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

@@ -0,0 +1,69 @@
+package cn.com.qmth.examcloud.commons.base.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;
+	}
+
+}

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

@@ -0,0 +1,95 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLDecoder;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * 路径工具
+ *
+ * @author WANGWEI
+ */
+public class PathUtil {
+
+	/**
+	 * 获取标准路径
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 * @return
+	 */
+	public static String getCanonicalPath(String path) {
+		path = path.replaceAll("\\+", "/").replaceAll("/+", "/").replaceAll("/\\./", "/");
+		return StringUtils.replace(path, "/", File.separator);
+	}
+
+	/**
+	 * 获取路径
+	 *
+	 * @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.");
+		}
+	}
+
+}

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

@@ -0,0 +1,266 @@
+package cn.com.qmth.examcloud.commons.base.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.Properties;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+
+import com.google.common.collect.Sets;
+
+import cn.com.qmth.examcloud.commons.base.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.base.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.base.logging.ExamCloudLogFactory;
+
+/**
+ * Properties 工具
+ *
+ * @author WANGWEI
+ */
+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 static boolean initialized = false;
+
+	static {
+		init();
+	}
+
+	/**
+	 * 构造函数
+	 *
+	 */
+	private PropertiesUtil() {
+	}
+
+	/**
+	 * 初始化方法
+	 *
+	 * @author WANGWEI
+	 */
+	public static void init() {
+		if (initialized) {
+			return;
+		}
+		initialized = true;
+		configure(PathUtil.getResoucePath("resource.properties"));
+	}
+
+	/**
+	 * 加载配置文件并观察配置文件
+	 *
+	 * @author WANGWEI
+	 * @param path
+	 */
+	public static synchronized void configure(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 path
+	 * @param props
+	 */
+	public static void loadFromDir(String path, Properties props) {
+		File dir = new File(path + "");
+		if ((!dir.exists()) || (!dir.isDirectory())) {
+			LOG.error("directory [" + path + "] is illegal.");
+			return;
+		}
+
+		LOG.info("Loading all Properties files from path [" + path + "].");
+
+		File[] files = dir.listFiles();
+		if (null == files || 0 == files.length) {
+			return;
+		}
+		for (File file : files) {
+			if (file.isDirectory()) {
+				continue;
+			}
+			if (file.getName().toLowerCase().endsWith(".properties")) {
+				loadFromFile(file, props);
+			}
+		}
+	}
+
+	/**
+	 * @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"));
+			props.load(reader);
+		} 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;
+		}
+	}
+
+	/**
+	 * 设置属性
+	 *
+	 * @author WANGWEI
+	 * @param key
+	 * @param value
+	 */
+	public static void setProperty(String key, String value) {
+		PROPS.setProperty(key, value);
+	}
+
+}

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

@@ -0,0 +1,103 @@
+package cn.com.qmth.examcloud.commons.base.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
src/main/java/cn/com/qmth/examcloud/commons/base/util/ResourceLoader.java

@@ -0,0 +1,67 @@
+package cn.com.qmth.examcloud.commons.base.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
src/main/java/cn/com/qmth/examcloud/commons/base/util/SHA256.java

@@ -0,0 +1,43 @@
+package cn.com.qmth.examcloud.commons.base.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
src/main/java/cn/com/qmth/examcloud/commons/base/util/SecurityUtil.java

@@ -0,0 +1,124 @@
+package cn.com.qmth.examcloud.commons.base.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());
+		}
+	}
+
+}

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

@@ -0,0 +1,337 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.util.List;
+
+/**
+ *
+ * @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 RuntimeException(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();
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static Boolean isInteger(String s) {
+		try {
+			Integer.parseInt(s);
+			return true;
+		} catch (NumberFormatException e) {
+			return false;
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @param s
+	 * @return
+	 */
+	public static Boolean isLong(String s) {
+		try {
+			Long.parseLong(s);
+			return true;
+		} catch (NumberFormatException e) {
+			return false;
+		}
+	}
+
+}

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

@@ -0,0 +1,109 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.google.common.collect.Maps;
+
+/**
+ * 线程本地化工具
+ *
+ * @author WANGWEI
+ */
+public class ThreadLocalUtil {
+	/**
+	 * 属性注释
+	 */
+	private static final ThreadLocal<String> LOCAL_TRACE_ID = new ThreadLocal<String>() {
+		@Override
+		public String initialValue() {
+			return null;
+		}
+	};
+
+	/**
+	 * 属性注释
+	 */
+	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;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @author WANGWEI
+	 * @return
+	 */
+	public static String getTraceID() {
+		String traceID = LOCAL_TRACE_ID.get();
+		if (traceID == null) {
+			traceID = next();
+		} else {
+			return traceID;
+		}
+
+		return traceID;
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @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);
+	}
+}

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

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

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

@@ -0,0 +1,127 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 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);
+			}
+			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);
+		}
+	}
+
+	/**
+	 * 方法注释
+	 *
+	 * @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];
+			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();
+	}
+
+}

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

@@ -0,0 +1,74 @@
+package cn.com.qmth.examcloud.commons.base.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
+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 WANGWEI [QQ:522080330] 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) {
+		ByteArrayOutputStream os = null;
+		PrintWriter printWriter = null;
+		try {
+			os = new ByteArrayOutputStream();
+			printWriter = new PrintWriter(os);
+			e.printStackTrace(printWriter);
+			return printWriter.toString();
+		} catch (Exception ex) {
+			throw new RuntimeException(ex);
+		} finally {
+			IOUtils.closeQuietly(printWriter);
+			IOUtils.closeQuietly(os);
+		}
+	}
+
+}

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

@@ -0,0 +1,147 @@
+package cn.com.qmth.examcloud.commons.base.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);
+		}
+
+	}
+
+}