{"record":{"id":"40631fa07fd625f0","repo":"YunaiV/ruoyi-vue-pro","slug":"900","errorCode":"900","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/idempotent/core/aop/IdempotentAspect.java","lineNumber":52,"sourceCode":"    public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {\n        this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass);\n        this.idempotentRedisDAO = idempotentRedisDAO;\n    }\n\n    @Around(value = \"@annotation(idempotent)\")\n    public Object aroundPointCut(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {\n        // 获得 IdempotentKeyResolver\n        IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());\n        Assert.notNull(keyResolver, \"找不到对应的 IdempotentKeyResolver\");\n        // 解析 Key\n        String key = keyResolver.resolver(joinPoint, idempotent);\n\n        // 1. 锁定 Key\n        boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit());\n        // 锁定失败，抛出异常\n        if (!success) {\n            log.info(\"[aroundPointCut][方法({}) 参数({}) 存在重复请求]\", joinPoint.getSignature().toString(), joinPoint.getArgs());\n            throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message());\n        }\n\n        // 2. 执行逻辑\n        try {\n            return joinPoint.proceed();\n        } catch (Throwable throwable) {\n            // 3. 异常时，删除 Key\n            // 参考美团 GTIS 思路：https://tech.meituan.com/2016/09/29/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html\n            if (idempotent.deleteKeyWhenException()) {\n                idempotentRedisDAO.delete(key);\n            }\n            throw throwable;\n        }\n    }\n\n}\n","sourceCodeStart":34,"sourceCodeEnd":69,"githubUrl":"https://github.com/YunaiV/ruoyi-vue-pro/blob/0418084e222612af2fc1141f566af454f9236ab1/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/idempotent/core/aop/IdempotentAspect.java#L34-L69","documentation":"yudao's IdempotentAspect acquires a Redis key per request (keyResolver + idempotentRedisDAO.setIfAbsent). If the key already exists within the timeout window, the request is a duplicate and it throws ServiceException(REPEATED_REQUESTS code 900, default message '重复请求，请稍后重试'). It is intentional behavior, not a bug — the @Idempotent annotation requested it.","triggerScenarios":"A client retried/submitted the same @Idempotent-guarded request twice within the timeout window (same key from DefaultIdempotentKeyResolver = method+args, or from Expr/Args resolvers); double-click submit; automatic retry storm.","commonSituations":"Front-end double submit; network retry by an HTTP client; an idempotent operation whose key is too coarse (collides across distinct business calls).","solutions":["On the client, disable the submit button after first click and only retry after the timeout.","Tune @Idempotent(timeout, timeUnit, keyResolver) so the window and key match the business semantics.","If duplicates are expected and harmless, set deleteKeyWhenException=true (already handled) or remove the annotation.","Use a business-id-based keyResolver so distinct business requests do not collide."],"exampleFix":"// before: too-broad key collides across users\n@Idempotent(timeout = 10, keyResolver = DefaultIdempotentKeyResolver.class)\n// after: include business id in the key\n@Idempotent(timeout = 10, keyResolver = ExprIdempotentKeyResolver.class, key = \"#request.orderNo\")","handlingStrategy":"try-catch","validationCode":"String key = keyResolver.resolver(joinPoint, idempotent);\nboolean first = redisTemplate.opsForValue().setIfAbsent(key, \"1\", timeout, unit);\nif (!first) throw new ServiceException(REPEATED_REQUESTS);","typeGuard":"null","tryCatchPattern":"try { return aspect.aroundPointCut(pjp, idempotent); }\ncatch (ServiceException e) { if (e.getCode()==900) return ResponseEntity.status(409).body(\"duplicate\"); throw e; }","preventionTips":["Disable the submit button after first click","Tune @Idempotent timeout/key to business semantics","Use a business-id keyResolver to avoid collisions"],"tags":["yudao","idempotent","redis","aop"],"backgroundTag":null,"analyzedSha":"0418084e222612af2fc1141f566af454f9236ab1","analyzedAt":"2026-08-14T00:56:18.412Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}