Kaynağa Gözat

merge from release_v4.0.2

deason 4 yıl önce
ebeveyn
işleme
a392bb32ae
40 değiştirilmiş dosya ile 2422 ekleme ve 1046 silme
  1. 24 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/DataRuleType.java
  2. 14 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/User.java
  3. 68 0
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/UserDataRule.java
  4. 26 24
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/enums/RoleMeta.java
  5. 1 1
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/util/PropertiesUtil.java
  6. 5 6
      examcloud-config-center-starter/src/main/java/cn/com/qmth/examcloud/config/center/configuration/ExamCloudWebMvcConfigurer.java
  7. 1 1
      examcloud-java-sdk/src/main/java/cn/com/qmth/sdk/util/PropertiesUtil.java
  8. 0 19
      examcloud-parent/.gitignore
  9. 0 0
      examcloud-parent/README.md
  10. 52 0
      examcloud-reports-commons/src/main/java/cn/com/qmth/examcloud/reports/commons/bean/AdminOperateReport.java
  11. 6 1
      examcloud-reports-commons/src/main/java/cn/com/qmth/examcloud/reports/commons/enums/Tag.java
  12. 4 1
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/enums/ExamProperties.java
  13. 61 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ColumnSetting.java
  14. 35 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelCell.java
  15. 48 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelError.java
  16. 98 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelErrorType.java
  17. 337 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelExportUtil.java
  18. 34 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelProperty.java
  19. 7 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelReaderHandle.java
  20. 29 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelRow.java
  21. 38 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelSheet.java
  22. 14 1
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/helper/IdentityNumberHelper.java
  23. 397 0
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/util/FileDisposeUtil.java
  24. BIN
      examcloud-support/src/main/resources/exceloriginal/exceloriginal.zip
  25. 295 301
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/bootstrap/AppBootstrap.java
  26. 1 1
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/bootstrap/PropertyHolder.java
  27. 54 49
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/enums/HttpServletRequestAttribute.java
  28. 93 115
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/FirstInterceptor.java
  29. 6 11
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/SeqlockInterceptor.java
  30. 6 10
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/redis/CustomRedisConfiguration.java
  31. 142 141
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/redis/SimpleRedisClient.java
  32. 22 0
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/DataRule.java
  33. 105 0
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/DataRuleInterceptor.java
  34. 3 3
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/RequestPermissionInterceptor.java
  35. 39 28
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/ResourceManager.java
  36. 3 3
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/RpcInterceptor.java
  37. 4 1
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/ControllerAspect.java
  38. 48 0
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/ControllerSupport.java
  39. 224 242
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/CustomExceptionHandler.java
  40. 78 87
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/ServletUtil.java

+ 24 - 0
examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/enums/DataRuleType.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.examcloud.api.commons.enums;
+
+/**
+ * 数据权限规则类型
+ */
+public enum DataRuleType {
+
+    ORG("学习中心权限"),
+
+    EXAM("考试权限"),
+
+    COURSE("课程权限");
+
+    DataRuleType(String desc) {
+        this.desc = desc;
+    }
+
+    private String desc;
+
+    public String getDesc() {
+        return desc;
+    }
+
+}

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

