Files
dsh_shenxian/dsh-server-docs/04-调整方案/59-重连反馈-实例启动加载动画.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
   保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
   工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
   必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
   + ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
   ⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
   验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

6.0 KiB
Raw Blame History

59 · 重连过程的可见反馈:实例启动中的加载动画

  • 日期:2026-09-12
  • 状态:✅ 已实施并验证(服务器已生效;本地待提交)
  • 触发:用户报「admin 会话出现连接异常,点击重连成功了,但过程中没有加载/重连或重启进程的加载动画」
  • 关联:档案 49(导航过渡页 wake.html)、50(401 自愈注入 __dshRecover)、51(401 透明重放)、56(实例助手 __dshAssist)、58(本次重启的起因:实例内存优化)

一、现象与根因

用户遇到的"连接异常"是平台重启导致实例被 drain(档案 58 的内存优化需要重启 dshs)。重连本身成功了,问题在于过程无反馈。

排查后发现平台对"实例未就绪"的处理分两条路径,只有一条有动画:

路径 触发场景 现有处理 反馈
浏览器导航(刷新/首次进入) GET + Accept: text/html 302 → /wake.html?next=…(自带旋转 + 进度条 + 步骤文案) ✅ 有动画
XHR / fetch(页面已打开时点重连) /api/* proxy 阻塞等待实例拉起(launch + waitForLaunchTokenForUser,最长 20 秒)后继续转发 ❌ 无任何反馈

proxy.ts 的注释写着这套设计的前提假设:

非导航(XHR / API)——这是"页面已打开、再对话没反应"的主场景。正解不是"回一个错误/动画",而是等实例就绪后继续转发:请求最终成功,dsh 前端自己会保持它的 loading 态 → 用户无感,不需要刷新。

假设不成立:dsh 客户端在"连接异常 → 重连"这条路径上并不显示等待态。于是用户看到的是"点了重连,界面毫无动静,几十秒后突然好了"。


二、修复(纯客户端增强,不动协议)

在注入脚本里给挂起的 API 请求加计时器:超过阈值就亮出覆盖层,请求结束(无论成败)立刻撤掉。

设计点 取值 / 做法 理由
阈值 SLOW_MS 3000 ms 正常 API 请求 < 1 s;超 3 s 基本只可能是服务端在等实例。低于此值完全无感,不打扰正常使用
覆盖层文案 「实例正在启动,请稍候…(已等待 N 秒)」 带秒数让用户知道"在推进"而不是卡死
与 401 自愈共存 同一个覆盖层元素,state 分 pending / expired expired(401,随后整页 reload)是终态,不被 pending 覆盖、也不主动撤除
不误伤流式接口 只在 fetch promise 未 resolve 时计时 SSE / ReadableStream 在响应头到达时 promise 已 resolve → 计时已清 → 不会误触发。这正是本方案安全的关键
spinner 只建一次 首次创建 DOM 后仅改文案节点的 textContent 若每次重设 innerHTML,元素会被重建、animation 被打断 → 看起来像"转圈卡住不动"
覆盖范围 仍只对 /api/ 请求生效 与原有 401 逻辑一致,避免影响静态资源
失败安全 全部逻辑包在 try/catch 语义内,且 if (window.__dshRecover) return 幂等 注入脚本绝不能把页面弄坏

顺带把这段脚本从"1516 字符单行双引号字符串"改成多行模板字符串,并加注释说明两个用途 —— 这段代码后续还会改,单行形式已无法维护。


三、验证

项 结果
构建 npm run build(tsc)退出码 0 —— 证明模板字符串改写无语法错(含反引号 / ${ 的检查)
重启 drain 实例 → systemctl restart dshs → active、门户 200
注入生效 实例页 HTML 实测含 __dshRecoverMsg×2、showPending×2、实例正在启动×1、已等待×2、var SLOW_MS = 3000 ✓
原有功能未丢 __dshRecover×7、__dshAssist×6 ✓
文本片段 「实例正在启动,请稍候…」与「会话已过期(实例被回收重建)」两段文案都在 ✓

验证过程中的三个假阴性(都值得记)

取实例页 HTML 做断言时连着踩了三次坑,每次看起来都像"注入坏了":

  1. 漏了 Accept: text/html → 门户只在客户端接受 HTML 时才把上游 accept-encoding 覆盖为 identity(否则注入必然被 gzip 挡掉)→ 不带头就永远注入不上。日志里的 [inject-recovery] skip: content-encoding=gzip 是判断这个的直接线索。
  2. 漏了 cookie jar → ?token= 会 303 到 / 并下发 dsh-auth-*;不用 -c/-b jar 跟随就拿不到最终页面(size=0)。此坑档案 56 已记过一次,又踩了。
  3. 忘了 curl 不解压 → 响应被边缘 nginx 压缩,size=8684 就是 24.8 KB gzip 后的大小;grep 的是压缩字节流,自然一个关键字都搜不到。加 --compressed 即解。

结论:验证实例页 HTML 的标准姿势(三条缺一不可):

curl -s -L --compressed -c jar -b jar -b "sid=$TOKEN" \
  -H "Accept: text/html,application/xhtml+xml" https://admin.alotbuy.com/

未做的验证:真实浏览器里"点重连 → 看到动画"的端到端观感(需用户在实际断连时确认)。逻辑链路已全部验证;阈值 3 s 与实际等待时长(最长 20 s)的关系也已在代码里对齐。


四、顺带发现:web/wake.html 双端不一致

位置 大小 mtime
本机 dsh_shenxian/web/wake.html 4708 B 09-11 21:26
服务器 /opt/dshs/web/wake.html 4591 B 09-11 18:11

本机版本更新但没有推到服务器 → 过渡页的实际表现与仓库记录不一致。本次未处理(不属于当前任务,且疑似另一会话的在途改动),列入待办:核对后同步。


五、回滚

cp /opt/dsh/backups/proxy.ts.bak-20260912-wake /opt/dshs/src/supervisor/proxy.ts
cd /opt/dshs && npm run build
# drain + systemctl restart dshs

本次改动只影响注入脚本内容,不涉及路由、权限、等待时长 —— 回滚面极小。