瀏覽代碼

merge from release_v4.1.2

deason 3 年之前
父節點
當前提交
49850b3419
共有 15 個文件被更改,包括 1126 次插入1025 次删除
  1. 198 183
      examcloud-api-commons/src/main/java/cn/com/qmth/examcloud/api/commons/security/bean/User.java
  2. 124 0
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/QuestionOptionHelper.java
  3. 2 2
      examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/pipeline/SimpleNode.java
  4. 64 63
      examcloud-support/src/main/java/cn/com/qmth/examcloud/support/enums/ExamProperties.java
  5. 14 22
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/ApiFlowLimitedInterceptor.java
  6. 2 2
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/FirstInterceptor.java
  7. 107 112
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/SeqlockInterceptor.java
  8. 28 17
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/redis/SimpleRedisClient.java
  9. 121 128
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/RequestPermissionInterceptor.java
  10. 160 166
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/security/RpcInterceptor.java
  11. 2 10
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/CustomExceptionHandler.java
  12. 0 4
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/ServletUtil.java
  13. 41 40
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/task/AbstractTask.java
  14. 31 34
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/task/DistributionJob.java
  15. 232 242
      examcloud-web/src/main/java/cn/com/qmth/examcloud/web/task/QuartzManager.java

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

@@ -1,10 +1,10 @@
 package cn.com.qmth.examcloud.api.commons.security.bean;
 
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
 import java.util.Date;
 import java.util.List;
 