@@ -75,6 +75,11 @@ public class User implements JsonSerializable {
 	 * 会话失效时长
 	 */
 	private Integer sessionTimeout;
+	
+	/**
+	 * 密码虚弱
+	 */
+	private Boolean passwordWeak;
 
 	/**
 	 * 构建key
@@ -184,4 +189,13 @@ public class User implements JsonSerializable {
 		this.sessionTimeout = sessionTimeout;
 	}
 
+	public Boolean getPasswordWeak() {
+		return passwordWeak;
+	}
+
+	public void setPasswordWeak(Boolean passwordWeak) {
+		this.passwordWeak = passwordWeak;
+	}
+
+	
 }

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

@@ -0,0 +1,68 @@
+package cn.com.qmth.examcloud.api.commons.security.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class UserDataRule implements JsonSerializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * “默认全部”状态
+     */
+    private boolean globalStatus;
+
+    /**
+     * 关联ID列表
+     */
+    private Set<Long> refIds;
+
+    /**
+     * 断言“当前查询”是否返回空的数据结果集
+     */
+    public boolean assertEmptyQueryResult() {
+        if (!globalStatus && this.getRefIds().isEmpty()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * 断言“refIds”是否作为查询条件
+     */
+    public boolean assertNeedQueryRefIds() {
+        if (!globalStatus && !this.getRefIds().isEmpty()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public boolean getGlobalStatus() {
+        return globalStatus;
+    }
+
+    public void setGlobalStatus(boolean globalStatus) {
+        this.globalStatus = globalStatus;
+    }
+
+    public Set<Long> getRefIds() {
+        if (refIds == null) {
+            return new HashSet<>();
+        }
+        return refIds;
+    }
+
+    public void setRefIds(Set<Long> refIds) {
+        this.refIds = refIds;
+    }
+
+    @Override
+    public String toString() {
+        return "globalStatus = " + globalStatus + ", refIds = " + refIds;
+    }
+
+}

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

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

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

@@ -167,7 +167,7 @@ public class PropertiesUtil {
 			return value.trim();
 		} else {
 			if (LOG.isDebugEnabled()) {
-				LOG.debug("No  property key named [" + key + "] is configured.");
+				LOG.debug("No property value, key = " + key);
 			}
 			return null;
 		}

+ 5 - 6
examcloud-config-center-starter/src/main/java/cn/com/qmth/examcloud/config/center/configuration/ExamCloudWebMvcConfigurer.java

@@ -1,11 +1,10 @@
 package cn.com.qmth.examcloud.config.center.configuration;
 
+import cn.com.qmth.examcloud.web.interceptor.FirstInterceptor;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
-import cn.com.qmth.examcloud.web.interceptor.FirstInterceptor;
-
 /**
  * WebMvcConfigurer
  *
@@ -16,9 +15,9 @@ import cn.com.qmth.examcloud.web.interceptor.FirstInterceptor;
 @Configuration
 public class ExamCloudWebMvcConfigurer implements WebMvcConfigurer {
 
-	@Override
-	public void addInterceptors(InterceptorRegistry registry) {
-		registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**");
-	}
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(new FirstInterceptor()).addPathPatterns("/**");
+    }
 
 }

+ 1 - 1
examcloud-java-sdk/src/main/java/cn/com/qmth/sdk/util/PropertiesUtil.java

@@ -158,7 +158,7 @@ public class PropertiesUtil {
 			return value.trim();
 		} else {
 			if (LOG.isDebugEnabled()) {
-				LOG.debug("No  property key named [" + key + "] is configured.");
+				LOG.debug("No property value, key = " + key);
 			}
 			return null;
 		}

+ 0 - 19
examcloud-parent/.gitignore

@@ -1,19 +0,0 @@
-*.class
-
-# Proguard folder generated by ide
-.project
-.classpath
-.settings
-target/
-.idea/
-*.iml
-
-# Log Files
-*.log
-*.class
-
-
-# Package Files #
-*.jar
-*.war
-*.ear

+ 0 - 0
examcloud-parent/README.md


+ 52 - 0
examcloud-reports-commons/src/main/java/cn/com/qmth/examcloud/reports/commons/bean/AdminOperateReport.java

@@ -0,0 +1,52 @@
+package cn.com.qmth.examcloud.reports.commons.bean;
+
+import cn.com.qmth.examcloud.reports.commons.enums.Tag;
+import cn.com.qmth.examcloud.reports.commons.enums.Topic;
+
+public class AdminOperateReport extends BaseReport {
+	private Long operateUserId;
+	private String operate;
+	private String operateInfo;
+	private Long rootOrgId;
+
+
+	public AdminOperateReport(Long rootOrgId,Long operateUserId,String operate,String operateInfo) {
+		super();
+		this.rootOrgId = rootOrgId;
+		this.operateUserId = operateUserId;
+		this.operate = operate;
+		this.operateInfo=operateInfo;
+		this.topic = Topic.REPORT_TOPIC.getCode();
+		this.tag = Tag.ADMIN_OPERATE_INFO.getCode();
+	}
+	public AdminOperateReport() {
+		super();
+	}
+	public Long getOperateUserId() {
+		return operateUserId;
+	}
+	public void setOperateUserId(Long operateUserId) {
+		this.operateUserId = operateUserId;
+	}
+	public String getOperate() {
+		return operate;
+	}
+	public void setOperate(String operate) {
+		this.operate = operate;
+	}
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+	public String getOperateInfo() {
+		return operateInfo;
+	}
+	public void setOperateInfo(String operateInfo) {
+		this.operateInfo = operateInfo;
+	}
+
+	
+
+}

+ 6 - 1
examcloud-reports-commons/src/main/java/cn/com/qmth/examcloud/reports/commons/enums/Tag.java

@@ -25,7 +25,12 @@ public enum Tag {
 	/**
 	 * 考试过程记录
 	 */
-	EXAM_PROCESS_RECORD("EXAM_PROCESS_RECORD","EXAM_PROCESS_RECORD_GROUP","考试过程记录")
+	EXAM_PROCESS_RECORD("EXAM_PROCESS_RECORD","EXAM_PROCESS_RECORD_GROUP","考试过程记录"),
+	
+	/**
+	 * 后台用户操作日志
+	 */
+	ADMIN_OPERATE_INFO("ADMIN_OPERATE_INFO","ADMIN_OPERATE_INFO_GROUP","后台用户操作日志"),
 	;
 
 	// ===========================================================================

+ 4 - 1
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/enums/ExamProperties.java

@@ -50,7 +50,10 @@ public enum ExamProperties {
 	IS_STRANGER_ENABLE("是否启用陌生人检测"),
 	LIMITED_IF_NO_SPECIAL_SETTINGS("无特殊设置时禁止考试"),
 	WEIXIN_ANSWER_ENABLED("是否开放微信小程序作答"),
-	APP_EXAM_ENABLED("是否开放app考试");
+	APP_EXAM_ENABLED("是否开放app考试"),
+	EXAM_CYCLE_ENABLED("是否启用考试周期设置"),
+	EXAM_CYCLE_WEEK("考试周期星期设置"),
+	EXAM_CYCLE_TIME_RANGE("考试周期时间段设置");
 
 	private ExamProperties(String desc){
 		this.desc = desc;

+ 61 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ColumnSetting.java

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

+ 35 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelCell.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.support.excel;
+
+public class ExcelCell {
+
+	private String r;
+
+	private String s;
+	private String v;
+
+	public String getR() {
+		return r;
+	}
+
+	public void setR(String r) {
+		this.r = r;
+	}
+
+	public String getS() {
+		return s;
+	}
+
+	public void setS(String s) {
+		this.s = s;
+	}
+
+
+	public String getV() {
+		return v;
+	}
+
+	public void setV(String v) {
+		this.v = v;
+	}
+
+}

+ 48 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelError.java

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

+ 98 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelErrorType.java

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

+ 337 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelExportUtil.java

@@ -0,0 +1,337 @@
+package cn.com.qmth.examcloud.support.excel;
+
+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.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FileUtils;
+
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.util.UUID;
+import cn.com.qmth.examcloud.support.util.FileDisposeUtil;
+
+/*
+ * excel导出工具
+ */
+public class ExcelExportUtil {
+	
+
+	private static final String TEMP_FILE_EXP = "excelExport/";
+	
+	private static final String DEFALUT_CONTENT_TYPE = "application/vnd.ms-excel";
+
+	private static final String DEFALUT_EXT = ".xlsx";
+
+	private static final String ZIP_SUFFIX = ".zip";
+
+
+	public static void exportExcel(String fileName, Class<?> dataClass, List<?> dataset,
+			HttpServletResponse response) throws Exception {
+		File directory = new File(TEMP_FILE_EXP + File.separator + UUID.randomUUID());
+		ServletOutputStream outputStream = null;
+		InputStream in=null;
+		try {
+			response.setHeader("Content-Disposition",
+					"inline;filename=" + URLEncoder.encode(fileName, "UTF-8") + DEFALUT_EXT);
+			response.setContentType(DEFALUT_CONTENT_TYPE);
+			outputStream=response.getOutputStream();
+			File file=createExcelFile(UUID.randomUUID(), dataClass, dataset, directory);
+			in=new FileInputStream(file);
+			int len=0;
+			byte[] buffer=new byte[1024];
+			while((len=in.read(buffer))>0){
+				outputStream.write(buffer,0,len);
+			}
+		}finally {
+			if(in!=null)in.close();
+			if(outputStream!=null)outputStream.close();
+			FileUtils.deleteDirectory(directory);
+		}
+	}
+	
+	public static void exportExcel(Class<?> dataClass, List<?> dataset,OutputStream outputStream) throws Exception {
+		File directory = new File(TEMP_FILE_EXP + File.separator + UUID.randomUUID());
+		InputStream in=null;
+		try {
+			File file=createExcelFile(UUID.randomUUID(), dataClass, dataset, directory);
+			in=new FileInputStream(file);
+			int len=0;
+			byte[] buffer=new byte[1024];
+			while((len=in.read(buffer))>0){
+				outputStream.write(buffer,0,len);
+			}
+		}finally {
+			if(in!=null)in.close();
+			if(outputStream!=null)outputStream.close();
+			FileUtils.deleteDirectory(directory);
+		}
+	}
+
+
+	private static File createExcelFile(String fileName,Class<?> dataClass, List<?> dataset, File directory) throws Exception{
+		File excelTargetDir = new File(directory.getAbsolutePath() + "/excel/");
+		File exceloriginalZip=new File(directory.getAbsolutePath() + "/excel/exceloriginal.zip");
+		File excelfile=null;
+		try {
+			excelTargetDir.mkdirs();
+			exceloriginalZip.createNewFile();
+			InputStream is=ExcelExportUtil.class.getClass().getResourceAsStream("/exceloriginal/exceloriginal.zip");
+			FileUtils.copyInputStreamToFile(is, exceloriginalZip);
+			FileDisposeUtil.unZip(excelTargetDir, exceloriginalZip);
+			exceloriginalZip.delete();
+			dispose(excelTargetDir, dataClass,dataset);
+			File zipfile = new File(
+					excelTargetDir.getParentFile().getAbsolutePath() + File.separator + fileName + ZIP_SUFFIX);
+			FileDisposeUtil.createZip(excelTargetDir.getAbsolutePath(), zipfile.getAbsolutePath());
+			excelfile = new File(
+					excelTargetDir.getParentFile().getAbsolutePath() + File.separator + fileName + DEFALUT_EXT);
+			zipfile.renameTo(excelfile);
+		}finally {
+//			try {
+//				FileUtils.deleteDirectory(excelTargetDir);
+//			} catch (IOException e) {
+//			}
+		}
+		return excelfile;
+
+	}
+
+	private static void dispose(File excelTargetDir, Class<?> dataClass, List<?> dataset)
+			throws IOException, NoSuchMethodException, SecurityException,
+			IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+		//excel列名A B C AA AB
+		List<String> colName=new ArrayList<String>();
+		//单元格值
+		Map<String,Integer> vmap=new LinkedHashMap<String, Integer>();
+		int vmapSize=0;
+		ExcelSheet sheet=new ExcelSheet();
+		int index = 1;//第一行开始
+		//处理头
+		List<ColumnSetting> columnSettings = getColumnSettings(dataClass);
+		ExcelRow headrow=new ExcelRow();
+		headrow.setR(String.valueOf(index));
+		for(int i=0;i<columnSettings.size();i++) {
+			colName.add(getExcelColumnName(i));
+			ExcelCell cell=new ExcelCell();
+			cell.setR(colName.get(i)+index);
+			cell.setS("1");
+			Integer cellV=vmap.get(columnSettings.get(i).getHeader());
+			if(cellV==null) {
+				vmapSize++;
+				cellV=vmapSize;
+				vmap.put(columnSettings.get(i).getHeader(), cellV);
+			}
+			cell.setV(cellV.toString());
+			headrow.addCell(cell);
+		}
+		colName.add(getExcelColumnName(colName.size()));
+		sheet.addRow(headrow);
+		sheet.setDimension(colName.get(0)+1+":"+colName.get(colName.size()-1)+(dataset.size()+1));
+		File sheetFile=writeSheetHead(excelTargetDir, sheet, columnSettings);
+		//处理数据
+		for(int k=0;k<dataset.size();k++) {
+			index++;
+        	Object obj=dataset.get(k);
+            ExcelRow row=new ExcelRow();
+            row.setR(String.valueOf(index));
+            // 利用反射,根据javabean属性的先后顺序,动态调用getXxx()方法得到属性值
+            for (short i = 0; i < columnSettings.size(); i++) {
+                String methodName = columnSettings.get(i).getGetMethodName();
+                Method method = dataClass.getMethod(methodName, new Class[]{});
+                Object value = method.invoke(obj, new Object[] {});
+                ExcelCell cell=new ExcelCell();
+                cell.setR(colName.get(i)+index);
+                cell.setS("0");
+                if(value!=null) {
+	    			Integer cellV=vmap.get(value.toString());
+	    			if(cellV==null) {
+	    				vmapSize++;
+	    				cellV=vmapSize;
+	    				vmap.put(value.toString(), cellV);
+	    			}
+	    			cell.setV(cellV.toString());
+                }else {
+                	cell.setV(String.valueOf(0));
+                }
+                row.addCell(cell);
+            }
+            sheet.addRow(row);
+            //早点回收内存
+            dataset.set(k, null);
+            if(sheet.getRowCount()>=10000) {
+            	writeSheetBody(sheetFile, sheet);
+            	sheet.clearRows();
+            }
+        }
+		if(sheet.getRowCount()>0) {
+        	writeSheetBody(sheetFile, sheet);
+        	sheet.clearRows();
+        }
+		writeSheetTail(sheetFile);
+        
+        writeSharedStrings(excelTargetDir, vmap,index*(colName.size()-1));
+        vmap=null;
+	}
+
+	private static void writeSharedStrings(File excelTargetDir, Map<String,Integer> vmap,Integer count) throws IOException {
+		File file = new File(excelTargetDir.getAbsolutePath() + "/xl/sharedStrings.xml");
+		FileOutputStream outputStream = null;
+        try {
+            file.createNewFile();//创建文件
+            outputStream = new FileOutputStream(file,true);
+            String data1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+            outputStream.write(data1.getBytes("utf-8"));
+            String data2 = "<sst count=\""+count+"\" uniqueCount=\""+(vmap.size()+1)+"\"\n";
+            outputStream.write(data2.getBytes("utf-8"));
+            String data3="xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\"><si><t/></si>\n";
+            outputStream.write(data3.getBytes("utf-8"));
+            for(String k:vmap.keySet()) {
+            	String data4="<si><t>"+k+"</t></si>\n";
+            	outputStream.write(data4.getBytes("utf-8"));
+            }
+            String data5="</sst>";
+        	outputStream.write(data5.getBytes("utf-8"));
+        	outputStream.flush();
+        } catch (Exception e) {
+        	throw new ExamCloudRuntimeException(e);
+        } finally {
+            outputStream.close();
+        }
+	}
+	private static File writeSheetHead(File excelTargetDir, ExcelSheet sheet,List<ColumnSetting> columnSettings) throws IOException {
+		File file = new File(excelTargetDir.getAbsolutePath() + "/xl/worksheets/sheet1.xml");
+		FileOutputStream outputStream = null;
+        try {
+            outputStream = new FileOutputStream(file,true);
+            String data1 = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
+            outputStream.write(data1.getBytes("utf-8"));
+            String data2 = "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">\n";
+            outputStream.write(data2.getBytes("utf-8"));
+            String data3="<dimension ref=\""+sheet.getDimension()+"\"/>\n";
+            outputStream.write(data3.getBytes("utf-8"));
+            String data4="<sheetViews><sheetView workbookViewId=\"0\" tabSelected=\"true\"/></sheetViews><sheetFormatPr defaultRowHeight=\"15.0\" baseColWidth=\"15\"/><cols>\n";
+            outputStream.write(data4.getBytes("utf-8"));
+            int index=0;
+            for(ColumnSetting col:columnSettings) {
+            	index++;
+            	if(col.getWidth()!=0) {
+            		String data10 = "<col min=\""+index+"\" max=\""+index+"\" width=\""+col.getWidth()+"\" customWidth=\"true\"/>\n";
+                    outputStream.write(data10.getBytes("utf-8"));
+            	}else {
+            		String data10 = "<col min=\""+index+"\" max=\""+index+"\" width=\""+15+"\" customWidth=\"true\"/>\n";
+	                outputStream.write(data10.getBytes("utf-8"));
+            	}
+            }
+            String data11 = "</cols><sheetData>\n";
+            outputStream.write(data11.getBytes("utf-8"));
+        	outputStream.flush();
+        	return file;
+        } catch (Exception e) {
+        	throw new ExamCloudRuntimeException(e);
+        } finally {
+            outputStream.close();
+        }
+	}
+	private static void writeSheetBody(File file, ExcelSheet sheet) throws IOException {
+		FileOutputStream outputStream = null;
+        try {
+            outputStream = new FileOutputStream(file,true);
+            for(ExcelRow row:sheet.getRows()) {
+            	String data5="<row r=\""+row.getR()+"\">\n";
+            	outputStream.write(data5.getBytes("utf-8"));
+	            for(ExcelCell cell:row.getCells()) {
+	            	String data6="<c r=\""+cell.getR()+"\" s=\""+cell.getS()+"\" t=\"s\"><v>"+cell.getV()+"</v></c>\n";
+	            	outputStream.write(data6.getBytes("utf-8"));
+	            }
+	            String data7="</row>\n";
+            	outputStream.write(data7.getBytes("utf-8"));
+            	row=null;
+            }
+        	outputStream.flush();
+        } catch (Exception e) {
+        	throw new ExamCloudRuntimeException(e);
+        } finally {
+            outputStream.close();
+        }
+	}
+	
+	private static void writeSheetTail(File file) throws IOException {
+		FileOutputStream outputStream = null;
+        try {
+            outputStream = new FileOutputStream(file,true);
+            String data7="</sheetData><pageMargins bottom=\"0.75\" footer=\"0.3\" header=\"0.3\" left=\"0.7\" right=\"0.7\" top=\"0.75\"/></worksheet>";
+        	outputStream.write(data7.getBytes("utf-8"));
+        	outputStream.flush();
+        } catch (Exception e) {
+        	throw new ExamCloudRuntimeException(e);
+        } finally {
+            outputStream.close();
+        }
+	}
+	
+	 /**
+     * 提取ExcelProperty注解类的字段信息
+     * @param dataClass 需要解析 写入excel的数据类型
+     * @return
+     */
+    private static List<ColumnSetting> getColumnSettings(Class<?> dataClass){
+        List<ColumnSetting> columnSettings = new ArrayList<>();
+        //先在方法上找ExcelProperty注解
+        Method[] methods = dataClass.getDeclaredMethods();
+        for(Method method : methods){
+            ExcelProperty exportProperty = method.getAnnotation(ExcelProperty.class);
+            if(exportProperty != null && exportProperty.name().trim().length() > 0){
+                ColumnSetting columnSetting = new ColumnSetting(exportProperty.name(),method.getName(),
+                        exportProperty.width(),exportProperty.index());
+                columnSettings.add(columnSetting);
+            }
+        }
+        //如果方法上找不到注解,再到属性上找 
+        if(columnSettings.size() == 0){
+        	Field[] fields = dataClass.getDeclaredFields();
+        	for(Field field:fields){
+        		ExcelProperty exportProperty = field.getAnnotation(ExcelProperty.class);
+        		if(exportProperty != null && exportProperty.name().trim().length() > 0){
+                    ColumnSetting columnSetting = new ColumnSetting(exportProperty.name(),"get"+toUpperCaseFirstOne(field.getName()),
+                            exportProperty.width(),exportProperty.index());
+                    columnSettings.add(columnSetting);
+                }
+        	}
+        }
+        Collections.sort(columnSettings);
+        return columnSettings;
+    }
+    
+    private static String toUpperCaseFirstOne(String s){
+	  if(Character.isUpperCase(s.charAt(0)))
+	    return s;
+	  else
+	    return (new StringBuilder()).append(Character.toUpperCase(s.charAt(0))).append(s.substring(1)).toString();
+	}
+    
+    private static String getExcelColumnName(int celNum) {
+		int num = celNum+1;//celNum是从0算起
+		String tem = "";
+		while(num > 0) {
+			int lo = (num - 1) % 26;//取余,A到Z是26进制,
+			tem = (char)(lo + 'A') + tem;
+			num = (num - 1) / 26;//取模
+		}
+		return tem;
+	}
+}

+ 34 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelProperty.java

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

+ 7 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelReaderHandle.java

@@ -0,0 +1,7 @@
+package cn.com.qmth.examcloud.support.excel;
+
+public interface ExcelReaderHandle {
+	
+	ExcelError handle(Object dto);
+
+}

+ 29 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelRow.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.support.excel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExcelRow {
+	private String r;
+	private List<ExcelCell> cells=new ArrayList<ExcelCell>();
+
+	public List<ExcelCell> getCells() {
+		return cells;
+	}
+
+	public void setCells(List<ExcelCell> cells) {
+		this.cells = cells;
+	}
+
+	public String getR() {
+		return r;
+	}
+
+	public void setR(String r) {
+		this.r = r;
+	}
+	
+	public void addCell(ExcelCell cell) {
+		cells.add(cell);
+	}
+}

+ 38 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/excel/ExcelSheet.java

@@ -0,0 +1,38 @@
+package cn.com.qmth.examcloud.support.excel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ExcelSheet {
+	private String dimension;
+	private List<ExcelRow> rows=new ArrayList<ExcelRow>();
+
+	public String getDimension() {
+		return dimension;
+	}
+
+	public void setDimension(String dimension) {
+		this.dimension = dimension;
+	}
+
+	public List<ExcelRow> getRows() {
+		return rows;
+	}
+
+	public void setRows(List<ExcelRow> rows) {
+		this.rows = rows;
+	}
+	
+	public void addRow(ExcelRow row) {
+		rows.add(row);
+	}
+	
+	public void clearRows() {
+		rows=new ArrayList<ExcelRow>();
+	}
+	
+	public int getRowCount() {
+		return rows.size();
+	}
+
+}

+ 14 - 1
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/helper/IdentityNumberHelper.java

@@ -33,6 +33,19 @@ public class IdentityNumberHelper {
 
         return replaceStrToSymbol(identityNumber, startIndex, identityNumber.length() - 2, "*");
     }
+    
+    public static String conceal(String identityNumber) {
+        if (StringUtils.isBlank(identityNumber)) {
+            return identityNumber;
+        }
+        if (identityNumber.length() <= 2) {
+            return identityNumber;
+        }
+
+        int startIndex = (identityNumber.length() - 8) < 0 ? 0 : (identityNumber.length() - 8);
+
+        return replaceStrToSymbol(identityNumber, startIndex, identityNumber.length() - 2, "*");
+    }
 
     /**
      * 将字符串内指定字符替换为特殊符号(包头不包尾)
@@ -64,7 +77,7 @@ public class IdentityNumberHelper {
         return sb.toString();
     }
 
-    private static boolean identityNumbeConceal(Long rootOrgId) {
+    public static boolean identityNumbeConceal(Long rootOrgId) {
         OrgPropertyCacheBean orgProperty = CacheHelper.getOrgProperty(rootOrgId, Constants.ID_NUMBER_PRIVATE_MODE_KEY);
 
         if (orgProperty == null) {

+ 397 - 0
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/util/FileDisposeUtil.java

@@ -0,0 +1,397 @@
+package cn.com.qmth.examcloud.support.util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+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 java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+
+
+/**
+ * @author chenken
+ * @date 2017年7月17日 上午9:36:32
+ * @company QMTH
+ * @description FileUtil.java
+ */
+public class FileDisposeUtil {
+	
+	/**
+	 * 将网络文件保存到本地
+	 * @param fileUrl		   网络文件URL
+	 * @param localFilePath  例如D:/123.txt
+	 * @return
+	 */
+	public static boolean saveUrlAs(String fileUrl,String localFilePath) {
+		HttpURLConnection connection = null;
+		FileOutputStream fileOutputStream = null;
+		DataOutputStream dataOutputStream = null;
+		DataInputStream dataInputStream = null;
+		try {
+			URL url = new URL(fileUrl);
+			connection = (HttpURLConnection) url.openConnection();
+			dataInputStream = new DataInputStream(connection.getInputStream());
+			fileOutputStream = new FileOutputStream(localFilePath);
+			dataOutputStream = new DataOutputStream(fileOutputStream);
+			byte[] buffer = new byte[4096];
+			int count = 0;
+			while ((count = dataInputStream.read(buffer)) > 0) {
+				dataOutputStream.write(buffer, 0, count);
+			}
+			return true;
+		} catch (Exception e) {
+			return false;
+		}finally {
+			try {
+				if(fileOutputStream!=null){
+					fileOutputStream.flush();
+					fileOutputStream.close();
+					fileOutputStream = null;
+				}
+				if (dataOutputStream != null) {
+					dataOutputStream.flush();
+					dataOutputStream.close();
+					dataOutputStream = null;
+				}
+				if (dataInputStream != null) {
+					dataInputStream.close();
+					dataInputStream = null;
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+			if (connection != null) {
+				connection.disconnect();
+				connection = null;
+			}
+		}
+	}
+	
+	/**
+	 * 下载服务器上的文件
+	 * @param filename		文件名称
+	 * @param fullFilePath	文件全路径
+	 * @param response
+	 */
+	public static void downloadFile(String filename,String fullFilePath,HttpServletResponse response){
+		FileInputStream input = null;
+		OutputStream output = null;
+		try {
+	        //设置文件MIME类型  
+			response.setContentType(getContentType(filename));
+	        response.setHeader("Content-Disposition", "attachment;filename="+URLEncoder.encode(filename,"UTF-8"));
+	        //读取目标文件,通过response将目标文件写到客户端  
+	        input =  new FileInputStream(fullFilePath);
+			output =  response.getOutputStream();  
+	        //写文件  
+			byte[] b = new byte[2048];
+            int len;
+            while ((len = input.read(b)) != -1) {
+            	output.write(b, 0, len);
+            } 
+            response.setHeader("Content-Length", String.valueOf(input.getChannel().size()));
+	        input.close();  
+		} catch (FileNotFoundException e) {
+			e.printStackTrace();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+	/**
+	 * 获得文件MIME类型
+	 * @param filename
+	 * @return
+	 */
+	public static String getContentType(String filename){
+		String type = null;
+		Path path = Paths.get(filename);
+		try {
+			type = Files.probeContentType(path);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return type;
+	}
+
+	/**
+	 * 将存放在sourceFilePath目录下的源文件,打包成fileName名称的zip文件,并存放到zipFilePath路径下
+	 * 
+	 * @param sourceFilePath
+	 *            :待压缩的文件夹路径
+	 * @param zipFilePath
+	 *            :压缩后zip文件的存放路径
+	 * @param fileName
+	 *            :zip文件的名称
+	 * @return
+	 */
+	public static boolean fileToZip(String sourceFilePath, String zipFilePath,String fileName) {
+		boolean flag = false;
+		File sourceFile = new File(sourceFilePath);
+		FileInputStream fis = null;
+		BufferedInputStream bis = null;
+		FileOutputStream fos = null;
+		ZipOutputStream zos = null;
+		if (sourceFile.exists() == false) {
+			throw new RuntimeException("待压缩的文件目录:" + sourceFilePath + "不存在.");
+		} else {
+			try {
+				File zipFile = new File(zipFilePath+File.separator+fileName+".zip");
+				if (zipFile.exists()) {
+					throw new RuntimeException(zipFilePath + "目录下存在名字为:"+fileName+".zip"+"打包文件.");
+				} else {
+					File[] sourceFiles = sourceFile.listFiles();
+					if (null == sourceFiles || sourceFiles.length < 1) {
+						throw new RuntimeException("待压缩的文件目录:" + sourceFilePath+ "里面不存在文件,无需压缩.");
+					} else {
+						fos = new FileOutputStream(zipFile);
+						zos = new ZipOutputStream(new BufferedOutputStream(fos));
+						byte[] bufs = new byte[1024 * 10];
+						for (int i = 0; i < sourceFiles.length; i++) {
+							try{
+								//创建ZIP实体,并添加进压缩包
+								String fileEncode = System.getProperty("file.encoding");
+								String name = new String(sourceFiles[i].getName().getBytes(fileEncode),"UTF-8");
+								ZipEntry zipEntry = new ZipEntry(name);
+								zos.putNextEntry(zipEntry);
+								//读取待压缩的文件并写进压缩包里
+								fis = new FileInputStream(sourceFiles[i]);
+								bis = new BufferedInputStream(fis, 1024 * 10);
+								int read = 0;
+								while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
+									zos.write(bufs, 0, read);
+								}
+								zos.flush();
+							}catch(Exception e){
+								e.printStackTrace();
+							}finally{
+								IOUtils.closeQuietly(bis);
+								IOUtils.closeQuietly(fis);
+							}
+						}
+						flag = true;
+					}
+				}
+			} catch (Exception e) {
+				e.printStackTrace();
+			} finally {
+				IOUtils.closeQuietly(bis);
+				IOUtils.closeQuietly(fis);
+				IOUtils.closeQuietly(zos);
+				IOUtils.closeQuietly(fos);
+			}
+		}
+		return flag;
+	}
+	
+	public static void createDirectory(String downloadDirectory) {
+		File directory = new File(downloadDirectory);
+		if(!directory.exists()){
+			directory.mkdirs();
+		}else{
+			FileUtils.deleteQuietly(directory);
+			directory.mkdirs();
+		}
+	}
+	
+	/**
+	 * 获得文件的byte数组
+	 * @param filePath
+	 * @return
+	 * @throws IOException
+	 */
+	public static byte[] getBytes(String filePath) throws IOException{  
+    	File file = new File(filePath);
+        if (!file.exists()) {
+            throw new FileNotFoundException(filePath);
+        }
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(((int) file.length()));
+        BufferedInputStream in = null;
+        try {
+            in = new BufferedInputStream(new FileInputStream(file));
+            int bufSize = 1024;
+            byte[] buffer = new byte[bufSize];
+            int len = 0;
+            while (-1 != (len = in.read(buffer, 0, bufSize))) {
+                bos.write(buffer, 0, len);
+            }
+            return bos.toByteArray();
+        } finally {
+            try {
+                if (in != null) {
+                    in.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            bos.close();
+        }
+    }
+	
+    public static File createZip(String sourceFilePath, String targetFilePath) throws IOException {
+        OutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            File zipfile = new File(targetFilePath);
+            zipfile.deleteOnExit();
+            fos = new FileOutputStream(zipfile);
+            zos = new ZipOutputStream(fos);
+            // zos.setEncoding("utf-8"); // Solve linxu's mess
+            writeZip(new File(sourceFilePath), null, zos);
+            return zipfile;
+        } finally {
+            if (zos != null) {
+                zos.close();
+            }
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+    
+    private static void writeZip(File file, String parentPath, ZipOutputStream zos) throws IOException {
+        if (file.exists()) {
+            ZipEntry ze = null;
+            if (file.isDirectory()) {// Processing folder
+                if (parentPath == null) {
+                    parentPath = "";
+                } else {
+                    parentPath += file.getName() + File.separator;
+                }
+                File[] files = file.listFiles();
+                if (files != null) {
+                    for (File f : files) {
+                        writeZip(f, parentPath, zos);
+                    }
+                } else { // An empty directory creates the current directory
+                    try {
+                        ze = new ZipEntry(parentPath);
+                        // ze.setUnixMode(755);// Solve Linux mess file Settings
+                        // 644 directory Settings 755
+                        zos.putNextEntry(ze);
+
+                        zos.flush();
+                    } finally {
+                        if (zos != null) {
+                            zos.closeEntry();
+                        }
+                    }
+                }
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+                    ze = new ZipEntry(parentPath + file.getName());
+                    // ze.setUnixMode(644);// Solve Linux mess file Settings 644
+                    // directory Settings 755
+                    zos.putNextEntry(ze);
+                    byte[] content = new byte[1024];
+                    int len;
+                    while ((len = fis.read(content)) != -1) {
+                        zos.write(content, 0, len);
+                        zos.flush();
+                    }
+
+                } finally {
+                    if (fis != null) {
+                        fis.close();
+                    }
+                    if (zos != null) {
+                        zos.closeEntry();
+                    }
+                }
+            }
+        }
+    }
+    
+    /**
+     * 解压文件
+     *
+     * @param targetDir 解压目录
+     * @param zipFile   待解压的ZIP文件
+     */
+    public static List<File> unZip(File targetDir, File zipFile) {
+        if (targetDir == null) {
+        	throw new RuntimeException("解压目录不能为空!");
+        }
+
+        if (zipFile == null) {
+        	throw new RuntimeException("待解压的文件不能为空!");
+        }
+
+        if (!zipFile.exists()) {
+        	throw new RuntimeException("待解压的文件不存在!" + zipFile.getAbsolutePath());
+        }
+
+        String zipName = zipFile.getName().toLowerCase();
+        if (zipFile.isDirectory() || zipName.indexOf(".zip") < 0) {
+        	throw new RuntimeException("待解压的文件格式错误!");
+        }
+
+        if (!targetDir.exists()) {
+            targetDir.mkdir();
+        }
+
+        List<File> result = new LinkedList<>();
+
+        try (ZipFile zip = new ZipFile(zipFile, Charset.forName("UTF-8"));) {
+
+            Enumeration entries = zip.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) entries.nextElement();
+
+                //Linux中需要替换掉路径的反斜杠
+                String entryName = (File.separator + entry.getName()).replaceAll("\\\\", "/");
+
+                String filePath = targetDir.getAbsolutePath() + entryName;
+                File target = new File(filePath);
+                if (entry.isDirectory()) {
+                    target.mkdirs();
+                } else {
+                    File dir = target.getParentFile();
+                    if (!dir.exists()) {
+                        dir.mkdirs();
+                    }
+
+                    try (OutputStream os = new FileOutputStream(target);
+                         InputStream is = zip.getInputStream(entry);) {
+                        IOUtils.copy(is, os);
+                        os.flush();
+                    } catch (IOException e) {
+                    	throw new RuntimeException(e.getMessage(), e);
+                    }
+                    result.add(target);
+                }
+            }
+
+        } catch (IOException e) {
+        	throw new RuntimeException(e.getMessage(), e);
+        }
+
+        return result;
+    }
+}

BIN
examcloud-support/src/main/resources/exceloriginal/exceloriginal.zip


+ 295 - 301
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/bootstrap/AppBootstrap.java

@@ -1,10 +1,14 @@
 package cn.com.qmth.examcloud.web.bootstrap;
 
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.Set;
-
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.*;
+import cn.com.qmth.examcloud.web.cloud.AppSelf;
+import cn.com.qmth.examcloud.web.cloud.AppSelfHolder;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import okhttp3.Response;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.ThreadContext;
@@ -12,21 +16,10 @@ import org.springframework.boot.SpringApplication;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.http.HttpStatus;
 
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
-import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
-import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
-import cn.com.qmth.examcloud.commons.util.HttpMethod;
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
-import cn.com.qmth.examcloud.commons.util.OKHttpUtil;
-import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
-import cn.com.qmth.examcloud.commons.util.StringUtil;
-import cn.com.qmth.examcloud.commons.util.Util;
-import cn.com.qmth.examcloud.web.cloud.AppSelf;
-import cn.com.qmth.examcloud.web.cloud.AppSelfHolder;
-import okhttp3.Response;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
 
 /**
  * 系统启动器
@@ -37,286 +30,287 @@ import okhttp3.Response;
  */
 public class AppBootstrap {
 
-	private static ExamCloudLog log = ExamCloudLogFactory.getLog(AppBootstrap.class);
-
-	private static Map<String, String> properties;
-
-	private static String active;
-
-	private static String startupCode;
-
-	private static Long appId;
-
-	private static String secretKey;
-
-	private static String appCode;
-
-	private static String configCenterHost;
-
-	private static String configCenterPort;
-
-	/**
-	 * 启动方法
-	 *
-	 * @author WANGWEI
-	 * @param primarySource
-	 * @param args
-	 * @return
-	 */
-	public static synchronized ConfigurableApplicationContext run(Class<?> primarySource,
-			String... args) {
-
-		ThreadContext.put("TRACE_ID", Thread.currentThread().getName());
-		log.info("Starting...");
-
-		checkBootstrapParams(args);
-
-		sendStartupRequest2ConfigCenter();
-
-		if (null != args) {
-			for (String arg : args) {
-				arg = arg.trim();
-				if (arg.startsWith("--")) {
-					String key = arg.substring(2, arg.indexOf("=")).trim();
-					String value = arg.substring(arg.indexOf("=") + 1).trim();
-					properties.put(key, value);
-				}
-			}
-		}
-
-		// 网卡选择
-		String preferredNetworks = properties.get("examcloud.inet.preferredNetworks");
-		if (StringUtils.isNotBlank(preferredNetworks)) {
-			System.setProperty("spring.cloud.inetutils.preferred-networks", preferredNetworks);
-			properties.put("spring.cloud.inetutils.preferred-networks", preferredNetworks);
-		}
-
-		properties.put("spring.profiles.active", active);
-
-		Set<String> argSet = Sets.newLinkedHashSet();
-		for (Entry<String, String> p : properties.entrySet()) {
-			String key = p.getKey();
-			String value = p.getValue();
-			if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
-				continue;
-			}
-			key = key.trim();
-			value = value.trim();
-			PropertyHolder.setProperty(key, value);
-			PropertiesUtil.setProperty(key, value);
-			String arg = "--" + key + "=" + value;
-			argSet.add(arg);
-		}
-
-		AppSelfHolder.set(buildAppSelf());
-
-		String[] newArgs = argSet.toArray(new String[argSet.size()]);
-		ConfigurableApplicationContext context = null;
-		try {
-			context = SpringApplication.run(primarySource, newArgs);
-			noticeConfigCenter("success");
-		} catch (Exception e) {
-			String stackTrace = Util.getStackTrace(e);
-			noticeConfigCenter("failure:\n" + stackTrace);
-			log.error("fail to run spring app.", e);
-			System.exit(-1);
-		}
-
-		return context;
-	}
-
-	/**
-	 * 创建 {@link AppSelf} 实例
-	 *
-	 * @author WANGWEI
-	 * @return
-	 */
-	private static AppSelf buildAppSelf() {
-		return new AppSelf() {
-
-			@Override
-			public Long getAppId() {
-				return appId;
-			}
-
-			@Override
-			public String getAppCode() {
-				return appCode;
-			}
-
-			@Override
-			public String getSecretKey() {
-				return secretKey;
-			}
-		};
-	}
-
-	/**
-	 * 通知配置中心
-	 *
-	 * @author WANGWEI
-	 * @param message
-	 */
-	private static void noticeConfigCenter(String message) {
-		String url = "http://" + configCenterHost + ":" + configCenterPort + "/configCenter/notice";
-
-		Map<String, String> req = Maps.newHashMap();
-		req.put("active", active);
-		req.put("appCode", appCode);
-		req.put("startupCode", startupCode);
-		req.put("message", message);
-
-		Response resp = null;
-		try {
-			Map<String, String> headers = Maps.newHashMap();
-			headers.put("Trace-Id", startupCode);
-			resp = OKHttpUtil.call(HttpMethod.POST, url.toString(), headers, req);
-		} catch (Exception e) {
-			log.error("fail to notice config center.", e);
-			System.exit(-1);
-		} finally {
-			IOUtils.closeQuietly(resp);
-		}
-	}
-
-	/**
-	 * 向配置中心发送启动请求
-	 *
-	 * @author WANGWEI
-	 */
-	private static void sendStartupRequest2ConfigCenter() {
-		StringBuilder url = new StringBuilder(
-				"http://" + configCenterHost + ":" + configCenterPort + "/configCenter/startup");
-
-		Map<String, String> req = Maps.newHashMap();
-		req.put("active", active);
-		req.put("appCode", appCode);
-		req.put("startupCode", startupCode);
-
-		Response resp = null;
-		try {
-			Map<String, String> headers = Maps.newHashMap();
-			headers.put("Trace-Id", startupCode);
-			resp = OKHttpUtil.call(HttpMethod.POST, url.toString(), headers, req);
-			String body = resp.body().string();
-			if (resp.code() != HttpStatus.OK.value()) {
-				throw new ExamCloudRuntimeException("fail to get configuration. " + body);
-			}
-
-			String encryptedAppId = resp.header("App-Id");
-			String encryptedSecretKey = resp.header("Secret-Key");
-
-			appId = StringUtil.toLong(BootstrapSecurityUtil.decrypt(encryptedAppId, startupCode));
-			secretKey = BootstrapSecurityUtil.decrypt(encryptedSecretKey, startupCode);
-
-			String decryptedBody = BootstrapSecurityUtil.decrypt(body, startupCode);
-			@SuppressWarnings("unchecked")
-			Map<String, String> map = JsonUtil.fromJson(decryptedBody, Map.class);
-			properties = map;
-
-		} catch (Exception e) {
-			log.error("fail to send startup request to config center.", e);
-			System.exit(-1);
-		} finally {
-			IOUtils.closeQuietly(resp);
-		}
-
-	}
-
-	/**
-	 * 检查启动参数
-	 *
-	 * @author WANGWEI
-	 * @param args
-	 * @return
-	 */
-	private static void checkBootstrapParams(String... args) {
-
-		Properties props = new Properties();
-		PropertiesUtil.loadFromResource("application.properties", props);
-
-		if (null != args) {
-			for (String s : args) {
-				s = s.trim();
-				if (s.startsWith("--spring.profiles.active=")) {
-					active = s.substring(s.indexOf("=") + 1);
-				} else if (s.startsWith("--examcloud.startup.startupCode=")) {
-					startupCode = s.substring(s.indexOf("=") + 1);
-				} else if (s.startsWith("--examcloud.startup.configCenterHost=")) {
-					configCenterHost = s.substring(s.indexOf("=") + 1);
-				} else if (s.startsWith("--examcloud.startup.configCenterPort=")) {
-					configCenterPort = s.substring(s.indexOf("=") + 1);
-				} else if (s.startsWith("--examcloud.startup.appCode=")) {
-					appCode = s.substring(s.indexOf("=") + 1);
-				}
-			}
-		}
-
-		// active
-		if (null == active) {
-			String value = props.getProperty("spring.profiles.active");
-			if (StringUtils.isNotBlank(value)) {
-				active = value.trim();
-			}
-		}
-		log.info("active=" + active);
-		if (StringUtils.isBlank(active)) {
-			log.error("property[spring.profiles.active] is not specified");
-			System.exit(-1);
-		}
-
-		// startupCode
-		if (null == startupCode) {
-			String value = props.getProperty("examcloud.startup.startupCode");
-			if (StringUtils.isNotBlank(value)) {
-				startupCode = value.trim();
-			}
-		}
-		log.info("startupCode=" + startupCode);
-		if (StringUtils.isBlank(startupCode)) {
-			log.error("property[examcloud.startup.startupCode] is not specified");
-			System.exit(-1);
-		}
-
-		// appCode
-		if (null == appCode) {
-			String value = props.getProperty("examcloud.startup.appCode");
-			if (StringUtils.isNotBlank(value)) {
-				appCode = value.trim();
-			}
-		}
-		log.info("appCode=" + appCode);
-		if (StringUtils.isBlank(appCode)) {
-			log.error("property[examcloud.startup.appCode] is not specified");
-			System.exit(-1);
-		}
-
-		// configCenterHost
-		if (null == configCenterHost) {
-			String value = props.getProperty("examcloud.startup.configCenterHost");
-			if (StringUtils.isNotBlank(value)) {
-				configCenterHost = value.trim();
-			}
-		}
-		log.info("configCenterHost=" + configCenterHost);
-		if (StringUtils.isBlank(configCenterHost)) {
-			log.error("property[examcloud.startup.configCenterHost] is not specified");
-			System.exit(-1);
-		}
-
-		// configCenterPort
-		if (null == configCenterPort) {
-			String value = props.getProperty("examcloud.startup.configCenterPort");
-			if (StringUtils.isNotBlank(value)) {
-				configCenterPort = value.trim();
-			}
-		}
-		log.info("configCenterPort=" + configCenterPort);
-		if (null == configCenterPort) {
-			log.error("property[examcloud.startup.configCenterPort] is not specified");
-			System.exit(-1);
-		}
-
-	}
+    private static ExamCloudLog log = ExamCloudLogFactory.getLog(AppBootstrap.class);
+
+    private static Map<String, String> properties;
+
+    private static String active;
+
+    private static String startupCode;
+
+    private static Long appId;
+
+    private static String secretKey;
+
+    private static String appCode;
+
+    private static String configCenterHost;
+
+    private static String configCenterPort;
+
+    /**
+     * 启动方法
+     *
+     * @param primarySource
+     * @param args
+     * @return
+     * @author WANGWEI
+     */
+    public static synchronized ConfigurableApplicationContext run(Class<?> primarySource,
+                                                                  String... args) {
+
+        ThreadContext.put("TRACE_ID", Thread.currentThread().getName());
+        log.info("Starting...");
+
+        checkBootstrapParams(args);
+
+        sendStartupRequest2ConfigCenter();
+
+        if (null != args) {
+            for (String arg : args) {
+                arg = arg.trim();
+                if (arg.startsWith("--")) {
+                    String key = arg.substring(2, arg.indexOf("=")).trim();
+                    String value = arg.substring(arg.indexOf("=") + 1).trim();
+                    properties.put(key, value);
+                }
+            }
+        }
+
+        // 网卡选择
+        String preferredNetworks = properties.get("examcloud.inet.preferredNetworks");
+        if (StringUtils.isNotBlank(preferredNetworks)) {
+            System.setProperty("spring.cloud.inetutils.preferred-networks", preferredNetworks);
+            properties.put("spring.cloud.inetutils.preferred-networks", preferredNetworks);
+        }
+
+        properties.put("spring.profiles.active", active);
+        // properties.put("spring.application.name", properties.get("spring.application.name") + "-temp");
+
+        Set<String> argSet = Sets.newLinkedHashSet();
+        for (Entry<String, String> p : properties.entrySet()) {
+            String key = p.getKey();
+            String value = p.getValue();
+            if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
+                continue;
+            }
+            key = key.trim();
+            value = value.trim();
+            PropertyHolder.setProperty(key, value);
+            PropertiesUtil.setProperty(key, value);
+            String arg = "--" + key + "=" + value;
+            argSet.add(arg);
+        }
+
+        AppSelfHolder.set(buildAppSelf());
+
+        String[] newArgs = argSet.toArray(new String[argSet.size()]);
+        ConfigurableApplicationContext context = null;
+        try {
+            context = SpringApplication.run(primarySource, newArgs);
+            noticeConfigCenter("success");
+        } catch (Exception e) {
+            String stackTrace = Util.getStackTrace(e);
+            noticeConfigCenter("failure:\n" + stackTrace);
+            log.error("fail to run spring app.", e);
+            System.exit(-1);
+        }
+
+        return context;
+    }
+
+    /**
+     * 创建 {@link AppSelf} 实例
+     *
+     * @return
+     * @author WANGWEI
+     */
+    private static AppSelf buildAppSelf() {
+        return new AppSelf() {
+
+            @Override
+            public Long getAppId() {
+                return appId;
+            }
+
+            @Override
+            public String getAppCode() {
+                return appCode;
+            }
+
+            @Override
+            public String getSecretKey() {
+                return secretKey;
+            }
+        };
+    }
+
+    /**
+     * 通知配置中心
+     *
+     * @param message
+     * @author WANGWEI
+     */
+    private static void noticeConfigCenter(String message) {
+        String url = "http://" + configCenterHost + ":" + configCenterPort + "/configCenter/notice";
+
+        Map<String, String> req = Maps.newHashMap();
+        req.put("active", active);
+        req.put("appCode", appCode);
+        req.put("startupCode", startupCode);
+        req.put("message", message);
+
+        Response resp = null;
+        try {
+            Map<String, String> headers = Maps.newHashMap();
+            headers.put("Trace-Id", startupCode);
+            resp = OKHttpUtil.call(HttpMethod.POST, url.toString(), headers, req);
+        } catch (Exception e) {
+            log.error("fail to notice config center.", e);
+            System.exit(-1);
+        } finally {
+            IOUtils.closeQuietly(resp);
+        }
+    }
+
+    /**
+     * 向配置中心发送启动请求
+     *
+     * @author WANGWEI
+     */
+    private static void sendStartupRequest2ConfigCenter() {
+        StringBuilder url = new StringBuilder(
+                "http://" + configCenterHost + ":" + configCenterPort + "/configCenter/startup");
+
+        Map<String, String> req = Maps.newHashMap();
+        req.put("active", active);
+        req.put("appCode", appCode);
+        req.put("startupCode", startupCode);
+
+        Response resp = null;
+        try {
+            Map<String, String> headers = Maps.newHashMap();
+            headers.put("Trace-Id", startupCode);
+            resp = OKHttpUtil.call(HttpMethod.POST, url.toString(), headers, req);
+            String body = resp.body().string();
+            if (resp.code() != HttpStatus.OK.value()) {
+                throw new ExamCloudRuntimeException("fail to get configuration. " + body);
+            }
+
+            String encryptedAppId = resp.header("App-Id");
+            String encryptedSecretKey = resp.header("Secret-Key");
+
+            appId = StringUtil.toLong(BootstrapSecurityUtil.decrypt(encryptedAppId, startupCode));
+            secretKey = BootstrapSecurityUtil.decrypt(encryptedSecretKey, startupCode);
+
+            String decryptedBody = BootstrapSecurityUtil.decrypt(body, startupCode);
+            @SuppressWarnings("unchecked")
+            Map<String, String> map = JsonUtil.fromJson(decryptedBody, Map.class);
+            properties = map;
+
+        } catch (Exception e) {
+            log.error("fail to send startup request to config center.", e);
+            System.exit(-1);
+        } finally {
+            IOUtils.closeQuietly(resp);
+        }
+
+    }
+
+    /**
+     * 检查启动参数
+     *
+     * @param args
+     * @return
+     * @author WANGWEI
+     */
+    private static void checkBootstrapParams(String... args) {
+
+        Properties props = new Properties();
+        PropertiesUtil.loadFromResource("application.properties", props);
+
+        if (null != args) {
+            for (String s : args) {
+                s = s.trim();
+                if (s.startsWith("--spring.profiles.active=")) {
+                    active = s.substring(s.indexOf("=") + 1);
+                } else if (s.startsWith("--examcloud.startup.startupCode=")) {
+                    startupCode = s.substring(s.indexOf("=") + 1);
+                } else if (s.startsWith("--examcloud.startup.configCenterHost=")) {
+                    configCenterHost = s.substring(s.indexOf("=") + 1);
+                } else if (s.startsWith("--examcloud.startup.configCenterPort=")) {
+                    configCenterPort = s.substring(s.indexOf("=") + 1);
+                } else if (s.startsWith("--examcloud.startup.appCode=")) {
+                    appCode = s.substring(s.indexOf("=") + 1);
+                }
+            }
+        }
+
+        // active
+        if (null == active) {
+            String value = props.getProperty("spring.profiles.active");
+            if (StringUtils.isNotBlank(value)) {
+                active = value.trim();
+            }
+        }
+        log.info("active=" + active);
+        if (StringUtils.isBlank(active)) {
+            log.error("property[spring.profiles.active] is not specified");
+            System.exit(-1);
+        }
+
+        // startupCode
+        if (null == startupCode) {
+            String value = props.getProperty("examcloud.startup.startupCode");
+            if (StringUtils.isNotBlank(value)) {
+                startupCode = value.trim();
+            }
+        }
+        log.info("startupCode=" + startupCode);
+        if (StringUtils.isBlank(startupCode)) {
+            log.error("property[examcloud.startup.startupCode] is not specified");
+            System.exit(-1);
+        }
+
+        // appCode
+        if (null == appCode) {
+            String value = props.getProperty("examcloud.startup.appCode");
+            if (StringUtils.isNotBlank(value)) {
+                appCode = value.trim();
+            }
+        }
+        log.info("appCode=" + appCode);
+        if (StringUtils.isBlank(appCode)) {
+            log.error("property[examcloud.startup.appCode] is not specified");
+            System.exit(-1);
+        }
+
+        // configCenterHost
+        if (null == configCenterHost) {
+            String value = props.getProperty("examcloud.startup.configCenterHost");
+            if (StringUtils.isNotBlank(value)) {
+                configCenterHost = value.trim();
+            }
+        }
+        log.info("configCenterHost=" + configCenterHost);
+        if (StringUtils.isBlank(configCenterHost)) {
+            log.error("property[examcloud.startup.configCenterHost] is not specified");
+            System.exit(-1);
+        }
+
+        // configCenterPort
+        if (null == configCenterPort) {
+            String value = props.getProperty("examcloud.startup.configCenterPort");
+            if (StringUtils.isNotBlank(value)) {
+                configCenterPort = value.trim();
+            }
+        }
+        log.info("configCenterPort=" + configCenterPort);
+        if (null == configCenterPort) {
+            log.error("property[examcloud.startup.configCenterPort] is not specified");
+            System.exit(-1);
+        }
+
+    }
 
 }

+ 1 - 1
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/bootstrap/PropertyHolder.java

@@ -52,7 +52,7 @@ public class PropertyHolder {
 			return value.trim();
 		} else {
 			if (LOG.isDebugEnabled()) {
-				LOG.debug("No  property key named [" + key + "] is configured.");
+				LOG.debug("No property value, key = " + key);
 			}
 			return null;
 		}

+ 54 - 49
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/enums/HttpServletRequestAttribute.java

@@ -9,54 +9,59 @@ package cn.com.qmth.examcloud.web.enums;
  */
 public enum HttpServletRequestAttribute {
 
-	/**
-	 * 请求映射
-	 */
-	$_MAPPING,
-
-	/**
-	 * API 信息
-	 */
-	$_API_INFO,
-
-	/**
-	 * http status恒为200
-	 */
-	$_ALWAYS_OK,
-
-	/**
-	 * 接入用户
-	 */
-	$_ACCESS_USER,
-
-	/**
-	 * 自定义顺序锁
-	 */
-	$_CUSTOM_SEQUENCE_LOCK,
-
-	/**
-	 * 已鉴权(其他拦截器处理)
-	 */
-	$_AUTHORIZED_BY_OTHER_INTERCEPTOR,
-
-	/**
-	 * 企业顶级机构(对外服务接口)
-	 */
-	$_ENTERPRISE_ROOT_ORG_ID,
-
-	/**
-	 * 接口调用异常
-	 */
-	$_EXCEPTION_HAPPENED,
-
-	/**
-	 * METRICS Timer context
-	 */
-	$_METRICS_TIMER_CTX,
-
-	/**
-	 * ApiStatisticInterceptor 开始时间
-	 */
-	API_STATISTIC_INTERCEPTOR_START_TIME,
+    /**
+     * 请求映射
+     */
+    $_MAPPING,
+
+    /**
+     * API 信息
+     */
+    $_API_INFO,
+
+    /**
+     * http status恒为200
+     */
+    $_ALWAYS_OK,
+
+    /**
+     * 接入用户
+     */
+    $_ACCESS_USER,
+
+    /**
+     * 自定义顺序锁
+     */
+    $_CUSTOM_SEQUENCE_LOCK,
+
+    /**
+     * 已鉴权(其他拦截器处理)
+     */
+    $_AUTHORIZED_BY_OTHER_INTERCEPTOR,
+
+    /**
+     * 企业顶级机构(对外服务接口)
+     */
+    $_ENTERPRISE_ROOT_ORG_ID,
+
+    /**
+     * 接口调用异常
+     */
+    $_EXCEPTION_HAPPENED,
+
+    /**
+     * METRICS Timer context
+     */
+    $_METRICS_TIMER_CTX,
+
+    /**
+     * ApiStatisticInterceptor 开始时间
+     */
+    API_STATISTIC_INTERCEPTOR_START_TIME,
+
+    /**
+     * 数据权限
+     */
+    $_USER_DATA_RULE
 
 }

+ 93 - 115
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/FirstInterceptor.java

@@ -1,13 +1,12 @@
 package cn.com.qmth.examcloud.web.interceptor;
 
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import cn.com.qmth.examcloud.commons.util.ThreadLocalUtil;
+import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
+import cn.com.qmth.examcloud.web.support.*;
+import com.google.common.collect.Maps;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.ThreadContext;
 import org.springframework.http.HttpStatus;
@@ -15,19 +14,12 @@ import org.springframework.web.method.HandlerMethod;
 import org.springframework.web.servlet.HandlerInterceptor;
 import org.springframework.web.servlet.ModelAndView;
 
-import com.google.common.collect.Maps;
-
-import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
-import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
-import cn.com.qmth.examcloud.commons.util.StringUtil;
-import cn.com.qmth.examcloud.commons.util.ThreadLocalUtil;
-import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
-import cn.com.qmth.examcloud.web.support.ApiId;
-import cn.com.qmth.examcloud.web.support.ApiInfo;
-import cn.com.qmth.examcloud.web.support.ApiInfoHolder;
-import cn.com.qmth.examcloud.web.support.ServletUtil;
-import cn.com.qmth.examcloud.web.support.StatusResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * 首发拦截器
@@ -38,99 +30,85 @@ import cn.com.qmth.examcloud.web.support.StatusResponse;
  */
 public class FirstInterceptor implements HandlerInterceptor {
 
-	private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(FirstInterceptor.class);
-
-	private static final ExamCloudLog INTERFACE_LOG = ExamCloudLogFactory
-			.getLog("INTERFACE_LOGGER");
-
-	@Override
-	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
-			Object handler) throws Exception {
-		ThreadLocalUtil.clearAll();
-		String traceId = request.getHeader("Trace-Id");
-		if (StringUtils.isBlank(traceId)) {
-			traceId = ThreadLocalUtil.next();
-		} else {
-			ThreadLocalUtil.setTraceId(traceId);
-		}
-
-		// 设置log4j线程上下文
-		ThreadContext.put("TRACE_ID", traceId);
-		ThreadContext.put("CALLER", "$$$$$");
-
-		String path = request.getServletPath();
-		String method = request.getMethod();
-
-		Set<String> headerNames = new HashSet<String>(20);
-		if (INTERFACE_LOG.isDebugEnabled()) {
-			Map<String, String> headers = Maps.newHashMap();
-			Enumeration<String> e = request.getHeaderNames();
-			while (e.hasMoreElements()) {
-				String name = (String) e.nextElement();
-				String value = request.getHeader(name);
-				headers.put(name, value);
-				headerNames.add(name);
-			}
-			INTERFACE_LOG.debug(StringUtil.join("[preHandle]. path=\"", path, "\"; method=[",
-					method, "]", "; headers=", JsonUtil.toJson(headers)));
-		}
-
-		if (path.equals("/error")) {
-			response.setStatus(HttpStatus.NOT_FOUND.value());
-			ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.NOT_FOUND.value()),
-					String.valueOf(HttpStatus.NOT_FOUND.getReasonPhrase())), response);
-			return false;
-		}
-
-		if ((!org.springframework.http.HttpMethod.OPTIONS.matches(method))
-				&& handler instanceof HandlerMethod) {
-			HandlerMethod handlerMethod = (HandlerMethod) handler;
-			ApiId apiId = handlerMethod.getMethodAnnotation(ApiId.class);
-
-			ApiInfo apiInfo = null;
-			if (null != apiId) {
-				apiInfo = ApiInfoHolder.getApiInfo(apiId.value());
-			} else {
-				apiInfo = ApiInfoHolder.getApiInfo(handlerMethod.getMethod());
-			}
-			String mapping = apiInfo.getMapping();
-			request.setAttribute(HttpServletRequestAttribute.$_API_INFO.name(), apiInfo);
-
-			if (INTERFACE_LOG.isDebugEnabled()) {
-				INTERFACE_LOG.debug("[preHandle]. mapping = " + mapping);
-			}
-			request.setAttribute(HttpServletRequestAttribute.$_MAPPING.name(), mapping);
-
-		} else {
-			if (INTERFACE_LOG.isDebugEnabled()) {
-				INTERFACE_LOG.debug("not handle by HandlerMethod.");
-			}
-
-			String mapping = StringUtils.join("_[", path, "][", method, "]");
-
-			if (INTERFACE_LOG.isDebugEnabled()) {
-				INTERFACE_LOG.debug("[preHandle]. mapping = " + mapping);
-			}
-
-			request.setAttribute(HttpServletRequestAttribute.$_MAPPING.name(), mapping);
-			return true;
-		}
-
-		return true;
-	}
-
-	@Override
-	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
-			ModelAndView modelAndView) throws Exception {
-		LOG.debug("postHandle... ...");
-	}
-
-	@Override
-	public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
-			Object handler, Exception ex) throws Exception {
-		LOG.debug("afterCompletion... ...");
-		// 清理log4j线程上下文
-		ThreadContext.clearAll();
-	}
+    private static final ExamCloudLog LOG = ExamCloudLogFactory.getLog(FirstInterceptor.class);
+
+    private static final ExamCloudLog INTERFACE_LOG = ExamCloudLogFactory.getLog("INTERFACE_LOGGER");
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
+                             Object handler) throws Exception {
+        ThreadLocalUtil.clearAll();
+        String traceId = request.getHeader("Trace-Id");
+        if (StringUtils.isBlank(traceId)) {
+            traceId = ThreadLocalUtil.next();
+        } else {
+            ThreadLocalUtil.setTraceId(traceId);
+        }
+
+        // 设置log4j线程上下文
+        ThreadContext.put("TRACE_ID", traceId);
+        ThreadContext.put("CALLER", "$$$$$");
+
+        String path = request.getServletPath();
+        String method = request.getMethod();
+
+        Set<String> headerNames = new HashSet<>(20);
+
+        if (INTERFACE_LOG.isDebugEnabled()) {
+            Map<String, String> headers = Maps.newHashMap();
+            Enumeration<String> e = request.getHeaderNames();
+            while (e.hasMoreElements()) {
+                String name = e.nextElement();
+                String value = request.getHeader(name);
+                headers.put(name, value);
+                headerNames.add(name);
+            }
+
+            INTERFACE_LOG.debug(String.format("[FirstInterceptor][%s] - %s, headers = %s", method, path, JsonUtil.toJson(headers)));
+        }
+
+        if (path.equals("/error")) {
+            response.setStatus(HttpStatus.NOT_FOUND.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.NOT_FOUND.value()), HttpStatus.NOT_FOUND.getReasonPhrase()), response);
+            return false;
+        }
+
+        if ((!org.springframework.http.HttpMethod.OPTIONS.matches(method)) && handler instanceof HandlerMethod) {
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            ApiId apiId = handlerMethod.getMethodAnnotation(ApiId.class);
+
+            ApiInfo apiInfo = null;
+            if (null != apiId) {
+                apiInfo = ApiInfoHolder.getApiInfo(apiId.value());
+            } else {
+                apiInfo = ApiInfoHolder.getApiInfo(handlerMethod.getMethod());
+            }
+
+            String mapping = apiInfo.getMapping();
+            request.setAttribute(HttpServletRequestAttribute.$_API_INFO.name(), apiInfo);
+
+            INTERFACE_LOG.debug("[FirstInterceptor][mapping] ==> " + mapping);
+            request.setAttribute(HttpServletRequestAttribute.$_MAPPING.name(), mapping);
+        } else {
+            String mapping = StringUtils.join("_[", path, "][", method, "]");
+
+            INTERFACE_LOG.debug("[FirstInterceptor][mapping] --> " + mapping);
+            request.setAttribute(HttpServletRequestAttribute.$_MAPPING.name(), mapping);
+        }
+
+        return true;
+    }
+
+    @Override
+    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
+                           ModelAndView modelAndView) throws Exception {
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
+                                Object handler, Exception ex) throws Exception {
+        // 清理log4j线程上下文
+        ThreadContext.clearAll();
+    }
 
 }

