# 切角色 / 刷新令牌「掉登」已修 + 前端必改一项（原子保存 token）

> 模块：admin 鉴权（hl-user-service）/ 管理后台
> 类型：后端 bug 修复（PR #4529 已合 dev-v3 + 测试服实测生效）+ **前端必改一项加固**
> 日期：2026-06-28

## 背景 / 修了什么（后端）

两个掉登 bug 已修：

- **Bug1（切角色被登出）**：超管切到「房务」等角色后刷新页面报「刷新令牌无效或已过期」被踢回原角色。根因：切角色会轮转 refresh 令牌、使旧 refresh 立即失效，前端若没存住新 refresh 就被登出。
  - 后端修复：**切角色不再轮转 refresh**（只换发带新角色的 access，refresh 复用、保持有效）。即使前端不改也不会再因切角色被登出。
- **Bug2（刷新偶发掉登）**：整页刷新偶发「刷新令牌无效或已过期」。根因：refresh 校验与续期非原子，并发刷新读到中间态。
  - 后端修复：refresh 校验+续期改为 Redis 原子执行，并发刷新不再误判。

测试服实测：切角色后用原 refresh 刷新 200（不掉登）；同一 refresh 并发 3 次刷新全 200；切「定制师/房务」回归全正常。

## 前端必改（1 项加固）

**切角色 `POST /admin/auth/switch-role` 与刷新 `POST /admin/auth/refresh` 的响应里，`token`(access) 与 `refreshToken` 要原子地一起保存**（两者同时落本地存储，避免只存其一），切角色后用响应里的新 `token` 调后续接口。

- 说明：后端每次 switch-role / refresh 都换发**新 access**并把旧 access 轮转出（access 白名单 last-issued-wins）。所以切角色/刷新后**必须改用响应里的新 access**，旧 access 会失效（这是既有设计，非本次改动）。本次修复保证的是 **refresh 不会因切角色失效**，刷新链路稳定。

## 影响接口
- `POST /admin/auth/switch-role`（行为：refresh 不再轮转；响应结构不变）
- `POST /admin/auth/refresh`（行为：校验+续期原子化；响应结构不变）
