我是靠谱客的博主 阳光棒棒糖,最近开发中收集的这篇文章主要介绍SpringBoot自定义接口限流注解(拦截器实现,AOP实现)拦截器版AOP实现,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

  1. 拦截器版

实现思路
1.通过拦截器,读取方法上的注解
2.累计请求数量,进行限流

1.1 定义注解RequestLimit

package com.zhf.model.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

@Documented
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestLimit {

    /**
     * 限流的时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * 限流的时长
     */
    int limit() default 1;

    /**
     * 最大限流量
     * @return
     */
    int maxCount() default 1;
}

1.2 定义拦截器RequestLimitInterceptor

package com.zhf.model.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.zhf.model.annotation.RequestLimit;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;

@Component
public class RequestLimitInterceptor extends HandlerInterceptorAdapter {

    /**
     * 限流Map,懒得搭建Redis,暂时放在Map里面,然后通过定时任务实现限流
     * 实际业务中可以放在Redis,利用过期时间限流
     */
    private final Map<String,Integer> map = new HashMap<>();

    /**
     * 拦截请求执行的方法
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("限流的拦截器!");
        //判断处理类是否为HandlerMethod
        if(handler.getClass().isAssignableFrom(HandlerMethod.class)){
            //进行强制转换
            HandlerMethod handlerMethod = (HandlerMethod)handler;
            //获取拦截的方法
            Method method = handlerMethod.getMethod();
            //获取方法上的注解对象,看是否被RequestLimit修饰
            RequestLimit limiter = getTagAnnotation(method, RequestLimit.class);
            //判断是否限流
            if(null != limiter){
                if(isLimit(request,limiter)){
                    responseOut(response,limiter.maxCount());
                    return false;
                }
            }
        }
        return super.preHandle(request, response, handler);
    }

    /**
     * 封装返回结果
     */
    private void responseOut(HttpServletResponse response,Integer limit) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        Map<String,String> resultMap = new HashMap<>();
        resultMap.put("status","502");
        resultMap.put("msg","接口超出最大请求数:" + limit);
        String s = JSONObject.toJSON(resultMap).toString();
        writer.append(s);
    }

    //获取处理类上的注解
    public <T extends Annotation> T getTagAnnotation(Method method, Class<T> annotationClass){
        //获取方法中是否有相关注解
        T methodAnnotation = method.getAnnotation(annotationClass);
        //获取类上是否有相关注解
        T classAnnotation = method.getDeclaringClass().getAnnotation(annotationClass);
        //判断是否存在相关注解
        if(null != methodAnnotation){
            return methodAnnotation;
        }else return classAnnotation;
    }

    /**
     * 判断接口是否限流,通过请求的SessionId进行限流
     */
    public boolean isLimit(HttpServletRequest request,RequestLimit limiter){
        //获取请求的SessionID
        String id = request.getSession().getId();
        //查看是否在限流map里面
        Integer num = map.get(id);
        System.out.println("SessionId:" + id + "n" + "num:" + num + "n" + "limiterCount:" + limiter.maxCount() + "n" + "limit:" + limiter.limit());
        //没有则初始化限流map,并创建定时任务(解除限流)
        if(null == num){
            //初始化计数器
            map.put(id,1);
            //创建定时器任务,删除限流器
            Timer timer = new Timer();
            //获取限流的时间毫秒数
            long delay = getDelay(limiter.timeUnit(), limiter.limit());
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    System.out.println("删除任务执行");
                    map.remove(id);
                }
            },delay);
        }else{
            //累加请求
            ++num;
            //判断是否超出最大限流次数
            if(num > limiter.maxCount()){
                return true;
            }
            //更新计数器
            map.put(id,num);
        }
        return false;
    }

    /**
     * 获取限流时间,总共限流的毫秒数
     * @param timeUnit
     * @param limit
     * @return
     */
    public long getDelay(TimeUnit timeUnit,Integer limit){
        if(null == timeUnit || limit == 0){
            return 0;
        }
        switch (timeUnit){
            case MILLISECONDS:
                return limit;
            case MINUTES:
                return limit*60*1000;
            case HOURS:
                return limit*60*60*1000;
            default:
                return limit*1000;
        }
    }

}

1.3 将自定义拦截器加入到Spring的拦截器

package com.zhf.model.config;
import com.zhf.model.interceptor.RequestLimitInterceptor;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Component
public class WebMVCConfig implements WebMvcConfigurer {

    @Resource
    RequestLimitInterceptor limiter;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(limiter);
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}

1.4 创建MainController进行测试

为了方便测试,这里设置为1分钟5次
package com.zhf.model.controller;

import com.zhf.model.annotation.RequestLimit;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/main")
public class MainController {