+ 6 - 11
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/SeqlockInterceptor.java

@@ -46,7 +46,6 @@ public class SeqlockInterceptor implements HandlerInterceptor {
 	 * 构造函数
 	 *
 	 * @param redisClient
-	 * @param exclusions
 	 */
 	public SeqlockInterceptor(RedisClient redisClient) {
 		super();
@@ -56,7 +55,9 @@ public class SeqlockInterceptor implements HandlerInterceptor {
 	@Override
 	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
 			Object handler) throws Exception {
-		LOG.debug("preHandle... ...");
+		if(LOG.isDebugEnabled()) {
+			LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
+		}
 
 		if (handler instanceof HandlerMethod) {
 
@@ -73,9 +74,7 @@ public class SeqlockInterceptor implements HandlerInterceptor {
 				String key = GLOBAL_LOCK_PREFIX + mapping;
 
 				if (redisClient.setIfAbsent(key, ThreadLocalUtil.getTraceId(), 60 * 5)) {
-					if (LOG.isDebugEnabled()) {
-						LOG.debug("partition locked");
-					}
+					LOG.info("partition locked");
 
 					request.setAttribute(LOCK_ATTRIBUTE, key);
 					return true;
@@ -93,9 +92,8 @@ public class SeqlockInterceptor implements HandlerInterceptor {
 				String key = SESSION_LOCK_PREFIX + user.getKey();
 
 				if (redisClient.setIfAbsent(key, ThreadLocalUtil.getTraceId(), 60 * 5)) {
-					if (LOG.isDebugEnabled()) {
-						LOG.debug("sesssion locked");
-					}
+					LOG.info("session locked");
+
 					request.setAttribute(LOCK_ATTRIBUTE, key);
 					return true;
 				} else {
@@ -112,13 +110,11 @@ public class SeqlockInterceptor implements HandlerInterceptor {
 	@Override
 	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
 			ModelAndView modelAndView) throws Exception {
-		LOG.debug("postHandle... ...");
 	}
 
 	@Override
 	public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
 			Object handler, Exception ex) throws Exception {
-		LOG.debug("afterCompletion... ...");
 
 		Object key = request.getAttribute(LOCK_ATTRIBUTE);
 		if (null != key) {
@@ -133,7 +129,6 @@ public class SeqlockInterceptor implements HandlerInterceptor {
 				redisClient.delete(cur);
 			}
 		}
-
 	}
 
 }

+ 6 - 10
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/redis/CustomRedisConfiguration.java

@@ -33,8 +33,6 @@ public class CustomRedisConfiguration {
 			return redisTemplate;
 		}
 
-		LOG.info("start to create a custom instance of RedisTemplate... ...");
-
 		redisTemplate.setConnectionFactory(redisConnectionFactory);
 		Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
 				Object.class);
@@ -46,25 +44,22 @@ public class CustomRedisConfiguration {
 		redisTemplate.setKeySerializer(new StringRedisSerializer());
 		redisTemplate.afterPropertiesSet();
 
-		LOG.info("custom instance of RedisTemplate is created.");
+		LOG.info("RedisTemplate init...");
 
 		return redisTemplate;
 	}
 
 	@Bean
-	public RedisClient redisClient(
-			@Autowired(required = true) RedisTemplate<String, Object> redisTemplate) {
-
-		LOG.info("start to create a custom instance of RedisClient... ...");
-
+	public RedisClient redisClient(@Autowired(required = true) RedisTemplate<String, Object> redisTemplate) {
 		RedisClient redisClient = new SimpleRedisClient(redisTemplate);
 		RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
+
 		if (connectionFactory instanceof MarkedJedisConnectionFactory) {
 			redisClient.setEnable(false);
-			LOG.info("disabled instance of RedisClient is created. please be careful !");
+			LOG.warn("redisClient init... enable = false !!!");
 		} else {
 			redisClient.set("test", "test");
-			LOG.info("enabled instance of RedisClient is created.");
+			LOG.info("redisClient init... enable = true");
 		}
 
 		return redisClient;
@@ -78,6 +73,7 @@ public class CustomRedisConfiguration {
 	 * @Copyright (c) 2018-? http://qmth.com.cn All Rights Reserved.
 	 */
 	private class MarkedJedisConnectionFactory extends JedisConnectionFactory {
+
 	}
 
 }

+ 142 - 141
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/redis/SimpleRedisClient.java

@@ -1,12 +1,11 @@
 package cn.com.qmth.examcloud.web.redis;
 
-import java.util.concurrent.TimeUnit;
-
-import org.springframework.data.redis.core.RedisTemplate;
-
 import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
 import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
 import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.concurrent.TimeUnit;
 
 /**
  * redis client
@@ -17,151 +16,153 @@ import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
  */
 public final class SimpleRedisClient implements RedisClient {
 
-	private static final ExamCloudLog REDIS_LOG = ExamCloudLogFactory.getLog("REDIS_LOGGER");
-
-	private RedisTemplate<String, Object> redisTemplate;
-
-	private boolean enable = true;
-
-	public SimpleRedisClient(RedisTemplate<String, Object> redisTemplate) {
-		super();
-		this.redisTemplate = redisTemplate;
-	}
-
-	private void beforeMethod() {
-		if (!enable) {
-			throw new ExamCloudRuntimeException("RedisClient is not enabled");
-		}
-	}
-
-	private void afterMethod(String method, long startTimeMillis) {
-		if (REDIS_LOG.isDebugEnabled()) {
-			String s = String.format("[SimpleRedisClient.%s] cost %d ms.", method,
-					System.currentTimeMillis() - startTimeMillis);
-			REDIS_LOG.debug(s);
-		}
-	}
-
-	@Override
-	public void set(String key, Object value) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		redisTemplate.opsForValue().set(key, value);
-		afterMethod("set(String key, Object value)", s);
-	}
-
-	@Override
-	public void set(String key, Object value, int timeout) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		redisTemplate.opsForValue().set(key, value,timeout,TimeUnit.SECONDS);
-		afterMethod("set(String key, Object value, int timeout)", s);
-	}
-
-	@Override
-	public void expire(String key, int timeout) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
-		afterMethod("expire(String key, int timeout)", s);
-	}
-
-	/**
-	 * 方法注释
-	 *
-	 * @author WANGWEI
-	 * @param key
-	 * @param timeout
-	 * @param unit
-	 */
-	public void expire(String key, final long timeout, final TimeUnit unit) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		redisTemplate.expire(key, timeout, unit);
-		afterMethod("(String key, final long timeout, final TimeUnit unit)", s);
-	}
-
-	@Override
-	public <T> T get(String key, Class<T> c, int timeout) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		Object object = redisTemplate.opsForValue().get(key);
-		@SuppressWarnings("unchecked")
-		T t = (T) object;
-		expire(key, timeout);
-		afterMethod("get(String key, Class<T> c, int timeout)", s);
-		return t;
-	}
-
-	@Override
-	public <T> T get(String key, Class<T> c) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		Object object = redisTemplate.opsForValue().get(key);
-		@SuppressWarnings("unchecked")
-		T t = (T) object;
-		afterMethod("get(String key, Class<T> c)", s);
-		return t;
-	}
-
-
-    @Override
-    public <T> T get(String key,String hashKey, Class<T> c) {
+    private static final ExamCloudLog REDIS_LOG = ExamCloudLogFactory.getLog("REDIS_LOGGER");
+
+    private RedisTemplate<String, Object> redisTemplate;
+
+    private boolean enable = true;
+
+    public SimpleRedisClient(RedisTemplate<String, Object> redisTemplate) {
+        super();
+        this.redisTemplate = redisTemplate;
+    }
+
+    private void beforeMethod() {
+        if (!enable) {
+            throw new ExamCloudRuntimeException("RedisClient is not enabled");
+        }
+    }
+
+    private void afterMethod(String content, long startTimeMillis) {
+        if (REDIS_LOG.isDebugEnabled()) {
+            String msg = String.format("[SimpleRedisClient] %s, cost %dms",
+                    content, System.currentTimeMillis() - startTimeMillis);
+            REDIS_LOG.debug(msg);
+        }
+    }
+
+    @Override
+    public void set(String key, Object value) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        redisTemplate.opsForValue().set(key, value);
+        afterMethod("set " + key, s);
+    }
+
+    @Override
+    public void set(String key, Object value, int timeout) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
+        afterMethod("set " + key, s);
+    }
+
+    @Override
+    public void expire(String key, int timeout) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
+        afterMethod("expire " + key, s);
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param key
+     * @param timeout
+     * @param unit
+     * @author WANGWEI
+     */
+    @Override
+    public void expire(String key, final long timeout, final TimeUnit unit) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        redisTemplate.expire(key, timeout, unit);
+        afterMethod("expire " + key, s);
+    }
+
+    @Override
+    public <T> T get(String key, Class<T> c, int timeout) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        Object object = redisTemplate.opsForValue().get(key);
+        @SuppressWarnings("unchecked")
+        T t = (T) object;
+        expire(key, timeout);
+        afterMethod("get " + key, s);
+        return t;
+    }
+
+    @Override
+    public <T> T get(String key, Class<T> c) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        Object object = redisTemplate.opsForValue().get(key);
+        @SuppressWarnings("unchecked")
+        T t = (T) object;
+        afterMethod("get " + key, s);
+        return t;
+    }
+
+
+    @Override
+    public <T> T get(String key, String hashKey, Class<T> c) {
         long s = System.currentTimeMillis();
         beforeMethod();
-        Object object = redisTemplate.opsForHash().get(key,hashKey);
+        Object object = redisTemplate.opsForHash().get(key, hashKey);
         @SuppressWarnings("unchecked")
         T t = (T) object;
-        afterMethod("get(String key, Class<T> c)", s);
+        afterMethod("get " + key, s);
         return t;
     }
