单设备登录机制实现
定位智慧衣橱FastAPI MySQL Android Compose中「同一账号仅保留一条有效会话」的后端与客户端方案说明。一、AI 辅助开发本专文直接相关的提示词以下为在Cursor中针对本功能的真实表述整理SPEC → 提示词 → 仓库改动 → 自测。“实现单设备登录并作友好提示。参考常见做法——服务端维护当前有效会话或 token 校验请求拦截比对不一致则返回明确中文说明前端清 token 并回登录再次登录希望有「其他终端已登录是否从该终端登出并在本机登录」的 取消 / 确定流程效果参考下图。”参考博客https://blog.csdn.net/m0_74802419/article/details/135396651二、总体思路版本号 JWT 载荷业界常见两种路线集中存储当前 token如 RedisuserId → token或数据库会话版本号单调递增写入 JWT。本仓库采用后者理由已有 MySQL、无额外中间件依赖且与无状态 JWT 仍能配合使用。核心不变量表app_users中维护整数token_version随「颁发新会话」递增。JWT 载荷中携带同一语义字段实现里为tv与库中token_version相等时才视为当前有效会话。任意一次新的登录成功或服务端主动登出会使token_version变化从而使旧 JWT 中的tv全部作废。这样单设备的含义是同一时刻只有最新一次登录所对应的token_version能通过current_user_id校验。三、后端实现要点FastAPI3.1 签发与校验签发create_token(user_id, token_version)将sub、tv、exp写入 JWTexp与业务上会话长度一致如 7 天。校验current_user_id依赖中解码 JWT读出user_id与jwt_tv查库得到app_users.token_version若jwt_tv ! token_version返回401detail为面向用户的说明例如账号已在其他终端登录、请重新登录。3.2 登录时递增版本号登录成功在密码校验通过后对对应用户执行token_version token_version 1再用新的token_version签发 JWT。这样上一台设备上的旧 JWT 立即与库不一致下一次请求即 401。3.3 「友好提示」的第二层409 与force若仅在登录时无脑递增另一台设备正在用时当前设备登录会静默顶掉对端对端只在下次请求才发现——产品更希望先问一句。为此在库中增加或沿用启动时init_db自动补列server_session_untilDATETIME(3) NULL语义服务端认为「可能存在仍有效的在线会话」的截止时间与 JWT 有效期对齐例如同样 7 天。逻辑概要注册成功、登录成功后将server_session_until更新为「当前时间 与 JWT 一致的时长」。POST /api/v1/auth/login增加请求体字段force: bool falseforce false且server_session_until未过期 → 返回409 Conflictdetail为固定中文提示供 App 弹取消 / 确定。用户点确定后客户端以force: true再次请求登录跳过 409 分支照常递增token_version并刷新server_session_until完成对旧端的踢下线。3.4 服务端登出/api/v1/auth/logout用户在本机点「退出登录」时若仅清本地 token库里的server_session_until仍可能在未来一段时间内视为「有会话」导致另一台设备误收 409。因此增加需 Bearer 的登出接口递增token_version并清空server_session_until使当前 token 失效并释放「会话窗口」。客户端应先调该接口再清本地存储网络失败时仍可清本地避免卡死。3.5 数据库与迁移建表 / 补列逻辑见backend/main.py中init_db()。手工迁移可参考backend/sql/add_token_version_app_users.sqlbackend/sql/add_server_session_until_app_users.sql四、客户端实现要点Android Compose409解析为LoginConflictException或等价分支在登录页展示Material 3AlertDialog标题「提示」正文为服务端detail取消关闭对话框确定以force true重试登录。401含被顶下线统一清除本地 token 后进入登录流可对SESSION_KICKED_DETAIL一类文案再弹「知道了」对话框避免仅用 Snackbar 不够醒目。LoginBody、ApiService.logout、UserRepository.login(..., force)与AppViewModel中的冲突状态机需保持一致。具体类名与路径以仓库SmartWardrobe/app/src/main/java/com/mywardrobe/app/下当前代码为准。五、行为小结联调自检清单场景预期A 已登录B 用同一账号在登录页登录先409确认后 B 登录成功A 下次请求401并看到友好说明用户在本机退出登录调用服务端 logout 后他机登录一般不再误报 409server_session_until已清仅升级库、旧行server_session_until为NULL通常不会触发 409仍以token_version踢人为主六、小结单设备登录在本项目中的硬核机制是JWT 中的会话版本tv与数据库token_version强一致产品层的友好提示则依赖server_session_until 409/force 401 文案与Compose 对话框配合完成。与《运行说明》、用户表设计文档不一致时以仓库已合并版本为准。