-import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
-
 /**
  * 用户
  *
@@ -14,188 +14,203 @@ import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
  */
 public class User implements JsonSerializable {
 
-	private static final long serialVersionUID = 8766713125414955078L;
-
-	/**
-	 * 全局唯一用户标识符
-	 */
-	private String key;
-
-	/**
-	 * 用户类型
-	 */
-	private UserType userType;
-
-	/**
-	 * 用户ID(包含普通用户ID,学生用户ID)
-	 */
-	private Long userId;
-
-	/**
-	 * 显示名
-	 */
-	private String displayName;
-
-	/**
-	 * 顶级机构ID
-	 */
-	private Long rootOrgId;
-
-	/**
-	 * 顶级机构名称
-	 */
-	private String rootOrgName;
-
-	/**
-	 * 顶级机构域名
-	 */
-	private String rootOrgDomain;
-
-	/**
-	 * 创建时间
-	 */
-	private Date creationTime;
-
-	/**
-	 * 角色集合
-	 */
-	private List<Role> roleList;
-
-	/**
-	 * 客户端IP
-	 */
-	private String clientIp;
-
-	/**
-	 * 鉴权token
-	 */
-	private String token;
-
-	/**
-	 * 会话失效时长
-	 */
-	private Integer sessionTimeout;
-	
-	/**
-	 * 密码虚弱
-	 */
-	private Boolean passwordWeak;
-
-	/**
-	 * 构建key
-	 *
-	 * @author WANGWEI
-	 * @return
-	 */
-	public String buildKey() {
-		this.key = new StringBuilder().append("U_").append(userType.getCode()).append("_")
-				.append(rootOrgId).append("_").append(userId).toString();
-		return this.key;
-	}
-
-	public String getKey() {
-		return key;
-	}
-
-	public void setKey(String key) {
-		this.key = key;
-	}
-
-	public UserType getUserType() {
-		return userType;
-	}
-
-	public void setUserType(UserType userType) {
-		this.userType = userType;
-	}
-
-	public Long getUserId() {
-		return userId;
-	}
-
-	public void setUserId(Long userId) {
-		this.userId = userId;
-	}
-
-	public String getDisplayName() {
-		return displayName;
-	}
-
-	public void setDisplayName(String displayName) {
-		this.displayName = displayName;
-	}
-
-	public Long getRootOrgId() {
-		return rootOrgId;
-	}
-
-	public void setRootOrgId(Long rootOrgId) {
-		this.rootOrgId = rootOrgId;
-	}
-
-	public String getRootOrgName() {
-		return rootOrgName;
-	}
-
-	public void setRootOrgName(String rootOrgName) {
-		this.rootOrgName = rootOrgName;
-	}
-
-	public String getRootOrgDomain() {
-		return rootOrgDomain;
-	}
-
-	public void setRootOrgDomain(String rootOrgDomain) {
-		this.rootOrgDomain = rootOrgDomain;
-	}
-
-	public Date getCreationTime() {
-		return creationTime;
-	}
-
-	public void setCreationTime(Date creationTime) {
-		this.creationTime = creationTime;
-	}
-
-	public List<Role> getRoleList() {
-		return roleList;
-	}
-
-	public void setRoleList(List<Role> roleList) {
-		this.roleList = roleList;
-	}
-
-	public String getClientIp() {
-		return clientIp;
-	}
-
-	public void setClientIp(String clientIp) {
-		this.clientIp = clientIp;
-	}
-
-	public String getToken() {
-		return token;
-	}
-
-	public void setToken(String token) {
-		this.token = token;
-	}
-
-	public Integer getSessionTimeout() {
-		return sessionTimeout;
-	}
-
-	public void setSessionTimeout(Integer sessionTimeout) {
-		this.sessionTimeout = sessionTimeout;
-	}
+    private static final long serialVersionUID = 8766713125414955078L;
+
+    /**
+     * 全局唯一用户标识符
+     */
+    private String key;
+
+    /**
+     * 用户类型
+     */
+    private UserType userType;
+
+    /**
+     * 用户ID(包含普通用户ID,学生用户ID)
+     */
+    private Long userId;
+
+    /**
+     * 显示名
+     */
+    private String displayName;
+
+    /**
+     * 顶级机构ID
+     */
+    private Long rootOrgId;
+
+    /**
+     * 顶级机构名称
+     */
+    private String rootOrgName;
+
+    /**
+     * 顶级机构域名
+     */
+    private String rootOrgDomain;
+
+    /**
+     * 创建时间
+     */
+    private Date creationTime;
+
+    /**
+     * 角色集合
+     */
+    private List<Role> roleList;
+
+    /**
+     * 客户端IP
+     */
+    private String clientIp;
+
+    /**
+     * 鉴权token
+     */
+    private String token;
+
+    /**
+     * 会话失效时长
+     */
+    private Integer sessionTimeout;
+
+    /**
+     * 密码虚弱
+     */
+    private Boolean passwordWeak;
+
+    /**
+     * 加密方案组合
+     */
+    private String salt;
+
+    /**
+     * 构建key
+     *
+     * @return
+     * @author WANGWEI
+     */
+    public String buildKey() {
+        this.key = new StringBuilder()
+                .append("U_").append(userType.getCode())
+                .append("_").append(rootOrgId)
+                .append("_").append(userId)
+                .toString();
+        return this.key;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public UserType getUserType() {
+        return userType;
+    }
+
+    public void setUserType(UserType userType) {
+        this.userType = userType;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public String getRootOrgName() {
+        return rootOrgName;
+    }
+
+    public void setRootOrgName(String rootOrgName) {
+        this.rootOrgName = rootOrgName;
+    }
+
+    public String getRootOrgDomain() {
+        return rootOrgDomain;
+    }
+
+    public void setRootOrgDomain(String rootOrgDomain) {
+        this.rootOrgDomain = rootOrgDomain;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public void setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+    }
+
+    public List<Role> getRoleList() {
+        return roleList;
+    }
+
+    public void setRoleList(List<Role> roleList) {
+        this.roleList = roleList;
+    }
+
+    public String getClientIp() {
+        return clientIp;
+    }
+
+    public void setClientIp(String clientIp) {
+        this.clientIp = clientIp;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Integer getSessionTimeout() {
+        return sessionTimeout;
+    }
+
+    public void setSessionTimeout(Integer sessionTimeout) {
+        this.sessionTimeout = sessionTimeout;
+    }
+
+    public Boolean getPasswordWeak() {
+        return passwordWeak;
+    }
+
+    public void setPasswordWeak(Boolean passwordWeak) {
+        this.passwordWeak = passwordWeak;
+    }
 
-	public Boolean getPasswordWeak() {
-		return passwordWeak;
-	}
+    public String getSalt() {
+        return salt;
+    }
 
-	public void setPasswordWeak(Boolean passwordWeak) {
-		this.passwordWeak = passwordWeak;
-	}
+    public void setSalt(String salt) {
+        this.salt = salt;
+    }
 
-	
 }

+ 124 - 0
examcloud-commons/src/main/java/cn/com/qmth/examcloud/commons/helpers/QuestionOptionHelper.java

@@ -0,0 +1,124 @@
+package cn.com.qmth.examcloud.commons.helpers;
+
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 试题选项辅助工具类
+ */
+public class QuestionOptionHelper {
+
+    private static final Logger log = LoggerFactory.getLogger(QuestionOptionHelper.class);
+
+    /**
+     * 将(0-25)之间的数字转换成大写字母
+     */
+    public static String numberToLetter(int number) {
+        if (number >= 0 && number <= 25) {
+            // 只处理0-25之间的数字
+            char c = (char) (65 + number);
+            return String.valueOf(c);
+        }
+
+        return null;
+    }
+
+    /**
+     * 将字符串中包含的“大写字母”全部对应转换成(0-25)之间的数字
+     */
+    public static List<Integer> letterToNumber(String letters) {
+        List<Integer> numbers = new ArrayList<>();
+
+        if (StringUtils.isNotEmpty(letters)) {
+            char[] chars = letters.toCharArray();
+            for (char c : chars) {
+                if (String.valueOf(c).matches("[A-Z]")) {
+                    // 只处理A-Z之间的大写字母
+                    numbers.add(c - 65);
+                }
+            }
+        }
+
+        return numbers;
+    }
+
+    /**
+     * 解析数字编号的选项数据
+     * 注:前期题库的选择题选项只支持0-9共十个选项,现需要扩充至最多支持26个选项
+     * 为了兼容旧数据需要转换为Json数字数组,如:0123456789 --> [0,1,2,3,4,5,6,7,8,9]
+     */
+    public static List<Integer> parseNumberOptions(String numberStr) {
+        List<Integer> numbers = new ArrayList<>();
+        if (StringUtils.isEmpty(numberStr)) {
+            return numbers;
+        }
+
+        if (numberStr.startsWith("[") && numberStr.endsWith("]")) {
+            // 已经是Json数字数组的新格式数据,直接解析并返回
+            String[] values = numberStr
+                    .replace("[", "")
+                    .replace("]", "")
+                    .split(",");
+
+            for (String x : values) {
+                if (StringUtils.isBlank(x)) {
+                    continue;
+                }
+
+                try {
+                    numbers.add(Integer.parseInt(x.trim()));
+                } catch (NumberFormatException e) {
+                    log.warn("{}不可转换为数字!", x);
+                }
+            }
+
+            return numbers;
+        }
+
+        // 旧数据格式
+        char[] chars = numberStr.toCharArray();
+        for (char c : chars) {
+            if (String.valueOf(c).matches("[0-9]")) {
+                // 只处理0-9之间的数字
+                numbers.add(c - 48);
+            }
+        }
+
+        return numbers;
+    }
+
+    /**
+     * 解析数字,返回JSON数字数组的字符串
+     */
+    public static String parseNumbers(String numberStr) {
+        List<Integer> numbers = parseNumberOptions(numberStr);
+        // 从小到大排序
+        Collections.sort(numbers);
+        return "[" + StringUtils.join(numbers, ",") + "]";
+    }
+
+    /**
+     * 比较正确答案和学生作答答案是否一致(兼容新、旧格式数据)
+     */
+    public static boolean isEqualAnswer(String correctAnswer, String studentAnswer) {
+        List<Integer> correctAnswers = parseNumberOptions(correctAnswer);
+        List<Integer> studentAnswers = parseNumberOptions(studentAnswer);
+        return CollectionUtils.isEqualCollection(correctAnswers, studentAnswers);
+    }
+
+    /*public static void main(String[] args) {
+        System.out.println(parseNumbers("987654321 "));
+        System.out.println(parseNumbers("[3,22,1 ]"));
+
+        System.out.println(isEqualAnswer("123", "321"));
+        System.out.println(isEqualAnswer("123", "[3,2,1]"));
+        System.out.println(isEqualAnswer("123", "[321]"));
+    }*/
+
+}

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

@@ -89,7 +89,7 @@ public class SimpleNode<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
                 ThreadContext.put("TRACE_ID", traceId);
 
                 if (LOG.isDebugEnabled()) {
-                    LOG.debug("{}, {} start", myself.getNodeStatusInfo(), nodeName);
+                    // LOG.debug("{}, {} start", myself.getNodeStatusInfo(), nodeName);
                 }
 
                 counter = new Counter();
@@ -103,7 +103,7 @@ public class SimpleNode<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
                     }
 
                     if (LOG.isDebugEnabled()) {
-                        LOG.debug("{}, {} end", myself.getNodeStatusInfo(), nodeName);
+                        // LOG.debug("{}, {} end", myself.getNodeStatusInfo(), nodeName);
                     }
                 }
 

+ 64 - 63
examcloud-support/src/main/java/cn/com/qmth/examcloud/support/enums/ExamProperties.java

@@ -1,76 +1,77 @@
 package cn.com.qmth.examcloud.support.enums;
 
 /**
- *
- * @author  	chenken
- * @date    	2018年11月15日 下午5:37:35
- * @company 	QMTH
+ * @author chenken
+ * @date 2018年11月15日 下午5:37:35
+ * @company QMTH
  * @description 考试属性
  */
 public enum ExamProperties {
 
-	SCORE_PUBLISHING("发布成绩"),
-	IS_ENTRANCE_EXAM("是否入学考试"),
-	FREEZE_TIME("交卷冻结时间"),
-	EXAM_RECONNECT_TIME("断点续考时间"),
-	BEFORE_EXAM_REMARK("考前说明"),
-	AFTER_EXAM_REMARK("考后说明"),
-	SHOW_CHEATING_REMARK("是否展示作弊"),
-	CHEATING_REMARK("作弊说明"),
-	PRACTICE_TYPE("练习模式"),
-	SINGLE_EDIT("单选题补充说明是否可填"),
-	MUTIPLE_EDIT("多选题补充说明是否可填"),
-	BOOL_EDIT("判断题补充说明是否可填"),
-	FILL_BLANK_EDIT("填空题补充说明是否可填"),
-	SINGLE_ANSWER_REMARK("单选题补充说明"),
-	MUTIPLE_ANSWER_REMARK("多选题补充说明"),
-	BOOL_ANSWER_REMARK("判断题补充说明"),
-	FILL_BLANK_REMARK("填空题补充说明"),
-	TEXT_ANSWER_REMARK("问答题补充说明"),
-	nestedAnswerRemark("套题补充说明"),
-	IS_FACE_ENABLE("是否启用人脸识别"),
-	IS_FACE_CHECK("进入考试是否验证人脸识别"),
-	WARN_THRESHOLD("人脸检测预警阈值"),
-	MARKING_TYPE("阅卷方式"),
-	IS_FACE_VERIFY("是否开启人脸活体检测"),
-	FACE_VERIFY_START_MINUTE("活体检测开始分钟数"),
-	FACE_VERIFY_END_MINUTE("活体检测结束分钟数"),
-	ADD_FACE_VERIFY_OUT_FREEZE_TIME("冻结时间外新加人脸活体检测"),
-	OUT_FREEZE_TIME_FACE_VERIFY_START_MINUTE("冻结时间外活体检测开始分钟数"),
-	OUT_FREEZE_TIME_FACE_VERIFY_END_MINUTE("冻结时间外活体检测结束分钟数"),
-	IP_LIMIT("是否IP限制"),
-	IP_ADDRESSES("IP白名单"),
-	IS_OBJ_SCORE_VIEW("是否显示客观题成绩"),
-	CAN_UPLOAD_ATTACHMENT("是否允许上传附件(离线考试)"),
-	LIVING_WARN_THRESHOLD("人脸真实性阈值"),
-	MARKING_TASK_BUILDED("阅卷是否生成评卷任务"),
-	OFFLINE_UPLOAD_FILE_TYPE("离线考试上传文件类型"),
-	PUSH_SCORE("是否推送分数"),
-	MAX_INTERRUPT_NUM("最大断点续考次数"),
-	IS_STRANGER_ENABLE("是否启用陌生人检测"),
-	LIMITED_IF_NO_SPECIAL_SETTINGS("无特殊设置时禁止考试"),
-	WEIXIN_ANSWER_ENABLED("是否开放微信小程序作答"),
-	APP_EXAM_ENABLED("是否开放app考试"),
-	EXAM_CYCLE_ENABLED("是否启用考试周期设置"),
-	EXAM_CYCLE_WEEK("考试周期星期设置"),
-	EXAM_CYCLE_TIME_RANGE("考试周期时间段设置"),
-	SHOW_UNDERTAKING("显示设置-显示考生承诺书"),
-	UNDERTAKING("显示设置-考生承诺书"),
-	MAX_SWITCH_SCREEN_COUNT("控制设置-切屏次数限制"),
-	;
+    SCORE_PUBLISHING("发布成绩"),
+    IS_ENTRANCE_EXAM("是否入学考试"),
+    FREEZE_TIME("交卷冻结时间"),
+    EXAM_RECONNECT_TIME("断点续考时间"),
+    BEFORE_EXAM_REMARK("考前说明"),
+    AFTER_EXAM_REMARK("考后说明"),
+    SHOW_CHEATING_REMARK("是否展示作弊"),
+    CHEATING_REMARK("作弊说明"),
+    PRACTICE_TYPE("练习模式"),
+    SINGLE_EDIT("单选题补充说明是否可填"),
+    MUTIPLE_EDIT("多选题补充说明是否可填"),
+    BOOL_EDIT("判断题补充说明是否可填"),
+    FILL_BLANK_EDIT("填空题补充说明是否可填"),
+    SINGLE_ANSWER_REMARK("单选题补充说明"),
+    MUTIPLE_ANSWER_REMARK("多选题补充说明"),
+    BOOL_ANSWER_REMARK("判断题补充说明"),
+    FILL_BLANK_REMARK("填空题补充说明"),
+    TEXT_ANSWER_REMARK("问答题补充说明"),
+    nestedAnswerRemark("套题补充说明"),
+    IS_FACE_ENABLE("是否启用人脸识别"),
+    IS_FACE_CHECK("进入考试是否验证人脸识别"),
+    WARN_THRESHOLD("人脸检测预警阈值"),
+    MARKING_TYPE("阅卷方式"),
+    IS_FACE_VERIFY("是否开启人脸活体检测"),
+    FACE_VERIFY_START_MINUTE("活体检测开始分钟数"),
+    FACE_VERIFY_END_MINUTE("活体检测结束分钟数"),
+    ADD_FACE_VERIFY_OUT_FREEZE_TIME("冻结时间外新加人脸活体检测"),
+    OUT_FREEZE_TIME_FACE_VERIFY_START_MINUTE("冻结时间外活体检测开始分钟数"),
+    OUT_FREEZE_TIME_FACE_VERIFY_END_MINUTE("冻结时间外活体检测结束分钟数"),
+    IP_LIMIT("是否IP限制"),
 
-	private ExamProperties(String desc){
-		this.desc = desc;
-	}
+    //已废弃
+    IP_ADDRESSES("IP白名单"),
 
-	private String desc;
+    IS_OBJ_SCORE_VIEW("是否显示客观题成绩"),
+    CAN_UPLOAD_ATTACHMENT("是否允许上传附件(离线考试)"),
+    LIVING_WARN_THRESHOLD("人脸真实性阈值"),
+    MARKING_TASK_BUILDED("阅卷是否生成评卷任务"),
+    OFFLINE_UPLOAD_FILE_TYPE("离线考试上传文件类型"),
+    PUSH_SCORE("是否推送分数"),
+    MAX_INTERRUPT_NUM("最大断点续考次数"),
+    IS_STRANGER_ENABLE("是否启用陌生人检测"),
+    LIMITED_IF_NO_SPECIAL_SETTINGS("无特殊设置时禁止考试"),
+    WEIXIN_ANSWER_ENABLED("是否开放微信小程序作答"),
+    APP_EXAM_ENABLED("是否开放app考试"),
+    EXAM_CYCLE_ENABLED("是否启用考试周期设置"),
+    EXAM_CYCLE_WEEK("考试周期星期设置"),
+    EXAM_CYCLE_TIME_RANGE("考试周期时间段设置"),
+    SHOW_UNDERTAKING("显示设置-显示考生承诺书"),
+    UNDERTAKING("显示设置-考生承诺书"),
+    MAX_SWITCH_SCREEN_COUNT("控制设置-切屏次数限制");
 
-	public String getDesc() {
-		return desc;
-	}
+    ExamProperties(String desc) {
+        this.desc = desc;
+    }
 
-	public void setDesc(String desc) {
-		this.desc = desc;
-	}
+    private String desc;
+
+    public String getDesc() {
+        return desc;
+    }
+
+    public void setDesc(String desc) {
+        this.desc = desc;
+    }
 
 }

+ 14 - 22
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/ApiFlowLimitedInterceptor.java

@@ -76,7 +76,7 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
                 try {
                     curPermitsPerSecond = Integer.parseInt(value);
                 } catch (NumberFormatException e) {
-                    LOG.error("error value. key= " + key, e);
+                    LOG.error("value invalid. key= " + key, e);
                     continue;
                 }
 
@@ -120,7 +120,7 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
                         try {
                             curPermitsPerSecond = Integer.parseInt(curValue);
                         } catch (NumberFormatException e) {
-                            LOG.error("error value. key= " + key, e);
+                            LOG.error("value invalid. key= " + key, e);
                             continue;
                         }
 
@@ -166,10 +166,11 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
             PROPS.clear();
             if (curProperties.size() > 0) {
                 PROPS.putAll(curProperties);
-                LOG.debug("Load config size is {}", PROPS.size());
+
+                LOG.info("Load api limited props size is {}", PROPS.size());
             }
         } catch (Exception e) {
-            LOG.error("Load config fail! " + e.getMessage(), e);
+            LOG.error("Load api limited config fail! " + e.getMessage(), e);
         } finally {
             try {
                 is.close();
@@ -189,12 +190,10 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
 
         boolean acquired = rateLimiter.tryAcquire();
         if (!acquired) {
-            if (LOG.isErrorEnabled()) {
-                LOG.error("[Limited]. G.");
-            }
+            LOG.error("Api Limited, url = {}", request.getServletPath());
 
             response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
-            ServletUtil.returnJson(new StatusResponse("503", "limited. G"), response);
+            ServletUtil.returnJson(new StatusResponse("503", "api limited"), response);
             return false;
         }
 
@@ -220,13 +219,10 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
         if (null != limiter) {
             acquired = limiter.tryAcquire();
             if (!acquired) {
-
-                if (LOG.isErrorEnabled()) {
-                    LOG.error("[Limited]. S. mapping=" + apiInfo.getMapping());
-                }
+                LOG.error("[S] Api Limited, mapping = {}", apiInfo.getMapping());
 
                 response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
-                ServletUtil.returnJson(new StatusResponse("503", "limited. S"), response);
+                ServletUtil.returnJson(new StatusResponse("503", "api limited"), response);
                 return false;
             }
         }
@@ -261,7 +257,7 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
         try {
             limited = (Boolean) AviatorEvaluator.execute(expression, env, true);
         } catch (Exception e) {
-            LOG.error("fail to get value of expression. expression= " + expression, e);
+            LOG.error("Get value fail. expression= " + expression, e);
         }
 
         if (limited) {
@@ -281,8 +277,7 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
                 }
 
                 if (LOG.isDebugEnabled()) {
-                    LOG.debug(
-                            "minCallRate=" + curMinCallRate + "; mapping=" + apiInfo.getMapping());
+                    LOG.debug("minCallRate=" + curMinCallRate + "; mapping=" + apiInfo.getMapping());
                 }
 
                 if (oneMinuteRate < curMinCallRate) {
@@ -290,12 +285,10 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
                 }
             }
 
-            if (LOG.isErrorEnabled()) {
-                LOG.error("[Limited]. ER. mapping=" + apiInfo.getMapping());
-            }
+            LOG.error("[ER] Api Limited, mapping = {}", apiInfo.getMapping());
 
             response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
-            ServletUtil.returnJson(new StatusResponse("503", "limited. ER"), response);
+            ServletUtil.returnJson(new StatusResponse("503", "api limited"), response);
             return false;
         }
 