-	@Override
-	public void delete(String key) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		redisTemplate.expire(key,0,TimeUnit.SECONDS);
-		afterMethod("delete(String key)", s);
-	}
-
-	@Override
-	public void convertAndSend(String channel, Object message) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		redisTemplate.convertAndSend(channel, message);
-		afterMethod("convertAndSend(String channel, Object message)", s);
-	}
-
-	@Override
-	public Boolean setIfAbsent(String key, String value, int timeout) {
-		long s = System.currentTimeMillis();
-		beforeMethod();
-		Boolean b = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
-		afterMethod("setIfAbsent(String key, String value, int timeout)", s);
-		return b;
-	}
-
-	@Override
-	public boolean isEnable() {
-		return enable;
-	}
-
-	@Override
-	public void setEnable(boolean enable) {
-		this.enable = enable;
-	}
-
-	@Override
-	public Long getExpire(String key, TimeUnit timeUnit) {
-		return redisTemplate.getExpire(key, timeUnit);
-	}
+
+    @Override
+    public void delete(String key) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        redisTemplate.expire(key, 0, TimeUnit.SECONDS);
+        afterMethod("delete " + key, s);
+    }
+
+    @Override
+    public void convertAndSend(String channel, Object message) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        redisTemplate.convertAndSend(channel, message);
+        afterMethod("convertAndSend " + channel, s);
+    }
+
+    @Override
+    public Boolean setIfAbsent(String key, String value, int timeout) {
+        long s = System.currentTimeMillis();
+        beforeMethod();
+        Boolean b = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
+        afterMethod("setIfAbsent " + key, s);
+        return b;
+    }
+
+    @Override
+    public boolean isEnable() {
+        return enable;
+    }
+
+    @Override
+    public void setEnable(boolean enable) {
+        this.enable = enable;
+    }
+
+    @Override
+    public Long getExpire(String key, TimeUnit timeUnit) {
+        return redisTemplate.getExpire(key, timeUnit);
+    }
 
     @Override
     public void set(String key, String subkey, Object value, int timeout) {
         long s = System.currentTimeMillis();
         beforeMethod();
-		set(key,subkey, value);
-		expire(key, timeout);
-        afterMethod("set(String key,String subkey,Object value, int timeout)", s);
+        set(key, subkey, value);
+        expire(key, timeout);
+        afterMethod("set " + key + " " + subkey, s);
 
     }
 
