1.限流的原理嚼黔,根據(jù)ip(也可以對(duì)總的訪問頻次做限制)和固定時(shí)間內(nèi)的訪問次數(shù)细层,做限制
2.springboot實(shí)現(xiàn)限流:
? ? 1)新建一個(gè)自定義注解:AccessLimit 用于接口上(注解實(shí)現(xiàn)更簡(jiǎn)單方便)
? ? ? ??@Inherited
????????@Documented
????????@Target({ElementType.FIELD, ElementType.TYPE, ElementType.METHOD})
????????@Retention(RetentionPolicy.RUNTIME)
????????public @interface AccessLimit {
???????????? /**
? ????????? * 指定second 時(shí)間內(nèi),API最多的請(qǐng)求次數(shù)
????????????*/
????????????int times() default 3;
????????????/**
???????????? * 指定時(shí)間second唬涧,redis數(shù)據(jù)過期時(shí)間
???????????? */
????????????int second() default 10;
????????}
? ? 2)新建一個(gè)攔截器疫赎,獲取自定義注解上的參數(shù),然后緩存到redis中做校驗(yàn)
????????@Component
????????public class AccessLimitInterceptimplements HandlerInterceptor {
????????private static final Loggerlogger = LoggerFactory.getLogger(AccessLimitIntercept.class);
? ? ????????@Autowired
? ????????? private RedisUtilredisUtil;
????????????@Override
????????????public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler)throws Exception{
????????????????try{
????????????????????// handler 是否為 HandlerMethod 實(shí)例
????????????????????if(handlerinstanceof HandlerMethod){
????????????????????????// 強(qiáng)轉(zhuǎn)
????????????????????????HandlerMethod handlerMethod = (HandlerMethod) handler;
????????????????????????// 獲取方法
????????????????????????Method method = handlerMethod.getMethod();
????????????????????????// 判斷方法是否有AccessLimit注解碎节,有的才需要做限流
????????????????????????if(!method.isAnnotationPresent(AccessLimit.class)){
????????????????????????????return true;
????????????????????????}
????????????????????????// 獲取注釋上的內(nèi)容
????????????????????????AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
????????????????????????????if(accessLimit ==null){
????????????????????????????return true;
????????????????????????}
????????????????????????// 獲取方法上的請(qǐng)求次數(shù)
????????????????????????int times = accessLimit.times();
????????????????????????// 獲取方法注釋上的請(qǐng)求時(shí)間
????????????????????????Integer second = accessLimit.second();
????????????????????????// 拼接redis key = ip + api限流
????????????????????????String key = IpUtils.getIpAddr(request) + request.getRequestURI();
????????????????????????Integer maxTimes =null;
????????????????????????// 獲取 redis 的 value
????????????????????????String value =redisUtil.get(key) ==null?"":redisUtil.get(key).toString();
????????????????????????if(!"".equals(value)){
????????????????????????????maxTimes = Integer.valueOf(value);
????????????????????????}
????????????????????????if(maxTimes ==null){
????????????????????????????// 如果redis中沒有該ip對(duì)應(yīng)的時(shí)間則表示第一次調(diào)用捧搞,保存key到redis
????????????????????????????redisUtil.set(key,"1",second, TimeUnit.SECONDS);
????????????????????????}else if (maxTimes < times){
????????????????????????????// 如果 redis 中的時(shí)間比注解上的時(shí)間小,則表示可以允許訪問狮荔,這是修改redis的value時(shí)間
????????????????????????????redisUtil.set(key,maxTimes +1 +"",second,TimeUnit.SECONDS);
????????????????????????}else {
????????????????????????????logger.info(key +"請(qǐng)求過于頻繁");
????????????????????????????return setResponse(Result.error(ResultStatus.REQUEST_SEND_FREQUENTLY_ERROR.getStatus(),"請(qǐng)求過于頻繁胎撇,請(qǐng)稍后重試"),response);
????????????????????????}
????????????????????}
????????????????}catch (Exception ex){
????????????????????logger.error("API請(qǐng)求限流攔截異常,異常原因:" + ex);
????????????????????throw new ParameterException(ex.getMessage());
????????????????}
????????????????return true;
????????????}
????????????@Override
????????????public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception{
????????????}
????????????@Override
????????????public void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex)throws Exception{
????????????}
????????????private boolean setResponse(Result result,HttpServletResponse response)throws IOException{
????????????????ServletOutputStream outputStream =null;
????????????????try{
????????????????????response.setHeader("Content-Type","application/json;charset=utf-8");
????????????????????outputStream = response.getOutputStream();
????????????????????outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));
????????????????}catch (Exception ex){
????????????????????logger.error("setResponse方法報(bào)錯(cuò)",ex);
????????????????????return false;
? ? ? ? ? ? ? ? }finally {
????????????????????if(outputStream !=null){
????????????????????????outputStream.flush();
????????????????????????outputStream.close();
????????????????????}
? ? ? ? ? ? ? ? }
????????????????return false;
????????????}
????????}
? ? 3)配置攔截器
? ??????@Configuration
????????public class WebFilterConfig implements WebMvcConfigurer {
????????????/**
????????????* 這里需要先將限流攔截器入住殖氏,不然無法獲取到攔截器中的redistemplate
????????????* @return
????????????*/
????????????@Bean
????????????public AccessLimitIntercept getAccessLimitIntercept() {
????????????????return new AccessLimitIntercept();
????????????}
????????????/**
????????????* 多個(gè)攔截器組成一個(gè)攔截器鏈
????????????* @param registry
????????????*/
????????????@Override
????????????public void addInterceptors(InterceptorRegistry registry) {
????????????????registry.addInterceptor(getAccessLimitIntercept()).addPathPatterns("/**");
????????????}
????????}
? ? 4)在接口上添加注解创坞,設(shè)置固定秒數(shù)內(nèi),限制的訪問次數(shù)
? ??????@RestController
????????@RequestMapping("/")
????????public class PingController extends BaseController {
????????????@AccessLimit(times = 5, second = 10)
????????????@GetMapping(value = "/ping")
????????????public Results ping() {
????????????????return succeed("pong", "");
????????????}
? ? ? ? }
? ? 5)注意:該限流加完后受葛,接口依然可以被訪問,如果需要修改為接口不可被訪問,需要修改2)中setResponse()方法的放回值為false总滩,preHandle()方法的返回值纲堵,可以決定方法是否可被執(zhí)行,當(dāng)為false時(shí)闰渔,方法不會(huì)被執(zhí)行