基于Redis、AOP、注解实现的单用户限流

  • Redis简介
  1. 使用内存存储,使用单线程,采用IO多路复用模型,性能极好
  2. 支持String,Hash,List,Set,Zset等多种数据类型
  3. 支持key定时失效
  4. 可采用AOF,RDB方式进行持久化
  • AOP简介
  1. 定义:面向切面编程,将跟业务逻辑无关的重复并且萦绕在方法周围的代码进行抽取,提高了代码的可重用性
  2. 常用注解:


    切面.PNG
  • 注解简介
  1. 使用条件:定义注解,声明注解的生命周期、作用域,注解实现体
  2. 定义在自定义注解上的元注解:
    @Target:注解的作用域,包含ElementType参数
public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,//作用域在接口、类、枚举、注解

    /** Field declaration (includes enum constants) */
    FIELD,//作用域在字段、枚举的常量

    /** Method declaration */
    METHOD,//作用域在方法

    /** Formal parameter declaration */
    PARAMETER,//作用域在方法参数

    /** Constructor declaration */
    CONSTRUCTOR,//作用域在构造器

    /** Local variable declaration */
    LOCAL_VARIABLE,//作用域在局部变量

    /** Annotation type declaration */
    ANNOTATION_TYPE,//作用域在注解

    /** Package declaration */
    PACKAGE,//作用域在包

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,//作用域在类型参数

    /**
     * Use of a type 
     *
     * @since 1.8
     */
    TYPE_USE//作用域在使用类型的任何地方
}

@Retention:注解的作用域,包含RetentionPolicy参数

/**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,//存活在源文件中

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,//存活在字节码文件中

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME//存活在代码运行期间

@Inherited:允许子类继承父类的注解
@Documented:此注解会包含在javadoc中

  • 代码实现
    定义注解:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RestrictAccess {
    /**
     * 限制时间,单位是毫秒
     */
    long ttl() default 0;

    /**
     * 限制时间内的访问次数
     */
    int accessFrequency();
}

RedisTemplate对象的配置

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Integer> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        final RedisTemplate<String,Integer> redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericToStringSerializer<Integer>(Integer.class));
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

切面类

/**
 * 单用户限流切面
 */
/**
 * 单用户限流切面
 */
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class RestrictAccessAspect {
    private final RedisTemplate<String,Integer> redisTemplate;
    private static Integer INIT_VALUE = 1;
    /**
     * 用户登录的令牌
     */
    private static final String USER_TOKEN = "token";

    @Around("@annotation(cn.juh.annocation.RestrictAccess)")
    public Object RestrictAccessFrequency(ProceedingJoinPoint point) throws Throwable {
        //获取当前线程的访问对象
        HttpServletRequest request = WebUtils.getHttpServletRequest();
        //获取访问路径
        String requestURI = request.getRequestURI();
        //获取用户token
        String token = request.getParameter(USER_TOKEN);
        //存在redis中的key
        String key = RedisConstant.KeyPrefix.RESTRICT_ACCESS.code() + requestURI + ":" + token;

        MethodSignature sign = (MethodSignature) point.getSignature();
        Method method = sign.getMethod();
        //获取方法上的注解
        RestrictAccess annotation = method.getAnnotation(RestrictAccess.class);
        int accessFrequency = annotation.accessFrequency();
        long ttl = annotation.ttl();

        //从redis中获取用户再限定时间内访问接口的次数
        Integer value = redisTemplate.opsForValue().get(key);
        if (Objects.nonNull(value) && value >= accessFrequency){
            return "您的操作过于频繁,请待会再试";
        }

        if (Objects.isNull(value)){
            //不存在key则设置初始值并且设置过期时间
            redisTemplate.opsForValue().set(key,INIT_VALUE,ttl, TimeUnit.MILLISECONDS);
        }else {
            //存在则将访问次数+1
            redisTemplate.opsForValue().increment(key);
        }

        //执行接口逻辑
        return point.proceed();
    }

}

获取web对象工具

public class WebUtils {
    public static ServletRequestAttributes getServletRequestAttributes() {
        return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    }

    /**
     * 得到当前线程的请求对象
     *
     * @return
     */
    public static HttpServletRequest getHttpServletRequest() {
        return getServletRequestAttributes().getRequest();
    }

    /**
     * 得到当前线程的响应对象
     *
     * @return
     */
    public static HttpServletResponse getHttpServletResponse() {
        return getServletRequestAttributes().getResponse();
    }

}

测试接口

/**
 * 控制器
 */
@RestController
public class TestController {
    @RequestMapping("/test")
    @RestrictAccess(ttl = 10000,accessFrequency = 1)
    public Object first(String token) {
        return "test";
    }
}

正常访问:


正常访问.PNG

限流:


限流.PNG
  • 代码逻辑
    程序执行逻辑.PNG
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容