自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

接口防刷!利用Redisson快速實(shí)現(xiàn)自定義限流注解

數(shù)據(jù)庫(kù) Redis
利用Redis的有序集合即Sorted Set數(shù)據(jù)結(jié)構(gòu),構(gòu)造一個(gè)令牌桶來(lái)實(shí)施限流。而Redisson已經(jīng)幫我們封裝成了RRateLimiter,通過(guò)Redisson,即可快速實(shí)現(xiàn)我們的目標(biāo)。

問(wèn)題:

在日常開(kāi)發(fā)中,一些重要的對(duì)外接口,需要添加訪問(wèn)頻率限制,以免造成資產(chǎn)損失。

如登錄接口,當(dāng)用戶使用手機(jī)號(hào)+驗(yàn)證碼登錄時(shí),一般我們會(huì)生成6位數(shù)的隨機(jī)驗(yàn)證碼,并將驗(yàn)證碼有效期設(shè)置為1-3分鐘,如果對(duì)登錄接口不加以限制,理論上,通過(guò)技術(shù)手段,快速重試100000次,即可將驗(yàn)證碼窮舉出來(lái)。

解決思路:

對(duì)登錄接口加上限流操作,如限制一分鐘內(nèi)最多登錄5次,登錄次數(shù)過(guò)多,就返回失敗提示,或者將賬號(hào)鎖定一段時(shí)間。

實(shí)現(xiàn)手段:

利用redis的有序集合即Sorted Set數(shù)據(jù)結(jié)構(gòu),構(gòu)造一個(gè)令牌桶來(lái)實(shí)施限流。而redisson已經(jīng)幫我們封裝成了RRateLimiter,通過(guò)redisson,即可快速實(shí)現(xiàn)我們的目標(biāo)。

1. 定義一個(gè)限流注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GlobalRateLimiter {

    String key();

    long rate();

    long rateInterval() default 1L;

    RateIntervalUnit rateIntervalUnit() default RateIntervalUnit.SECONDS;

}

2. 利用aop進(jìn)行切面

@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無(wú)效");
            }
            key = value.toString();
        }
        key = applicationName + "_" + className + "_" + methodName + "_" + key;
        log.info("設(shè)置限流鎖key={}", key);
        RRateLimiter rateLimiter = this.redisson.getRateLimiter(key);
        if (!rateLimiter.isExists()) {
            log.info("設(shè)置流量,rate={},rateInterval={},rateIntervalUnit={}", rate, rateInterval, rateIntervalUnit);
            rateLimiter.trySetRate(RateType.OVERALL, rate, rateInterval, rateIntervalUnit);
            //設(shè)置一個(gè)過(guò)期時(shí)間,避免key一直存在浪費(fèi)內(nèi)存,這里設(shè)置為延長(zhǎng)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) {
            //這里直接拋出了異常  也可以拋出自定義異常,通過(guò)全局異常處理器攔截進(jìn)行一些其他邏輯的處理
            throw new RuntimeException("請(qǐng)求頻率過(guò)高,此操作已被限制");
        }
        return joinPoint.proceed();
    }
}

ok,通過(guò)以上兩步,即可完成我們的限流注解了,下面通過(guò)一個(gè)接口驗(yàn)證下效果。

新建一個(gè)controller,寫一個(gè)模擬登錄的方法。

@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("登錄成功");
    }
}

啟動(dòng)服務(wù),通過(guò)postman訪問(wèn)此接口進(jìn)行驗(yàn)證。

可以看到,在第6次訪問(wèn)接口的時(shí)候,拋出了請(qǐng)求限制的異常。

注意點(diǎn):

設(shè)置key的時(shí)候,一定要注意唯一性,比如登錄接口,可以將登錄賬號(hào)作為唯一性,查詢某個(gè)人的訂單記錄時(shí),將用戶id作為唯一性,要避免無(wú)意義的key,以免誤造成全局接口的限流。

設(shè)置rateLimiter的rate時(shí),RateType有兩種模式:全局 or 客戶端,可以根據(jù)需求自主設(shè)置,一般都使用全局。

責(zé)任編輯:姜華 來(lái)源: 今日頭條
相關(guān)推薦

2024-04-01 08:11:20

2024-06-14 09:30:58

2023-10-24 13:48:50

自定義注解舉值驗(yàn)證

2022-12-13 09:19:06

高并發(fā)SpringBoot

2023-03-03 09:11:12

高并發(fā)SpringBoot

2022-11-01 11:15:56

接口策略模式

2024-12-27 15:37:23

2015-02-12 15:33:43

微信SDK

2023-09-04 08:12:16

分布式鎖Springboot

2017-08-03 17:00:54

Springmvc任務(wù)執(zhí)行器

2023-10-09 07:37:01

2015-02-12 15:38:26

微信SDK

2021-12-30 12:30:01

Java注解編譯器

2022-02-17 07:10:39

Nest自定義注解

2024-07-02 11:42:53

SpringRedis自定義

2024-10-09 10:46:41

springboot緩存redis

2024-10-14 17:18:27

2023-10-11 07:57:23

springboot微服務(wù)

2009-08-21 15:38:45

ControllerF

2009-11-09 16:06:53

WCF自定義集合
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)