注解 + 拦截器:?秒防刷新
小工具篇:工具许多都是我以前在 github 之类开源平台找到的小工具类,作者的信息什么的许多都忘了。先说声不好意思了。若有相关信息,麻烦提醒一下~
解释
所谓的?秒防刷新,其实就是限制用户在某个时间内对某个 Controller 的访问时间限制。最常见的,比如学校教务系统(正方)的3s防刷新。虽然我不知道正方系统具体是如何实现的,不过可以通过 注解+拦截器 来实现。
前期准备
关于 注解+拦截器,我在上一篇小工具中已经有所介绍。
同时,关于系统轮询的问题,可以使用 @Scheduled 来进行一个全系统记录的轮询,但真的没必要……我觉得可以使用 java 自带的定时器小工具 Timer
与 TimerTask
。
* Timer 是一种定时器工具,用来在一个后台线程计划执行指定任务。
* TimerTask 一个抽象类,它的子类或者重写run方法代表一个可以被Timer计划的任务
参考资料:(Timer与TimerTask详解)http://blog.csdn.net/ahxu/article/details/249610
正式开工
- 自定义异常类
HttpServletException : 用于记录http请求或响应异常。
RequestLimitException : 用于记录用户HTTP请求超出设定的限制。
public class RequestLimitException extends Exception { public RequestLimitException() { super("HTTP请求超出设定的限制"); } public RequestLimitException(String message) { super(message); } public RequestLimitException(String message, Throwable cause){ super(message, cause); } }
- 注解类 RequestLimit.java
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface RequestLimit { //允许访问的次数,默认值MAX_VALUE int count() default Integer.MAX_VALUE; // 时间段,单位为毫秒,默认值一分钟 long time() default 60000; }
- 拦截器 RequestLimitContract.java
@Aspect @Component public class RequestLimitContract { private static final Logger logger = LoggerFactory.getLogger(RequestLimitContract.class); //用于存储记录 private Map<String, Integer> redisTemplate=new HashMap<String,Integer>(); @Before("within(@org.springframework.stereotype.Controller *) && @annotation(limit)") public void requestLimit(final JoinPoint joinPoint, RequestLimit limit) throws RequestLimitException { try { //获取 HttpServletRequest 参数 Object[] args = joinPoint.getArgs(); HttpServletRequest request = null; for (int i = 0; i < args.length; i++) { if (args[i] instanceof HttpServletRequest) { request = (HttpServletRequest) args[i]; break; } } if (request == null) { logger.error("方法中缺失HttpServletRequest参数"); throw new HttpServletException("方法中缺失HttpServletRequest参数"); } String ip = request.getLocalAddr(); String url = request.getRequestURL().toString(); final String key = "req_limit_".concat(url).concat(ip); System.out.println("ip = "+ip+" "+" url = "+url+" "+" key = "+key); if(redisTemplate.get(key)==null || redisTemplate.get(key)==0){ redisTemplate.put(key,1); }else{ redisTemplate.put(key,redisTemplate.get(key)+1); } int count = redisTemplate.get(key); if (count > 0) { Timer timer= new Timer(); TimerTask task = new TimerTask(){ //创建一个新的计时器任务。 @Override public void run() { if(!key.equals("")) { redisTemplate.remove(key); } } }; timer.schedule(task, limit.time()); //安排在指定延迟后执行指定的任务。task : 所要安排的任务。limit.time() : 执行任务前的延迟时间,单位是毫秒。 } if (count > limit.count()) { logger.info("用户IP[" + ip + "]访问地址[" + url + "]超过了限定的次数[" + limit.count() + "]"); throw new RequestLimitException(); } } catch (RequestLimitException e) { throw e; } catch (Exception e) { logger.error("发生异常: ", e); } } }
- 控制器
注意方法中一定要有 HttpServletRequest 参数!!!不然会抛出 HttpServletException 异常。
@ResponseBody @RequestMapping("/hello") @RequestLimit(count = 10) public String hello(HttpServletRequest request) { return "Hello World"; }
效果截图
1. 正常访问
-
访问受限
-
服务器后台