@Target @Target(ElementType.TYPE)——接口、类、枚举、注解
@Target(value = {ElementType.METHOD}) 只能表注在方法上
注释将由编译器记录在类文件中,并在运行时由 VM 保留,因此可以反射性地读取它们
@Retention(value = RetentionPolicy.RUNTIME)
/**
* 速率限制拦截切面处理类
* <p>
* 尊重知识产权,CV 请保留版权,爱组搭 http://aizuda.com 出品
*
* @author 青苗
* @since 2021-11-16
*/
@Aspect
@AllArgsConstructor
public class RateLimitAspect {
}
@Around("@annotation(com.aizuda.limiter.annotation.RateLimit)")
public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
final String classMethodName = MethodUtils.getClassMethodName(method);
}
RATE_LIMIT_MAP.computeIfAbsent(classMethodName, k -> method.getAnnotation(RateLimit.class));
private MethodMetadata buildMethodMetadata(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String classMethodName = MethodUtils.getClassMethodName(method);
RateLimit rateLimit = this.getRateLimit(method, classMethodName);
return new RateLimitMethodMetaData(method, joinPoint::getArgs, rateLimit);
}
MethodMetadata methodMetadata = this.buildMethodMetadata(pjp);
if (rateLimitHandler.proceed(methodMetadata)) {
return pjp.proceed();
} else {
throw new RateLimitException(StringUtils.hasLength(rateLimit.message()) ? rateLimit.message() :
"current limiting rule triggered");
}
}
RateLimit rateLimit = methodMetadata.getAnnotation();
private static RedisScript<Long> REDIS_SCRIPT_RATE_LIMIT = null;
if (null == REDIS_SCRIPT_RATE_LIMIT) {
REDIS_SCRIPT_RATE_LIMIT = RedisScript.of(this.luaScript(), Long.class);
}
/**
* Lua 限流脚本
*/
public String luaScript() {
return "local key = KEYS[1];" +
"local count = tonumber(ARGV[1]);" +
"local interval = tonumber(ARGV[2]);" +
"local current = tonumber(redis.call('get', key) or \"0\") " +
"if current + 1 > count then return 0 " +
"else redis.call(\"INCRBY\", key, \"1\") redis.call(\"expire\", key, interval) return current + 1 end";
}
// SpEL Key 解析
String parseKey = Optional.ofNullable(spELKey)
.filter(StringUtils::hasLength)
.map(str -> {
Method method = methodMetadata.getMethod();
Object[] args = methodMetadata.getArgs();
return this.parser(spELKey, method, args);
}).orElse("");
if (useDefaultStrategy) {
key.append(defaultKeyGenerateStrategy.getKey(methodMetadata, parseKey));
}
public final static String TYPE = "aizuda-default";
@Override
public String getType() {
return TYPE;
}
@Override
public String getKey(MethodMetadata methodMetadata, String parseKey) {
String result = methodMetadata.getClassMethodName();
if (StringUtils.hasLength(parseKey)) {
result += ":" + parseKey;
}
return result;
}
概括 : 该接口 通过 注解上的 strategy 遍历 通过 每个元素 对应的type 去执行 getkey 拼接后 返回 用户也可以着急实现该接口进行自定义扩展
/**
* 限制策略
*/
String[] strategy() default {};
private final List<IKeyGenerateStrategy> keyGenerateStrategyList;
// 组装自定义策略
if (strategy.length > 0) {
for (String str : strategy) {
keyGenerateStrategyList.stream()
.filter(t -> Objects.equals(t.getType(), str))
.findFirst()
.map(rateLimitStrategy -> rateLimitStrategy.getKey(methodMetadata, parseKey))
.filter(StringUtils::hasLength)
.ifPresent(currentGenerateKey -> {
if (key.length() > 0) {
key.append(":");
}
key.append(currentGenerateKey);
}
);
}
}
Long currentCount = redisTemplate.execute(REDIS_SCRIPT_RATE_LIMIT, Collections.singletonList(key),
返回结果
if (null != currentCount) {
long count = currentCount;
if (count > 0 && count <= rateLimit.count()) {
if (log.isDebugEnabled()) {
log.debug("The {}-th visit within the current limit period", count);
}
return true;
}
}
回馈
if (rateLimitHandler.proceed(methodMetadata)) {
return pjp.proceed();
} else {
throw new RateLimitException(StringUtils.hasLength(rateLimit.message()) ? rateLimit.message() :
"current limiting rule triggered");
}
/*
* 爱组搭 http://aizuda.com 低代码组件化开发平台
* ------------------------------------------
* 受知识产权保护,请勿删除版权申明
*/
package com.aizuda.limiter.autoconfigure;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
import java.time.Duration;
/**
* 爱组搭安全配置
* <p>
* 尊重知识产权,CV 请保留版权,爱组搭 http://aizuda.com 出品
*
* @author 青苗
* @since 2021-11-08
*/
@Getter
@Setter
@ConfigurationProperties(prefix = LimiterProperties.PREFIX)
public class LimiterProperties implements Serializable {
/**
* 配置前缀
*/
public static final String PREFIX = "aizuda.limiter";
/**
* 开启限流
*/
private boolean enableRateLimit;
/**
* 开启分布式锁
*/
private boolean enableDistributedLock;
/**
* 分布式锁的前缀名称:默认为'aizuda-redis-lock'
*/
private String distributedRootKey;
/**
* RedisLock的key失效时间
* 默认2分钟
* <p>
* 例如 5s 五秒,6m 六分钟,7h 七小时,8d 八天
*/
private Duration expireAfter;
public boolean isEnable() {
return this.enableRateLimit || this.enableDistributedLock;
}
}
/*
* 爱组搭 http://aizuda.com 低代码组件化开发平台
* ------------------------------------------
* 受知识产权保护,请勿删除版权申明
*/
package com.aizuda.limiter.autoconfigure;
import com.aizuda.common.toolkit.StringUtils;
import com.aizuda.limiter.aspect.DistributedLockAspect;
import com.aizuda.limiter.aspect.RateLimitAspect;
import com.aizuda.limiter.context.DefaultDistributedContext;
import com.aizuda.limiter.context.DistributedContext;
import com.aizuda.limiter.distributedlock.IDistributedLockTemplate;
import com.aizuda.limiter.distributedlock.RedisDistributedLockTemplate;
import com.aizuda.limiter.extend.IAcquireLockTimeoutHandler;
import com.aizuda.limiter.extend.IDistributedLockListener;
import com.aizuda.limiter.handler.*;
import com.aizuda.limiter.strategy.IKeyGenerateStrategy;
import com.aizuda.limiter.strategy.IpKeyGenerateStrategy;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
/**
* 速率限制配置
* <p>
* 尊重知识产权,CV 请保留版权,爱组搭 http://aizuda.com 出品
*
* @author 青苗
* @since 2021-11-18
*/
@Configuration
@EnableConfigurationProperties({LimiterProperties.class})
public class LimiterAutoConfiguration {
@Bean
public IpKeyGenerateStrategy ipRateLimitStrategy() {
return new IpKeyGenerateStrategy();
}
@Bean
public RateLimitKeyParser rateLimitKeyParser(List<IKeyGenerateStrategy> rateLimitStrategyList) {
return new RateLimitKeyParser(rateLimitStrategyList);
}
/* -------------------- 限流相关配置 -------------------- */
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableRateLimit", havingValue = "true")
public IRateLimitHandler rateLimitHandler(ObjectProvider<RedisTemplate<String, String>> redisTemplate,
RateLimitKeyParser rateLimitKeyParser) {
return new RedisRateLimitHandler(redisTemplate.getIfAvailable(), rateLimitKeyParser);
}
@Bean
@ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableRateLimit", havingValue = "true")
public RateLimitAspect rateLimitAspect(IRateLimitHandler rateLimitHandler) {
return new RateLimitAspect(rateLimitHandler);
}
/* -------------------- 分布式锁相关配置 -------------------- */
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableDistributedLock", havingValue = "true")
public IDistributedLockHandler distributedLockHandler(DistributedContext distributedContext,
RateLimitKeyParser rateLimitKeyParser) {
return new RedisDistributedLockHandler(rateLimitKeyParser, distributedContext);
}
@Bean
@ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableDistributedLock", havingValue = "true")
public DistributedLockAspect distributedLockAspect(IDistributedLockHandler distributedLockHandler) {
return new DistributedLockAspect(distributedLockHandler);
}
@Bean
@ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableDistributedLock", havingValue = "true")
public DistributedContext distributedContext(IDistributedLockTemplate distributedLockTemplate,
Optional<List<IAcquireLockTimeoutHandler>> acquireLockTimeoutHandlersOptional,
Optional<List<IDistributedLockListener>> distributedLimitListenersOptional) {
return new DefaultDistributedContext(distributedLockTemplate, acquireLockTimeoutHandlersOptional.orElse(null),
distributedLimitListenersOptional.orElse(null));
}
@Bean
@ConditionalOnProperty(prefix = LimiterProperties.PREFIX, name = "enableDistributedLock", havingValue = "true")
@ConditionalOnMissingBean(IDistributedLockTemplate.class)
public IDistributedLockTemplate iDistributedLockTemplate(LimiterProperties limiterProperties,
ObjectProvider<RedisTemplate<String, String>> redisTemplate) {
String rootKey = Optional.ofNullable(limiterProperties.getDistributedRootKey())
.filter(StringUtils::hasLength)
.orElse("aizuda-redis-lock");
Duration expireAfter = Optional.ofNullable(limiterProperties.getExpireAfter())
.orElse(Duration.ofMinutes(2));
return new RedisDistributedLockTemplate(redisTemplate.getIfAvailable(), rootKey, expireAfter.toMillis());
}
}
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.aizuda.limiter.autoconfigure.LimiterAutoConfiguration