@@ -170,15 +171,15 @@ public final class SimpleRedisClient implements RedisClient {
         long s = System.currentTimeMillis();
         beforeMethod();
         redisTemplate.opsForHash().delete(key, subkey);
-        afterMethod("delete(String key,String subkey)", s);
+        afterMethod("delete " + key + " " + subkey, s);
     }
 
     @Override
     public void set(String key, String subkey, Object value) {
         long s = System.currentTimeMillis();
         beforeMethod();
-        redisTemplate.opsForHash().put(key,subkey, value);
-        afterMethod("set(String key,String subkey, Object value)", s);
+        redisTemplate.opsForHash().put(key, subkey, value);
+        afterMethod("set " + key + " " + subkey, s);
     }
 
 

+ 22 - 0
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/DataRule.java

@@ -0,0 +1,22 @@
+package cn.com.qmth.examcloud.web.security;
+
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+@Documented
+@Target({METHOD})
+@Retention(RUNTIME)
+public @interface DataRule {
+
+    /**
+     * 数据权限规则类型
+     */
+    DataRuleType[] type();
+
+}

+ 105 - 0
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/DataRuleInterceptor.java

@@ -0,0 +1,105 @@
+package cn.com.qmth.examcloud.web.security;
+
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
+import cn.com.qmth.examcloud.api.commons.security.bean.Role;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserType;
+import cn.com.qmth.examcloud.api.commons.security.enums.RoleMeta;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
+import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
+import org.apache.commons.collections.CollectionUtils;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class DataRuleInterceptor extends HandlerInterceptorAdapter {
+
+    private final static ExamCloudLog log = ExamCloudLogFactory.getLog("INTERFACE_LOGGER");
+
+    private ResourceManager resourceManager;
+
+    public DataRuleInterceptor(ResourceManager resourceManager) {
+        this.resourceManager = resourceManager;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+        try {
+            DataRule dataRule = null;
+            if (handler instanceof HandlerMethod) {
+                HandlerMethod handlerMethod = (HandlerMethod) handler;
+                dataRule = handlerMethod.getMethodAnnotation(DataRule.class);
+            }
+
+            if (dataRule == null) {
+                return true;
+            }
+
+            boolean isEnable = PropertyHolder.getBoolean("examcloud.data.rule.enable", false);
+            if (!isEnable) {
+                // 未启用数据权限
+                log.info("[DataRuleInterceptor] examcloud.data.rule.enable = false");
+                return true;
+            }
+
+            User accessUser = (User) request.getAttribute(HttpServletRequestAttribute.$_ACCESS_USER.name());
+            if (accessUser == null) {
+                return true;
+            }
+
+            boolean isAdmimUser = this.hasAnyRoles(accessUser, RoleMeta.SUPER_ADMIN, RoleMeta.ORG_ADMIN);
+            boolean isStudent = UserType.STUDENT == accessUser.getUserType();
+
+            for (DataRuleType dataRuleType : dataRule.type()) {
+                UserDataRule userDataRule;
+
+                if (isAdmimUser || isStudent) {
+                    // “超管、机构管理员、学生” 暂例外
+                    userDataRule = new UserDataRule();
+                    userDataRule.setGlobalStatus(true);
+                    userDataRule.setRefIds(new HashSet<>());
+                } else {
+                    userDataRule = resourceManager.loadUserDataRule(accessUser.getUserId(), dataRuleType);
+                    log.info(String.format("[DataRuleInterceptor] userId = %s, type = %s, %s", accessUser.getUserId(), dataRuleType, userDataRule));
+                }
+
+                String attributeName = HttpServletRequestAttribute.$_USER_DATA_RULE.name() + "@" + dataRuleType.name();
+                request.setAttribute(attributeName, userDataRule);
+            }
+        } catch (Exception e) {
+            log.error("[DataRuleInterceptor] " + e.getMessage(), e);
+        }
+
+        return true;
+    }
+
+    /**
+     * 判断是否包含任一角色
+     */
+    private boolean hasAnyRoles(User accessUser, RoleMeta... roles) {
+        List<Role> roleList = accessUser.getRoleList();
+        if (CollectionUtils.isEmpty(roleList)) {
+            return false;
+        }
+
+        Set<String> roleCodes = roleList.stream().map(e -> e.getRoleCode()).collect(Collectors.toSet());
+        for (RoleMeta role : roles) {
+            if (roleCodes.contains(role.name())) {
+                log.info(String.format("[DataRuleInterceptor] userId = %s, role = %s", accessUser.getUserId(), roleCodes));
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+}

+ 3 - 3
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/RequestPermissionInterceptor.java

@@ -54,7 +54,9 @@ public class RequestPermissionInterceptor implements HandlerInterceptor {
 	@Override
 	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
 			Object handler) throws Exception {
-		LOG.debug("preHandle... ...");
+		if(LOG.isDebugEnabled()) {
+			LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
+		}
 
 		ApiInfo apiInfo = (ApiInfo) request
 				.getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
@@ -142,13 +144,11 @@ public class RequestPermissionInterceptor implements HandlerInterceptor {
 	@Override
 	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
 			ModelAndView modelAndView) throws Exception {
-		LOG.debug("postHandle... ...");
 	}
 
 	@Override
 	public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
 			Object handler, Exception ex) throws Exception {
-		LOG.debug("afterCompletion... ...");
 	}
 
 }

+ 39 - 28
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/ResourceManager.java

@@ -1,7 +1,9 @@
 package cn.com.qmth.examcloud.web.security;
 
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.AccessApp;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 import cn.com.qmth.examcloud.web.support.ApiInfo;
 
 /**
@@ -13,34 +15,43 @@ import cn.com.qmth.examcloud.web.support.ApiInfo;
  */
 public interface ResourceManager {
 
-	/**
-	 * 获取APP 密钥
-	 *
-	 * @author WANGWEI
-	 * @param appId
-	 * @return
-	 */
-	AccessApp getAccessApp(Long appId);
+    /**
+     * 获取APP 密钥
+     *
+     * @param appId
+     * @return
+     * @author WANGWEI
+     */
+    AccessApp getAccessApp(Long appId);
 
-	/**
-	 * 请求的接口是否裸奔
-	 *
-	 * @author WANGWEI
-	 * @param apiInfo
-	 * @param mapping
-	 * @return
-	 */
-	boolean isNaked(ApiInfo apiInfo, String mapping);
+    /**
+     * 请求的接口是否裸奔
+     *
+     * @param apiInfo
+     * @param mapping
+     * @return
+     * @author WANGWEI
+     */
+    boolean isNaked(ApiInfo apiInfo, String mapping);
 
-	/**
-	 * 分区用户是否有权限访问接口
-	 *
-	 * @author WANGWEI
-	 * @param user
-	 * @param apiInfo
-	 * @param mapping
-	 * @return
-	 */
-	boolean hasPermission(User user, ApiInfo apiInfo, String mapping);
+    /**
+     * 分区用户是否有权限访问接口
+     *
+     * @param user
+     * @param apiInfo
+     * @param mapping
+     * @return
+     * @author WANGWEI
+     */
+    boolean hasPermission(User user, ApiInfo apiInfo, String mapping);
 
-}
+    /**
+     * UserDataRule
+     *
+     * @param userId
+     * @param dataRuleType
+     * @return
+     */
+    UserDataRule loadUserDataRule(Long userId, DataRuleType dataRuleType);
+
+}

+ 3 - 3
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/RpcInterceptor.java

@@ -47,7 +47,9 @@ public final class RpcInterceptor implements HandlerInterceptor {
 	@Override
 	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
 			Object handler) throws Exception {
-		LOG.debug("preHandle... ...");
+		if(LOG.isDebugEnabled()) {
+			LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
+		}
 
 		ApiInfo apiInfo = (ApiInfo) request
 				.getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
@@ -173,13 +175,11 @@ public final class RpcInterceptor implements HandlerInterceptor {
 	@Override
 	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
 			ModelAndView modelAndView) throws Exception {
-		LOG.debug("postHandle... ...");
 	}
 
 	@Override
 	public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
 			Object handler, Exception ex) throws Exception {
-		LOG.debug("afterCompletion... ...");
 	}
 
 	public void setResourceManager(ResourceManager resourceManager) {

+ 4 - 1
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/ControllerAspect.java

@@ -217,7 +217,10 @@ public class ControllerAspect {
             jsonEnable = true;
         } else if (obj instanceof Serializable) {
             String classPath = obj.getClass().getName();
-            if (classPath.toLowerCase().startsWith("cn.com.qmth")) {
+
+            if (classPath.startsWith("org.springframework.data.domain.PageImpl")
+                    || classPath.startsWith("cn.com.qmth")
+                    || classPath.startsWith("com.qmth")) {
                 jsonEnable = true;
             }
         }

+ 48 - 0
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/ControllerSupport.java

@@ -1,15 +1,19 @@
 package cn.com.qmth.examcloud.web.support;
 
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.Role;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 import cn.com.qmth.examcloud.api.commons.security.enums.RoleMeta;
 import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
 import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
 import cn.com.qmth.examcloud.commons.util.ObjectUtil;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
 import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
 import com.google.common.collect.Lists;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.web.context.request.RequestContextHolder;
@@ -20,9 +24,11 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.*;
 import java.lang.reflect.Field;
 import java.net.URLEncoder;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * contorller 基类
@@ -152,6 +158,48 @@ public abstract class ControllerSupport {
         return false;
     }
 
+    /**
+     * 判断是否包含任一角色
+     */
+    protected boolean hasAnyRoles(User accessUser, RoleMeta... roles) {
+        List<Role> roleList = accessUser.getRoleList();
+        if (CollectionUtils.isEmpty(roleList)) {
+            return false;
+        }
+
+        Set<String> roleCodes = roleList.stream().map(e -> e.getRoleCode()).collect(Collectors.toSet());
+        for (RoleMeta role : roles) {
+            if (roleCodes.contains(role.name())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 数据权限
+     */
+    protected UserDataRule getUserDataRule(DataRuleType dataRuleType) {
+        boolean isEnable = PropertyHolder.getBoolean("examcloud.data.rule.enable", false);
+
+        UserDataRule userDataRule;
+        if (isEnable) {
+            String attributeName = HttpServletRequestAttribute.$_USER_DATA_RULE.name() + "@" + dataRuleType.name();
+            userDataRule = (UserDataRule) ServletUtil.getRequest().getAttribute(attributeName);
+            if (userDataRule == null) {
+                throw new StatusException("500403", "获取数据权限失败!");
+            }
+        } else {
+            // 未启用数据权限,默认全部权限
+            userDataRule = new UserDataRule();
+            userDataRule.setGlobalStatus(true);
+            userDataRule.setRefIds(new HashSet<>());
+        }
+
+        return userDataRule;
+    }
+
     /**
      * 获取request对象
      *

+ 224 - 242
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/CustomExceptionHandler.java

@@ -1,12 +1,14 @@
 package cn.com.qmth.examcloud.web.support;
 
-import java.util.List;
-import java.util.Set;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.validation.ConstraintViolation;
-import javax.validation.ConstraintViolationException;
-
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.JsonMapper;
+import cn.com.qmth.examcloud.web.cloud.AppSelfHolder;
+import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
+import cn.com.qmth.examcloud.web.exception.ApiFlowLimitedException;
+import cn.com.qmth.examcloud.web.exception.SequenceLockException;
+import cn.com.qmth.examcloud.web.jpa.DataIntegrityViolationTransverter;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
@@ -20,15 +22,11 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.multipart.support.MissingServletRequestPartException;
 
-import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
-import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
-import cn.com.qmth.examcloud.web.cloud.AppSelfHolder;
-import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
-import cn.com.qmth.examcloud.web.exception.ApiFlowLimitedException;
-import cn.com.qmth.examcloud.web.exception.SequenceLockException;
-import cn.com.qmth.examcloud.web.jpa.DataIntegrityViolationTransverter;
+import javax.servlet.http.HttpServletRequest;
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import java.util.List;
+import java.util.Set;
 
 /**
  * 异常转换器
@@ -41,250 +39,234 @@ import cn.com.qmth.examcloud.web.jpa.DataIntegrityViolationTransverter;
 @ResponseBody
 public class CustomExceptionHandler {
 
-	/**
-	 * 接口日志
-	 */
-	private static final ExamCloudLog INTERFACE_LOG = ExamCloudLogFactory
-			.getLog("INTERFACE_LOGGER");
-
-	/**
-	 * 异常处理
-	 *
-	 * @author WANGWEI
-	 * @param e
-	 * @param request
-	 * @return
-	 */
-	@ExceptionHandler(RuntimeException.class)
-	public ResponseEntity<StatusResponse> handleException(RuntimeException e,
-			HttpServletRequest request) {
-
-		Throwable cause = e.getCause();
-		StatusResponse body = null;
-
-		if (null == cause) {
-			body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", "系统异常");
-			cause = e;
-		} else if (cause instanceof ApiFlowLimitedException) {
-			body = new StatusResponse("503", "limited. RPC");
-		} else if (cause instanceof StatusException) {
-			StatusException se = (StatusException) cause;
-			body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-" + se.getCode(),
-					se.getDesc());
-		} else if (cause instanceof DataIntegrityViolationException) {
-			try {
-				DataIntegrityViolationTransverter
-						.throwIfDuplicateEntry((DataIntegrityViolationException) cause);
-				body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", "数据完整性错误");
-			} catch (StatusException se) {
-				body = new StatusResponse(se.getCode(), se.getDesc());
-			}
-		} else if (cause instanceof IllegalStateException) {
-			body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500",
-					cause.getMessage());
-		} else if (cause instanceof javax.validation.ConstraintViolationException) {
-			javax.validation.ConstraintViolationException cvExcp = (ConstraintViolationException) cause;
-			Set<ConstraintViolation<?>> constraintViolations = cvExcp.getConstraintViolations();
+    /**
+     * 接口日志
+     */
+    private static final ExamCloudLog INTERFACE_LOG = ExamCloudLogFactory.getLog("INTERFACE_LOGGER");
 
-			StringBuffer errorMsg = new StringBuffer();
-			boolean isFirst = true;
-			for (ConstraintViolation<?> cv : constraintViolations) {
-				if (isFirst) {
-					errorMsg.append(cv.getMessage());
-					isFirst = false;
-				} else {
-					errorMsg.append("; ").append(cv.getMessage());
-				}
-			}
-			body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500",
-					errorMsg.toString());
-		} else if (cause instanceof SequenceLockException) {
-			body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500",
-					cause.getMessage());
-		} else if (cause instanceof org.springframework.jdbc.CannotGetJdbcConnectionException
-				|| cause instanceof org.springframework.orm.jpa.JpaSystemException
-				|| cause instanceof org.springframework.transaction.CannotCreateTransactionException) {
-			body = new StatusResponse("503", "limited. JDBC");
-		} else {
-			body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", "系统异常");
-			cause = e;
-		}
+    /**
+     * 异常处理
+     *
+     * @param e
+     * @param request
+     * @return
+     * @author WANGWEI
+     */
+    @ExceptionHandler(RuntimeException.class)
+    public ResponseEntity<StatusResponse> handleException(RuntimeException e, HttpServletRequest request) {
+        Throwable cause = e.getCause();
+        StatusResponse body = null;
 
-		return asResult(cause, body, request);
-	}
+        if (null == cause) {
+            body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", "系统异常");
+            cause = e;
+        } else if (cause instanceof ApiFlowLimitedException) {
+            body = new StatusResponse("503", "limited. RPC");
+        } else if (cause instanceof StatusException) {
+            StatusException se = (StatusException) cause;
+            body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-" + se.getCode(), se.getDesc());
+        } else if (cause instanceof DataIntegrityViolationException) {
+            try {
+                DataIntegrityViolationTransverter.throwIfDuplicateEntry((DataIntegrityViolationException) cause);
+                body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", "数据完整性错误");
+            } catch (StatusException se) {
+                body = new StatusResponse(se.getCode(), se.getDesc());
+            }
+        } else if (cause instanceof IllegalStateException) {
+            body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", cause.getMessage());
+        } else if (cause instanceof javax.validation.ConstraintViolationException) {
+            javax.validation.ConstraintViolationException cvExcp = (ConstraintViolationException) cause;
+            Set<ConstraintViolation<?>> constraintViolations = cvExcp.getConstraintViolations();
 
-	/**
-	 * 异常处理
-	 *
-	 * @author WANGWEI
-	 * @param e
-	 * @param request
-	 * @return
-	 */
-	@ExceptionHandler(MethodArgumentNotValidException.class)
-	public ResponseEntity<StatusResponse> handleException(MethodArgumentNotValidException e,
-			HttpServletRequest request) {
-		BindingResult result = e.getBindingResult();
-		List<ObjectError> allErrors = result.getAllErrors();
-		StringBuffer errorMsg = new StringBuffer();
-		boolean isFirst = true;
-		for (ObjectError err : allErrors) {
-			if (isFirst) {
-				errorMsg.append(err.getDefaultMessage());
-				isFirst = false;
-			} else {
-				errorMsg.append("; ").append(err.getDefaultMessage());
-			}
-		}
-		StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500",
-				errorMsg.toString());
-		return asResult(e, body, request);
-	}
+            StringBuffer errorMsg = new StringBuffer();
+            boolean isFirst = true;
+            for (ConstraintViolation<?> cv : constraintViolations) {
+                if (isFirst) {
+                    errorMsg.append(cv.getMessage());
+                    isFirst = false;
+                } else {
+                    errorMsg.append("; ").append(cv.getMessage());
+                }
+            }
+            body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", errorMsg.toString());
+        } else if (cause instanceof SequenceLockException) {
+            body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", cause.getMessage());
+        } else if (cause instanceof org.springframework.jdbc.CannotGetJdbcConnectionException
+                || cause instanceof org.springframework.orm.jpa.JpaSystemException
+                || cause instanceof org.springframework.transaction.CannotCreateTransactionException) {
+            body = new StatusResponse("503", "limited. JDBC");
+        } else {
+            body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", "系统异常");
+            cause = e;
+        }
 
-	/**
-	 * 异常处理
-	 *
-	 * @author WANGWEI
-	 * @param e
-	 * @param request
-	 * @return
-	 */
-	@ExceptionHandler(MissingServletRequestParameterException.class)
-	public ResponseEntity<StatusResponse> handleException(MissingServletRequestParameterException e,
-			HttpServletRequest request) {
+        return asResult(cause, body, request);
+    }
 
-		StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500",
-				e.getMessage());
-		return asResult(e, body, request);
-	}
+    /**
+     * 异常处理
+     *
+     * @param e
+     * @param request
+     * @return
+     * @author WANGWEI
+     */
+    @ExceptionHandler(MethodArgumentNotValidException.class)
+    public ResponseEntity<StatusResponse> handleException(MethodArgumentNotValidException e, HttpServletRequest request) {
+        BindingResult result = e.getBindingResult();
+        List<ObjectError> allErrors = result.getAllErrors();
+        StringBuffer errorMsg = new StringBuffer();
 
-	/**
-	 * 异常处理
-	 *
-	 * @author WANGWEI
-	 * @param e
-	 * @param request
-	 * @return
-	 */
-	@ExceptionHandler(MissingServletRequestPartException.class)
-	public ResponseEntity<StatusResponse> handleException(MissingServletRequestPartException e,
-			HttpServletRequest request) {
+        boolean isFirst = true;
+        for (ObjectError err : allErrors) {
+            if (isFirst) {
+                errorMsg.append(err.getDefaultMessage());
+                isFirst = false;
+            } else {
+                errorMsg.append("; ").append(err.getDefaultMessage());
+            }
+        }
 
-		StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500",
-				e.getMessage());
-		return asResult(e, body, request);
-	}
+        StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", errorMsg.toString());
+        return asResult(e, body, request);
+    }
 
