Files
wecom_it_smart_desk/docs/04-运维文档/部署运维/DEPLOY-REQ-用户-006-智能推荐重构-v1.0.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

447 lines
16 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.
# 部署运维说明 — H5 智能推荐重构(v1.0)
> **REQ 编号**: REQ-用户-006
> **部署日期**: 2026-07-28(首次部署)
> **版本**: v1.0
> **关联文档**:
> - **PRD(冻结)**`docs/01-产品文档/05-用户端H5/PRD-REQ-用户-006-智能推荐重构-v1.0-Frozen.md`
> - **技术方案**`docs/02-技术文档/技术架构/技术方案-REQ-用户-006-智能推荐重构-v1.0.md`
> - **任务说明书**:`docs/07-项目管理/任务说明书/任务说明书-REQ-用户-006-智能推荐重构.md`
> - **原型图**`docs/01-产品文档/05-用户端H5/原型-REQ-用户-006-智能推荐重构-v1.0.html`
> - **测试用例**`docs/03-测试文档/03-功能测试用例/TC-用户-006-智能推荐重构.md`
> - **部署总手册**`docs/04-运维文档/部署运维/DEPLOY-GUIDE.md`
---
## 一、变更摘要
| 维度 | 变更 |
|------|------|
| **后端新增** | `app/services/recommend_progress_service.py`T2 webhook + 60s 轮询) |
| **后端新增** | `app/services/topic_detector.py`Jaccard 相似度) |
| **后端新增** | `app/api/recommend.py`REST API:进度查询 + webhook 接收) |
| **后端重构** | `app/services/asset_recommend_service.py`merge_recommends + 同源抑制 + 中文匹配) |
| **后端重构** | `app/config/assets.yaml`(中文 role key + 同义词表 + 排除关键词) |
| **后端扩展** | `app/services/employee_profile_service.py`DB 缓存画像降级) |
| **后端集成** | `app/tasks/h5_ai_task.py::_step_assets`3s 超时 + 降级链路) |
| **后端集成** | `app/tasks/h5_ai_task.py::_step_persist`source/trigger_timing/layer 标识) |
| **数据库** | 新增 `recommend_progress` + `recommend_event` 双表 + 索引 |
| **前端新增** | `src/frontend-h5/src/stores/recommendStore.ts`Pinia 状态机 + localStorage |
| **前端新增** | `src/frontend-h5/src/composables/useRecommendWs.ts`WS 接收处理) |
| **前端重构** | `src/frontend-h5/src/components/assistant/DynamicRecommend.vue`(无标题 + FIFO + 4 类卡片) |
| **前端适配** | `src/frontend-h5/src/components/assistant/RightPanel.vue`(引用 store |
| **nginx** | **无改动** |
---
## 二、部署前 Checklist
### 2.1 文档就位(5 件套)
- [x] PRD v1.0-Frozen(含 §4.7 冻结声明 + §13 扩展计划)
- [x] 技术方案 v1.019 章节)
- [x] 任务说明书 v1.0(8 阶段 WBS)
- [x] 原型图 v1.07 场景)
- [x] 测试用例 v1.076 条用例)
### 2.2 代码就位
- [ ] 中文路径 `D:\资料\03-项目开发\wecom_it_smart_desk\src\` 已改
- [ ] ASCII 路径 `D:\dev\wecom\src\` 已同步改(**多路径同步铁律**
- [ ] 后端 AST 静态校验通过(`python -c "import ast; ast.parse(open(f).read())"`
- [ ] 前端 `npm run build` 通过(无 TS 报错)
- [ ] 单元测试通过:`pytest src/backend/tests/services/test_asset_recommend_v2.py -v`
- [ ] 单元测试通过:`pytest src/backend/tests/services/test_recommend_progress.py -v`
- [ ] 单元测试通过:`pytest src/backend/tests/services/test_topic_detector.py -v`
- [ ] 前端单元测试通过:`vitest run recommendStore.test.ts`
### 2.3 数据库迁移准备
- [ ] Alembic 迁移脚本就位:`alembic/versions/{revision}_add_recommend_progress.py`
- [ ] Alembic 迁移脚本就位:`alembic/versions/{revision}_add_recommend_event.py`
- [ ] Init SQL 应急脚本就位(容器内 alembic 失败时备用):
- `scripts/init_recommend_tables.sql`(含 `ON CONFLICT DO NOTHING` 幂等)
- [ ] 迁移在 staging 环境 dry-run 通过
### 2.4 部署包就位
- [ ] 后端部署包:`backend-recommend-v1.0.zip`(含 17 项代码 + Init SQL
- [ ] 前端部署包:`frontend-h5-recommend-v1.0.zip`(含 dist 完整产物)
- [ ] MD5 校验通过
### 2.5 灰度策略已对齐
- [ ] 运维确认灰度名单(10 → 100 → 500 人)
- [ ] 数据监控大盘已配置(点击率、自助解决率、降级次数)
- [ ] 应急沟通群已建(产品 + 工程 + 运维)
---
## 三、部署顺序
> **铁律**:后端 → 前端 → nginx(如有改动)→ DB 迁移 → 端到端验收
### 3.1 后端部署
#### 3.1.1 压缩后端代码
```powershell
# 在中文路径下打包
Compress-Archive -Path "D:\资料\03-项目开发\wecom_it_smart_desk\src\backend\app\*" -DestinationPath "D:\资料\03-项目开发\wecom_it_smart_desk\backend-recommend-v1.0.zip" -Force
# ASCII 副本也打包(防止源路径含中文导致的 GBK 误读)
Compress-Archive -Path "D:\dev\wecom\src\backend\app\*" -DestinationPath "D:\dev\wecom\backend-recommend-v1.0.zip" -Force
```
#### 3.1.2 通过 jumpserver-V2 上传到 /tmp
```powershell
# 使用 v2_ops.pyPowerShell 工具)
& "C:\Users\simon\.workbuddy\binaries\python\versions\3.13.12\python.exe" "C:\Users\simon\.workbuddy\skills\jumpserver-V2\scripts\v2_ops.py" upload "D:\dev\wecom\backend-recommend-v1.0.zip" "backend-recommend-v1.0.zip"
```
#### 3.1.3 服务器解压到挂载源路径
```bash
# ⚠️ 必须用绝对路径(batch 模式 cd 不生效)
unzip -o /tmp/backend-recommend-v1.0.zip -d /tmp/backend-recommend-extract
# 备份旧代码
cp -r /opt/wecom-it-desk/app/app /tmp/app_bak_$(date +%Y%m%d_%H%M%S)
# 拷贝新代码(保留其他文件,只覆盖改动部分)
cp -rf /tmp/backend-recommend-extract/app/* /opt/wecom-it-desk/app/app/
```
#### 3.1.4 数据库迁移(Alembic 双轨)
**主路径**Alembic 升级
```bash
# 容器内执行 alembic
docker exec -it wecom_it_backend alembic upgrade head
```
**应急路径**:手动执行 Init SQL(如果 alembic 失败)
```bash
# 上传 Init SQL
& "C:\Users\simon\.workbuddy\binaries\python\versions\3.13.12\python.exe" "C:\Users\simon\.workbuddy\skills\jumpserver-V2\scripts\v2_ops.py" upload "scripts\init_recommend_tables.sql" "init_recommend_tables.sql"
# 服务器执行
docker exec -i wecom_it_postgres psql -U postgres -d wecom_it_desk < /tmp/init_recommend_tables.sql
```
#### 3.1.5 验证后端表创建
```bash
docker exec -i wecom_it_postgres psql -U postgres -d wecom_it_desk -c "\d recommend_progress"
docker exec -i wecom_it_postgres psql -U postgres -d wecom_it_desk -c "\d recommend_event"
```
#### 3.1.6 重启后端(必须 --workers 1
```bash
# ⚠️ ws_manager 是进程内单例,多 worker 会导致 WS 消息丢失(~50%)
docker compose restart backend
# 验证 healthy
docker ps | grep wecom_it_backend
```
#### 3.1.7 后端日志验证
```bash
# 应该看到资产推荐 + recommend_progress 启动日志
docker logs wecom_it_backend --tail 100 | grep -E "AssetRecommend|recommend_progress|Loaded.*recommend"
```
### 3.2 前端部署
#### 3.2.1 本地构建(必须在 ASCII 路径,pnpm 不卡死)
```bash
cd D:\dev\wecom\src\frontend-h5
npm run build
# 验证 dist 产物含特征字符串
grep -r "recommendStore" dist/assets/ | head -5
grep -r "layer-progress" dist/assets/ | head -5
```
#### 3.2.2 压缩 dist
```powershell
Compress-Archive -Path "D:\dev\wecom\src\frontend-h5\dist\*" -DestinationPath "D:\dev\wecom\frontend-h5-recommend-v1.0.zip" -Force
```
#### 3.2.3 上传 + 解压
```powershell
& "C:\Users\simon\.workbuddy\binaries\python\versions\3.13.12\python.exe" "C:\Users\simon\.workbuddy\skills\jumpserver-V2\scripts\v2_ops.py" upload "D:\dev\wecom\frontend-h5-recommend-v1.0.zip" "frontend-h5-recommend-v1.0.zip"
```
```bash
# 服务器解压
unzip -o /tmp/frontend-h5-recommend-v1.0.zip -d /tmp/frontend-h5-extract
rm -rf /opt/wecom-it-desk/frontend-h5/dist
mv /tmp/frontend-h5-extract /opt/wecom-it-desk/frontend-h5/dist
```
#### 3.2.4 重启 nginx(必须!bind mount 不会自动刷新新文件)
```bash
docker restart wecom_it_nginx
```
### 3.3 nginx 配置(无改动)
本次部署 **无 nginx 配置变更**
---
## 四、端到端验证
### 4.1 API 验证
```bash
# 1. 后端健康检查
curl -I http://localhost:8000/health
# 2. 推荐进度 API(如果有进行中的审批)
curl -H 'X-Forwarded-For: 10.240.1.100' http://localhost:8000/recommend/progress/test_approval_id
# 3. WS 端口可达
curl -I http://localhost:8000/ws/employee/{employee_id}
```
### 4.2 数据库验证
```bash
# 1. 表存在
docker exec -i wecom_it_postgres psql -U postgres -d wecom_it_desk -c "\dt recommend_*"
# 2. 索引存在
docker exec -i wecom_it_postgres psql -U postgres -d wecom_it_desk -c "\di idx_recommend_*"
# 3. recommend_progress 表能 INSERT
docker exec -i wecom_it_postgres psql -U postgres -d wecom_it_desk -c "INSERT INTO recommend_progress (recommend_id, employee_id, approval_id, approval_type, status) VALUES ('rec_test', 'test_emp', 'test_appr', 'vpn_access', 'pending') RETURNING id;"
```
### 4.3 前端验证(agent-browser 自动 + 用户人工)
```bash
# agent-browser 自动验证
# 1. 打开 H5 员工端
# 2. 进会话但不发言 → 验证右侧栏完全空白(决策 ① A)
# 3. 发"VPN 申请" → 验证左侧气泡 + 右侧栏操作卡
# 4. 切换话题 → 验证 L1 清空
# 5. 关闭浏览器 → 重新打开 → 验证 L2/L3/progress 持久化
```
### 4.4 用户人工验证(必做,企微扫码限制)
- [ ] 登录 H5 员工端(需企微扫码)
- [ ] 进入新会话,不发言,观察右侧栏空白
- [ ] 发"VPN 怎么连",观察左侧气泡 + 右侧栏操作卡
- [ ] 切换话题到"会议室",观察 L1 清空
- [ ] 触发审批,观察进度卡回流
- [ ] 关闭浏览器重新打开,观察持久化卡
---
## 五、灰度策略
### 5.1 4 阶段灰度
| 阶段 | 规模 | 持续时间 | 通过条件 | 不达标处理 |
|------|------|---------|---------|----------|
| **1%** | 10 人(运维 + 产品 + 工程) | 1 天 | 无 P0/P1 错误 | 立即回滚 |
| **10%** | 100 人(早期种子用户) | 2 天 | 点击率 > 5%(无 P0/P1 错误) | 暂停灰度排查 |
| **50%** | 500 人(半个部门) | 3 天 | 点击率 > 10% | 暂停灰度排查 |
| **100%** | 全量(约 7000 人) | 长期 | 点击率 > 15%,自助解决率 > 10% | 长期监控优化 |
### 5.2 数据埋点验证(关键指标)
通过 `recommend_event` 表统计:
```sql
-- 各 layer 推荐曝光数
SELECT layer, source, COUNT(*) AS exposure_count
FROM recommend_event
WHERE event_type = 'shown'
AND created_at > NOW() - INTERVAL '24 hours'
GROUP BY layer, source;
-- 各 layer 推荐点击数
SELECT layer, source, COUNT(*) AS click_count
FROM recommend_event
WHERE event_type = 'clicked'
AND created_at > NOW() - INTERVAL '24 hours'
GROUP BY layer, source;
-- 点击率
SELECT
shown.layer,
shown.source,
shown.exposure_count,
COALESCE(clicked.click_count, 0) AS click_count,
ROUND(100.0 * COALESCE(clicked.click_count, 0) / shown.exposure_count, 2) AS click_rate
FROM (
SELECT layer, source, COUNT(*) AS exposure_count
FROM recommend_event
WHERE event_type = 'shown'
GROUP BY layer, source
) shown
LEFT JOIN (
SELECT layer, source, COUNT(*) AS click_count
FROM recommend_event
WHERE event_type = 'clicked'
GROUP BY layer, source
) clicked ON shown.layer = clicked.layer AND shown.source = clicked.source;
```
### 5.3 降级监控
```sql
-- 降级触发次数(C → D / 全部失败)
SELECT
DATE(created_at) AS date,
extra->>'degradation_path' AS degradation,
COUNT(*) AS count
FROM recommend_event
WHERE event_type = 'degraded'
AND created_at > NOW() - INTERVAL '7 days'
GROUP BY date, degradation
ORDER BY date DESC;
```
---
## 六、回滚预案
### 6.1 触发条件(任一)
- P0/P1 错误率 > 5%
- 推荐卡片渲染失败 > 2%
- WS 推送延迟 > 5s
- 后端 healthy 状态丢失
- 推荐卡数量异常(同一用户 > 5 张)
### 6.2 回滚步骤(< 30 分钟)
#### Step 1: 前端回滚(5 分钟)
```bash
# 备份当前 dist
mv /opt/wecom-it-desk/frontend-h5/dist /opt/wecom-it-desk/frontend-h5/dist_bak_$(date +%Y%m%d_%H%M%S)
# 恢复备份的 dist
mv /opt/wecom-it-desk/frontend-h5/dist_rollback /opt/wecom-it-desk/frontend-h5/dist
# 重启 nginx
docker restart wecom_it_nginx
# 验证
curl -I http://localhost/itdesk/
```
#### Step 2: 后端回滚(10 分钟)
```bash
# 停止后端
docker compose stop backend
# 恢复代码
rm -rf /opt/wecom-it-desk/app/app
mv /opt/wecom-it-desk/app_bak_$(ls -t /opt/wecom-it-desk/app_bak_* | head -1 | sed 's/.*app_bak_/app_bak_/') /opt/wecom-it-desk/app/app
# 重启后端
docker compose up -d backend
# 验证
curl -I http://localhost:8000/health
```
#### Step 3: 数据库回滚(5 分钟,极端情况)
```bash
# Alembic 降级
docker exec -it wecom_it_backend alembic downgrade -1
# 或手动 DROP 新增双表
docker exec -i wecom_it_postgres psql -U postgres -d wecom_it_desk -c "DROP TABLE IF EXISTS recommend_progress CASCADE; DROP TABLE IF EXISTS recommend_event CASCADE;"
```
#### Step 4: 通知(5 分钟)
- 群发"已回滚至 v2.3 现状"通知到产品 + 工程 + 客服群
- 收集失败原因,写入 BUG 单
- 排查修复后重新走灰度流程
### 6.3 回滚时间预算
| 阶段 | 时间 |
|------|------|
| Step 1 前端 | 5 分钟 |
| Step 2 后端 | 10 分钟 |
| Step 3 DB | 5 分钟 |
| Step 4 通知 | 5 分钟 |
| **合计** | **< 30 分钟** |
---
## 七、风险与边界
### 7.1 部署期风险
| 风险 | 缓解措施 |
|------|---------|
| Alembic 迁移失败 | Init SQL 应急脚本 |
| models/__init__.py 整体覆盖丢失其他模型 | 字符串 replace 比 sed/awk 稳(详见任务说明书 §1.4 关键决策点) |
| 前端 dist build 触发 safe-delete hook | Rename-Item + .NET Delete 绕路 |
| 后端 --workers 不为 1 | docker-compose.yml 校验,强制 1 worker |
| 中文字符乱码 | 容器内 locale 检查 + UTF-8 编码 |
| 蓝绿共用 PG/Redis 干扰 | 仅升级不降级,DB migration 谨慎执行 |
### 7.2 运行期风险
| 风险 | 等级 | 缓解措施 |
|------|------|---------|
| Dify 升级 action 结构变化 | 中 | 兼容旧字段解析 |
| 画像 API 长时间不可用 | 中 | DB 缓存画像降级(来源 C 改造) |
| localStorage 配额超限(5MB | 低 | 仅持久 L2/L3/progressLRU 淘汰 |
| 企微 webhook 推送失败 | 中 | 60s 轮询兜底 |
| 灰度期间指标不达标 | 中 | 每阶段不达标暂停 |
---
## 八、常见问题(FAQ
### Q1: Alembic 容器内不可用怎么办?
**A**: 使用 Init SQL 应急脚本(详见 §3.1.4)。
### Q2: ws_manager 多 worker 导致 WS 消息丢失怎么办?
**A**: 后端必须 `--workers 1`docker-compose.yml 已强制。
### Q3: 中文路径下 pnpm install 卡死怎么办?
**A**: 在 ASCII 路径 `D:\dev\wecom\` 下执行 `npm run build`(详见多路径同步铁律)。
### Q4: 前端 build 触发 safe-delete hook 怎么办?
**A**: 使用 `Rename-Item dist __dist_movetmp`(同目录)→ vite 跳过 fs.rmSync → build 完 .NET Delete(详见 memory 跨项目铁律)。
### Q5: 模型注册丢失怎么办?
**A**: 不能整体覆盖 `models/__init__.py`,需用 Python 脚本字符串 replace(详见任务说明书 §1.4)。
### Q6: 容器内 alembic.ini 路径不对怎么办?
**A**: 使用绝对路径 `docker exec -it wecom_it_backend alembic -c /app/alembic.ini upgrade head`
### Q7: psftp 上传大于 100KB 文件慢怎么办?
**A**: 已优化,使用 `v2_ops.py upload` 子命令,自动 md5 校验 + 断点续传。
### Q8: 灰度名单怎么选?
**A**: 1% 选运维 + 产品 + 工程核心成员;10% 选早期种子用户(高频用户);50% 选半个部门(覆盖不同角色);100% 全量。
---
## 九、变更日志
| 版本 | 日期 | 变更内容 | 作者 |
|------|------|---------|------|
| v1.0 | 2026-07-28 19:36 | 初版:基于 PRD v1.0-Frozen + 技术方案 v1.0 + 任务说明书 v1.0,给出完整部署 SOP(5 件套齐全):后端 7 步 + 前端 4 步 + DB 迁移双轨 + 端到端验证 + 4 阶段灰度 + 30 分钟回滚 + 8 FAQ | Duckula + 宋献 |