1. 注解类 Frequency
- @Target ({ElementType.TYPE, ElementType.METHOD})
- @Retention (RetentionPolicy.RUNTIME)
- @Documented
- @Component
- public @interface Frequency {
- String name() default "all";
- int time() default 0;
- int limit() default 0;
- }
2. 拦截对象封装 FrequencyStruct
- public class FrequencyStruct {
- String uniqueKey;
- long start;
- long end;
- int time;
- int limit;
- List<Long> accessPoints = new ArrayList<Long>();
- public void reset(long timeMillis) {
- start = end = timeMillis;
- accessPoints.clear();
- accessPoints.add(timeMillis);
- }
- @Override
- public String toString() {
- return "FrequencyStruct [uniqueKey=" + uniqueKey + ", start=" + start
- + ", end=" + end + ", time=" + time + ", limit=" + limit
- + ", accessPoints=" + accessPoints + "]";
- }
- }
3. FrequencyHandlerInterceptor会拦截所有带Frequency注解的类或方法
如果是有负载情况下,会取x-forwarded-for头里的ip地址,经过负载的请求必须带x-forwarded-for头,记录用户ip。
频率限制使用本地内存做数据基站,初始化时会开辟MAX_BASE_STATION_SIZE长度的HashMap,MAX_BASE_STATION_SIZE得默认值是100000。
- public class FrequencyHandlerInterceptor extends HandlerInterceptorAdapter {
- private Logger logger = LoggerFactory.getLogger(FrequencyHandlerInterceptor.class);
- private static final int MAX_BASE_STATION_SIZE = 100000;
- private static Map<String, FrequencyStruct> BASE_STATION = new HashMap<String, FrequencyStruct>(MAX_BASE_STATION_SIZE);
- private static final float SCALE = 0.75F;
- private static final int MAX_CLEANUP_COUNT = 3;
- private static final int CLEANUP_INTERVAL = 1000;
- private Object syncRoot = new Object();
- private int cleanupCount = 0;
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
- Frequency methodFrequency = ((HandlerMethod) handler).getMethodAnnotation(Frequency.class);
- Frequency classFrequency = ((HandlerMethod) handler).getBean().getClass().getAnnotation(Frequency.class);
- boolean going = true;
- if(classFrequency != null) {
- going = handleFrequency(request, response, classFrequency);
- }
- if(going && methodFrequency != null) {
- going = handleFrequency(request, response, methodFrequency);
- }
- return going;
- }
- private boolean handleFrequency(HttpServletRequest request, HttpServletResponse response, Frequency frequency) {
- boolean going = true;
- if(frequency == null) {
- return going;
- }
- String name = frequency.name();
- int limit = frequency.limit();
- int time = frequency.time();
- if(time == 0 || limit == 0) {
- going = false;
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);
- return going;
- }
- long currentTimeMilles = System.currentTimeMillis() / 1000;
- String ip = getRemoteIp(request);
- String key = ip + "_" + name;
- FrequencyStruct frequencyStruct = BASE_STATION.get(key);
- if(frequencyStruct == null) {
- frequencyStruct = new FrequencyStruct();
- frequencyStruct.uniqueKey = name;
- frequencyStruct.start = frequencyStruct.end = currentTimeMilles;
- frequencyStruct.limit = limit;
- frequencyStruct.time = time;
- frequencyStruct.accessPoints.add(currentTimeMilles);
- synchronized (syncRoot) {
- BASE_STATION.put(key, frequencyStruct);
- }
- if(BASE_STATION.size() > MAX_BASE_STATION_SIZE * SCALE) {
- cleanup(currentTimeMilles);
- }
- } else {
- frequencyStruct.end = currentTimeMilles;
- frequencyStruct.accessPoints.add(currentTimeMilles);
- }
- //时间是否有效
- if(frequencyStruct.end - frequencyStruct.start >= time) {
- if(logger.isDebugEnabled()) {
- logger.debug("frequency struct be out of date, struct will be reset., struct: {}", frequencyStruct.toString());
- }
- frequencyStruct.reset(currentTimeMilles);
- } else {
- int count = frequencyStruct.accessPoints.size();
- if(count > limit) {
- if(logger.isDebugEnabled()) {
- logger.debug("key: {} too frequency. count: {}, limit: {}.", key, count, limit);
- }
- going = false;
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);
- }
- }
- return going;
- }
- private void cleanup(long currentTimeMilles) {
- synchronized (syncRoot) {
- Iterator<String> it = BASE_STATION.keySet().iterator();
- while(it.hasNext()) {
- String key = it.next();
- FrequencyStruct struct = BASE_STATION.get(key);
- if((currentTimeMilles - struct.end) > struct.time) {
- it.remove();
- }
- }
- if((MAX_BASE_STATION_SIZE - BASE_STATION.size()) > CLEANUP_INTERVAL) {
- cleanupCount = 0;
- } else {
- cleanupCount++;
- }
- if(cleanupCount > MAX_CLEANUP_COUNT ) {
- randomCleanup(MAX_CLEANUP_COUNT);
- }
- }
- }
- /**
- * 随机淘汰count个key
- *
- * @param maxCleanupCount
- */
- private void randomCleanup(int count) {
- //防止调用错误
- if(BASE_STATION.size() < MAX_BASE_STATION_SIZE * SCALE) {
- return;
- }
- Iterator<String> it = BASE_STATION.keySet().iterator();
- Random random = new Random();
- int tempCount = 0;
- while(it.hasNext()) {
- if(random.nextBoolean()) {
- it.remove();
- tempCount++;
- if(tempCount >= count) {
- break;
- }
- }
- }
- }
- private String getRemoteIp(HttpServletRequest request) {
- String ip = request.getHeader("x-forwarded-for");
- if(StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("Proxy-Client-IP");
- }
- if(StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("WL-Proxy-Client-IP");
- }
- if(StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getRemoteAddr();
- }
- return ip;
- }
- }
4. 配置使用
- <!-- 拦截器配置 -->
- <mvc:interceptors>
- <!-- 国际化操作拦截器 如果采用基于(请求/Session/Cookie)则必需配置 -->
- <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
- <!-- 如果不定义 mvc:mapping path 将拦截所有的URL请求 -->
- <bean class="xxx.annotation.FrequencyHandlerInterceptor"></bean>
- </mvc:interceptors>
5. 可以使用在Controller或单独某个方法上。最好给每个name都定义单独的name,默认all的范围太广,使用方法如下:
- @Controller
- @RequestMapping("/demo")
- @Frequency(name="demo", limit=3, time=1)
- public class DemoController {
- @RequestMapping(value = {"index"})
- @Frequency(name="method", limit=3, time=1)
- public void method()
- }