一、过滤器
过滤器配置
/*
* 重复请求过滤器
* */
@Bean
public FilterRegistrationBean repeatFilterRegistration(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
//设置一个filter
registrationBean.setFilter(new RepeatableFilter());
//设置filter生效地址
registrationBean.addUrlPatterns("/*");
//设置filter的名字
registrationBean.setName("repeatableFilter");
//设置filter优先级
registrationBean.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registrationBean;
}
过滤器
public class RepeatableFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest servletRequest = null;
if (request instanceof HttpServletRequest && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)){
servletRequest = new RepeatedlyRequestWarpper((HttpServletRequest) request, response);
}
if (null == servletRequest){
chain.doFilter(request,response);
}else {
chain.doFilter(servletRequest,response);
}
}
}
过滤器指定包装实现
public class RepeatedlyRequestWarpper extends HttpServletRequestWrapper {
private final byte [] body;
/**
* Constructs a request object wrapping the given request.
*
* @param request The request to wrap
* @throws IllegalArgumentException if the request is null
*/
public RepeatedlyRequestWarpper(HttpServletRequest request, ServletResponse response) throws IOException{
super(request);
request.setCharacterEncoding(Constants.UTF8);
response.setCharacterEncoding(Constants.UTF8);
body = HttpUtil.getString(request.getInputStream(), StandardCharsets.UTF_8,false).getBytes(StandardCharsets.UTF_8);
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public int available() throws IOException {
return body.length;
}
};
}
}
二、拦截器
拦截器抽象
@Component
public abstract class RepeatSubmitInterceptor implements HandlerInterceptor{
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod)handler;
Method method = handlerMethod.getMethod();
RepeatSubmit annotition = method.getAnnotation(RepeatSubmit.class);
if (annotition != null){
if (this.isRepeatSubmit(request,annotition)){
JSONResult result = JSONResult.error(annotition.message());
ServletUtils.renderString(response, JSON.toJSONString(result));
return false;
}
}
return true;
}else {
return true;
}
}
public abstract boolean isRepeatSubmit(HttpServletRequest request,RepeatSubmit annotion);
}
重点方法isRepeatSubmit
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
public final String REPEAT_PARAMS = "repeatParams";
public final String REPEAT_TIME = "repeatTime";
// 令牌自定义标识
@Value("${token.header}")
private String header;
@Autowired
private RedisCache redisCache;
/**
* 判断是不是重复提交
* @param request
* @param annotion
* @return
*/
@Override
public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotion) {
//定义一个本次请求的参数
String nowParams = "";
/*
* instanceof关键字,测试它左边的对象是否是它右边的类的实例,判断有没有经过重复包装(utf8字符集转换)
* RepeatedlyRequestWarpper---继承-----》HttpServletRequestWrapper----继承-----》HttpServletRequest=HttpServletRequest
*
* */
if (request instanceof RepeatedlyRequestWarpper){
RepeatedlyRequestWarpper repeatedlyRequestWarpper = (RepeatedlyRequestWarpper) request;
nowParams = HttpUtils.getBodyString(request);
}
if (StringUtils.isNull(nowParams) || StringUtils.isEmpty(nowParams)){
nowParams = JSON.toJSONString(request.getParameterMap());
}
//获取请求体参数后存储到一个map中
Map<String,Object> nowDataMap = new HashMap<>();
nowDataMap.put(REPEAT_PARAMS,nowParams);
nowDataMap.put(REPEAT_TIME,System.currentTimeMillis());
//获取访问地址
String url = request.getRequestURI();
//获得token令牌
String submitKey = StringUtils.trimToEmpty(request.getHeader(header));
//拼接成最终的redis key(这个只能说明一个令牌在一个访问地址)
String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY+url+submitKey;
//尝试去redis中查找有没有对应的key
Object cacheObject = redisCache.getCacheObject(cacheRepeatKey);
if (cacheObject != null){
//将redis中查到的对象强转
Map<String,Object> cacheMap = (Map<String,Object>)cacheObject;
if (cacheMap.containsKey(url)){
Map<String,Object> preDataMap = (Map<String, Object>)cacheMap.get(url);
if (compareParams(nowDataMap,preDataMap) && compareTime(nowDataMap,preDataMap,annotion.interval())){
return true;
}
}
}
//查不到说明redis中没有这个访问地址的请求,进行缓存
Map<String,Object> cacheMap = new HashMap<String,Object>();
//key是访问地址,value是访问参数
cacheMap.put(url,nowDataMap);
redisCache.setCacheObject(cacheRepeatKey,annotion.interval(),annotion.interval(), TimeUnit.MILLISECONDS);
return false;
}
/** 判断参数是否相同
* @param nowMap
* @param preMap
* @return
*/
private boolean compareParams(Map<String,Object> nowMap, Map<String,Object> preMap){
String nowParams = (String) nowMap.get(REPEAT_PARAMS);
String preParams = (String) preMap.get(REPEAT_PARAMS);
return nowParams.equals(preParams);
}
/** 判断两次间隔时间
* @param nowMap
* @param preMap
* @param interval
* @return
*/
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval)
{
long time1 = (Long) nowMap.get(REPEAT_TIME);
long time2 = (Long) preMap.get(REPEAT_TIME);
if ((time1 - time2) < interval)
{
return true;
}
return false;
}
}
三、防重复注解
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
public int interval() default 5000;
public String message() default "不允许重复提交,请稍后再试";
}
四、最终使用
@RequestMapping("/test/repeat")
@RestController
public class TestRepeatController {
@RepeatSubmit(interval = 1000,message = "请求过于频繁,请稍后重试~")
@RequestMapping("/add")
public String addUser(String id) {
return "ok";
}
}
五、总结
所谓重复请求,就是两个请求体里内容相同,我们要做的就是比对两个请求。
过滤器主要作用是保证请求体内字符编码相同
拦截器拦截我们指定的方法,将相同请求地址封装成key,请求体内容为value
保存到redis缓存数据库,并设置失效时间
进行比对即可。