单用户限制接口请求频率
HandlerInterceptor
SpringMVC的处理器拦截器,类似于Servlet中的过滤器filter。
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception; }
|
适用场景
- 日志记录,记录请求信息的日志,以便进行信息监控,信息统计等;
- 权限检查,如登陆检测,进入处理器检测是否登陆,如果没有直接返回到登陆页面;
- 性能监控,如慢日志;
HandlerInterceptorAdapter
有时只需实现三个回调方法中的某个,如果实现HandlerInterceptor接口,三个方法必须都实现,此时Spring提供了一个HandlerInterceptorAdapter适配器,允许只实现需要的回调方法。
public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { public HandlerInterceptorAdapter() { }
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; }
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { }
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }
public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
|
运行流程总结如下:
1、拦截器执行顺序是按照Spring配置文件中定义的顺序而定的;
2、先按照顺序执行所有拦截器的preHandle方法,一直遇到return false为止,return false之后的所有拦截器都不会执行;若都是return true,则按顺序加载完preHandle方法;
3、执行主方法(自己的controller接口),若中间抛出异常,则跟return false效果一致,不会继续执行postHandle,只会倒序执行afterCompletion方法;
4、在主方法执行完业务逻辑(页面还未渲染数据)时,按倒序执行postHandle方法。若第三个拦截器的preHandle方法return false,则会执行第二个和第一个的postHandle方法和afterCompletion(postHandle都执行完才会执行这个,也就是页面渲染完数据后,执行after进行清理工作)方法。(postHandle和afterCompletion都是倒序执行);
项目中的实现
声明注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AccessLimit { int seconds(); int maxCount(); boolean needLogin() default true; }
|
重写 HandlerInterceptorAdapter
@Service public class AccessInterceptor extends HandlerInterceptorAdapter {
@Autowired private UserService userService;
@Autowired private RedisService redisService;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { SkUser skUser = getUser(request, response); UserContext.setUser(skUser);
HandlerMethod handlerMethod = (HandlerMethod) handler; AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class); if (accessLimit == null) { return true; } int seconds = accessLimit.seconds(); int maxCount = accessLimit.maxCount(); boolean needLogin = accessLimit.needLogin(); String key = request.getRequestURI(); if (needLogin) { if (skUser == null) { render(response, ResultStatus.SESSION_ERROR); return false; } key = key + "_" + skUser.getId(); } else { }
AccessKey accessKey = AccessKey.withExpire(seconds); Integer count = redisService.get(accessKey, key, Integer.class); if (count == null) { redisService.set(accessKey, key, 1); } else if (count < maxCount) { redisService.incr(accessKey, key); } else { render(response, ResultStatus.ACCESS_LIMIT_REACHED); return false; } } return true; }
private void render(HttpServletResponse response, ResultStatus resultStatus) throws Exception { response.setContentType("application/json;charset=UTF-8"); OutputStream outputStream = response.getOutputStream(); String str = JSON.toJSONString(ResultSk.error(resultStatus)); outputStream.write(str.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); outputStream.close(); } ... }
|