Files
wecom_it_smart_desk/docs/02-技术文档/技术架构/技术方案-REQ-通用-005-选项选择持久化-v1.1-方案设计.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

143 lines
12 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.
# 技术方案(设计)— 选项选择持久化 v1.1
> **REQ 编号**: REQ-通用-005
> **版本**: v1.1 — 方案设计稿
> **日期**: 2026-08-02
> **作者**: 高见远(架构师 · Bob)
> **状态**: ⏳ 待用户决策(不进开发,先评审修复路径)
> **关联**:
> - PRD v1.0`docs/01-产品文档/00-产品规划/PRD-REQ-通用-005-选项选择持久化-v1.0.md`(已批准,不改)
> - PRD v1.1 增量草案:待 PM(许清楚)出
> - 技术方案 v1.0`docs/02-技术文档/技术架构/技术方案-REQ-通用-005-选项选择持久化-v1.0.md`(实施基础,已部署)
> - 代码真相:`src/backend/app/api/ws.py` + `src/backend/app/api/h5.py` + `src/backend/app/tasks/h5_ai_task.py` + `src/frontend-h5/src/stores/conversation.ts` + `src/frontend-h5/src/composables/useH5WebSocket.ts` + `src/frontend-h5/src/components/chat/MessageBubble.vue`
> - Dify v3 DRAFT`docs/02-技术文档/实现配置/dify_dsl/itdesk_main_v3_feedback-vars_DRAFT.yml`(用户尚未导入)
---
## §1 技术现状评估
v1.0 已部署生产。整体架构稳定:后端 `_handle_option_select`ws.py:254-448)走 advisory lock + 5 秒幂等 + COMMIT → 广播 → Dify 的 5 阶段管线;H5 `sendOptionSelect`conversation.ts:1621-1737)补齐 6 字段、UUID 守卫、5 条复用判定;REST 历史接口(messages.py:158-173)已对 `option_select` 做 mask 并去除审计原值字段;坐席 `MessageBubble.vue` 徽标已生效。本次用户反馈的 4 个问题中:
- **Bug 4(员工端 AI 回答延迟显示,P0)** — 后端 ws 广播与 Dify 返回两通道时序错位 + H5 `handleAiReply` 依赖 `currentConversation.value.conversation_id` 精确匹配,两者叠加在弱网下导致 AI 回复丢失。属于 v1.0 边界场景(弱网 / 会话切换竞态),未单测覆盖。
- **Bug 5(选选项后时序错位,P1)** — 与 Bug 4 同源,但用户视角是"思考占位 + AI 答案 + 上一选项同时出现"。根因是 v1.0 把"上一选项也保留在列表"作为 PRD §6 的撤回语义,但 UI 没有视觉分组,3 个气泡挤在一起显得错位。
- **Bug 6(刷新后选择消失,待确认)** — `selectedOptionLabels` 是 store 内 refconversation.ts:1612),无持久化;`ai_structured.options` 按钮的"已选"状态依赖该 ref 计算(MessageBubble.vue:332-343),刷新后 ref 重置为 `[]`,所有选项按钮均不再显示 ✓,但实际 `option_select` 消息仍在历史中可查(masked REST 已包含)。属实现缺陷,非 PRD 漏写。
- **Req 7(实时同步坐席端,用户强调)** — 已实现(ws.py:407 `await ws_manager.broadcast(...)`),仅缺 E2E 时延指标验证与监控埋点。
依赖关系:B4 / B5 同根(H5 收 ai_reply 的时序),建议同工单修复;B6 独立(H5 前端状态派生);Req 7 仅需观测与监控。
---
## §2 4 个问题的修复路径
| 编号 | 真实根因(基于代码) | 修复位置 | 行数估算 | 阻塞 |
|---|---|---|---|---|
| Bug 4 | 后端 `_handle_option_select` 仅广播 `new_message` 给**坐席**ws.py:407 `ws_manager.broadcast`),员工端要等 Dify 回包后的 `ai_reply`h5_ai_task.py:443 `broadcast_to_employees`);若 Dify 超时或 WS 抖动,员工只看到 `ai_thinking` 占位。叠加 H5 `handleAiReply`conversation.ts:1395)的 `conversation_id` 严格相等校验 — 任何切换 / 重复挂载场景下被静默丢弃 | (a) ws.py:407 增加 `broadcast_to_employees` 同步推送自己的 `option_select` 给员工;(b) conversation.ts:1395 改为软校验 + 一次性提示;(c) h5_ai_task.py:443 增加 `still_thinking` 时主动 `broadcast_to_employees` 一个 `option_select` 确认事件(兜底) | < 50 | 无 |
| Bug 5 | UI 没有"同一 question 的 option_select + 后续 AI reply"视觉分组,3 个气泡线性排列;上一选项的 ✓ 气泡和新的 AI 答案同时显示在列表里。`ai_thinking` 占位气泡渲染时切到了"仍在思考",但前一次的 option_select 气泡没折叠 | (a) MessageBubble.vue:174 在 `option_select` 分支上加 `data-question-id` 属性;(b) ChatPanel.vue 在消息列表中加一个 computed:当连续 N 条 `option_select + ai_text` 来自同一 `question_id`,对 option_select 加 `collapsed` 灰底 class | < 50 | 无 |
| Bug 6 | `selectedOptionLabels`conversation.ts:1612)纯内存 ref,刷新即空;`isOptionSelected`MessageBubble.vue:332)依赖它判定 ai_structured 按钮的 ✓ 样式。`option_select` 消息本身在 messages 里(masked REST 完整返回),但用户视觉上"按钮未选中" | (a) `isOptionSelected` 改为派生自 `messages.filter(m => m.msg_type==='option_select')`,不依赖内存 ref(b) `selectedOptionLabels` 标记 deprecated,写注释保留仅作兼容 | < 30 | 无 |
| Req 7 | **已实现**:ws.py:407 同步广播给所有坐席(commit 后立即触发,不等 Dify)。缺的是时延监控埋点与端到端可观测性 | (a) BackendObserver 增加 `option_select_broadcast_latency_ms` 指标(落库 commit 时间戳 vs 广播发送时间戳);(b) H5 `useH5WebSocket.ts` 上报端到端时延(WS 收到 `option_select` 时间 vs 点击时间) | < 30 | 无 |
> **关于阻塞列**:经分析本次 4 个问题全部为局部修复,无跨模块强依赖;后端 ws 链路经 v1.0 实战验证稳定;Dify 未导入不影响修复(H5 视角独立)。
---
## §3 实施建议(按优先级)
**P0 必做(影响主链路 / 审计可追溯)**
- **Bug 4** — 员工端 AI 回答延迟。涉及 ws.py:407、conversation.ts:1395、h5_ai_task.py:443 三处小改;建议同工单合入以保证时序回归一次到位。
- **Bug 6** — 刷新后选择消失。改 1 个文件(MessageBubble.vue:332)即可,技术风险极低;建议先于 Bug 4 完成以便坐席侧 + H5 侧 QA 同步测试。
**P1 建议(体验问题)**
- **Bug 5** — 时序错位。需要 ChatPanel.vue 加计算 + MessageBubble.vue 加 attr,影响视觉但不动数据,建议作为视觉打磨任务排在下个 Sprint。
- **Req 7** — 实时同步。技术已实现,建议把"验证 + 监控"作为快速胜利任务(< 1 人日):写一个 5 分钟观测脚本(puppeteer 模拟点击 + ws 抓包)出时延报告。
**P2 可选**
- 把 H5 store 的 `selectedOptionLabels` 全量替换为派生计算(v1.0 已落地 P0-C 的 UUID 复用守卫,逻辑可平滑迁移)
---
## §4 实施步骤(粗粒度,不写代码)
```
Step 1: 修复 Bug 6(前置 / 解锁 QA)
Owner: 工程师(寇豆码)
Files: src/frontend-h5/src/components/chat/MessageBubble.vue:332-343
思路: 把 isOptionSelected 改为从 store.messages 派生(过滤 msg_type==='option_select'
用 extra_data.question_id/option_id 匹配),不再依赖内存 ref
验证: H5 刷新页面 → ai_structured 按钮仍带 ✓ 样式(即使 selectedOptionLabels 为空)
强校验: dist 必须包含派生计算的特征字符串;旧 selectedOptionLabels 引用计数清零
Step 2: 修复 Bug 4H5 收 ai_reply 的时序)
Owner: 工程师(寇豆码)
Files: src/backend/app/api/ws.py:407(加 broadcast_to_employees
src/frontend-h5/src/stores/conversation.ts:1395(软校验 + 一次性提示)
src/backend/app/tasks/h5_ai_task.py:443(兜底 still_thinking 时补 option_select 确认)
思路: a) 员工选选项后立即看到自己的"✓ 选项"气泡(不等 Dify
b) AI 答案到达时即使 conversation_id 轻微偏差也能落入 UI(但记录 warning)
验证: 模拟弱网(用 puppeteer throttle 到 1Mbps + 100ms RTT)→ 选项后 100ms 内看到 ✓
模拟会话切换竞态 → 切换前点选的选项在新会话仍可见
强校验: dist 包含 broadcast_to_employees 与软校验 fallback 关键字
Step 3: 修复 Bug 5(视觉分组)
Owner: 工程师(寇豆码)
Files: src/frontend-h5/src/components/chat/MessageBubble.vue:174
src/frontend-h5/src/components/chat/ChatPanel.vue:96-112
思路: option_select 加 data-question-idChatPanel 用 computed 把"同一 question 的
option_select + 紧随其后的 ai_text"折叠为一个视觉组;折叠展开可点击
验证: 选同一 question 不同 option → 上次选项灰底显示,新选项高亮,新 AI 答案紧随
强校验: dist 包含折叠 class 关键字
Step 4: 验证 Req 7(实时同步)
Owner: 工程师 + QA(寇豆码 + QA
工具: puppeteer 脚本(src/frontend-h5/scripts/measure-option-latency.mjs
+ BackendObserver 埋点(option_select_broadcast_latency_ms
步骤: a) 启动两个浏览器上下文(员工 + 坐席)
b) 员工点击选项,记录 t0(点击)→ t1(员工端看到 ✓)→ t2(坐席端看到 ✓)
c) 输出 p50/p95 时延 + 告警阈值(>1s 触发 BackendObserver warning
验证: t2 - t0 < 100ms(坐席感知);< 50ms 为优
强校验: 抓 ws 帧确认 type=new_message 且 msg_type=option_select
Step 5: 跨端回归 + 发布
Owner: 全员
范围: H5 dist + 后端 container 重启(仍 --workers 1
验证: 5 场景 E2E(在线点选 / 重连 REST / 弱网重试 / 同题重选 / 跨会话切换)
```
---
## §5 风险与依赖
| 风险 | 等级 | 说明 | 缓解 |
|---|:--:|---|---|
| **Dify v3 仍未导入** | 中 | 若 Dify 没导入 v3 feedback vars,员工端 AI 答案内容不变(B4/B5 修复后用户可能感觉不到差异)。但 Bug 6(刷新后 ✓)与 Dify 无关 | 强烈建议 PM 与用户同步 Dify 导入时间表;若 1 周内不导入,先修 Bug 6 + Req 7(无 Dify 依赖) |
| **后端 ws 单 worker** | 低 | v1.0 已固定 `--workers 1`;本次修复不引入跨进程广播 | 保持单 worker;如未来扩容,先接 Redis Pub/Sub |
| **H5 dist 强校验铁律** | 低 | 3 次踩坑经验(v0.5 缓存路径 / v1.0 P0-B 字符串匹配 / v1.0 P0-C UUID 守卫) | 每次修复后强校验 4 个证据链:dist 关键字 + 产物 hash + 部署 HTTP 200 + 浏览器实测 |
| **H5 `processedMessageIds` 误伤** | 中 | 若 `handleAiReply` 因去重而丢弃,可能与 Bug 4 同症 | 修复 B4 时同步核对该集合的清空时机(特别在 conversation 切换时) |
| **坐席端 regress** | 低 | 坐席 MessageBubble 已有徽标逻辑(v1.0 已部署),本次不动坐席代码 | 仅做回归测试覆盖 AC-03 / AC-04 |
---
## §6 待用户确认的 5 个问题
1. **修复顺序是否同意 P0 → P1 → 验证 Req 7** 推荐先 Bug 6(最小风险、解锁 QA)→ Bug 4(最大价值)→ Bug 5(视觉打磨)→ Req 7 验证 + 监控。
2. **是否需要等 Dify v3 导入后再开始 Bug 4 修复?** 若用户能在 1 周内导入 Dify,可推迟 B4;若超过 1 周,建议先发 B6 + Req 7。
3. **Req 7 实时同步指标是否同意 < 100ms(端到端,员工→坐席)?** v1.0 后端 ws commit 后立即广播,单 worker 下 50ms 内可达;< 100ms 是合理预算。若需要 < 50ms 需引入 Redis Pub/Sub(不在本次范围)。
4. **是否需要拉 UX 重新设计"上一选项 + 新 AI 答案"的视觉分组?** Bug 5 修复方案 1 是程序计算分组(成本低、行为可解释),方案 2 是 UX 重新设计(成本高、效果更好)。建议先用方案 1 兜底,再决定是否启动 UX。
5. **是否需要扩展 BackendObserver 监控实时性(option_select_broadcast_latency_ms + option_select_end_to_end_latency_ms)?** 强烈推荐:缺失监控是本次 Bug 4 难以快速定位的主因。建议作为 Step 4 的强制交付物。
---
## §7 交付物清单(待 PM / 工程师同步)
- [ ] PRD v1.1 增量草案(PM 许清楚负责)
- [ ] 本方案设计稿经用户确认后,转为正式技术方案 v1.1(包含完整任务分解)
- [ ] Dify v3 导入时间表(用户决策)
- [ ] 测试用例 v1.0 增量(QA 编制,覆盖 B4/B5/B6 + Req 7
- [ ] BackendObserver 指标名 + 告警阈值(运维确认)
---
## §8 变更记录
| 版本 | 日期 | 变更内容 | 变更人 |
|---|---|---|---|
| v1.1 设计稿 | 2026-08-02 | 基于已部署代码 + 用户 2026-08-02 17:45 反馈,对 4 个问题逐一给出根因 + 修复路径;不写完整方案,待用户决策 | 高见远、宋献 |