@@ -303,8 +296,7 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
     }
 
     @Override
-    public void afterCompletion(HttpServletRequest request,
-                                HttpServletResponse response,
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                 Object handler, Exception ex) throws Exception {
 
     }

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

@@ -85,12 +85,12 @@ public class FirstInterceptor implements HandlerInterceptor {
             String mapping = apiInfo.getMapping();
             request.setAttribute(HttpServletRequestAttribute.$_API_INFO.name(), apiInfo);
 
-            LOG.debug("[mapping] ==> " + mapping);
+            // LOG.debug("[mapping] ==> " + mapping);
             request.setAttribute(HttpServletRequestAttribute.$_MAPPING.name(), mapping);
         } else {
             String mapping = StringUtils.join("_[", path, "][", method, "]");
 
-            LOG.debug("[mapping] --> " + mapping);
+            // LOG.debug("[mapping] --> " + mapping);
             request.setAttribute(HttpServletRequestAttribute.$_MAPPING.name(), mapping);
         }
 

+ 107 - 112
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/interceptor/SeqlockInterceptor.java

@@ -1,10 +1,11 @@
 package cn.com.qmth.examcloud.web.interceptor;
 
-import java.util.List;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.util.ThreadLocalUtil;
+import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.support.ServletUtil;
+import cn.com.qmth.examcloud.web.support.StatusResponse;
 import org.apache.commons.collections.CollectionUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -13,12 +14,9 @@ import org.springframework.web.method.HandlerMethod;
 import org.springframework.web.servlet.HandlerInterceptor;
 import org.springframework.web.servlet.ModelAndView;
 
