{"record":{"id":"2a768ade8c980a32","repo":"YunaiV/yudao-cloud","slug":"900","errorCode":"900","errorMessage":"idempotent.message()","messagePattern":"idempotent\\.message\\(\\)","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/yudao-cloud/blob/477be9dd49ab7223a972a6abdff0684d6423dec3/yudao-framework/yudao-spring-boot-starter-protection/src/main/java/cn/iocoder/yudao/framework/idempotent/core/aop/IdempotentAspect.java#L34-L69","documentation":"IdempotentAspect implements deduplication: before the method runs it setIfAbsent's a Redis key derived from the user/keyResolver. If the key already exists (a duplicate request inside the timeout window), it throws ServiceException with code 900 (REPEATED_REQUESTS) and the annotation's message (default 'idempotent.message()' literal shown when no custom message set). If the method throws, the key is optionally deleted so a genuine retry can succeed.","triggerScenarios":"The same user hits an @Idempotent endpoint twice within idempotent.timeout() (default window) — double-click submit, frontend retry, or concurrent duplicate requests. Key is typically user+method+params (UserIdempotentKeyResolver) or md5 of args (ExprIdempotentKeyResolver).","commonSituations":"Form double-submission without disabling the button; HTTP client retrying on timeout while the first request is still processing; mobile clients on flaky networks auto-retrying; batch scripts re-invoking an API.","solutions":["On the client: disable submit while in flight, and retry only after the previous request definitively failed.","If business semantics allow, set idempotent.timeout() short enough to cover only the processing window.","Set deleteKeyWhenException=true (if not default) so failed calls can be retried immediately.","On the server, catch ServiceException and map code 900 to a friendly '请求正在处理，请勿重复提交' response instead of a 500."],"exampleFix":"@Before(\"...\") // client-side guard\nfunction submit() {\n  if (submitting) return;\n  submitting = true;\n  try { await api.saveOrder(form); }\n  finally { submitting = false; }\n}","handlingStrategy":"try-catch","validationCode":"// client-side: guard against double submit\nif (inFlight) return;\ninFlight = true;\ntry { /* call */ } finally { inFlight = false; }","typeGuard":null,"tryCatchPattern":"try {\n    result = api.submit(form);\n} catch (ServiceException e) {\n    if (GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode().equals(e.getCode())) {\n        return friendly(\"请求正在处理，请勿重复提交\"); // no retry\n    }\n    throw e;\n}","preventionTips":["Disable submit controls during in-flight requests","Enable deleteKeyWhenException so failed requests are retryable","Never auto-retry on code 900 — it means a duplicate, not a failure"],"tags":["yudao","idempotency","redis","duplicate-request","aop"],"backgroundTag":null,"analyzedSha":"477be9dd49ab7223a972a6abdff0684d6423dec3","analyzedAt":"2026-08-14T13:35:31.121Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}