-	/**
-	 * 异常处理
-	 *
-	 * @author WANGWEI
-	 * @param e
-	 * @param request
-	 * @return
-	 */
-	@ExceptionHandler(HttpMessageNotReadableException.class)
-	public ResponseEntity<StatusResponse> handleException(HttpMessageNotReadableException e,
-			HttpServletRequest request) {
+    /**
+     * 异常处理
+     *
+     * @param e
+     * @param request
+     * @return
+     * @author WANGWEI
+     */
+    @ExceptionHandler(MissingServletRequestParameterException.class)
+    public ResponseEntity<StatusResponse> handleException(MissingServletRequestParameterException e, HttpServletRequest request) {
+        StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", e.getMessage());
+        return asResult(e, body, request);
+    }
 
-		StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500",
-				"Required request body is missing");
-		return asResult(e, body, request);
-	}
+    /**
+     * 异常处理
+     *
+     * @param e
+     * @param request
+     * @return
+     * @author WANGWEI
+     */
+    @ExceptionHandler(MissingServletRequestPartException.class)
+    public ResponseEntity<StatusResponse> handleException(MissingServletRequestPartException e, HttpServletRequest request) {
+        StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", e.getMessage());
+        return asResult(e, body, request);
+    }
 
-	/**
-	 * 异常处理
-	 *
-	 * @author WANGWEI
-	 * @param e
-	 * @param request
-	 * @return
-	 */
-	@ExceptionHandler(Exception.class)
-	public ResponseEntity<StatusResponse> handleException(Exception e, HttpServletRequest request) {
+    /**
+     * 异常处理
+     *
+     * @param e
+     * @param request
+     * @return
+     * @author WANGWEI
+     */
+    @ExceptionHandler(HttpMessageNotReadableException.class)
+    public ResponseEntity<StatusResponse> handleException(HttpMessageNotReadableException e, HttpServletRequest request) {
+        StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500",
+                "Required request body is missing");
+        return asResult(e, body, request);
+    }
 
