模块说明
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());
}