-import cn.com.qmth.examcloud.api.commons.security.bean.User;
-import cn.com.qmth.examcloud.commons.util.ThreadLocalUtil;
-import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
-import cn.com.qmth.examcloud.web.redis.RedisClient;
-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.List;
 
 /**
  * 顺序锁拦截器
@@ -29,106 +27,103 @@ import cn.com.qmth.examcloud.web.support.StatusResponse;
  */
 public class SeqlockInterceptor implements HandlerInterceptor {
 
-	private static final Logger LOG = LoggerFactory.getLogger(SeqlockInterceptor.class);
-
-	/**
-	 * redis client
-	 */
-	private RedisClient redisClient;
-
-	private static final String GLOBAL_LOCK_PREFIX = "$_LOCK_G:";
-
-	private static final String SESSION_LOCK_PREFIX = "$_LOCK_S:";
-
-	private static final String LOCK_ATTRIBUTE = "$sequenceLock";
-
-	/**
-	 * 构造函数
-	 *
-	 * @param redisClient
-	 */
-	public SeqlockInterceptor(RedisClient redisClient) {
-		super();
-		this.redisClient = redisClient;
-	}
-
-	@Override
-	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
-			Object handler) throws Exception {
-		if(LOG.isDebugEnabled()) {
-			LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
-		}
-
-		if (handler instanceof HandlerMethod) {
-
-			HandlerMethod handlerMethod = (HandlerMethod) handler;
-			GlobalSequenceLock globalSeqlock = handlerMethod
-					.getMethodAnnotation(GlobalSequenceLock.class);
-			SessionSequenceLock sessionSeqlock = handlerMethod
-					.getMethodAnnotation(SessionSequenceLock.class);
-
-			String mapping = (String) request
-					.getAttribute(HttpServletRequestAttribute.$_MAPPING.name());
-
-			if (null != globalSeqlock) {
-				String key = GLOBAL_LOCK_PREFIX + mapping;
-
-				if (redisClient.setIfAbsent(key, ThreadLocalUtil.getTraceId(), 60 * 5)) {
-					LOG.info("partition locked");
-
-					request.setAttribute(LOCK_ATTRIBUTE, key);
-					return true;
-				} else {
-					response.setStatus(HttpStatus.CONFLICT.value());
-					ServletUtil.returnJson(new StatusResponse("409", "请求等待,请稍后重试!"), response);
-					return false;
-				}
-
-			} else if (null != sessionSeqlock) {
-
-				User user = (User) request
-						.getAttribute(HttpServletRequestAttribute.$_ACCESS_USER.name());
-
-				String key = SESSION_LOCK_PREFIX + user.getKey();
-
-				if (redisClient.setIfAbsent(key, ThreadLocalUtil.getTraceId(), 60 * 5)) {
-					LOG.info("session locked");
-
-					request.setAttribute(LOCK_ATTRIBUTE, key);
-					return true;
-				} else {
-					response.setStatus(HttpStatus.CONFLICT.value());
-					ServletUtil.returnJson(new StatusResponse("409", "请稍后重试... ..."), response);
-					return false;
-				}
-			}
-		}
-
-		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 {
-
-		Object key = request.getAttribute(LOCK_ATTRIBUTE);
-		if (null != key) {
-			redisClient.delete((String) key);
-		}
-
-		@SuppressWarnings("unchecked")
-		List<String> keyList = (List<String>) ServletUtil.getRequest()
-				.getAttribute(HttpServletRequestAttribute.$_CUSTOM_SEQUENCE_LOCK.name());
-		if (CollectionUtils.isNotEmpty(keyList)) {
-			for (String cur : keyList) {
-				redisClient.delete(cur);
-			}
-		}
-	}
+    private static final Logger LOG = LoggerFactory.getLogger(SeqlockInterceptor.class);
+
+    /**
+     * redis client
+     */
+    private RedisClient redisClient;
+
+    private static final String GLOBAL_LOCK_PREFIX = "$_LOCK_G:";
+
+    private static final String SESSION_LOCK_PREFIX = "$_LOCK_S:";
+
+    private static final String LOCK_ATTRIBUTE = "$sequenceLock";
+
+    /**
+     * 构造函数
+     *
+     * @param redisClient
+     */
+    public SeqlockInterceptor(RedisClient redisClient) {
+        super();
+        this.redisClient = redisClient;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
+                             Object handler) throws Exception {
+        // if (LOG.isDebugEnabled()) {
+        //     LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
+        // }
+
+        if (handler instanceof HandlerMethod) {
+
+            HandlerMethod handlerMethod = (HandlerMethod) handler;
+            GlobalSequenceLock globalSeqlock = handlerMethod
+                    .getMethodAnnotation(GlobalSequenceLock.class);
+            SessionSequenceLock sessionSeqlock = handlerMethod
+                    .getMethodAnnotation(SessionSequenceLock.class);
+
+            String mapping = (String) request.getAttribute(HttpServletRequestAttribute.$_MAPPING.name());
+
+            if (null != globalSeqlock) {
+                String key = GLOBAL_LOCK_PREFIX + mapping;
+
+                if (redisClient.setIfAbsent(key, ThreadLocalUtil.getTraceId(), 60 * 5)) {
+                    LOG.info("{} global locked", key);
+
+                    request.setAttribute(LOCK_ATTRIBUTE, key);
+                    return true;
+                } else {
+                    response.setStatus(HttpStatus.CONFLICT.value());
+                    ServletUtil.returnJson(new StatusResponse("409", "请求等待,请稍后重试!"), response);
+                    return false;
+                }
+
+            } else if (null != sessionSeqlock) {
+                User user = (User) request.getAttribute(HttpServletRequestAttribute.$_ACCESS_USER.name());
+
+                String key = SESSION_LOCK_PREFIX + user.getKey();
+
+                if (redisClient.setIfAbsent(key, ThreadLocalUtil.getTraceId(), 60 * 5)) {
+                    LOG.info("{} session locked", key);
+
+                    request.setAttribute(LOCK_ATTRIBUTE, key);
+                    return true;
+                } else {
+                    response.setStatus(HttpStatus.CONFLICT.value());
+                    ServletUtil.returnJson(new StatusResponse("409", "请稍后重试..."), response);
+                    return false;
+                }
+            }
+        }
+
+        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 {
+
+        Object key = request.getAttribute(LOCK_ATTRIBUTE);
+        if (null != key) {
+            redisClient.delete((String) key);
+        }
+
+        @SuppressWarnings("unchecked")
+        List<String> keyList = (List<String>) ServletUtil.getRequest()
+                .getAttribute(HttpServletRequestAttribute.$_CUSTOM_SEQUENCE_LOCK.name());
+        if (CollectionUtils.isNotEmpty(keyList)) {
+            for (String cur : keyList) {
+                redisClient.delete(cur);
+            }
+        }
+    }
 
 }

+ 28 - 17
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/redis/SimpleRedisClient.java

@@ -34,9 +34,11 @@ public final class SimpleRedisClient implements RedisClient {
     }
 
     private void afterMethod(String content, long startTimeMillis) {
-        if (LOG.isDebugEnabled()) {
-            String msg = String.format("%s, cost %dms", content, System.currentTimeMillis() - startTimeMillis);
-            LOG.debug(msg);
+        long cost = System.currentTimeMillis() - startTimeMillis;
+        if (cost > 1000) {
+            LOG.warn("{} cost {} ms", content, cost);
+        } else {
+            LOG.debug("{} cost {} ms", content, cost);
         }
     }
 
@@ -45,6 +47,7 @@ public final class SimpleRedisClient implements RedisClient {
         long s = System.currentTimeMillis();
         beforeMethod();
         redisTemplate.opsForValue().set(key, value);
+
         afterMethod("set " + key, s);
     }
 
@@ -53,6 +56,7 @@ public final class SimpleRedisClient implements RedisClient {
         long s = System.currentTimeMillis();
         beforeMethod();
         redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
+
         afterMethod("set " + key, s);
     }
 
@@ -61,6 +65,7 @@ public final class SimpleRedisClient implements RedisClient {
         long s = System.currentTimeMillis();
         beforeMethod();
         redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
+
         afterMethod("expire " + key, s);
     }
 
@@ -77,6 +82,7 @@ public final class SimpleRedisClient implements RedisClient {
         long s = System.currentTimeMillis();
         beforeMethod();
         redisTemplate.expire(key, timeout, unit);
+
         afterMethod("expire " + key, s);
     }
 
@@ -88,6 +94,7 @@ public final class SimpleRedisClient implements RedisClient {
         @SuppressWarnings("unchecked")
         T t = (T) object;
         expire(key, timeout);
+
         afterMethod("get " + key, s);
         return t;
     }
@@ -99,19 +106,21 @@ public final class SimpleRedisClient implements RedisClient {
         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) {
+    public <T> T get(String key, String subKey, Class<T> c) {
         long s = System.currentTimeMillis();
         beforeMethod();
-        Object object = redisTemplate.opsForHash().get(key, hashKey);
+        Object object = redisTemplate.opsForHash().get(key, subKey);
         @SuppressWarnings("unchecked")
         T t = (T) object;
-        afterMethod("get " + key, s);
+
+        afterMethod("hash get " + key + " " + subKey, s);
         return t;
     }
 
@@ -120,6 +129,7 @@ public final class SimpleRedisClient implements RedisClient {
         long s = System.currentTimeMillis();
         beforeMethod();
         redisTemplate.expire(key, 0, TimeUnit.SECONDS);
+
         afterMethod("delete " + key, s);
     }
 
@@ -128,6 +138,7 @@ public final class SimpleRedisClient implements RedisClient {
         long s = System.currentTimeMillis();
         beforeMethod();
         redisTemplate.convertAndSend(channel, message);
+
         afterMethod("convertAndSend " + channel, s);
     }
 
@@ -136,6 +147,7 @@ public final class SimpleRedisClient implements RedisClient {
         long s = System.currentTimeMillis();
         beforeMethod();
         Boolean b = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
+
         afterMethod("setIfAbsent " + key, s);
         return b;
     }
@@ -156,30 +168,29 @@ public final class SimpleRedisClient implements RedisClient {
     }
 
     @Override
-    public void set(String key, String subkey, Object value, int timeout) {
+    public void set(String key, String subKey, Object value, int timeout) {
         long s = System.currentTimeMillis();
         beforeMethod();
-        set(key, subkey, value);
+        set(key, subKey, value);
         expire(key, timeout);
-        afterMethod("set " + key + " " + subkey, s);
-
     }
 
     @Override
-    public void delete(String key, String subkey) {
+    public void delete(String key, String subKey) {
         long s = System.currentTimeMillis();
         beforeMethod();
-        redisTemplate.opsForHash().delete(key, subkey);
-        afterMethod("delete " + key + " " + subkey, s);
+        redisTemplate.opsForHash().delete(key, subKey);
+
+        afterMethod("hash delete " + key + " " + subKey, s);
     }
 
     @Override
-    public void set(String key, String subkey, Object value) {
+    public void set(String key, String subKey, Object value) {
         long s = System.currentTimeMillis();
         beforeMethod();
-        redisTemplate.opsForHash().put(key, subkey, value);
-        afterMethod("set " + key + " " + subkey, s);
-    }
+        redisTemplate.opsForHash().put(key, subKey, value);
 
+        afterMethod("hash put " + key + " " + subKey, s);
+    }
 
 }

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

@@ -1,16 +1,5 @@
 package cn.com.qmth.examcloud.web.security;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.logging.log4j.ThreadContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.ModelAndView;
-
 import cn.com.qmth.examcloud.api.commons.CloudService;
 import cn.com.qmth.examcloud.api.commons.EnterpriseService;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
@@ -19,6 +8,16 @@ import cn.com.qmth.examcloud.web.redis.RedisClient;
 import cn.com.qmth.examcloud.web.support.ApiInfo;
 import cn.com.qmth.examcloud.web.support.ServletUtil;
 import cn.com.qmth.examcloud.web.support.StatusResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 /**
  * 请求鉴权
@@ -29,122 +28,116 @@ import cn.com.qmth.examcloud.web.support.StatusResponse;
  */
 public class RequestPermissionInterceptor implements HandlerInterceptor {
 
-	private static final Logger LOG = LoggerFactory.getLogger(RequestPermissionInterceptor.class);
-
-	private ResourceManager resourceManager;
-
-	private RedisClient redisClient;
-
-	/**
-	 * 构造函数
-	 *
-	 * @param resourceManager
-	 * @param redisClient
-	 */
-	public RequestPermissionInterceptor(ResourceManager resourceManager, RedisClient redisClient) {
-		super();
-		this.resourceManager = resourceManager;
-		this.redisClient = redisClient;
-	}
-
-	@Override
-	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
-			Object handler) throws Exception {
-		if(LOG.isDebugEnabled()) {
-			LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
-		}
-
-		ApiInfo apiInfo = (ApiInfo) request
-				.getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
-
-		if (null != apiInfo) {
-			Class<?> ctrClass = apiInfo.getBeanType();
-			if (CloudService.class.isAssignableFrom(ctrClass)
-					|| EnterpriseService.class.isAssignableFrom(ctrClass)) {
-				return true;
-			}
-		}
-
-		String mapping = (String) request
-				.getAttribute(HttpServletRequestAttribute.$_MAPPING.name());
-
-		boolean naked = resourceManager.isNaked(apiInfo, mapping);
-		if (naked) {
-			return true;
-		}
-
-		String key = null;
-		String token = null;
-		String kt = request.getHeader("user_token");
-		if (StringUtils.isNotBlank(kt)) {
-			String[] arr = kt.split(":");
-			if (null != arr && 2 == arr.length) {
-				key = arr[0];
-				token = arr[1];
-			}
-		} else {
-			key = request.getHeader("key");
-			token = request.getHeader("token");
-		}
-
-		if (StringUtils.isBlank(key) && StringUtils.isBlank(token)) {
-			key = request.getParameter("$key");
-			token = request.getParameter("$token");
-		}
-
-		if (StringUtils.isBlank(key) || StringUtils.isBlank(token)) {
-			response.setStatus(HttpStatus.FORBIDDEN.value());
-			ServletUtil.returnJson(new StatusResponse("403", "unallowed"), response);
-			return false;
-		}
-
-		if (StringUtils.isBlank(key)) {
-			response.setStatus(HttpStatus.FORBIDDEN.value());
-			ServletUtil.returnJson(new StatusResponse("403", "key is blank."), response);
-			return false;
-		}
-		if (StringUtils.isBlank(token)) {
-			response.setStatus(HttpStatus.FORBIDDEN.value());
-			ServletUtil.returnJson(new StatusResponse("403", "token is blank."), response);
-			return false;
-		}
-
-		User user = redisClient.get(key, User.class);
-
-		if (null == user) {
-			response.setStatus(HttpStatus.FORBIDDEN.value());
-			ServletUtil.returnJson(new StatusResponse("403", "no login."), response);
-			return false;
-		} else if (!token.equals(user.getToken())) {
-			response.setStatus(HttpStatus.FORBIDDEN.value());
-			ServletUtil.returnJson(new StatusResponse("403", "token is wrong."), response);
-			return false;
-		}
-
-		redisClient.expire(key, user.getSessionTimeout());
-
-		ThreadContext.put("CALLER", key);
-
-		if (!resourceManager.hasPermission(user, apiInfo, mapping)) {
-			response.setStatus(HttpStatus.METHOD_NOT_ALLOWED.value());
-			ServletUtil.returnJson(new StatusResponse("405", "no permission."), response);
-			return false;
-		}
-
-		request.setAttribute(HttpServletRequestAttribute.$_ACCESS_USER.name(), user);
-		request.setAttribute("$kt", key + ":" + token);
-
-		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 {
-	}
+    private static final Logger LOG = LoggerFactory.getLogger(RequestPermissionInterceptor.class);
+
+    private ResourceManager resourceManager;
+
+    private RedisClient redisClient;
+
+    /**
+     * 构造函数
+     *
+     * @param resourceManager
+     * @param redisClient
+     */
+    public RequestPermissionInterceptor(ResourceManager resourceManager, RedisClient redisClient) {
+        super();
+        this.resourceManager = resourceManager;
+        this.redisClient = redisClient;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
+                             Object handler) throws Exception {
+        // if (LOG.isDebugEnabled()) {
+        //     LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
+        // }
+
+        ApiInfo apiInfo = (ApiInfo) request
+                .getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
+
+        if (null != apiInfo) {
+            Class<?> ctrClass = apiInfo.getBeanType();
+            if (CloudService.class.isAssignableFrom(ctrClass)
+                    || EnterpriseService.class.isAssignableFrom(ctrClass)) {
+                return true;
+            }
+        }
+
+        String mapping = (String) request
+                .getAttribute(HttpServletRequestAttribute.$_MAPPING.name());
+
+        boolean naked = resourceManager.isNaked(apiInfo, mapping);
+        if (naked) {
+            return true;
+        }
+
+        String key = null;
+        String token = null;
+        String kt = request.getHeader("user_token");
+        if (StringUtils.isNotBlank(kt)) {
+            String[] arr = kt.split(":");
+            if (null != arr && 2 == arr.length) {
+                key = arr[0];
+                token = arr[1];
+            }
+        } else {
+            key = request.getHeader("key");
+            token = request.getHeader("token");
+        }
+
+        if (StringUtils.isBlank(key) && StringUtils.isBlank(token)) {
+            key = request.getParameter("$key");
+            token = request.getParameter("$token");
+        }
+
+        if (StringUtils.isBlank(key)) {
+            response.setStatus(HttpStatus.FORBIDDEN.value());
+            ServletUtil.returnJson(new StatusResponse("403", "key is blank."), response);
+            return false;
+        }
+        if (StringUtils.isBlank(token)) {
+            response.setStatus(HttpStatus.FORBIDDEN.value());
+            ServletUtil.returnJson(new StatusResponse("403", "token is blank."), response);
+            return false;
+        }
+
+        User user = redisClient.get(key, User.class);
+
+        if (null == user) {
+            response.setStatus(HttpStatus.FORBIDDEN.value());
+            ServletUtil.returnJson(new StatusResponse("403", "no login."), response);
+            return false;
+        } else if (!token.equals(user.getToken())) {
+            response.setStatus(HttpStatus.FORBIDDEN.value());
+            ServletUtil.returnJson(new StatusResponse("403", "token is wrong."), response);
+            return false;
+        }
+
+        redisClient.expire(key, user.getSessionTimeout());
+
+        ThreadContext.put("CALLER", key);
+
+        if (!resourceManager.hasPermission(user, apiInfo, mapping)) {
+            response.setStatus(HttpStatus.METHOD_NOT_ALLOWED.value());
+            ServletUtil.returnJson(new StatusResponse("405", "no permission."), response);
+            return false;
+        }
+
+        request.setAttribute(HttpServletRequestAttribute.$_ACCESS_USER.name(), user);
+        request.setAttribute("$kt", key + ":" + token);
+
+        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 {
+    }
 
 }

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

@@ -1,16 +1,5 @@
 package cn.com.qmth.examcloud.web.security;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.logging.log4j.ThreadContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.http.HttpStatus;
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.ModelAndView;
-
 import cn.com.qmth.examcloud.api.commons.CloudService;
 import cn.com.qmth.examcloud.api.commons.security.bean.AccessApp;
 import cn.com.qmth.examcloud.commons.util.ByteUtil;
@@ -20,6 +9,16 @@ import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
 import cn.com.qmth.examcloud.web.support.ApiInfo;
 import cn.com.qmth.examcloud.web.support.ServletUtil;
 import cn.com.qmth.examcloud.web.support.StatusResponse;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 /**
  * spring cloud 请求接入
@@ -30,160 +29,155 @@ import cn.com.qmth.examcloud.web.support.StatusResponse;
  */
 public final class RpcInterceptor implements HandlerInterceptor {
 
-	private static final Logger LOG = LoggerFactory.getLogger(RpcInterceptor.class);
-
-	private ResourceManager resourceManager;
-
-	/**
-	 * 构造函数
-	 *
-	 * @param resourceManager
-	 */
-	public RpcInterceptor(ResourceManager resourceManager) {
-		super();
-		this.resourceManager = resourceManager;
-	}
-
-	@Override
-	public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
-			Object handler) throws Exception {
-		if(LOG.isDebugEnabled()) {
-			LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
-		}
-
-		ApiInfo apiInfo = (ApiInfo) request
-				.getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
-		if (null == apiInfo) {
-			return true;
-		}
-		Class<?> ctrClass = apiInfo.getBeanType();
-		if (!CloudService.class.isAssignableFrom(ctrClass)) {
-			return true;
-		}
-
-		String appId = request.getHeader("App-Id");
-		String appCode = request.getHeader("App-Code");
-		String timestamp = request.getHeader("timestamp");
-		String accessToken = request.getHeader("Access-Token");
-		if (StringUtils.isBlank(appId)) {
-			// 401
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil
-					.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-							"'App-Id' is blank"), response);
-			return false;
-		}
-		Long appIdLong = null;
-		try {
-			appIdLong = Long.parseLong(appId);
-		} catch (Exception e) {
-			// 401
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil
-					.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-							"'App-Id' must be a long"), response);
-			return false;
-		}
-		if (StringUtils.isBlank(appCode)) {
-			// 401
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil
-					.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-							"'App-Code' is blank"), response);
-			return false;
-		}
-		if (StringUtils.isBlank(timestamp)) {
-			// 401
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil
-					.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-							"'App-Code' is blank"), response);
-			return false;
-		}
-		Long timestampLong = null;
-		try {
-			timestampLong = Long.parseLong(timestamp);
-		} catch (Exception e) {
-			// 401
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil
-					.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-							"'timestamp' must be a long"), response);
-			return false;
-		}
-
-		if (StringUtils.isBlank(accessToken)) {
-			// 401
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil
-					.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-							"'Access-Token' is blank"), response);
-			return false;
-		}
-
-		AccessApp accessApp = null;
-		try {
-			accessApp = resourceManager.getAccessApp(appIdLong);
-		} catch (Exception e) {
-			LOG.error("fail to get App info. appId=" + appIdLong, e);
-		}
-		if (null == accessApp) {
-			// 401
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil
-					.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-							"'App-Id' is wrong"), response);
-			return false;
-		}
-		if (!appCode.equals(accessApp.getAppCode())) {
-			// 401
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil
-					.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-							"'App-Id' & 'App-Code' are wrong"), response);
-			return false;
-		}
-
-		if (null != accessApp.getTimeRange()) {
-			long currentTimeMillis = System.currentTimeMillis();
-			if (Math.abs(currentTimeMillis - timestampLong) > accessApp.getTimeRange()) {
-				response.setStatus(HttpStatus.UNAUTHORIZED.value());
-				ServletUtil.returnJson(
-						new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
-								"'timestamp' is out"),
-						response);
-				return false;
-			}
-		}
-
-		String secretKey = accessApp.getSecretKey();
-		String joinStr = StringUtil.join(appId, appCode, timestamp, secretKey);
-		byte[] bytes = SHA256.encode(joinStr);
-		String hexAscii = ByteUtil.toHexAscii(bytes);
-
-		if (!hexAscii.equalsIgnoreCase(accessToken)) {
-			response.setStatus(HttpStatus.UNAUTHORIZED.value());
-			ServletUtil.returnJson(new StatusResponse(
-					String.valueOf(HttpStatus.UNAUTHORIZED.value()), "access failure"), response);
-			return false;
-		}
-
-		ThreadContext.put("CALLER", "APP:" + accessApp.getAppCode());
-		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 {
-	}
-
-	public void setResourceManager(ResourceManager resourceManager) {
-		this.resourceManager = resourceManager;
-	}
+    private static final Logger LOG = LoggerFactory.getLogger(RpcInterceptor.class);
+
+    private ResourceManager resourceManager;
+
+    /**
+     * 构造函数
+     *
+     * @param resourceManager
+     */
+    public RpcInterceptor(ResourceManager resourceManager) {
+        super();
+        this.resourceManager = resourceManager;
+    }
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
+                             Object handler) throws Exception {
+        // if (LOG.isDebugEnabled()) {
+        //     LOG.debug(String.format("[%s] - %s", request.getMethod(), request.getServletPath()));
+        // }
+
+        ApiInfo apiInfo = (ApiInfo) request
+                .getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
+        if (null == apiInfo) {
+            return true;
+        }
+        Class<?> ctrClass = apiInfo.getBeanType();
+        if (!CloudService.class.isAssignableFrom(ctrClass)) {
+            return true;
+        }
+
+        String appId = request.getHeader("App-Id");
+        String appCode = request.getHeader("App-Code");
+        String timestamp = request.getHeader("timestamp");
+        String accessToken = request.getHeader("Access-Token");
+        if (StringUtils.isBlank(appId)) {
+            // 401
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
+                    "App-Id is blank"), response);
+            return false;
+        }
+
+        Long appIdLong = null;
+        try {
+            appIdLong = Long.parseLong(appId);
+        } catch (Exception e) {
+            // 401
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
+                    "App-Id must be a number"), response);
+            return false;
+        }
+        if (StringUtils.isBlank(appCode)) {
+            // 401
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
+                    "App-Code is blank"), response);
+            return false;
+        }
+        if (StringUtils.isBlank(timestamp)) {
+            // 401
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
+                    "App-Code is blank"), response);
+            return false;
+        }
+        Long timestampLong = null;
+        try {
+            timestampLong = Long.parseLong(timestamp);
+        } catch (Exception e) {
+            // 401
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
+                    "timestamp must be a number"), response);
+            return false;
+        }
+
+        if (StringUtils.isBlank(accessToken)) {
+            // 401
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
+                    "Access-Token is blank"), response);
+            return false;
+        }
+
+        AccessApp accessApp = null;
+        try {
+            accessApp = resourceManager.getAccessApp(appIdLong);
+        } catch (Exception e) {
+            LOG.error("AccessApp load fail, appId=" + appIdLong, e);
+        }
+        if (null == accessApp) {
+            // 401
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
+                    "App-Id is wrong"), response);
+            return false;
+        }
+        if (!appCode.equals(accessApp.getAppCode())) {
+            // 401
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()),
+                    "App-Id & App-Code are wrong"), response);
+            return false;
+        }
+
+        if (null != accessApp.getTimeRange()) {
+            long currentTimeMillis = System.currentTimeMillis();
+            if (Math.abs(currentTimeMillis - timestampLong) > accessApp.getTimeRange()) {
+                response.setStatus(HttpStatus.UNAUTHORIZED.value());
+                ServletUtil.returnJson(
+                        new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()), "timestamp invalid"),
+                        response
+                );
+                return false;
+            }
+        }
+
+        String secretKey = accessApp.getSecretKey();
+        String joinStr = StringUtil.join(appId, appCode, timestamp, secretKey);
+        byte[] bytes = SHA256.encode(joinStr);
+        String hexAscii = ByteUtil.toHexAscii(bytes);
+
+        if (!hexAscii.equalsIgnoreCase(accessToken)) {
+            response.setStatus(HttpStatus.UNAUTHORIZED.value());
+            ServletUtil.returnJson(
+                    new StatusResponse(String.valueOf(HttpStatus.UNAUTHORIZED.value()), "access failure"),
+                    response
+            );
+            return false;
+        }
+
+        ThreadContext.put("CALLER", "APP:" + accessApp.getAppCode());
+        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 {
+    }
+
+    public void setResourceManager(ResourceManager resourceManager) {
+        this.resourceManager = resourceManager;
+    }
 
 }

