{"record":{"id":"33f922f73a910166","repo":"xkcoding/spring-boot-demo","slug":"error-33f922","errorCode":null,"errorMessage":"手速太快了，慢点儿吧~","messagePattern":"手速太快了，慢点儿吧~","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"demo-ratelimit-redis/README.md","lineNumber":179,"sourceCode":"        Method method = signature.getMethod();\n        // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解\n        RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class);\n        if (rateLimiter != null) {\n            String key = rateLimiter.key();\n            // 默认用类名+方法名做限流的 key 前缀\n            if (StrUtil.isBlank(key)) {\n                key = method.getDeclaringClass().getName()+StrUtil.DOT+method.getName();\n            }\n            // 最终限流的 key 为 前缀 + IP地址\n            // TODO: 此时需要考虑局域网多用户访问的情况，因此 key 后续需要加上方法参数更加合理\n            key = key + SEPARATOR + IpUtil.getIpAddr();\n\n            long max = rateLimiter.max();\n            long timeout = rateLimiter.timeout();\n            TimeUnit timeUnit = rateLimiter.timeUnit();\n            boolean limited = shouldLimited(key, max, timeout, timeUnit);\n            if (limited) {\n                throw new RuntimeException(\"手速太快了，慢点儿吧~\");\n            }\n        }\n\n        return point.proceed();\n    }\n\n    private boolean shouldLimited(String key, long max, long timeout, TimeUnit timeUnit) {\n        // 最终的 key 格式为：\n        // limit:自定义key:IP\n        // limit:类名.方法名:IP\n        key = REDIS_LIMIT_KEY_PREFIX + key;\n        // 统一使用单位毫秒\n        long ttl = timeUnit.toMillis(timeout);\n        // 当前时间毫秒数\n        long now = Instant.now().toEpochMilli();\n        long expired = now - ttl;\n        // 注意这里必须转为 String,否则会报错 java.lang.Long cannot be cast to java.lang.String\n        Long executeTimes = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList(key), now + \"\", ttl + \"\", expired + \"\", max + \"\");","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/xkcoding/spring-boot-demo/blob/87a142f9604c1a5365b4d24d22c2c11c26a9d5ab/demo-ratelimit-redis/README.md#L161-L197","documentation":"RateLimiterAspect.pointcut runs a Redis Lua script (shouldLimited); when the request count in the window reaches/exceeds max the script returns 0 and the aspect throws RuntimeException('手速太快了，慢点儿吧~'). The runtime source is RateLimiterAspect.java:68; README:179 documents the same logic.","triggerScenarios":"A client exceeds @RateLimiter(max=, timeout=) within the window for the same key, where the key is the custom key or class.method + client IP.","commonSituations":"Burst or loop traffic; max configured too low; multiple users behind one NAT IP colliding on the key (the code TODO explicitly flags this LAN shared-IP problem); a client with no backoff.","solutions":["Raise @RateLimiter max or widen the timeout window","Return HTTP 429 with Retry-After and have the client back off","Add method parameters to the limit key for finer granularity","Address the LAN shared-IP key collision noted in the TODO"],"exampleFix":"// before\nthrow new RuntimeException('手速太快了，慢点儿吧~');\n// after (in a controller advice)\n@ExceptionHandler(RuntimeException.class)\npublic ResponseEntity<String> onRateLimit(RuntimeException e) {\n    if ('手速太快了，慢点儿吧~'.equals(e.getMessage())) {\n        return ResponseEntity.status(429).header('Retry-After', '2').body('rate limited');\n    }\n    throw e;\n}","handlingStrategy":"retry","validationCode":"// best-effort pre-check: remaining quota before the guarded call\nLong used = stringRedisTemplate.execute(limitRedisScript, Collections.singletonList('limit:' + key), now + '', ttl + '', expired + '', (max - 1) + '');\nif (used != null && used == 0) { /* back off instead of calling */ }","typeGuard":null,"tryCatchPattern":"long backoff = 500;\nfor (int i = 0; i < 3; i++) {\n    try { return point.proceed(); }\n    catch (RuntimeException e) {\n        if (!'手速太快了，慢点儿吧~'.equals(e.getMessage())) throw e;\n        Thread.sleep(backoff); backoff *= 2;\n    }\n}","preventionTips":["Honor 429/Retry-After on the client with exponential backoff","Tune @RateLimiter max/timeout to real traffic, not the default","Add method params to the key to avoid the LAN shared-IP collision"],"tags":["rate-limit","redis","throttling"],"backgroundTag":null,"analyzedSha":"87a142f9604c1a5365b4d24d22c2c11c26a9d5ab","analyzedAt":"2026-08-14T01:16:58.217Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}