{"record":{"id":"e4b3af46eea6e339","repo":"dromara/Sa-Token","slug":"error-e4b3af","errorCode":null,"errorMessage":"验证码错误","messagePattern":"验证码错误","errorType":"validation","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"sa-token-demo/sa-token-demo-device-lock/src/main/java/com/pj/util/PhoneCodeUtil.java","lineNumber":25,"sourceCode":" * 手机验证码工具类 （仅做逻辑模拟，不做真实发送）\n *\n * @author click33\n * @since 2024/8/23\n */\npublic class PhoneCodeUtil {\n\n    // 指定手机号发送验证码\n    public static void sendCode(String phone) {\n        String code = SaFoxUtil.getRandomNumber(100000, 999999) + \"\";\n        SaManager.getSaTokenDao().set(\"phone_code:\" + phone, code, 60 * 5);\n        System.out.println(\"手机号：\" + phone + \"，验证码：\" + code + \"，已发送成功\");\n    }\n\n    // 校验验证码是否正确，不正确则抛出异常\n    public static void checkCode(String phone, String code) {\n        String oldCode = SaManager.getSaTokenDao().get(\"phone_code:\" + phone);\n        if( ! code.equals(oldCode) ) {\n            throw new RuntimeException(\"验证码错误\");\n        }\n        // 验证通过后，立即删除验证码\n        SaManager.getSaTokenDao().delete(\"phone_code:\" + phone);\n    }\n\n}","sourceCodeStart":7,"sourceCodeEnd":31,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-demo/sa-token-demo-device-lock/src/main/java/com/pj/util/PhoneCodeUtil.java#L7-L31","documentation":"Thrown by PhoneCodeUtil.checkCode when the SMS verification code submitted by the user does not match the code stored in SaTokenDao under key 'phone_code:<phone>'. Codes are generated as a 6-digit random number and stored with a 5-minute (300s) TTL, then deleted immediately after one successful verification. A mismatch — including an expired (null) or already-consumed code — produces this RuntimeException.","triggerScenarios":"Calling checkCode(phone, code) where the submitted code differs from SaManager.getSaTokenDao().get(\"phone_code:\" + phone). This happens when: user typos the code; more than 300s elapsed since sendCode() (DAO returns null, and code.equals(null-String) is false); the code was already verified once (deleted after success); or sendCode() was called twice and the user reads the first SMS.","commonSituations":"Demo device-lock login flows where the user waits too long on the input page, resends the code but enters the older SMS, or retries login after a successful attempt. Also when the underlying SaTokenDao is a no-op/in-memory dao and the app restarted between send and check.","solutions":["Re-send a fresh code via PhoneCodeUtil.sendCode(phone) and have the user enter the newest SMS within 5 minutes.","If codes seem valid but always fail, verify both sendCode and checkCode hit the same SaTokenDao instance (e.g. both go to Redis, not one to memory after a restart).","Treat a null stored code distinctly (expired/never sent) instead of reporting 'wrong code', to guide the user to resend.","For production, replace the demo's RuntimeException with a typed exception and rate-limit sendCode to prevent abuse."],"exampleFix":"// before\npublic static void checkCode(String phone, String code) {\n    String oldCode = SaManager.getSaTokenDao().get(\"phone_code:\" + phone);\n    if( ! code.equals(oldCode) ) {\n        throw new RuntimeException(\"验证码错误\");\n    }\n    SaManager.getSaTokenDao().delete(\"phone_code:\" + phone);\n}\n\n// after\npublic static void checkCode(String phone, String code) {\n    String oldCode = SaManager.getSaTokenDao().get(\"phone_code:\" + phone);\n    if (oldCode == null) {\n        throw new RuntimeException(\"验证码已过期或未发送，请重新获取\");\n    }\n    if (!oldCode.equals(code)) {\n        throw new RuntimeException(\"验证码错误\");\n    }\n    SaManager.getSaTokenDao().delete(\"phone_code:\" + phone);\n}","handlingStrategy":"validation","validationCode":"String stored = SaManager.getSaTokenDao().get(\"phone_code:\" + phone);\nif (stored == null) {\n    // expired or never sent; do not call checkCode\n    return \"code expired, please resend\";\n}\nif (!stored.equals(inputCode)) {\n    return \"wrong code\";\n}\nPhoneCodeUtil.checkCode(phone, inputCode);","typeGuard":null,"tryCatchPattern":"try { PhoneCodeUtil.checkCode(phone, code); } catch (RuntimeException e) { if (\"验证码错误\".equals(e.getMessage())) { /* prompt resend */ } else throw e; }","preventionTips":["Always call sendCode immediately before prompting the user, and document the 5-minute TTL in the UI.","Invalidate/resend rather than reuse after a failed attempt; the stored code is only deleted on success.","Ensure the same SaTokenDao backend (e.g. Redis) serves both send and check across app restarts."],"tags":["sms","verification-code","sa-token-dao","demo"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}