前置拦截器的使用

前置拦截器的使用

在项目开发中,想要实现一个防刷的功能,主要是通过在redis中缓存URL(path+userId)被某个用户访问次数的标记,当在规定时间内访问次数过多则会进行拒绝访问,防止恶意刷单破坏公平性,也过滤了一些访问底层数据库的无效操作。

初步实现:

刚开始是在控制层对应接口方法上加一层验证代码,这样导致了控制层代码显得很臃肿,而且如果其他方法也需要的相同的验证,还要再写一段类似的代码,没有实现代码复用。

优雅实现:

使用注解来实现
我的另一篇博客简单介绍了自定义注解的实现,这次我是用拦截器来实现注解的功能。同时我将参数解析器的实现一并放到拦截器中,然后导入到ThreadLocal中以实现私有。(参数解析实现文章链接

拦截器:我个人觉得就是面向切面编程的一种体现,我这里使用的是前置拦截。

  • 首先先建立一个注解
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(METHOD)
public @interface AccessLimit {
	int seconds();
	int maxCount();
	boolean needLogin() default true;
}
  • 拦截器的实现
    1、继承HandlerInterceptorAdapter实现preHandle方法
    2、注册到WebMvcConfigurerAdapterInterceptorRegistry
    3、将参数解析器实现整合到拦截器中
@Service
public class AccessInterceptor  extends HandlerInterceptorAdapter{
	
	@Autowired
	MiaoshaUserService userService;
	
	@Autowired
	RedisService redisService;
	
	/*
	 * 前置拦截器
	 */
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		if(handler instanceof HandlerMethod) {//判断是否为控制层方法
			MiaoshaUser user = getUser(request, response);
			UserContext.setUser(user);//导入到当前线程中
			HandlerMethod hm = (HandlerMethod)handler;
			AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
			if(accessLimit == null) {//是否有@AccessLimit注解
				return true;
			}
			int seconds = accessLimit.seconds();
			int maxCount = accessLimit.maxCount();
			boolean needLogin = accessLimit.needLogin();
			String key = request.getRequestURI();
			if(needLogin) {
				if(user == null) {
					render(response, CodeMsg.SESSION_ERROR);
					return false;
				}
				key += "_" + user.getId();
			}else {
				//do nothing
			}
			AccessKey ak = AccessKey.withExpire(seconds);//seconds秒内更新缓存
			Integer count = redisService.get(ak, key, Integer.class);
	    	if(count  == null) {
	    		 redisService.set(ak, key, 1);
	    	}else if(count < maxCount) {
	    		 redisService.incr(ak, key);
	    	}else {
	    		render(response, CodeMsg.ACCESS_LIMIT_REACHED);
	    		return false;
	    	}
		}
		return true;
	}
	//发送给前端错误信息
	private void render(HttpServletResponse response, CodeMsg cm)throws Exception {
		response.setContentType("application/json;charset=UTF-8");//防乱码
		OutputStream out = response.getOutputStream();
		String str  = JSON.toJSONString(Result.error(cm));
		out.write(str.getBytes("UTF-8"));
		out.flush();
		out.close();
	}
	//获得用户信息
	private MiaoshaUser getUser(HttpServletRequest request, HttpServletResponse response) {
		String paramToken = request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);
		String cookieToken = getCookieValue(request, MiaoshaUserService.COOKI_NAME_TOKEN);
		if(StringUtils.isEmpty(cookieToken) && StringUtils.isEmpty(paramToken)) {
			return null;
		}
		String token = StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
		return userService.getByToken(response, token);
	}
	
	private String getCookieValue(HttpServletRequest request, String cookiName) {
		Cookie[]  cookies = request.getCookies();
		if(cookies == null || cookies.length <= 0){
			return null;
		}
		for(Cookie cookie : cookies) {
			if(cookie.getName().equals(cookiName)) {
				return cookie.getValue();
			}
		}
		return null;
	}
	
}
  • 注册到web配置适配器中
@Configuration
public class WebConfig  extends WebMvcConfigurerAdapter{
	
	@Autowired
	UserArgumentResolver userArgumentResolver;
	
	@Autowired
	AccessInterceptor accessInterceptor;
	/*
	*注册参数解析器
	*/
	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
		argumentResolvers.add(userArgumentResolver);
	}
	/*
	*注册拦截器
	*/
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(accessInterceptor);
	}
	
}
  • 用户对象信息共享类
/**
 * 获得当前线程的user信息
 *通过ThreadLocal实现线程公用
 */
public class UserContext {
	
	private static ThreadLocal<MiaoshaUser> userHolder = new ThreadLocal<MiaoshaUser>();
	
	public static void setUser(MiaoshaUser user) {
		userHolder.set(user);
	}
	
	public static MiaoshaUser getUser() {
		return userHolder.get();
	}

}
  • 参数解析器的实现(将前端信息转化为控制层所需的对象)
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

	@Autowired
	MiaoshaUserService userService;
	
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> clazz = parameter.getParameterType();
		return clazz==MiaoshaUser.class;
	}

	/*
	 * 获取当前线程的user
	 */
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		return UserContext.getUser();
	}

}

总结
实践了一个前置拦截器的简单实现,同时还使用了ThreadLocal以实现线程中对象的共用。
拦截器和注解的设计,使代码更加简洁优雅,实现业务代码和功能代码的解耦,提高了代码的复用性和整个代码架构的结构性,对后续开发和他人了解项目都很有帮助。

SpringMVC中处理请求的方法叫做HandlerMethod,HandlerMethod可以通过多种方式声明它的参数来源,同时对应着不同的解析过程。

public HandlerMethod createWithResolvedBean() {
    Object handler = this.bean;
    if (this.bean instanceof String) {
        String beanName = (String) this.bean;//获得控制层方法名称
        handler = this.beanFactory.getBean(beanName);
    }
    return new HandlerMethod(this, handler);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值