+ 2 - 10
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/CustomExceptionHandler.java

@@ -1,7 +1,6 @@
 package cn.com.qmth.examcloud.web.support;
 
 import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.commons.util.JsonMapper;
 import cn.com.qmth.examcloud.web.config.SystemProperties;
 import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
 import cn.com.qmth.examcloud.web.exception.ApiFlowLimitedException;
@@ -235,17 +234,10 @@ public class CustomExceptionHandler {
             }
         }
 
-        String msg = String.format("[%s] response status = %s, %s", request.getServletPath(),
-                httpStatus.value(), new JsonMapper().toJson(body));
-
         if (printStackTrace) {
-            LOG.error(msg, err);
+            LOG.error("[{}] response status = {}, Cause is {}", request.getServletPath(), httpStatus.value(), err.getMessage(), err);
         } else {
-            LOG.error("{}, Cause is {}", msg, err.getMessage());
-
-            if (LOG.isDebugEnabled()) {
-                LOG.error(err.getMessage(), err);
-            }
+            LOG.error("[{}] response status = {}, Cause is {}", request.getServletPath(), httpStatus.value(), err.getMessage());
         }
 
         HttpHeaders headers = new HttpHeaders();

+ 0 - 4
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/support/ServletUtil.java

@@ -64,10 +64,6 @@ public class ServletUtil {
                 json = JsonUtil.toJson(body);
             }
             writer.write(json);