-		StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", "系统错误");
+    /**
+     * 异常处理
+     *
+     * @param e
+     * @param request
+     * @return
+     * @author WANGWEI
+     */
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<StatusResponse> handleException(Exception e, HttpServletRequest request) {
+        StatusResponse body = new StatusResponse(AppSelfHolder.get().getAppCode() + "-500", "系统错误");
+        return asResult(e, body, request);
+    }
 
-		return asResult(e, body, request);
-	}
+    /**
+     * 构建响应结果
+     *
+     * @param err
+     * @param body
+     * @param request
+     * @return
+     * @author WANGWEI
+     */
+    private ResponseEntity<StatusResponse> asResult(Throwable err, StatusResponse body, HttpServletRequest request) {
+        boolean alwaysOK = alwaysOK(request);
+        ApiInfo apiInfo = (ApiInfo) request.getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
 
-	/**
-	 * 构建响应结果
-	 *
-	 * @author WANGWEI
-	 * @param t
-	 * @param body
-	 * @param request
-	 * @return
-	 */
-	private ResponseEntity<StatusResponse> asResult(Throwable t, StatusResponse body,
-			HttpServletRequest request) {
-		boolean alwaysOK = alwaysOK(request);
-		ApiInfo apiInfo = (ApiInfo) request
-				.getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
+        boolean printStackTrace = true;
+        if (null != apiInfo) {
+            printStackTrace = !apiInfo.isWithoutStackTrace();
+        }
 
-		boolean printStackTrace = true;
-		if (null != apiInfo) {
-			printStackTrace = !apiInfo.isWithoutStackTrace();
-		}
-		if (!printStackTrace) {
-			String forcePrintStackTrace = System.getProperty("log.forcePrintStackTrace");
-			if (null != forcePrintStackTrace
-					&& forcePrintStackTrace.equalsIgnoreCase(Boolean.toString(true))) {
-				printStackTrace = true;
-			}
-		}
+        if (!printStackTrace) {
+            String forcePrintStackTrace = System.getProperty("log.forcePrintStackTrace");
+            if (null != forcePrintStackTrace && forcePrintStackTrace.equalsIgnoreCase(Boolean.toString(true))) {
+                printStackTrace = true;
+            }
+        }
 
-		HttpStatus httpStatus = null;
-		if (alwaysOK) {
-			if (t instanceof ApiFlowLimitedException
-					|| t instanceof org.springframework.jdbc.CannotGetJdbcConnectionException
-					|| t instanceof org.springframework.orm.jpa.JpaSystemException
-					|| t instanceof org.springframework.transaction.CannotCreateTransactionException) {
-				httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
-			} else {
-				httpStatus = HttpStatus.OK;
-			}
-		} else {
-			if (t instanceof ApiFlowLimitedException
-					|| t instanceof org.springframework.jdbc.CannotGetJdbcConnectionException
-					|| t instanceof org.springframework.orm.jpa.JpaSystemException
-					|| t instanceof org.springframework.transaction.CannotCreateTransactionException) {
-				httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
-			} else {
-				httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
-			}
-		}
+        HttpStatus httpStatus = null;
+        if (alwaysOK) {
+            if (err instanceof ApiFlowLimitedException
+                    || err instanceof org.springframework.jdbc.CannotGetJdbcConnectionException
+                    || err instanceof org.springframework.orm.jpa.JpaSystemException
+                    || err instanceof org.springframework.transaction.CannotCreateTransactionException) {
+                httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
+            } else {
+                httpStatus = HttpStatus.OK;
+            }
+        } else {
+            if (err instanceof ApiFlowLimitedException
+                    || err instanceof org.springframework.jdbc.CannotGetJdbcConnectionException
+                    || err instanceof org.springframework.orm.jpa.JpaSystemException
+                    || err instanceof org.springframework.transaction.CannotCreateTransactionException) {
+                httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
+            } else {
+                httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
+            }
+        }
 
-		INTERFACE_LOG.error("[HTTP-RESP]. status=" + httpStatus.value());
+        String msg = String.format("[CustomExceptionHandler] status = %s, responseMsg = %s",
+                httpStatus.value(), new JsonMapper().toJson(body));
+        if (printStackTrace) {
+            INTERFACE_LOG.error(msg, err);
+        } else {
+            INTERFACE_LOG.error(msg + ", err is " + err.getMessage());
 
-		if (printStackTrace) {
-			INTERFACE_LOG.error("[HTTP-RESP]. response=" + JsonUtil.toJson(body), t);
-		} else {
-			INTERFACE_LOG.error("[HTTP-RESP]. response=" + JsonUtil.toJson(body));
-		}
+            if (INTERFACE_LOG.isDebugEnabled()) {
+                INTERFACE_LOG.debug(err.getMessage(), err);
+            }
+        }
 
-		return new ResponseEntity<StatusResponse>(body, httpStatus);
-	}
+        return new ResponseEntity<>(body, httpStatus);
+    }
 
