{"record":{"id":"833dc1b043cb9cf9","repo":"dromara/Sa-Token","slug":"id","errorCode":null,"errorMessage":"此设备id目前未绑定任何用户","messagePattern":"此设备id目前未绑定任何用户","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"sa-token-demo/sa-token-demo-device-lock/src/main/java/com/pj/util/DeviceLockCheckUtil.java","lineNumber":32,"sourceCode":"     * 保存设备id与用户id的映射关系\n     * @param deviceId /\n     * @param userId /\n     */\n    public static void setDeviceIdToUserId(String deviceId, long userId) {\n        if(SaFoxUtil.isEmpty(deviceId) || SaFoxUtil.isEmpty(userId)) {\n            throw new RuntimeException(\"设备id或用户id不能为空\");\n        }\n        SaManager.getSaTokenDao().set(saveKeyPrefix() + deviceId, String.valueOf(userId), 1200);\n    }\n\n    /**\n     * 返回设备id绑定的用户id\n     * @param deviceId /\n     */\n    public static long getUserIdByDeviceId(String deviceId) {\n        String userIdStr = SaManager.getSaTokenDao().get(saveKeyPrefix() + deviceId);\n        if(userIdStr == null) {\n            throw new RuntimeException(\"此设备id目前未绑定任何用户\");\n        }\n        return Long.parseLong(userIdStr);\n    }\n\n    // 返回数据保存时使用的前缀\n    public static Object saveKeyPrefix() {\n        return SaManager.getConfig().getTokenName() + \":device-to-userid:\";\n    }\n\n}","sourceCodeStart":14,"sourceCodeEnd":42,"githubUrl":"https://github.com/dromara/Sa-Token/blob/ac2c7f6e94a78573cf0bcb932dd8b04e68fad189/sa-token-demo/sa-token-demo-device-lock/src/main/java/com/pj/util/DeviceLockCheckUtil.java#L14-L42","documentation":"Demo utility in sa-token-demo-device-lock: getUserIdByDeviceId looks up the persisted deviceId -> userId mapping in SaTokenDao under key '<tokenName>:device-to-userid:<deviceId>'. A null result (key absent or expired) throws RuntimeException('此设备id目前未绑定任何用户'). Because the mapping is written with a 1200-second (20-minute) TTL, the binding silently disappears after 20 minutes or a Redis flush.","triggerScenarios":"Calling getUserIdByDeviceId with a deviceId never registered, a deviceId whose binding expired (TTL 1200s since setDeviceIdToUserId), or after the underlying SaTokenDao was cleared/restarted (in-memory dao lost on reboot).","commonSituations":"User returning after >20 minutes on a bound device; server restart wiping the default in-memory dao; typo'd or rotated device id from the client; Redis eviction under memory pressure.","solutions":["Re-bind before lookup: call setDeviceIdToUserId(deviceId, userId) at login, then getUserIdByDeviceId on subsequent requests","Handle the exception (or a null check) as 'device not bound' and route the user to re-login/re-bind","If longer bindings are needed, raise the 1200-second TTL in setDeviceIdToUserId or refresh it on each successful check"],"exampleFix":"// before\nlong userId = DeviceLockCheckUtil.getUserIdByDeviceId(deviceId); // throws after TTL\n\n// after\ntry {\n    long userId = DeviceLockCheckUtil.getUserIdByDeviceId(deviceId);\n} catch (RuntimeException e) {\n    // binding expired: force re-login / re-bind device\n}","handlingStrategy":"try-catch","validationCode":"String v = SaManager.getSaTokenDao().get(\n    DeviceLockCheckUtil.saveKeyPrefix() + deviceId);\nif (v == null) {\n    // binding missing/expired -> ask user to re-login, no exception needed\n}","typeGuard":null,"tryCatchPattern":"try {\n    long userId = DeviceLockCheckUtil.getUserIdByDeviceId(deviceId);\n} catch (RuntimeException e) {\n    if (e.getMessage().contains(\"未绑定\")) {\n        // treat as 'device not bound': redirect to re-bind flow\n    }\n}","preventionTips":["Remember the 1200s TTL: refresh the binding on each successful request or raise it","Re-bind devices at every login","Check the dao key directly instead of relying on the exception if you need control flow without exceptions"],"tags":["sa-token","demo","device-lock","cache-expiry"],"backgroundTag":null,"analyzedSha":"ac2c7f6e94a78573cf0bcb932dd8b04e68fad189","analyzedAt":"2026-08-14T14:36:10.271Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}