-
-            if (LOG.isDebugEnabled()) {
-                LOG.debug("responseMsg = " + json);
-            }
         } catch (IOException e) {
             LOG.error("returnJson fail... " + e.getMessage(), e);
         } finally {

+ 41 - 40
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/task/AbstractTask.java

@@ -1,7 +1,6 @@
 package cn.com.qmth.examcloud.web.task;
 
 import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -14,44 +13,46 @@ import org.slf4j.LoggerFactory;
  */
 public abstract class AbstractTask implements Task {
 
-	protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractTask.class);
-
-	public abstract TaskTracker getTaskTracker();
-
-	public abstract void run(ScheduleJob scheduleJob) throws Exception;
-
-	@Override
-	public void execute(ScheduleJob scheduleJob, String traceId) {
-		try {
-			getTaskTracker().start(scheduleJob, traceId);
-		} catch (Exception e) {
-			LOGGER.error("[TASK TRACKER]. start", e);
-		}
-		if (LOGGER.isDebugEnabled()) {
-			LOGGER.debug("[TASK IN]. detail: " + JsonUtil.toJson(scheduleJob));
-		}
-		try {
-			run(scheduleJob);
-		} catch (Exception e) {
-			try {
-				getTaskTracker().whenException(scheduleJob, traceId, e);
-			} catch (Exception ex) {
-				LOGGER.error("[TASK TRACKER]. whenException", ex);
-			}
-			if (LOGGER.isErrorEnabled()) {
-				LOGGER.error("[TASK EXCEPTION]. detail: " + JsonUtil.toJson(scheduleJob), e);
-			}
-			throw new ExamCloudRuntimeException(e);
-		}
-		try {
-			getTaskTracker().onEnd(scheduleJob, traceId);
-		} catch (Exception e) {
-			LOGGER.error("[TASK TRACKER]. onEnd", e);
-		}
-		if (LOGGER.isDebugEnabled()) {
-			LOGGER.debug("[TASK OUT]. detail: " + JsonUtil.toJson(scheduleJob));
-		}
-
-	}
+    protected static final Logger LOGGER = LoggerFactory.getLogger(AbstractTask.class);
+
+    public abstract TaskTracker getTaskTracker();
+
+    public abstract void run(ScheduleJob scheduleJob) throws Exception;
+
+    @Override
+    public void execute(ScheduleJob scheduleJob, String traceId) {
+        try {
+            getTaskTracker().start(scheduleJob, traceId);
+        } catch (Exception e) {
+            LOGGER.error("[JOB] start err.. jobId={}", scheduleJob.getJobId(), e);
+        }
+
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("[JOB] start.. jobId={} jobImpl={}", scheduleJob.getJobId(), scheduleJob.getSpringBean());
+        }
+
+        try {
+            run(scheduleJob);
+        } catch (Exception e) {
+            try {
+                getTaskTracker().whenException(scheduleJob, traceId, e);
+            } catch (Exception ex) {
+                LOGGER.error("[JOB] whenException.. jobId={}", scheduleJob.getJobId(), ex);
+            }
+
+            LOGGER.error("[JOB] run err.. jobId={}", scheduleJob.getJobId(), e);
+            throw new ExamCloudRuntimeException(e);
+        }
+
+        try {
+            getTaskTracker().onEnd(scheduleJob, traceId);
+        } catch (Exception e) {
+            LOGGER.error("[JOB] end err.. jobId={}", scheduleJob.getJobId(), e);
+        }
+
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("[JOB] end.. jobId={} jobImpl={}", scheduleJob.getJobId(), scheduleJob.getSpringBean());
+        }
+    }
 
 }

