实现原理:
利用spring拦截器来实现,定义注解,在需要的方法上加上该注解,通过拦截器拦截这些注解的方法后,进行接口存储到redis中,当用户多次请求时,我们可以累积他的请求次数,达到了上限,我们就可以给他提示信息。
实现方法:
1.定义注解
package com.myzzb.mall.core.bean;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author :zzb
* @createDate :2020/7/21 16:17
* @desc :定义接口恶意请求多次注解
*/
@Retention(RUNTIME)//表示它在运行时
@Target(METHOD) //表示它只能放在方法上
@SuppressWarnings("all")
public @interface AccessLimit {
int seconds();//规定几秒
int maxCount();//最大请求数
boolean needLogin()default true;//是否需要登录
}
2.配置拦截器
package com.myzzb.mall.restful.bean;
import com.alibaba.fastjson.JSONObject;
import com.myzzb.mall.core.bean.AccessLimit;
import com.myzzb.mall.core.utils.RedisUtil;
import com.myzzb.mall.core.utils.ResultBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author :zzb
* @createDate :2020/7/18 6:54 下午
* @desc : 接口拦截器
*/
@Component
@SuppressWarnings("all")
public class APIInterceptor extends HandlerInterceptorAdapter {
private final Log log = LogFactory.getLog(APIInterceptor.class);
@Resource
private RedisUtil.redisString redisString;
/**
* 预处理回调方法,实现处理器的预处理(如登陆检查/判断同一对象短时间内是否重复调用接口等) 第三个参数为相应的处理器即controller
* f返回true表示流程继续,调用下一个拦截器或者处理器,返回false表示流程中断,通过response产生响应
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//判断同一用户短时间内是否重复请求接口
log.info("========================request path==============================>"+ request.getRequestURI());
//判断请求是否属于方法的请求
if(handler instanceof HandlerMethod){
HandlerMethod hm = (HandlerMethod) handler;
//获取方法中的注解,看是否有该注解
AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);
log.info("========================accessLimit==============================>"+ accessLimit);
if(accessLimit == null){
return true;
}
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
boolean login = accessLimit.needLogin();
//如果需要登录
if(login){
//获取登录的session进行判断
log.info("------------------------需要登录 ----------->"+request.getRemoteAddr());
}
String ip=request.getRemoteAddr();
String key = request.getServletPath() + ":" + ip ;
Integer count = (Integer) redisString.get(key);
if (null == count || -1 == count) {
redisString.set(key, 1,seconds);
return true;
}
if (count < maxCount) {
count = count+1;
redisString.set(key, count,seconds);
return true;
}
if (count >= maxCount) {
//response 返回 json 请求过于频繁请稍后再试
ResultBean resultBean = new ResultBean(9999,"操作过于频繁");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
Object obj = JSONObject.toJSON(resultBean);
response.getWriter().write(JSONObject.toJSONString(obj));
return false;
}
}
return super.preHandle(request, response, handler);
}
/**
* 当请求进行处理之后,也就是controller方法调用之后执行,但是他会在DispatcherServlet进行视图渲染之前被调用
* 此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
/**
* 方法将在整个请求结束之后,也就是DispatcheServlet进行视图渲染之后执行,这个方法的主要作用是对资源的清理工作
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
3.注入拦截器
package com.myzzb.mall.restful.bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author :zzb
* @createDate :2020/7/18 19:20
* @desc :
*/
@SuppressWarnings("all")
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private APIInterceptor apiInterceptor;
// 这个方法是用来配置静态资源的,比如html,js,css,等等
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
// 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 多个拦截器组成一个拦截器链
// addPathPatterns 用于添加拦截规则
// excludePathPatterns 用于排除拦截路径
registry.addInterceptor(apiInterceptor).addPathPatterns("/**").excludePathPatterns("/test/*"); }
}
4.在接口方上引用注解
package com.myzzb.mall.restful.order;
import com.alibaba.dubbo.config.annotation.Reference;
import com.myzzb.mall.core.bean.AccessLimit;
import com.myzzb.mall.core.exception.BaseException;
import com.myzzb.mall.core.utils.ResultBean;
import com.myzzb.mall.facade.order.IOrderService;
import com.myzzb.mall.restful.bean.SystemUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* @author :zzb
* @createDate :2020/7/25 19:08
* @desc : 订单模块
*/
@RestController
@RequestMapping("/order")
public class OrderAPI {
@Reference(version = "1.0.0")
private IOrderService orderService;
@Resource
private SystemUtil systemUtil;
@RequestMapping("/add")
@AccessLimit(seconds = 5, maxCount = 3 , needLogin = true) //5秒内 允许请求3次
public ResultBean addOrder(HttpServletRequest request){
return orderService.addOrder(systemUtil.getRequestParams(request));
}
}
总结:
采用注解方式加拦截器,结合redis来存储请求次数,可以灵活配置请求的接口是否需要登录,同一用户在规定的时间内请求同一接口最大次数。