-	/**
-	 * 是否总是响应200
-	 *
-	 * @author WANGWEI
-	 * @param request
-	 * @return
-	 */
-	private boolean alwaysOK(HttpServletRequest request) {
-		boolean alwaysOK = false;
-		Object attribute = request.getAttribute(HttpServletRequestAttribute.$_ALWAYS_OK.name());
-		if (null != attribute) {
-			try {
-				alwaysOK = (boolean) attribute;
-			} catch (Exception ex) {
-				// ignore
-			}
-		}
-		return alwaysOK;
-	}
+    /**
+     * 是否总是响应200
+     *
+     * @param request
+     * @return
+     * @author WANGWEI
+     */
+    private boolean alwaysOK(HttpServletRequest request) {
+        boolean alwaysOK = false;
+        Object attribute = request.getAttribute(HttpServletRequestAttribute.$_ALWAYS_OK.name());
+        if (null != attribute) {
+            try {
+                alwaysOK = (boolean) attribute;
+            } catch (Exception ex) {
+                // ignore
+            }
+        }
+        return alwaysOK;
+    }
 
 }

+ 78 - 87
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/ServletUtil.java

@@ -1,18 +1,16 @@
 package cn.com.qmth.examcloud.web.support;
 
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
 import org.apache.commons.io.IOUtils;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
-import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
-import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
 
 /**
  * servlet 工具
@@ -23,88 +21,81 @@ import cn.com.qmth.examcloud.commons.util.JsonUtil;
  */
 public class ServletUtil {
 
-	/**
-	 * 接口日志
-	 */
-	protected static final ExamCloudLog INTERFACE_LOG = ExamCloudLogFactory
-			.getLog("INTERFACE_LOGGER");
-
-	/**
-	 * 获取request对象
-	 * 
-	 * @return
-	 */
-	public static HttpServletRequest getRequest() {
-		ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
-				.getRequestAttributes();
-		HttpServletRequest request = requestAttributes.getRequest();
-		return request;
-	}
-
-	/**
-	 * 获取response对象
-	 * 
-	 * @return
-	 */
-	public static HttpServletResponse getResponse() {
-		ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
-				.getRequestAttributes();
-		HttpServletResponse response = requestAttributes.getResponse();
-		return response;
-	}
+    /**
+     * 接口日志
+     */
+    protected static final ExamCloudLog INTERFACE_LOG = ExamCloudLogFactory.getLog("INTERFACE_LOGGER");
 
-	/**
-	 * 输出响应流
-	 *
-	 * @author WANGWEI
-	 * @param respEntity
-	 * @param response
-	 */
-	public static void returnJson(Object body, HttpServletResponse response) {
+    /**
+     * 获取request对象
+     *
+     * @return
+     */
+    public static HttpServletRequest getRequest() {
+        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletRequest request = requestAttributes.getRequest();
+        return request;
+    }
 
-		response.setContentType("application/json;charset=utf-8");
-		PrintWriter writer = null;
-		try {
-			writer = response.getWriter();
-			String json = "{}";
-			if (null != body) {
-				json = JsonUtil.toJson(body);
-			}
-			writer.write(json);
+    /**
+     * 获取response对象
+     *
+     * @return
+     */
+    public static HttpServletResponse getResponse() {
+        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletResponse response = requestAttributes.getResponse();
+        return response;
+    }
 
-			if (INTERFACE_LOG.isDebugEnabled()) {
-				INTERFACE_LOG.debug("[HTTP-RESP]. Response=" + json);
-			}
-		} catch (IOException e) {
-			INTERFACE_LOG.error("fail to write json", e);
-		} finally {
-			IOUtils.closeQuietly(writer);
-		}
-	}
+    /**
+     * 输出响应流
+     *
+     * @param response
+     * @author WANGWEI
+     */
+    public static void returnJson(Object body, HttpServletResponse response) {
+        response.setContentType("application/json;charset=utf-8");
+        PrintWriter writer = null;
+        try {
+            writer = response.getWriter();
+            String json = "{}";
+            if (null != body) {
+                json = JsonUtil.toJson(body);
+            }
+            writer.write(json);
 
-	/**
-	 * 输出响应流(无日志)
-	 *
-	 * @author WANGWEI
-	 * @param respEntity
-	 * @param response
-	 */
-	public static void returnJsonWithoutLog(Object body, HttpServletResponse response) {
+            if (INTERFACE_LOG.isDebugEnabled()) {
+                INTERFACE_LOG.debug("[ServletUtil] responseMsg = " + json);
+            }
+        } catch (IOException e) {
+            INTERFACE_LOG.error("[ServletUtil] returnJson fail... " + e.getMessage(), e);
+        } finally {
+            IOUtils.closeQuietly(writer);
+        }
+    }
 
-		response.setContentType("application/json;charset=utf-8");
-		PrintWriter writer = null;
-		try {
-			writer = response.getWriter();
-			String json = "{}";
-			if (null != body) {
-				json = JsonUtil.toJson(body);
-			}
-			writer.write(json);
-		} catch (IOException e) {
-			// ignore
-		} finally {
-			IOUtils.closeQuietly(writer);
-		}
-	}
+    /**
+     * 输出响应流(无日志)
+     *
+     * @param response
+     * @author WANGWEI
+     */
+    public static void returnJsonWithoutLog(Object body, HttpServletResponse response) {
+        response.setContentType("application/json;charset=utf-8");
+        PrintWriter writer = null;
+        try {
+            writer = response.getWriter();
+            String json = "{}";
+            if (null != body) {
+                json = JsonUtil.toJson(body);
+            }
+            writer.write(json);
+        } catch (IOException e) {
+            // ignore
+        } finally {
+            IOUtils.closeQuietly(writer);
+        }
+    }
 
 }