View Javadoc
1   /**
2    * Copyright By Grandsoft Company Limited.  
3    * 2013-9-2 下午02:37:57
4    */
5   package gboat2.base.plugin.struts.interceptor;
6   
7   import gboat2.base.bridge.GboatAppContext;
8   import gboat2.base.plugin.exception.InternalInvalidException;
9   
10  import java.util.HashSet;
11  import java.util.Set;
12  import java.util.regex.Pattern;
13  
14  import javax.servlet.http.HttpServletRequest;
15  import javax.servlet.http.HttpSession;
16  
17  import org.apache.commons.collections.map.LRUMap;
18  import org.apache.commons.lang3.StringUtils;
19  import org.apache.commons.lang3.math.NumberUtils;
20  import org.slf4j.Logger;
21  import org.slf4j.LoggerFactory;
22  
23  import com.opensymphony.xwork2.ActionInvocation;
24  import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
25  import com.opensymphony.xwork2.util.TextParseUtil;
26  
27  /**
28   * 检查两次请求之间的时间间隔的拦截器,可以通过该拦截器来配置两次连续请求之间的时间间隔不能小于指定值。
29   * 使用方法:<pre>
30   * 1. 在 Struts.xml 中定义拦截器:
31   *    <code>&lt;interceptor name="requestInterval" class="gboat2.base.plugin.struts.interceptor.RequestIntervalCheckInterceptor" /&gt;</code>
32   * 2. 定义一个拦截器栈,并将 requestInterval 添加到该栈中:<code>
33   *    &lt;interceptor-stack name="gboat2Stack"&gt;
34   *        &lt;interceptor-ref name="requestInterval"&gt;
35   *            &lt;!-- 两次请求的最小时间间隔,单位为毫秒。默认值为 1000 --&gt;
36   *            &lt;param name="timeInterval"&gt;1000&lt;/param&gt;
37   *            &lt;!--- 需要排除的 URL 正则表达式列表,多值用英文逗号分隔 --&gt;
38   *            &lt;param name="excludeParams"&gt;^(/[^/]+)&#42;/upload.do,^(/[^/]+)&#42;/widget!widgetMsgs.do&lt;/param&gt;
39   *        &lt;/interceptor-ref&gt;
40   *    &lt;/interceptors&gt;</code>
41   * 3. 将刚才定义的拦截器栈设置为默认执行: <code>&lt;default-interceptor-ref name="gboat2Stack" /&gt;</code>
42   * 4. 可以在 javaWeb 项目的 web.xml 中添加如下配置,扩展排除的 URL 正则表达式:<code>
43   *    &lt;context-param&gt;
44   *        &lt;description&gt;定制不受时间间隔限制的请求的白名单&lt;/description&gt;
45   *        &lt;param-name&gt;time.interval.exclude.url&lt;/param-name&gt;
46   *        &lt;param-value&gt;
47   *            ^(/[^/]+)&#42;/profession!getProfessionTree\.do,
48   *            ^(/[^/]+)&#42;/gem-qualification!(queryTreeMenu(Asyn)?|queryQualNames|validateCode)\.do
49   *        &lt;/param-value&gt;
50   *    &lt;/context-param&gt;</code>
51   * </pre>
52   * @date 2014-2-27
53   * @author <a href="mailto:[email protected]">何明旺</a>
54   * @since 1.0
55   */
56  public class RequestIntervalCheckInterceptor extends AbstractInterceptor {
57  
58  	private static final long serialVersionUID = 1L;
59  	
60  	private static final Logger logger = LoggerFactory.getLogger(RequestIntervalCheckInterceptor.class.getClass());
61      
62      /** uri 及其访问时间的映射保存在 Session 中的 key */
63      public static final String URI_MAP = "uri_map";
64      /** uri 及其访问时间的映射最多保存的实体个数 */
65      public static final int DEFAULT_MAX_SIZE = 1000;
66      
67      public static final String TIME_INTERVAL_EXCLUDE_URL = "time.interval.exclude.url";
68      
69      /** 时间间隔,单位为毫秒 */
70      long timeInterval = 1000;
71      
72      /** 排除的 URL 规则列表,符合任意一条规则的 URL 将不进行拦截处理,直接放行 */
73      private Set<Pattern> excludeParams = new HashSet<Pattern>();
74      
75      /** 标识 web.xml 中配置需要排除的 URL(time.interval.exclude.url)是否被加载过了 */
76      private static volatile boolean flag ;
77      
78  	@Override
79  	public String intercept(ActionInvocation invocation) throws Exception {
80          HttpServletRequest request = GboatAppContext.getRequest();
81          HttpSession session = request.getSession();
82          String requestURI = request.getRequestURI();
83  
84          String parameter = request.getParameter("metadata");
85          // 元数据请求不做判断,直接放行
86          if (parameter != null && parameter.equals("")) {
87              return invocation.invoke();
88          }
89  
90          // 保证 web.xml 中的例外加一次
91          if (!flag) {
92              setExcludeParams(request.getSession().getServletContext().getInitParameter(TIME_INTERVAL_EXCLUDE_URL));
93              flag = Boolean.TRUE;
94          }
95  
96          // 例外配置中的路径进行检查
97          if (isExcluded(request.getServletPath())) {
98              return invocation.invoke();
99          }
100 
101         LRUMap uriMap = null;
102         // 防止创建多个map
103         synchronized (session) {
104             uriMap = (LRUMap) session.getAttribute(URI_MAP);
105             if (uriMap == null) {
106                 uriMap = new LRUMap(DEFAULT_MAX_SIZE);
107                 session.setAttribute(URI_MAP, uriMap);
108             }
109         }
110         // 获取当前uri上次访问时间
111         Long systemMill = (Long)uriMap.get(requestURI);
112         long currentTimeMillis = System.currentTimeMillis();
113         if (systemMill != null) {
114             if ((currentTimeMillis - systemMill) <= timeInterval) {
115                 logger.warn("{} request interval is less than {}s", requestURI, timeInterval / 1000);
116                 throw new InternalInvalidException("连续请求时间不能少于" + timeInterval / 1000 + "秒,请稍后再试。");
117             }
118         }
119         uriMap.put(requestURI, currentTimeMillis);
120         return invocation.invoke();
121 	}
122 
123 	/**
124 	 * Struts2 通过 setXxx 方法注入拦截器的参数值
125 	 * @param timeInterval 时间间隔,单位为毫秒 
126 	 */
127 	public void setTimeInterval(String timeInterval) {
128 		if(StringUtils.isNotBlank(timeInterval)){
129 		    this.timeInterval = NumberUtils.toLong(timeInterval.trim(), this.timeInterval);
130 		}
131 	}
132 
133 	/**
134 	 * Struts2 通过 setXxx 方法注入拦截器的参数值
135 	 * @param commaDelim 需要排除的 URL 正则表达式,多值用英文逗号分隔
136 	 */
137 	public void setExcludeParams(String commaDelim) {
138 	    if (StringUtils.isBlank(commaDelim))
139             return;
140 
141 	    Set<String> excludePatterns = TextParseUtil.commaDelimitedStringToSet(commaDelim);
142         if (!excludePatterns.isEmpty()) {
143             for (String pattern : excludePatterns) {
144                 excludeParams.add(Pattern.compile(pattern));
145             }
146         }
147 	}
148     
149     /**
150      * 检查请求的 URI 是否在例外中
151      * @param requestUri 请求的 URI
152      * @return 请求的 URI 符合例外的正则表达式则返回 true, 反之则返回 false
153      */
154     protected boolean isExcluded(String requestUri) {
155         if (!this.excludeParams.isEmpty()) {
156             for (Pattern pattern : excludeParams) {
157                 if (pattern.matcher(requestUri).matches()) {
158                     return true;
159                 }
160             }
161         }
162         return false;
163     }
164 
165 }