9.9 KiB
【前端待处理·管理后台】管理后台消息 SSE 鉴权重连与旧会话恢复
模块:管理后台全局消息 / 在线状态 / 聊天信令 | 服务:
hl-gateway+hl-user-service
类型:前端待处理 + 联调告知 | 更新时间:2026-07-19
影响范围:管理后台全局 SSE 连接、顶部未读角标、聊天、在线状态与抢单池信令
状态:后端已完成根因定位;前端尚未修复;接口契约未变
1. 结论与处理优先级
⚠️ 2026-07-19 测试环境启用 SSE 连接角色一致性校验后,发布前已签发且仍在有效期内的旧登录会话可能缺少当前角色标记。此时网关能够识别 access token,但用户服务会拒绝建立 SSE,前端当前实现会持续使用同一登录会话无限重连。
- 接口 URL、HTTP 方法、事件结构均未修改。
- 这不是
tokenQuery 参数名写错;当前前端 URL 拼接方式与网关读取方式一致。 - 用户立即恢复方式:退出当前账号,重新登录并选择当前角色,再建立 SSE。
- 前端必须处理:Token/角色变化时主动重建连接、限制连续失败重试、给出重新登录提示,并消除默认
message事件的重复注册。 - 本次现象包含后端发布前旧会话兼容问题;前端改造用于正确管理连接生命周期和避免无限重试,不代表把后端兼容责任转移给前端。
2. 当前接口契约
GET /ws/admin-msg/stream?token=<accessToken>
Accept: text/event-stream
- 认证:管理后台 access token。
- 当前使用原生
EventSource,浏览器 API 不能自定义AuthorizationHeader,因此现有实现通过 Query 参数传递 token。 token必须使用当前 Store 中的 access token,并通过encodeURIComponent做 URL 编码。- 成功建连后,请求应长期保持
Pending,响应类型为text/event-stream。 - 首个握手事件:
event: connected
data: ok
- 后续仍沿用现有具名事件,包括
unread-count、im-chat、im-chat-read、presence和grab-pool-changed;本次没有修改事件数据结构。
3. 已确认的问题链路
3.1 旧登录会话与新角色标记不兼容
测试环境运行链路已确认:
- 网关可以从
?token=读取并校验管理后台 JWT。 - 网关向用户服务转发可信的管理员身份及角色信息。
- 用户服务在下发任何 SSE 数据前校验“连接角色是否仍为当前登录角色”。
- 发布前签发的旧登录会话没有初始化新角色标记时,校验按安全策略失败并关闭连接。
- 新登录或重新选择角色会重新写入角色标记,因此重新登录后可恢复。
该校验采用 fail-closed(失败时拒绝)策略,目的是避免角色切换后旧 Token 继续接收不属于当前角色的消息。
3.2 前端当前会无限重试同一失败会话
当前 src/composables/useAdminMessageSSE.js 在 EventSource.onerror 后执行关闭和指数退避,但没有连续失败上限,也没有触发重新登录或鉴权恢复流程。
原生 EventSource.onerror 不暴露 HTTP 状态码和响应正文,前端不能仅凭 onerror 精确区分 401/403、服务异常和临时断网。因此不能把所有错误都直接判定为 Token 失效,但必须限制无休止重连。
3.3 默认 message 事件被重复注册
当前实现同时注册:
es.onmessage = handleMessage
es.addEventListener('message', handleMessage)
两种写法都会监听默认 message 事件,并不是互斥兜底。后端发送默认 message 时,同一数据可能被处理两次,必须只保留一种注册方式。
4. 用户立即恢复步骤
- 关闭当前页面产生的旧 SSE 连接。
- 正常退出管理后台。
- 重新登录,并重新选择当前需要使用的角色。
- 进入主布局后重新建立
/ws/admin-msg/stream。 - 在浏览器 Network 中确认请求保持
Pending,并收到一次connected事件。
不要通过手工复制、修改或在地址栏粘贴完整 Token 的方式恢复连接。
5. 【前端·管理后台】适配清单
5.1 让 SSE 生命周期跟随登录凭证和角色
- 监听
userStore.token变化;值变化时先关闭旧EventSource,再使用最新 Token 建立唯一的新连接。 - 角色切换成功并更新 Token 后,立即重建 SSE,不等待旧连接自行报错。
- 登出、主布局卸载或 Token 被清空时,关闭连接、清理重连定时器并禁止再次拉起。
- 保证全局最多只有一个管理后台消息 SSE 实例,避免布局重复挂载造成多连接。
- 重建连接时始终从 Store 现取 Token,不缓存旧登录会话中的 Token 字符串。
5.2 限制连续失败,避免无限重连
- 保留指数退避和最大间隔,但增加“连续失败次数/总时长”上限。
- 仅在收到后端
connected事件后清零连续失败计数;EventSource.onopen不能作为鉴权成功依据,也不能清零计数。 - 达到上限后停止自动重试,并显示中性、可操作的提示,例如“消息连接连续失败,请检查网络或重新登录”。
- 用户完成重新登录、Token 刷新、角色切换或主动点击重试后,才开启新一轮连接。
- 临时断网恢复后仍允许重连;可结合
online事件或显式重试入口恢复,而不是永久静默失效。
注意:由于原生
EventSource无法在onerror中读取响应状态,前端不要根据一次onerror立即清空登录态。需要使用连续失败阈值,并结合普通鉴权接口结果或既有 Token 刷新状态判断。
5.3 消除重复消息处理
es.onmessage与es.addEventListener('message', ...)只保留一种。connected、unread-count、im-chat、im-chat-read、presence、grab-pool-changed等具名事件继续分别注册。- 验证单条默认
message、聊天信令和未读数信令都只被业务层消费一次。
5.4 失败信息与联调反馈
- 前端提示中不要展示 Token、完整 SSE URL、Cookie 或管理员标识。
- 如重新登录后仍失败,只反馈发生时间、页面、错误
message和X-Trace-Id。 - 若 Network 原始响应确实为“未提供有效的Token”,请附
X-Trace-Id交后端继续检查路由/拦截器链;不要附 Token。
6. 验收场景
| 场景 | 期望结果 |
|---|---|
| 重新登录后首次进入主布局 | 只建立 1 条 SSE;请求保持 Pending;收到 1 次 connected |
| access token 刷新 | 旧连接关闭,使用新 Token 只重建 1 次 |
| 切换管理后台角色 | 旧角色连接立即关闭;新角色 Token 建立新连接;不接收旧角色后续数据 |
| 发布前旧会话无法建连 | 退避重试达到阈值后停止,并明确提示重新登录;不无限刷请求 |
只触发 onopen、未收到 connected、随后触发 onerror |
仍累计连续失败次数,不得被 onopen 反复清零 |
| 临时断网后恢复 | 在受控退避或用户重试后恢复连接,不产生并发 SSE |
收到默认 message |
同一事件只处理 1 次 |
| 正常登出 | SSE 和重连定时器均被清理,退出页不再发起连接 |
| 重新登录后仍失败 | 联调材料仅包含时间、页面、错误消息、X-Trace-Id,不包含 Token |
7. 后端状态与边界
- 当前接口路径、Query 参数名和 SSE 事件结构未变,不需要前端调整数据模型。
- 新登录/角色切换链路会写入当前角色标记,重新登录是当前可用的恢复手段。
- 发布前旧会话没有迁移标记是本次问题的触发条件;后端尚未交付旧会话兼容补丁。
- 角色一致性校验必须保留,不能为了兼容旧会话而允许旧角色 Token 接收消息。
- 若后续改为一次性 SSE Ticket、Fetch Streaming 或其他不在 URL 中携带 access token 的方案,将另发接口契约,不在本次前端适配范围内。
8. 安全要求
- 禁止把完整 Token、带 Token 的完整 SSE URL、Cookie 或真实管理员信息写入 Issue、PR、Changelog、日志和截图。
- Token 一旦通过聊天、工单或截图暴露,应立即停止使用和传播,通知后端/运维按当前鉴权策略显式吊销或拒绝该旧 Token,并验证它已无法访问;随后重新登录获取新 Token。
- 重新登录只是恢复 SSE 和获取新 Token,不等于旧 JWT 已自动吊销;尤其在仅校验 JWT 签名的环境中,必须单独完成旧 Token 的失效处置。
- 不得在前端代码中硬编码 Token,也不得把 Token 写入错误上报或埋点参数。
9. 影响范围
| 文件/能力 | 说明 |
|---|---|
src/composables/useAdminMessageSSE.js |
连接、重连、事件监听和清理逻辑 |
src/layouts/BasicLayout.vue |
主布局挂载、登出和 SSE 生命周期 |
| 角色切换流程 | Token 更新后主动重建 SSE |
| 顶部未读角标、聊天、在线状态、抢单池信令 | 共用同一 SSE,需防止连接缺失或事件重复消费 |
10. 发布说明
- 本文是前端联调和修复通知,不代表已修改或发布前端代码。
- 本文没有包含任何真实 Token、管理员 ID、Cookie 或其他敏感信息。
- 前端完成后应在
mmg/hl-ui走自身 Issue、分支、PR、测试和发布流程。
11. 相关历史契约
| 文档 | 当前说明 |
|---|---|
| 内部员工站内信收件箱 + SSE 实时推送 | SSE 路径、Query 鉴权和事件契约仍有效;其中“断线自动重连”的建议被本文补充为有上限的受控重连,鉴权持续失败时不得无限请求 |
| 切角色 / 刷新令牌原子保存 | token 与 refreshToken 原子保存要求仍有效;保存新 access token 后还必须关闭旧 SSE 并主动重建 |