Particle模块使用之注解方式

197 阅读3分钟
原文链接: github.com

模块说明

Particle模块是基于Spring Data Redis的封装,提供了以下限制器支持:

  • IdempotentLimiter:去重限制器
  • TimesLimiter: 次数限制器
  • BarrierLimiter:组合限制器,可以装配其它限制器以及执行的顺序(内部采用责任链设计模式)

该模块支持开发者根据自己的业务编写扩展的限制器,需要继承抽象类LimitHandler

配置

在使用注解方式前需要一下配置限制器:

@Configuration
public class ParticleConfig {
    // 配置次数限制器
    @Bean
    public TimesLimiter timesLimiter() {
        // 一分钟限制调用3次,这个根据自己业务配置,TimesType支持的时间单位有:秒、分、时、天
        return new TimesLimiter(TimesType.MIN, 3L);
    }

    // 配置过滤链限制器,IdempotentLimiter为去重限制器,内部已经自动注册过了
    @Bean
    public BarrierLimiter barrierLimiter(IdempotentLimiter idempotentLimiter, TimesLimiter timesLimiter) {
        BarrierLimiter barrierLimiter = new BarrierLimiter();
        // 这样可以根据自己业务配置自己的扩展限制器,下面配置的限制器链为:idempotentLimiter -> timesLimiter
        barrierLimiter.addLimitHandlerList(Arrays.asList(idempotentLimiter, timesLimiter));
        return barrierLimiter;
    }
}

@Limit注解

注解的使用是直接在请求方法上添加注解@Limit,包括了以下属性:

  • name(必填): 用于生成Redis的key的前辍名
  • key(必填): 用于获取一个有标识意义的值,并拼装在name后面,支持Spring EL表达式、HTTP Header取值
  • expire(根据不同的限制器可选):Redis的key过期时间,单位为秒,默认为-1,可能有的限制器不需要指定,如:次数限制器(内部有根据时间类型的自动过期机制)
  • limiterBeanName(可选):指定限制器的Bean名,默认为"",如果有值则优化级比limiterBeanClass
  • limiterBeanClass(可选):指定限制器的Bean类型,默认为IdempotentLimiter

Particle状态对象

@Limit注解时,为了能获得限制器的当前相关状态,需要在请求方法的参数列表里添加Particle particle,这个会由Particle模块内部自动注入状态对象,该类有以下常用方法:

  • isLimited:boolean类型,用于判断是否被限制了
  • getValue:Object类型,用于获取存放在Redis的值
  • getType:Class类型,用于获取当前限制器类型,只有在使用组合限制器才会用得到

开始使用

下面是根据用户token来实现该方法请求去重的例子:

    @RequestMapping("check2")
    // 注解的方式限制一次请求的重复调用,如果业务执行超过60秒则自动过期
    @Limit(name = "user:check", key = "#token", expire = 60L)
    public ResponseEntity check2(String token, Particle particle/*这个状态值自动注入*/) throws Throwable {
        log.info("check2: token={}", token);
        // 判断是否被限制
        if (particle.isLimited()) {
            return ResponseEntity.status(406).body("请求勿重复请求");
        }
        // 模拟业务处理耗时
        Thread.sleep(5000);
        return ResponseEntity.ok("ok");
    }

key属性支持HTTP请求的Header取值,使用的语法:@,如:

    @RequestMapping("check3")
    // 注解的方式限制一次请求的重复调用
    @Limit(name = "user:check", key = "@ACCESS_TOKEN", expire = 60L)
    public ResponseEntity check3(Particle particle/*这个状态值自动注入*/) throws Throwable {
        // 判断是否被限制
        if (particle.isLimited()) {
            return ResponseEntity.status(406).body("请求勿重复请求");
        }
        // 模拟业务处理耗时
        Thread.sleep(5000);
        return ResponseEntity.ok("ok");
    }

使用次数限制器,只需要配置limiterBeanClass为TimesLimiter

    @RequestMapping("send2")
    // 注解的方式限制调用次数
    @Limit(name = "user:send", key = "#phone", limiterBeanClass = TimesLimiter.class)
    public ResponseEntity send2(String phone, Particle particle/*这个状态值自动注入*/) {
        log.info("check2: phone={}", phone);
        if (particle.isLimited()) {
            return ResponseEntity.status(406).body("超过使用次数:" + particle.getValue() + "次");
        }
        return ResponseEntity.ok("发送成功,当前次数:" + particle.getValue());
    }

开发者可以基于内置组合限制器BarrierLimiter灵活装配不同的限制器的验证执行顺序,下面的例子中使用了上面配置的BarrierLimiter,先限制重复请求,再限制次数:

    @RequestMapping("verify2")
    @Limit(name = "user:send", key = "#phone", expire = 60L, limiterBeanClass = BarrierLimiter.class)
    public ResponseEntity verify2(String phone, Particle particle/*这个状态值自动注入*/) throws Throwable {
        log.info("verify2: phone={}", phone);
        // 判断是否被限制
        if (particle.isLimited()) {
            // 通过getType()方法判断限制器的类型
            if (particle.getType() == IdempotentLimiter.class) {
                return ResponseEntity.status(406).body("请求勿重复请求");
            } else if (particle.getType() == TimesLimiter.class) {
                return ResponseEntity.status(406).body("超过使用次数:" + particle.getValue() + "次");
            }
        }

        // 模拟业务处理耗时
        Thread.sleep(5000);
        return ResponseEntity.ok("发送成功,当前次数:" + particle.getValue());
    }