    @RequestMapping("/lock")
    @RequestLimit(maxCount = 5 , limit = 1 ,timeUnit = TimeUnit.MINUTES)
    public String testLock(){
        return "ok";
    }

}
  • 16:19点击5次,接口限流

  • 16:20再次点击,限流解除

  1. AOP实现

  • 没依赖先添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.7.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>4.3.7.RELEASE</version>
</dependency>

2.1 创建限流的注解类RateLimiter.java

package com.zhf.model.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {

    /**
     * 限流的时间单位
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * 限流的时长
     */
    int limit() default 1;

    /**
     * 最大限流量
     * @return
     */
    int maxCount() default 1;
}

2.2 创建切面RateLimiterAspect.java

这里直接使用Redis(String)的过期时间作为限流的计数器
直接使用String不方便管理,可以使用RedisScript进行管理
package com.zhf.model.aop;

import com.alibaba.fastjson.JSONObject;
import com.zhf.model.annotation.RateLimiter;
import com.zhf.model.exception.CommonException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 限流处理类
 * @Aspect 声明该类为切面
 */
@Aspect
@Component
public class RateLimiterAspect
{
    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * 执行前置方法,"@annotation(rateLimiter)"在注解rateLimiter之前执行
     * @param point
     * @param rateLimiter
     * @throws Throwable
     */
    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable
    {
        //获取请求
        ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        //获取SessionID
        String id = attributes.getRequest().getSession().getId();
        //这里使用redis过期时间作限流
        Integer count = (Integer)redisTemplate.opsForValue().get(id);
        //如果第一次请求或上次限流已经解除
        System.out.println("Count:" + count + ";TimeUnit:" + rateLimiter.timeUnit());
        if(null == count){
            //初始化限流器
            redisTemplate.opsForValue().set(id,1,rateLimiter.limit(),rateLimiter.timeUnit());
        }else{
            //累加
            ++count;
            redisTemplate.opsForValue().set(id,count,0);
            if(count > rateLimiter.maxCount()){
                //抛出自定义异常码,然后统一返回
                throw new CommonException(480);
            }
        }
    }
}

2.3 自定义异常类CommonException,同一异常返回

  • 自定义异常类

package com.zhf.model.exception;

import lombok.Data;

@Data
public class CommonException extends RuntimeException{

    private int code;

    private String msg;

    public CommonException() {
    }

    public CommonException(int code) {
        this.code = code;
    }

    public CommonException(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}
  • 统一异常处理,将限流的异常码catch

package com.zhf.model.handler;

import com.zhf.model.exception.CommonException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import java.time.format.DateTimeParseException;

@RestControllerAdvice
@Slf4j
public class CommonExceptionHandler {

    @ExceptionHandler(NullPointerException.class)
    @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
    public ReturnResult handleTypeMismatchException(NullPointerException ex){
        log.debug(ex.getMessage());
        return new ReturnResult(500,"空指针异常");
    }

    @ExceptionHandler(ArithmeticException.class)
    @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
    public ReturnResult handleArithmeticException(ArithmeticException ex){
        ex.printStackTrace();
        return new ReturnResult(500,"被除数不能为零");
    }

    @ExceptionHandler(DateTimeParseException.class)
    @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
    public ReturnResult handleDateTimeParseException(DateTimeParseException ex){
        ex.printStackTrace();
        return new ReturnResult(500,"时间转换格式错误");
    }

    @ExceptionHandler(CommonException.class)
    @ResponseStatus(value= HttpStatus.INTERNAL_SERVER_ERROR)
    public ReturnResult handleCommonException(CommonException ex){
        ex.printStackTrace();
        if(ex.getCode() == 480){
            return new ReturnResult(480,"接口限流");
        }
        if(ex.getCode() == 502){
            return new ReturnResult(502,"自定义异常处理502");
        }
        return new ReturnResult(500,"时间转换格式错误");
    }
}

2.4创建测试类

方便测试,一分钟五次
package com.zhf.model.controller;

import com.zhf.model.annotation.RateLimiter;
import com.zhf.model.annotation.RequestLimit;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/main")
public class MainController {

    /**
     * 测试限流
     * @return
     */
    @RequestMapping("/lock")
    @RequestLimit(maxCount = 5 , limit = 1 ,timeUnit = TimeUnit.MINUTES)
    public String testLock(){
        return "ok";
    }

    @RequestMapping("/testAopLimit")
    @RateLimiter(maxCount = 5 , limit = 1 ,timeUnit = TimeUnit.MINUTES)
    public String testAopLimit(){
        return "ok";
    }
}
18:00点击5次,接口限流
18:01再次请求,解除限流

最后

以上就是阳光棒棒糖为你收集整理的SpringBoot自定义接口限流注解(拦截器实现,AOP实现)拦截器版AOP实现的全部内容,希望文章能够帮你解决SpringBoot自定义接口限流注解(拦截器实现,AOP实现)拦截器版AOP实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(48)

评论列表共有 0 条评论

立即
投稿
返回
顶部