Files
Genarrative/docs/technical/RECOMMEND_RUNTIME_AUTH_FAILURE_ISOLATION_FIX_2026-05-09.md
2026-05-09 18:24:08 +08:00

4.9 KiB
Raw Permalink Blame History

推荐页运行态鉴权失败隔离修复

日期:2026-05-09

背景

登录成功进入平台推荐页后,推荐页会自动加载一个公开作品并启动嵌入式运行态。实际联调中出现过:作品刚加载出来,前端又瞬间回到未登录状态;停留在其他页面,或推荐页没有成功加载出作品时不会复现。

后续复测又发现:登录成功后,从推荐页点进拼图公开作品详情并启动完整拼图运行态,也可能在开局或通关后瞬间退回未登录。两类现象的底层问题一致,都是玩法/展示层局部请求把 401 扩散成全局鉴权事件。

根因

推荐页首屏的作品运行态启动是后台自动副作用,不是用户主动点击的账号操作。它会触发多条受保护请求,例如:

  1. 拼图、抓大鹅、方洞挑战、视觉小说的 start run
  2. 大鱼吃小鱼的 start run 与游玩记录上报。
  3. 视觉小说运行前的作品详情读取。

这些请求一旦遇到本地代理错配、后端短暂不可用或 token 刷新失败,原请求层会按普通受保护请求处理 401,清空 access token 并广播全局鉴权变更。AuthGate 收到事件后重新 hydrate于是当前用户界面被切回未登录态。

再次复测确认还有更深一层根因:即使单个业务请求显式传了 clearAuthOnUnauthorized: falserefreshAccessToken() 自身在 refresh 失败时也会先静默清空本地 access token。这样局部请求可能没有广播事件却已经把本地凭证掏空后续任意一次默认鉴权探测或 AuthGate hydrate 都会变成未登录。

推荐页进入公开拼图作品后还会伴随平台侧私有投影刷新,例如存档列表、浏览历史、个人看板和作品架列表。这些请求用于页面展示与局部缓存同步,不是账号会话权威;其中任意一个 401 都不应把整站登录态改写为未登录。

推荐页里还有一类更隐蔽的触发点:ResolvedAssetImage / useResolvedAssetReadUrl 在挂载时会请求 /api/assets/read-url 给 generated 私有图片换签。它本质上也是展示层后台请求,若按普通受保护请求处理 401,同样会把一次图片换签失败放大成全局掉线。

公开拼图作品的完整运行态还会在用户进入作品后自动发起 startPuzzleRun,通关后自动 submitPuzzleLeaderboard,点击下一关时 advancePuzzleNextLevel。这些请求属于当前玩法的运行态同步,失败时应该落到当前拼图错误态;它们不能清空全局 access token也不能触发 AuthGate 重新 hydrate。

修复

本次把推荐页自动运行态请求定义为“卡片级后台请求”:

  1. apiClient 增加 authImpact: 'global' | 'local' 策略,并导出 BACKGROUND_AUTH_REQUEST_OPTIONSlocal 请求统一跳过 refresh不清空 token不广播 AUTH_STATE_EVENT
  2. refreshAccessToken() 不再自行清空 token只有 refreshStoredAccessToken() 这类全局会话恢复入口和默认全局请求策略能决定清 token。
  3. 推荐页嵌入式运行态请求统一使用 BACKGROUND_AUTH_REQUEST_OPTIONS
  4. 推荐页自动启动作品前必须满足 canReadProtectedData,避免 AuthGate 仍在恢复阶段就提前发起受保护写请求。
  5. generated 图片换签请求同样使用局部后台鉴权选项并跳过 refresh失败只让当前图片为空不触发全局登录态清理。
  6. 公开拼图作品进入完整运行态后,把本次 run 标记为 isolated 鉴权模式;开局、重开、排行榜提交和下一关推进都沿用局部鉴权选项。
  7. 平台 bootstrap 的私有投影读写,包括个人看板、私有作品架、创作作品列表、浏览历史写入和存档列表刷新,也统一作为局部后台请求处理。
  8. Remix、发布、点赞、账号设置、退出登录等真正账号动作继续保留默认全局鉴权处理。

验证

  1. npm run test -- src/services/apiClient.test.ts src/services/assetReadUrlService.test.ts
  2. npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "home recommendation starts embedded puzzle"
  3. npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle runtime uses frontend move merge logic and backend leaderboard"
  4. npm run test -- src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx -t "formal puzzle similar work keeps current run level progression"
  5. npm run typecheck
  6. npm run check:encoding

关联文件

  1. src/services/apiClient.ts
  2. src/services/rpg-runtime/rpgRuntimeRequest.ts
  3. src/services/rpg-creation/rpgCreationRuntimeClient.ts
  4. src/components/rpg-entry/useRpgEntryBootstrap.ts
  5. src/components/platform-entry/PlatformEntryFlowShellImpl.tsx
  6. src/services/*-runtime/*RuntimeClient.ts
  7. src/services/visual-novel-works/visualNovelWorksClient.ts