+ 31 - 34
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/task/DistributionJob.java

@@ -1,13 +1,11 @@
 package cn.com.qmth.examcloud.web.task;
 
+import cn.com.qmth.examcloud.commons.util.ThreadLocalUtil;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
 import org.apache.logging.log4j.ThreadContext;
 import org.quartz.Job;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
-
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
-import cn.com.qmth.examcloud.commons.util.ThreadLocalUtil;
-import cn.com.qmth.examcloud.web.support.SpringContextHolder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -19,35 +17,34 @@ import org.slf4j.LoggerFactory;
  * @Copyright (c) 2018-2020 WANGWEI [QQ:522080330] All Rights Reserved.
  */
 public class DistributionJob implements Job {
-	private static final Logger LOG = LoggerFactory.getLogger(DistributionJob.class);
-
-	@Override
-	public void execute(JobExecutionContext context) throws JobExecutionException {
-		String traceId = ThreadLocalUtil.next();
-		ThreadContext.put("TRACE_ID", traceId);
-		ThreadContext.put("CALLER", "TASK");
-		ScheduleJob scheduleJob = null;
-		try {
-			scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
-
-			if (LOG.isDebugEnabled()) {
-				LOG.debug("distribute job. job detail :" + JsonUtil.toJson(scheduleJob));
-			}
-			Object bean = SpringContextHolder.getBean(scheduleJob.getSpringBean());
-
-			Task task = (Task) bean;
-			task.execute(scheduleJob, traceId);
-		} catch (Exception e) {
-			if (LOG.isErrorEnabled()) {
-				LOG.error(
-						"fail to distribute job. job detail :" + JsonUtil.toJson(scheduleJob), e);
-			}
-			throw new JobExecutionException(e);
-		} finally {
-			// 清理log4j线程上下文
-			ThreadContext.clearAll();
-		}
-
-	}
+
+    private static final Logger LOG = LoggerFactory.getLogger(DistributionJob.class);
+
+    @Override
+    public void execute(JobExecutionContext context) throws JobExecutionException {
+        String traceId = ThreadLocalUtil.next();
+        ThreadContext.put("TRACE_ID", traceId);
+        ThreadContext.put("CALLER", "TASK");
+
+        ScheduleJob scheduleJob = null;
+        try {
+            scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get("scheduleJob");
+
+            if (LOG.isInfoEnabled()) {
+                LOG.info("[JOB] execute.. jobId={} jobImpl={}", scheduleJob.getJobId(), scheduleJob.getSpringBean());
+            }
+
+            Object bean = SpringContextHolder.getBean(scheduleJob.getSpringBean());
+
+            Task task = (Task) bean;
+            task.execute(scheduleJob, traceId);
+        } catch (Exception e) {
+            LOG.error("[JOB] execute err.. jobId={}", scheduleJob.getJobId(), e);
+            throw new JobExecutionException(e);
+        } finally {
+            // 清理log4j线程上下文
+            ThreadContext.clearAll();
+        }
+    }
 
 }

+ 232 - 242
examcloud-web/src/main/java/cn/com/qmth/examcloud/web/task/QuartzManager.java

@@ -1,21 +1,8 @@
 package cn.com.qmth.examcloud.web.task;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-import org.quartz.CronScheduleBuilder;
-import org.quartz.CronTrigger;
-import org.quartz.Job;
-import org.quartz.JobBuilder;
-import org.quartz.JobDetail;
-import org.quartz.JobExecutionContext;
-import org.quartz.JobKey;
-import org.quartz.Scheduler;
-import org.quartz.SchedulerException;
-import org.quartz.Trigger;
-import org.quartz.TriggerBuilder;
-import org.quartz.TriggerKey;
+import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
+import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import org.quartz.*;
 import org.quartz.impl.matchers.GroupMatcher;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,8 +10,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 import org.springframework.stereotype.Component;
 
