Files
wecom_it_smart_desk/docs/03-测试文档/02-E2E测试/方案A-消息发送延时-E2E验证报告-20260708.md
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
2026-08-07 22:31:32 +08:00

147 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 方案A 消息发送延时改造 — E2E 浏览器验证报告
> 验证日期:2026-07-08
> 验证方式:**真实浏览器端到端实测**(系统 Chrome 驱动 H5Playwright Python
> 验证目标:员工端发送消息**瞬时返回不阻塞 UI**AI 回复经 **WebSocket 打字机流式**推送(方案A 核心机制)
> 结论:✅ **通过** — 发送即时、AI 经 WS 流式渲染、Duckula 头像/名称正常
---
## 1. 验真目标(方案A 是什么)
方案A 的核心改造:
- 员工发送消息 → 后端 `POST /h5/conversations/current/messages` **立即返回**`ai_reply: null`,消息落库 `status: sent`),**不再同步等待 AI**
- AI 回复由 `asyncio.create_task(process_h5_ai_reply)` 异步生成,逐 chunk 通过 WebSocket 广播 `ai_reply_chunk` / `ai_reply` 给 H5
- H5 监听 WS 帧,做**打字机流式渲染**;WS 断连时降级为 3s 轮询
本次 E2E 要证明的是:**UI 不阻塞(发送瞬时)** + **AI 回复走 WS 流式(非轮询兜底)**
---
## 2. 测试环境
| 组件 | 版本/地址 | 说明 |
|------|-----------|------|
| 前端 H5 | `localhost:5174/itdesk/`Vite dev | `docker-compose.dev.yml` frontend-h5 |
| 后端 | `localhost:8000`FastAPI, `--reload` | dev 栈,单 worker |
| 浏览器 | 系统 Chrome 150headless, Playwright 驱动) | 走系统 Chrome,非下载 Chromium |
| AI | Dify `app-UaTWYdBSwN6VktKQlbh5YN5H`(dev 临时切的可流式 app | 用于验证 typewriter 主路径 |
| 登录 | Mock 登录 `POST /h5/mock-login` | dev 模式免企微 OAuth |
---
## 3. 测试步骤
1. 打开 H5 登录页 `http://localhost:5174/itdesk/`
2. Mock 登录(employee_id=`E2E_BROWSER`employee_name=`浏览器实测`
3. 在输入框发送「打印机无法连接网络怎么办」
4. **计时发送点击返回**(验证 UI 不阻塞)
5. 监听页面 WebSocket 接收帧,轮询 `.chat-panel__messages` 文本,捕获:
- Duckula 头像/名称是否渲染
- AI 回复是否经 `ai_reply_chunk` 流式到达(打字机证据)
- 最终 AI 文本长度与真实内容
---
## 4. 验证结果(来自 `e2e-screenshots/e2e_result.json`
| 指标 | 值 | 判定 |
|------|-----|------|
| 发送点击耗时 `send_click_s` | **1.257s** | ✅ 瞬时(< 3s 阈值) |
| 发送 UI 不阻塞 `send_ui_instant` | **true** | ✅ |
| AI 首屏渲染 `ai_first_render_s` | **2.27s** | ✅ 发送后 2.3s 出现 |
| WS 收到 `ai_reply_chunk` 帧数 | **343** | ✅ 流式打字机路径成立 |
| 打字机已证实 `typewriter_proven` | **true** | ✅ 非轮询兜底 |
| 最终消息文本长度 `final_text_len` | **1469** 字符 | ✅ 完整真实 AI 内容 |
| 含 Duckula 名称 `final_has_duckula` | **true** | ✅ 头像/名称渲染 |
| 含真实内容 `final_has_real_content` | **true** | ✅("问题描述/打印机/网络" |
| 文本增长 `len_grew` | **true** | ✅ 流式累积 |
| WS 打开总数 `ws_open_total` | 2(含 1 个 Vite HMR | ✅ 无握手失败 |
**截图证据**(真实浏览器会话):
- `e2e-screenshots/01-login.png` — 登录页
- `e2e-screenshots/02-chat-after-login.png` — 登录后进会话
- `e2e-screenshots/03-after-send-instant.png` — 发送后即时(用户气泡出现,AI 占位)
- `e2e-screenshots/04-typewriter-mid.png` — 打字机进行中
- `e2e-screenshots/05-final.png` — AI 完整回复(Duckula 头像 + 1469 字)
**结论:方案A 在真实浏览器中验证通过** — 发送不阻塞 UIAI 经 WebSocket 流式打字机渲染。
---
## 5. 验证过程中发现并修复的 4 个阻断问题
> 这些不是方案A 本身的缺陷,而是「dev 容器化栈跑真实浏览器」暴露的环境/代码阻断。其中第 4 条是**真实后端 bug**,会影响生产 WS。
### ① H5 应用无法挂载(Vite 资源解析失败)
- **现象**:打开 `/itdesk/` 只剩骨架屏,`<vite-error-overlay>` 报错 `Failed to resolve import "/duckula.webp"`
- **根因**`MessageBubble.vue` / `MessageItem.vue` 引用 `<img src="/duckula.webp">`,该资源在**容器镜像烘焙时尚未加入 `public/`**,而 dev compose 只挂载了 `src` 没挂载 `public/`
- **修复**`docker-compose.dev.yml` frontend-h5 增加 `- ./frontend-h5/public:/app/public`(与 `src` 一致的热更新挂载)
### ② Mock 登录返回 500Vite 代理目标错误)
- **现象**:浏览器点登录 → 后端 500;但纯 Python 直连 `:8000` 却 200
- **根因**`vite.config.ts` 代理 `/api``target: 'http://localhost:8000'`,但**容器内 localhost 不是后端**(是容器自己)→ ECONNREFUSED → Vite 返 500
- **修复**:代理目标改为可配置 `process.env.VITE_PROXY_TARGET || 'http://localhost:8000'`dev compose 注入 `VITE_PROXY_TARGET=http://backend:8000`(compose 服务名)。本地非 Docker 开发仍走默认 `localhost:8000`
### ③ CSP 阻断 dev WebSocket
- **现象**`Connecting to 'ws://localhost:8000/ws/h5/...' violates CSP connect-src`(握手前被拦)
- **根因**`index.html` CSP `connect-src` 仅允许 `ws://localhost`(默认端口 80),但 dev WS 用显式端口 **8000**,CSP 按端口精确匹配 → 视为不同源被拒
- **修复**`index.html` CSP `connect-src` 增加 `ws://localhost:8000 ws://127.0.0.1:8000`
### ④ ⚠️ WebSocket 握手失败(真实后端 bug,生产相关)
- **现象**:CSP 放开后握手仍失败 `Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received`
- **根因**:浏览器用子协议 `Sec-WebSocket-Protocol: bearer.{token}` 传递 token**后端 `ws_manager.connect()` / `connect_employee()` 读取该头做认证,但 `websocket.accept()` 未回显子协议** → 浏览器严格拒绝握手
- **影响**:不仅 dev,生产环境坐席端/员工端 WS 同样会握手失败(之前被 3s 轮询兜底**掩盖**,导致 AI 回复实际走轮询而非流畅打字机)
- **修复**`backend/app/services/ws_manager.py``connect` / `connect_employee` 增加可选 `subprotocol` 参数,`accept(subprotocol=subprotocol if subprotocol else None)``backend/app/api/ws.py` 两处调用传入 `subprotocol`
- **验证**:修复后 WS 握手成功,`ai_reply_chunk` 343 帧正常流式到达
---
## 6. 遗留(非阻断)事项
- **CSP `font-src` 缺口**:控制台仍有 `data:font/woff2``at.alicdn.com` 字体被 CSP 拦截(仅影响字体显示,不影响功能)。如需消除,可在 CSP 增加 `font-src 'self' data: https://at.alicdn.com`
- **404**:一个资源 404(疑似 favicon 或字体文件),无害。
- **dev Dify key 临时切换**`docker-compose.dev.yml` 中 Dify key 为验证 typewriter 主路径临时切到可流式 app,已在注释中标注。**✅ 已还原 (2026-07-09) 回 `app-J3s8sHarZQ2SCaNF3xCppliL`dev 该 app 经 dify2openai 返回空 SSE,本地 typewriter 主路径需改用 backend/.env 工作 app 才能看到流式效果)。**
---
## 7. 结论
**方案A(员工端消息发送即时返回 + AI 经 WS 打字机流式推送)在真实浏览器端到端验证通过**
发送 1.26s 即时返回、AI 回复 2.3s 起经 343 个 WS chunk 流式渲染、Duckula 头像与完整 1469 字回复正常显示。
同时修复了 1 个真实 WebSocket 握手 bug(生产相关,此前被轮询兜底掩盖)及 3 个 dev 容器化环境阻断,使「Docker dev 栈 + 真实浏览器 E2E」链路从此可用。
---
## 8. 生产部署验证 (2026-07-09)
> WS 子协议修复(第④条 bug)按既定建议合入 main 并部署到生产服务器 `itsupport.servyou.com.cn` (10.90.5.110)。
### 8.1 部署方式
生产后端容器 `wecom_it_backend` 代码**烘焙进镜像**(唯一挂载是 `uploads`),无源码卷挂载。因此采用:
1. 将 2 个修复文件打包上传至生产服务器 `/tmp/``v2_ops.py upload`
2. `docker cp` 进运行容器:`/app/app/services/ws_manager.py``/app/app/api/ws.py`
3. 同步更新宿主机源码 `/opt/wecom-it-desk/backend/app/...`(供后续镜像重建)
4. `docker restart wecom_it_backend`
5. 原文件备份于 `/tmp/ws_manager.py.bak``/tmp/ws.py.bak`(回滚点)
### 8.2 部署后验证(真实证据)
| 检查项 | 结果 |
|--------|------|
| 容器状态 | `Up (healthy)` — 重启后健康 |
| 修复代码就位 | `ws_manager.py:76``:185` 均为 `accept(subprotocol=subprotocol if subprotocol else None)` |
| **真实 WS 连接(重启后)** | 日志显示 `WebSocket /ws/h5/tangzhenzhen [accepted]` + `H5员工 WebSocket 连接建立: employee_id=tangzhenzhen`,以及 `WebSocket /ws/sxn [accepted]` + 坐席连接建立 |
| 后端服务 | `GET /conversations ... 200 OK`(正常服务流量) |
**关键证据**:上述 H5 员工(tangzhenzhen)/坐席(sxn) 连接时间戳(01:15:03 / 01:15:06)均在容器重启(09:14:26)**之后**。用旧 bug 代码,浏览器会因「未回显 subprotocol」直接拒绝握手,连接根本到不了 `[accepted]`。现在成功建立 = **修复在生产真实生效**(旧代码下这些连接本应失败)。
### 8.3 Git 合并
- 修复 commit `bacd34c`feature/message-reliability
- cherry-pick → `6db1c0e`main),**已推送 `origin/main`**`6277db3..6db1c0e`
- 生产部署与主干合并相互独立:部署用 `docker cp`(即时生效、重启保留),主干合并保证代码源一致