Skip to content

Particle模块使用之API方式

Yizzuide edited this page Jul 2, 2019 · 8 revisions

依赖版本

1.5.1+

模块说明

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

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

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

配置

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

@Configuration
public class ParticleConfig {
    // 去重限制器`IdempotentLimiter`框架内部配置了一份,不作为限制器链时可省略

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

    // 注意:这里的`IdempotentLimiter`需要再创建一份用于限制器链组装,防止污染框架自动注册的对象
    @Bean("linkIdempotentLimiter")
    public IdempotentLimiter linkIdempotentLimiter() {
        return new IdempotentLimiter();
    }

    // 下面配置的限制器链为:linkIdempotentLimiter -> timesLimiter
    @Bean
    public BarrierLimiter barrierLimiter(IdempotentLimiter linkIdempotentLimiter,
                                         TimesLimiter timesLimiter) {
        BarrierLimiter barrierLimiter = new BarrierLimiter();
        barrierLimiter.addLimitHandlerList(Arrays.asList(linkIdempotentLimiter, timesLimiter));
        return barrierLimiter;
    }
}

Particle状态对象

为了能获得限制器的当前相关状态,在调用限制器的limit方法回调参数会传入Particle状态对象,该类有以下常用方法:

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

开始使用

先在控制器中注入使用的组件:

    // 注意:由于配置了限制器链,就有了两个去重限制器,这里使用@Resource按Bean名注入
    @Resource
    private IdempotentLimiter idempotentLimiter;
    @Autowired
    private TimesLimiter timesLimiter;
    @Autowired
    private BarrierLimiter barrierLimiter;

下面是API请求去重、次数限制的例子:

    @RequestMapping("check")
    public ResponseEntity check(String token) throws Throwable {
        // 使用内置的方法限制一次请求的重复调用
        return idempotentLimiter.limit(token, 60L, (particle) -> {
            // 判断是否被限制
            if (particle.isLimited()) {
                return ResponseEntity.status(406).body("请求勿重复请求");
            }
            // 模拟业务处理耗时
            Thread.sleep(5000);
            return ResponseEntity.ok("ok");
        });
    }
    
    @RequestMapping("send")
    public ResponseEntity send(String phone) throws Throwable {
        // 使用内置的方法限制调用次数
        return timesLimiter.limit(phone, (particle) -> {
            if (particle.isLimited()) {
                return ResponseEntity.status(406).body("超过使用次数:" + particle.getValue() + "");
            }
            return ResponseEntity.ok("发送成功,当前次数:" + particle.getValue());
        });
    }

下面不同限制器的嵌套例子:

    @RequestMapping("verify")
    public ResponseEntity verify(String phone) throws Throwable {
        // 先检查请求重复
        return idempotentLimiter.limit(phone, 60L, (particle) -> {
            // 判断是否被限制
            if (particle.isLimited()) {
                return ResponseEntity.status(406).body("请求勿重复请求");
            }

            // 再限制次数
            return timesLimiter.limit(phone, (p) -> {
                if (p.isLimited()) {
                    return ResponseEntity.status(406).body("超过使用次数:" + p.getValue() + "");
                }
                // 模拟业务处理耗时
                Thread.sleep(5000);

                return ResponseEntity.ok("发送成功,当前次数:" + p.getValue());
            });
        });
    }

看到这样的使用方式你可能会吐槽了,看一下使用组合限制器BarrierLimiter的精简方案:

    @RequestMapping("verify3")
    public ResponseEntity verify3(String phone) throws Throwable {
        // 先请求重复检查
        return barrierLimiter.limit(phone, 60L, (particle) -> {
            // 判断是否被限制
            if (particle.isLimited()) {
                // 如果是去重限制类型
                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(6000);

            return ResponseEntity.ok("发送成功,当前次数:" + particle.getValue());
        });
    }
You can’t perform that action at this time.