{"record":{"id":"8c1650c3d78b9143","repo":"YunaiV/ruoyi-vue-pro","slug":"429","errorCode":"429","errorMessage":"请求过于频繁，请稍后重试","messagePattern":"请求过于频繁，请稍后重试","errorType":"exception","errorClass":"ServiceException","httpStatus":null,"severity":"warning","filePath":"yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/aop/RateLimiterAspect.java","lineNumber":55,"sourceCode":"        this.rateLimiterRedisDAO = rateLimiterRedisDAO;\n    }\n\n    @Before(\"@annotation(rateLimiter)\")\n    public void beforePointCut(JoinPoint joinPoint, RateLimiter rateLimiter) {\n        // 获得 RateLimiterKeyResolver 对象\n        RateLimiterKeyResolver keyResolver = keyResolvers.get(rateLimiter.keyResolver());\n        Assert.notNull(keyResolver, \"找不到对应的 RateLimiterKeyResolver\");\n        // 解析 Key\n        String key = keyResolver.resolver(joinPoint, rateLimiter);\n\n        // 获取 1 次限流\n        boolean success = rateLimiterRedisDAO.tryAcquire(key,\n                rateLimiter.count(), rateLimiter.time(), rateLimiter.timeUnit());\n        if (!success) {\n            log.info(\"[beforePointCut][方法({}) 参数({}) 请求过于频繁]\", joinPoint.getSignature().toString(), joinPoint.getArgs());\n            String message = StrUtil.blankToDefault(rateLimiter.message(),\n                    GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getMsg());\n            throw new ServiceException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getCode(), message);\n        }\n    }\n\n}\n\n","sourceCodeStart":37,"sourceCodeEnd":61,"githubUrl":"https://github.com/YunaiV/ruoyi-vue-pro/blob/0418084e222612af2fc1141f566af454f9236ab1/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/ratelimiter/core/aop/RateLimiterAspect.java#L37-L61","documentation":"RateLimiterAspect applies a Redis token-bucket/counter limit per @RateLimiter. When tryAcquire returns false (count exceeded within the time window for the resolved key), it throws ServiceException(TOO_MANY_REQUESTS code 429, default '请求过于频繁，请稍后重试'). This is the intended throttle response, surfaced as a ServiceException the global handler maps to HTTP 429.","triggerScenarios":"A client exceeds the configured @RateLimiter count within time/timeUnit for the resolved key (per user, per IP, or per method); an automated/scripted client hammering the endpoint; key resolves to the same value for many users (collisions).","commonSituations":"Front-end polling/refresh loops; bot traffic; misconfigured rate that is too low for normal usage; keyResolver too coarse.","solutions":["Raise @RateLimiter(count, time) if the limit is genuinely too low for legit traffic.","Back off on the client (exponential backoff / respect Retry-After) when 429 is returned.","Choose a finer keyResolver so the limit is per-user rather than global.","Return a proper 429 with headers so clients throttle themselves."],"exampleFix":"// before\n@RateLimiter(keyResolver = DefaultRateLimiterKeyResolver.class, count = 1, time = 1)\n// after\n@RateLimiter(keyResolver = UserRateLimiterKeyResolver.class, count = 10, time = 1)","handlingStrategy":"try-catch","validationCode":"boolean ok = rateLimiterRedisDAO.tryAcquire(key, count, time, unit);\nif (!ok) throw new ServiceException(TOO_MANY_REQUESTS);","typeGuard":"null","tryCatchPattern":"try { return aspect.beforePointCut(pjp, rateLimiter); }\ncatch (ServiceException e) { if (e.getCode()==429) { setRetryAfter(response, 1); return; } throw e; }","preventionTips":["Back off on the client when 429 is returned","Choose a per-user keyResolver","Tune @RateLimiter count/time to real usage"],"tags":["yudao","rate-limiter","redis","aop"],"backgroundTag":null,"analyzedSha":"0418084e222612af2fc1141f566af454f9236ab1","analyzedAt":"2026-08-14T00:56:18.412Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}