94 lines
6.0 KiB
Markdown
94 lines
6.0 KiB
Markdown
# 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 的标准姿势**(三条缺一不可):
|
|||
|
|
> ```bash
|
|||
|
|
> 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 |
|
|||
|
|
|
|||
|
|
本机版本更新但没有推到服务器 → **过渡页的实际表现与仓库记录不一致**。本次未处理(不属于当前任务,且疑似另一会话的在途改动),**列入待办:核对后同步**。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、回滚
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
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
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
本次改动**只影响注入脚本内容**,不涉及路由、权限、等待时长 —— 回滚面极小。
|