-import cn.com.qmth.examcloud.commons.exception.ExamCloudRuntimeException;
-import cn.com.qmth.examcloud.commons.util.JsonUtil;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
 
 /**
  * Quartz 管理器
@@ -35,228 +23,230 @@ import cn.com.qmth.examcloud.commons.util.JsonUtil;
  */
 @Component
 public class QuartzManager {
-	private static final Logger LOG = LoggerFactory.getLogger(QuartzManager.class);
-
-	@Autowired
-	private SchedulerFactoryBean schedulerFactoryBean;
-
-	/**
-	 * 添加任务
-	 *
-	 * @author WANGWEI
-	 * @param job
-	 */
-	public void addJob(ScheduleJob job) {
-		if (LOG.isDebugEnabled()) {
-			LOG.debug("add a job. job detail: " + JsonUtil.toJson(job));
-		}
-
-		try {
-			Scheduler scheduler = schedulerFactoryBean.getScheduler();
-			TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
-
-			CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
-			if (null == trigger) {
-				Class<? extends Job> jobClass = job.isStateful()
-						? StatefulDistributionJob.class
-						: DistributionJob.class;
-				JobDetail jobDetail = JobBuilder.newJob(jobClass)
-						.withIdentity(job.getJobName(), job.getJobGroup()).build();
-
-				jobDetail.getJobDataMap().put("scheduleJob", job);
-
-				CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
-						.cronSchedule(job.getCronExpression());
-
-				trigger = TriggerBuilder.newTrigger()
-						.withIdentity(job.getJobName(), job.getJobGroup())
-						.withSchedule(scheduleBuilder).build();
-
-				scheduler.scheduleJob(jobDetail, trigger);
-			} else {
-				CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
-						.cronSchedule(job.getCronExpression());
-
-				trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
-						.withSchedule(scheduleBuilder).build();
-
-				scheduler.rescheduleJob(triggerKey, trigger);
-			}
-		} catch (SchedulerException e) {
-			LOG.error("Fail to add a job. job detail: " + JsonUtil.toJson(job), e);
-			throw new ExamCloudRuntimeException(e);
-		}
-
-		if (LOG.isDebugEnabled()) {
-			LOG.debug("add a job successfully. job detail: " + JsonUtil.toJson(job));
-		}
-	}
-
-	/**
-	 * 获取所有计划中的任务列表
-	 *
-	 * @author WANGWEI
-	 * @return
-	 */
-	public List<ScheduleJob> getAllJobs() {
-		try {
-			Scheduler scheduler = schedulerFactoryBean.getScheduler();
-			GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
-			Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
-			List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
-			for (JobKey jobKey : jobKeys) {
-				List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
-				for (Trigger trigger : triggers) {
-					ScheduleJob job = new ScheduleJob();
-					job.setJobName(jobKey.getName());
-					job.setJobGroup(jobKey.getGroup());
-					Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
-					job.setTriggerState(triggerState.name());
-					if (trigger instanceof CronTrigger) {
-						CronTrigger cronTrigger = (CronTrigger) trigger;
-						String cronExpression = cronTrigger.getCronExpression();
-						job.setCronExpression(cronExpression);
-					}
-					jobList.add(job);
-				}
-			}
-			return jobList;
-		} catch (SchedulerException e) {
-			LOG.error("Fail to get all jobs.", e);
-			throw new ExamCloudRuntimeException(e);
-		}
-	}
-
-	/**
-	 * 所有正在运行的job
-	 *
-	 * @author WANGWEI
-	 * @return
-	 */
-	public List<ScheduleJob> getRunningJobs() {
-		try {
-			Scheduler scheduler = schedulerFactoryBean.getScheduler();
-			List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
-			List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());
-			for (JobExecutionContext context : executingJobs) {
-				ScheduleJob job = new ScheduleJob();
-				JobDetail jobDetail = context.getJobDetail();
-				JobKey jobKey = jobDetail.getKey();
-				Trigger trigger = context.getTrigger();
-				job.setJobName(jobKey.getName());
-				job.setJobGroup(jobKey.getGroup());
-				Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
-				job.setTriggerState(triggerState.name());
-				if (trigger instanceof CronTrigger) {
-					CronTrigger cronTrigger = (CronTrigger) trigger;
-					String cronExpression = cronTrigger.getCronExpression();
-					job.setCronExpression(cronExpression);
-				}
-				jobList.add(job);
-			}
-			return jobList;
-		} catch (Exception e) {
-			LOG.error("Fail to get running jobs.", e);
-			throw new ExamCloudRuntimeException(e);
-		}
-	}
-
-	/**
-	 * 暂停一个job
-	 *
-	 * @author WANGWEI
-	 * @param scheduleJob
-	 */
-	public void pauseJob(ScheduleJob scheduleJob) {
-		try {
-			Scheduler scheduler = schedulerFactoryBean.getScheduler();
-			JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
-			scheduler.pauseJob(jobKey);
-		} catch (SchedulerException e) {
-			LOG.error("Fail to pause Job. job detail: " + JsonUtil.toJson(scheduleJob), e);
-			throw new ExamCloudRuntimeException(e);
-		}
-	}
-
-	/**
-	 * 恢复一个job
-	 *
-	 * @author WANGWEI
-	 * @param scheduleJob
-	 */
-	public void resumeJob(ScheduleJob scheduleJob) {
-		try {
-			Scheduler scheduler = schedulerFactoryBean.getScheduler();
-			JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
-			scheduler.resumeJob(jobKey);
-		} catch (SchedulerException e) {
-			LOG.error("Fail to resume job. job detail: " + JsonUtil.toJson(scheduleJob), e);
-			throw new ExamCloudRuntimeException(e);
-		}
-	}
-
-	/**
-	 * 删除一个job
-	 *
-	 * @author WANGWEI
-	 * @param scheduleJob
-	 */
-	public void deleteJob(ScheduleJob scheduleJob) {
-		try {
-			Scheduler scheduler = schedulerFactoryBean.getScheduler();
-			JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
-			scheduler.deleteJob(jobKey);
-		} catch (SchedulerException e) {
-			LOG.error("Fail to delete job. job detail: " + JsonUtil.toJson(scheduleJob), e);
-			throw new ExamCloudRuntimeException(e);
-		}
-
-	}
-
-	/**
-	 * 执行job
-	 *
-	 * @author WANGWEI
-	 * @param scheduleJob
-	 */
-	public void runJob(ScheduleJob scheduleJob) {
-		try {
-			Scheduler scheduler = schedulerFactoryBean.getScheduler();
-			JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
-			scheduler.triggerJob(jobKey);
-		} catch (SchedulerException e) {
-			LOG.error("Fail to run job. job detail: " + JsonUtil.toJson(scheduleJob), e);
-			throw new ExamCloudRuntimeException(e);
-		}
-	}
-
-	/**
-	 * 更新job时间表达式
-	 *
-	 * @author WANGWEI
-	 * @param scheduleJob
-	 */
-	public void updateJobCronExpression(ScheduleJob scheduleJob) {
-		try {
-			Scheduler scheduler = schedulerFactoryBean.getScheduler();
-
-			TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(),
-					scheduleJob.getJobGroup());
-
-			CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
-
-			CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
-					.cronSchedule(scheduleJob.getCronExpression());
-
-			trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
-					.withSchedule(scheduleBuilder).build();
 
-			scheduler.rescheduleJob(triggerKey, trigger);
-		} catch (SchedulerException e) {
-			LOG.error("Fail to update job cron expression. job detail :"
-					+ JsonUtil.toJson(scheduleJob), e);
-			throw new ExamCloudRuntimeException(e);
-		}
-	}
-}
+    private static final Logger LOG = LoggerFactory.getLogger(QuartzManager.class);
+
+    @Autowired
+    private SchedulerFactoryBean schedulerFactoryBean;
+
+    /**
+     * 添加任务
+     *
+     * @param job
+     * @author WANGWEI
+     */
+    public void addJob(ScheduleJob job) {
+        if (LOG.isInfoEnabled()) {
+            LOG.info("addJob detail: " + JsonUtil.toJson(job));
+        }
+
+        try {
+            Scheduler scheduler = schedulerFactoryBean.getScheduler();
+            TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
+
+            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+
+            if (null == trigger) {
+                Class<? extends Job> jobClass = job.isStateful()
+                        ? StatefulDistributionJob.class
+                        : DistributionJob.class;
+                JobDetail jobDetail = JobBuilder.newJob(jobClass)
+                        .withIdentity(job.getJobName(), job.getJobGroup()).build();
+
+                jobDetail.getJobDataMap().put("scheduleJob", job);
+
+                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
+                        .cronSchedule(job.getCronExpression());
+
+                trigger = TriggerBuilder.newTrigger()
+                        .withIdentity(job.getJobName(), job.getJobGroup())
+                        .withSchedule(scheduleBuilder).build();
+
+                scheduler.scheduleJob(jobDetail, trigger);
+            } else {
+                CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
+                        .cronSchedule(job.getCronExpression());
+
+                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
+                        .withSchedule(scheduleBuilder).build();
+
+                scheduler.rescheduleJob(triggerKey, trigger);
+            }
+        } catch (SchedulerException e) {
+            LOG.error("addJob fail, detail: " + JsonUtil.toJson(job), e);
+            throw new ExamCloudRuntimeException(e);
+        }
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("addJob successfully, detail: " + JsonUtil.toJson(job));
+        }
+    }
+
+    /**
+     * 获取所有计划中的任务列表
+     *
+     * @return
+     * @author WANGWEI
+     */
+    public List<ScheduleJob> getAllJobs() {
+        try {
+            Scheduler scheduler = schedulerFactoryBean.getScheduler();
+            GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
+            Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
+            List<ScheduleJob> jobList = new ArrayList<ScheduleJob>();
+            for (JobKey jobKey : jobKeys) {
+                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
+                for (Trigger trigger : triggers) {
+                    ScheduleJob job = new ScheduleJob();
+                    job.setJobName(jobKey.getName());
+                    job.setJobGroup(jobKey.getGroup());
+                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
+                    job.setTriggerState(triggerState.name());
+                    if (trigger instanceof CronTrigger) {
+                        CronTrigger cronTrigger = (CronTrigger) trigger;
+                        String cronExpression = cronTrigger.getCronExpression();
+                        job.setCronExpression(cronExpression);
+                    }
+                    jobList.add(job);
+                }
+            }
+            return jobList;
+        } catch (SchedulerException e) {
+            LOG.error("Fail to get all jobs.", e);
+            throw new ExamCloudRuntimeException(e);
+        }
+    }
+
+    /**
+     * 所有正在运行的job
+     *
+     * @return
+     * @author WANGWEI
+     */
+    public List<ScheduleJob> getRunningJobs() {
+        try {
+            Scheduler scheduler = schedulerFactoryBean.getScheduler();
+            List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
+            List<ScheduleJob> jobList = new ArrayList<ScheduleJob>(executingJobs.size());
+            for (JobExecutionContext context : executingJobs) {
+                ScheduleJob job = new ScheduleJob();
+                JobDetail jobDetail = context.getJobDetail();
+                JobKey jobKey = jobDetail.getKey();
+                Trigger trigger = context.getTrigger();
+                job.setJobName(jobKey.getName());
+                job.setJobGroup(jobKey.getGroup());
+                Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
+                job.setTriggerState(triggerState.name());
+                if (trigger instanceof CronTrigger) {
+                    CronTrigger cronTrigger = (CronTrigger) trigger;
+                    String cronExpression = cronTrigger.getCronExpression();
+                    job.setCronExpression(cronExpression);
+                }
+                jobList.add(job);
+            }
+            return jobList;
+        } catch (Exception e) {
+            LOG.error("Fail to get running jobs.", e);
+            throw new ExamCloudRuntimeException(e);
+        }
+    }
+
+    /**
+     * 暂停一个job
+     *
+     * @param scheduleJob
+     * @author WANGWEI
+     */
+    public void pauseJob(ScheduleJob scheduleJob) {
+        try {
+            Scheduler scheduler = schedulerFactoryBean.getScheduler();
+            JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
+            scheduler.pauseJob(jobKey);
+        } catch (SchedulerException e) {
+            LOG.error("Fail to pause Job. job detail: " + JsonUtil.toJson(scheduleJob), e);
+            throw new ExamCloudRuntimeException(e);
+        }
+    }
+
+    /**
+     * 恢复一个job
+     *
+     * @param scheduleJob
+     * @author WANGWEI
+     */
+    public void resumeJob(ScheduleJob scheduleJob) {
+        try {
+            Scheduler scheduler = schedulerFactoryBean.getScheduler();
+            JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
+            scheduler.resumeJob(jobKey);
+        } catch (SchedulerException e) {
+            LOG.error("Fail to resume job. job detail: " + JsonUtil.toJson(scheduleJob), e);
+            throw new ExamCloudRuntimeException(e);
+        }
+    }
+
+    /**
+     * 删除一个job
+     *
+     * @param scheduleJob
+     * @author WANGWEI
+     */
+    public void deleteJob(ScheduleJob scheduleJob) {
+        try {
+            Scheduler scheduler = schedulerFactoryBean.getScheduler();
+            JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
+            scheduler.deleteJob(jobKey);
+        } catch (SchedulerException e) {
+            LOG.error("Fail to delete job. job detail: " + JsonUtil.toJson(scheduleJob), e);
+            throw new ExamCloudRuntimeException(e);
+        }
+
+    }
+
+    /**
+     * 执行job
+     *
+     * @param scheduleJob
+     * @author WANGWEI
+     */
+    public void runJob(ScheduleJob scheduleJob) {
+        try {
+            Scheduler scheduler = schedulerFactoryBean.getScheduler();
+            JobKey jobKey = JobKey.jobKey(scheduleJob.getJobName(), scheduleJob.getJobGroup());
+            scheduler.triggerJob(jobKey);
+        } catch (SchedulerException e) {
+            LOG.error("Fail to run job. job detail: " + JsonUtil.toJson(scheduleJob), e);
+            throw new ExamCloudRuntimeException(e);
+        }
+    }
+
+    /**
+     * 更新job时间表达式
+     *
+     * @param scheduleJob
+     * @author WANGWEI
+     */
+    public void updateJobCronExpression(ScheduleJob scheduleJob) {
+        try {
+            Scheduler scheduler = schedulerFactoryBean.getScheduler();
+
+            TriggerKey triggerKey = TriggerKey.triggerKey(scheduleJob.getJobName(),
+                    scheduleJob.getJobGroup());
+
+            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
+
+            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder
+                    .cronSchedule(scheduleJob.getCronExpression());
+
+            trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
+                    .withSchedule(scheduleBuilder).build();
+
+            scheduler.rescheduleJob(triggerKey, trigger);
+        } catch (SchedulerException e) {
+            LOG.error("Fail to update job cron expression. job detail :"
+                    + JsonUtil.toJson(scheduleJob), e);
+            throw new ExamCloudRuntimeException(e);
+        }
+    }
+
+}