利用redisson快速實現(xiàn)自定義限流注解(接口防刷)
問題:
在日常開發(fā)中,一些重要的對外接口,需要加上訪問頻率限制,以免造成資損失。
如登錄接口,當用戶使用手機號+驗證碼登錄時,一般我們會生成6位數(shù)的隨機驗證碼,并將驗證碼有效期設置為1-3分鐘,如果對登錄接口不加以限制,理論上,通過技術手段,快速重試100000次,即可將驗證碼窮舉出來。
解決思路:對登錄接口加上限流操作,如限制一分鐘內最多登錄5次,登錄次數(shù)過多,就返回失敗提示,或者將賬號鎖定一段時間。
實現(xiàn)手段:利用redis的有序集合即Sorted Set數(shù)據(jù)結構,構造一個令牌桶來實施限流。而redisson已經(jīng)幫我們封裝成了RRateLimiter,通過redisson,即可快速實現(xiàn)我們的目標。
定義一個限流注解
import org.redisson.api.RateIntervalUnit;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalRateLimiter {
String key();
long rate();
long rateInterval() default 1L;
RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS;
}利用aop進行切面
import com.zj.demoshow.annotion.GlobalRateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Slf4j
public class GlobalRateLimiterAspect {
@Resource
private Redisson redisson;
@Value("${spring.application.name}")
private String applicationName;
private final DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
@Pointcut(value = "@annotation(com.zj.demoshow.annotion.GlobalRateLimiter)")
public void cut() {
}
@Around(value = "cut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
String className = method.getDeclaringClass().getName();
String methodName = method.getName();
GlobalRateLimiter globalRateLimiter = method.getDeclaredAnnotation(GlobalRateLimiter.class);
Object[] params = joinPoint.getArgs();
long rate = globalRateLimiter.rate();
String key = globalRateLimiter.key();
long rateInterval = globalRateLimiter.rateInterval();
RateIntervalUnit rateIntervalUnit = globalRateLimiter.rateIntervalUnit();
if (key.contains("#")) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext ctx = new StandardEvaluationContext();
String[] parameterNames = discoverer.getParameterNames(method);
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
ctx.setVariable(parameterNames[i], params[i]);
}
}
Expression expression = parser.parseExpression(key);
Object value = expression.getValue(ctx);
if (value == null) {
throw new RuntimeException("key無效");
}
key = value.toString();
}
key = applicationName + "_" + className + "_" + methodName + "_" + key;
log.info("設置限流鎖key={}", key);
RRateLimiter rateLimiter = this.redisson.getRateLimiter(key);
if (!rateLimiter.isExists()) {
log.info("設置流量,rate={},rateInterval={},rateIntervalUnit={}", rate, rateInterval, rateIntervalUnit);
rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, rateIntervalUnit);
//設置一個過期時間,避免key一直存在浪費內存,這里設置為延長5分鐘
long millis = rateIntervalUnit.toMillis(rateInterval);
this.redisson.getBucket(key).expire(Long.sum(5 * 1000 * 60, millis), TimeUnit.MILLISECONDS);
}
boolean acquire = rateLimiter.tryAcquire(1);
if (!acquire) {
//這里直接拋出了異常 也可以拋出自定義異常,通過全局異常處理器攔截進行一些其他邏輯的處理
throw new RuntimeException("請求頻率過高,此操作已被限制");
}
return joinPoint.proceed();
}
}ok,通過以上兩步,即可完成我們的限流注解了,下面通過一個接口驗證下效果。
新建一個controller,寫一個模擬登錄的方法。
@RestController
@RequestMapping(value = "/user")
public class UserController {
@PostMapping(value = "/testForLogin")
//以account為鎖的key,限制每分鐘最多登錄5次
@GlobalRateLimiter(key = "#params.account", rate = 5, rateInterval = 60)
R<Object> testForLogin(@RequestBody @Validated LoginParams params) {
//登錄邏輯
return R.success("登錄成功");
}
}啟動服務,通過postman訪問此接口進行驗證。

到此這篇關于利用redisson快速實現(xiàn)自定義限流注解的文章就介紹到這了,更多相關redisson自定義限流注解內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Redis使用bloom-filter過濾器實現(xiàn)推薦去重
這篇文章主要介紹了Redis使用bloom-filter過濾器實現(xiàn)推薦去重,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-11-11
Redis+aop實現(xiàn)接口防刷(冪等)的解決方案
在高并發(fā)場景下,可能會因為網(wǎng)絡或者服務器原因,造成延遲,同時就是有可能會有人用腳本大量訪問你的接口,造成資源崩潰,所以本文給大家介紹了Redis+aop實現(xiàn)接口防刷(冪等)的解決方案,需要的朋友可以參考下2024-03-03
Redis動態(tài)熱點數(shù)據(jù)緩存策略設計
本文主要介紹了Redis動態(tài)熱點數(shù)據(jù)緩存策略設計,包括熱點數(shù)據(jù)識別、動態(tài)緩存、多級緩存、預加載機制、更新策略以及監(jiān)控告警等,具有一定的參考價值,感興趣的可以了解一下2025-01-01

