Files
wecom_it_smart_desk/docs/项目经验与教训-可复用规则手册.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

170 lines
18 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.
# 项目经验与教训 · 可复用规则手册
> **用途**:作为每个项目 / 任务启动时的预设提示(prompt)导入。
> **结构**:第一部分 = 跨项目通用铁律(任何开发任务适用,建议原样复用);第二部分 = 本项目(IT智能服务台)专属经验(仅本项目相关,其他项目可整段删除)。
> **修订依据**:本版已通读 `docs/09-部署运维`(含补充部署指南/OTP/NAS/本地AI/一键包/toolbox)、`docs/10-项目管理`、`docs/06-测试质量`02-E2E/03-功能用例/04-版本测试报告)、`docs/08-安全审计`、`docs/03-技术架构`、`docs/02-产品需求` 并补全缺口(11-历史归档为已取代旧版,跳过)。
> **复用建议**:导入其他项目时,保留「第一部分」,删除「第二部分」,即可大幅降低 token 消耗。
## 给 AI 的执行约定
- 第一部分为**硬规则**,凡涉及「修复 / 部署 / 声明完成 / 删除代码 / 改配置 / 上线」动作,先对照对应条目。
- 宣布"已修复 / 已完成"前,必须能给出**真实可验证证据**(见 A 类),不得仅凭日志无报错或"我认为"。
- 本项目相关规则以 `docs/09-部署运维``docs/10-项目管理` 为准;修订本手册须通读这两类文档。
- 第二部分仅在当前项目生效,提供上下文,不改变第一部分的约束。
---
## 第一部分 · 跨项目通用开发铁律
### A. 验证与修复(最高优先级)
1. 宣布"已修复 / 已完成"前必须**端到端验证**并给真实证据;不得仅因日志无报错或主观判断。→ 教训:`[object Object]` 事故。
2. **容器内测试通过 ≠ 外部用户可用**;必须走完整链路(Mock 登录 → 发消息 → 查回复)。
3. 验证按分层选工具:前端 / 登录 / 交互用真实浏览器截图 + console / networkAPI 用 curl 真实返回;禁只信 `docker logs`
4. 关键字段 / 配置修改后立即 **Grep / Read 验证是否真生效**——Edit 大批量可能静默失败。→ 教训:v3.0 的 18 字段未真正写入。
5. 删除死代码 / 孤儿组件 / 死路由前,必须**前后端双向核对**消息类型与调用关系,避免"前端等 A 后端发 B"断链。→ 教训:结单确认断链。
6. **"验真"机制**:标注完成的功能必须真实验证,防风注释掉的路由、`[待AI生成]` 占位、空实现伪装完成。
7. AI 生成内容须**断言非占位符 + 低置信度门控**:低于阈值标记失败、不写脏数据。
### B. 测试
8. e2e 分页 / poll **不能依赖主键顺序**UUID ≠ 时间);用 `created_at` 过滤 + 唯一测试用户。→ 教训:复用用户 poll 到旧消息误判修复。
9. 容器化 dev 栈跑 E2EVite 代理 target 用 compose 服务名(`backend:8000`)非 `localhost`(容器内 localhost 是容器自身 → ECONNREFUSED)。
10. 测试脚本(单元 / 集成)是最有效验证手段;改完先跑测试再部署。
11. pytest fixture 用 generator 须 **`yield``return`**,否则 setup ERROR。
12. mock patch 路径须是**被测模块实际 import 的模块**,否则 patch 无效。
13. 外部依赖(Neo4j / PGfixture 自动**降级内存 mock**,无环境也可跑测试。
14. 核心逻辑变更须**同步更新测试辅助函数**(如 `_login_and_get_token` 适配新响应)。
15. 验收用浏览器**无痕模式**避缓存;人眼看到才算,不只信 curl。
16. 第三方库升级(starlette / pytest-asyncio)可能破坏 monkeypatch 签名,conftest 加 `encoding=None` 兼容。
17. 宣传卖点须**真实数据验证**(如"7 步达解"抽 100 问题验轮数),否则推广反噬信任。
### C. 部署与配置
18. `.env` 变量**不会自动传入容器**;compose `environment:` 须显式声明(`VAR=${VAR:-}`);新增变量同时加 `.env` 和 compose。
19. 前端 dist 只读 bind mount 只能在**宿主机改**,容器内改无效;rm 后重建须重启容器。
20. RO bind mount 目录 **`docker cp` 会假成功**,必须走宿主机 `cp`
21. `sed -i` 改 nginx 会换新 inodebind mount 不跟踪 → 须 `docker restart` 非 reload;或 sed + 重定向保 inode。
22. compose `override.yml` 自动合并并**覆盖主文件同名项**;部署前 `docker compose config | grep` 校验关键配置。
23. `POSTGRES_PASSWORD` 首次写入数据卷后**不可改**,改须删卷重建。
24. 改 FastAPI 路由须**同步部署 `router.py`**`include_router`);nginx 正则 `location` 不能 `proxy_pass` 带 URI,须 `rewrite` 剥离 `/api/` 前缀。
25. SPA 用 `try_files $uri $uri/ /index.html` 防刷新 404vite `base` 须与 nginx 路径一致;**OAuth 回调禁用 301 重定向**。
26. WS 反代须 `proxy_http_version 1.1` + Upgrade / Connection 头;浏览器传 token 用 `Sec-WebSocket-Protocol` 时后端 `accept()` **必须回显子协议**
27. `/health` 必须**真实验证 DB + Redis**`/health`(liveness) 与 `/ready`(readiness) 分离。
28. 新依赖须**同步 `requirements.txt` 并重建镜像**Dockerfile 设 `PYTHONDONTWRITEBYTECODE=1``__pycache__` 污染挂载目录。
29. 大文件(>100KB)用稳定通道上传 + **MD5 校验**JumpServer / PTY base64 **单行 ≤ 3800 字符**(超 4096 被静默丢弃),传输分块可 ~8000 字符 / 块。
30. 部署后核对"跑的是这版":暴露 **`/version` 端点返回 git hash / build time**。
31. pydantic-settings 带前缀变量(如 `WECOM_X`)须显式 **`validation_alias`**,否则匹配不上、配置静默为空。
32. WAF / 代理后 nginx 须**还原真实 IP**`set_real_ip_from` + `real_ip_header X-Forwarded-For` + `real_ip_recursive on`,否则 IP 白名单误拦 403。
33. 改生产配置标准流程:脚本(python heredoc 避 sed 转义 / 换 inode)改 → 备份 → 人眼确认 → `nginx -t` → reload → 浏览器验证,每步可回滚。
34. compose 多项目须 **`-p <project>`**,否则容器名冲突。
35. 临时放宽的安全配置(如 IP 白名单 `0.0.0.0/0`)须**排期收窄**,不可遗留生产。
36. 跨主机 Docker 网络不互通:反代用远程 IP,勿建 docker network。
37. 推送临时 token **用完即移除 remote URL**(安全)。
38. 重服务(AI)部署前**确认可用内存**,必要时停非必要容器。
### D. 重构与变更节奏
39. 高风险重构 / 变更留**清醒时段**;先止血(降超时 + 兜底)再大改;凌晨避 Dify / Prompt 类变更。→ 教训:凌晨改 Prompt 三次返工。
40. 重构后端后部署前做**三重本地验证**:AST 结构 + 调用一致性 + `py_compile`
41. **单例 / 连接池**:模块级懒加载单例,避免每次新建连接池泄漏。
42. 新功能发布前保留**旧路径自动降级**(兼容模式),零中断切换。→ 本案:主 Dify 结果缺 `intent_type` → 自动降级旧路径。
### E. 代码质量(后端)
43. 异步函数必须 **`await`**,否则 `coroutine never awaited` 且静默失效。
44. 生产日志用 **`info` 级**,否则排查时无日志。
45. 相同外部调用禁止复制多遍,抽通用函数 + grep 验证零命中;死分支须有兜底(返回全量非 None)。
46. Neo4j `CONTAINS` 方向写 `i.name CONTAINS $kw`;文件解压 / 部署目录须与容器 **mount 路径一致**。→ 教训:解压到 `/opt/.../backend/` 但 mount `/app/`,日志仍显示旧行为。
47. FastAPI 装饰器工厂必须 **`@require_role("x")` 装饰路由**;误用 `Depends(require_role("x"))` 会被当请求参数 → 全接口 422 鉴权失效(P0)。
48. 路由参数未用 Query / Path 装饰会被当请求体 → 422;第三方 OAuth 回调用 GET 走 `?code=&state=`,接口须认 `state` 且支持 GET / POST。
49. Python `re`**`\b` / `\w` 对中文按单词字符处理**;中文边界用 `(?<!\w)` / `[^\d]` / 显式 `\d` 替代。
50. 角色判断**勿硬编码**(如写死 `["agent"]`),须动态获取,否则越权 / 漏权。
51. 容器内读日志用"应用写文件 + bind mount 宿主机目录"**禁止挂 docker.sock(逃逸)/ 读 /var/lib/docker(随机 ID**。
52. 日志 / 文件读取端点目录不可读须**容错返回空 / 错误码,禁止 500**。
53. Pydantic 响应用 **`exclude` 剔除不应返回字段**(避免泄露 null / 敏感)。
### F. 前端与交互
54. React 受控输入框浏览器自动化:用「剪贴板 + 真实 Ctrl+V」;不可用 fill / insertText / paste 事件。
55. 自定义 `handlePaste``clipboardData.items` 为空时**不得 `preventDefault` / return 阻断默认**,否则纯文本 Ctrl+V 静默失效。
56. 企微 JS-SDK `wx.config`**appId 传 `corp_id``agent_id`**`nonceStr` 驼峰映射,`beta:true` 必须,签名 URL 去 `#`
57. Web Speech API 须 **HTTPS / localhost**`continuous` + `interimResults` 同 true 实现边说边出字;`onresult``event.resultIndex` 遍历。
58. 浏览器截图 / 摄像头 API 须 **HTTPS / localhost**;内嵌浏览器可能受限须 fallbackhtml2canvas)。
59. 设计令牌(色彩 / 字体 / 圆角 / 阴影 / 动效)**一套三端共享**;深色模式文字对比度 ≥ WCAG 4.5:1。
60. 辅助功能(语音 / 截图)须**降级策略**:不支持隐藏按钮、错误 Toast 不阻塞、失败保留已有内容。
### G. 架构与依赖
61. 新增功能优先**复用现有技术栈 / 存储**,不新建表、不加新依赖(铁律)。
62. 大文件 / 日志下载用 **StreamingResponse 流式**,避内存爆。
63. 异构数据源用**规范化层(composable / adapter)统一模型**,组件只消费统一结构。
### H. 安全与可观测性
64. CORS 生产禁用 `*` 走**精确白名单**CSP 先在 **Report-Only 观察约两周**再 enforce。
65. `.dockerignore` 排除 `.env` / `.git`;容器跑**非 root`USER`**CI 加 **Trivy 镜像漏洞 + gitleaks secret 扫描**exit-code 1 阻断)。
66. 安全头全套:HSTS / X-Frame-Options DENY / Referrer-Policy / Permissions-Policy / `server_tokens off`
67. 日志结构化:**JSON formatter + `request_id` 中间件**;错误响应统一带 `trace_id`,前端 axios 拦截器映射错误码。
68. 安全敏感值用**无特殊字符随机串**;轮换选低峰期、停机 30–60s、通知重登录、新旧双备、仅存 `.env`
### I. 版本 / 回滚 / 知识沉淀
69. 大改动前先 **commit 基线 + 打 git tag** 回滚点;不用 `git add -A`(误加 node_modules_old),精确 add 路径。
70. 部署前**备份配置(`.bak.<时间戳>`+ 打 docker tag**;回滚硬触发:health 连续 3 次失败 / 修复超 5 分钟 / 用户报打不开。
71. 一键部署每步配 rollback + 备份镜像 tag 与 **alembic 版本号**(回滚前记 `version_num`)。
72. 排查 > 30 分钟新案例必须**沉淀故障手册**;可复用脚本归档**工具箱并登记 README**;手册 = 作战手册 / 工具箱 = 弹药库 / SOP = 军规,职责不混。
73. BugFix 复盘:简单单文件不复盘;重复 / 复杂 / 有改进点必须复盘并**回流 SOP**。
### J. 任务执行 SOP
74. **部署三问**(部署前必确认):影响业务吗 / 重启服务吗 / 怎么验证。
75. 多步任务**先路由再执行**;路由阶段只传结论不传思考(上下文隔离)。
76. 前端改造**分批灰度发布**,降全量风险。
77. 功能未真可用前**不推广**(推广半成品透支信任);先打牢基础再做体验跃迁。
### K. 安全软件 / 环境
78. 企业安全软件(火绒)会**异步隔离解释器类 exe**python / bash),遇"exe 消失"先查隔离区;可改 node execFile 替代。
### L. 前端运行时与消息通道(2026-07-26 新增,源自 CASE-20260726-01~08
79. **Pinia setup store 外部访问不加 `.value`**setup store 返回的 ref 在组件 / store 外部访问时被 auto-unwrap,已是数组 / 对象本身;加 `.value``undefined` → 调用 `.includes()` 等方法抛 TypeError → 组件静默崩溃(不白屏但功能消失)。→ 教训:CASE-20260726-04 ✓选中导致 AI 消息消失。
80. **双通道(WS + 轮询)必须有去重**:WS 实时推送和轮询可能同时到达同一消息,任一通道无去重都会导致重复渲染。用 `Set<message_id>` 记录已处理 ID,**两通道共用同一去重集合**。→ 教训:CASE-20260726-06 坐席端消息重复。
81. **WS 推送消息不应更新轮询游标**:轮询 `after_message_id` 若用 UUID 字典序过滤,WS 推送的 AI 回复 UUID 可能大于后续员工消息 UUID,导致员工消息被跳过。WS 推送走独立通道,**不更新轮询游标**。(补充第 8 条"UUID≠时间"的具体场景)→ 教训:CASE-20260726-05 选项消息延迟出现。
82. **Edit 工具大批量修改可能写入损坏字符**:Edit 工具写入中文注释时偶发编码损坏(如注释变乱码),TypeScript 编译可能通过但运行时语法异常导致白屏。改后必须 **Grep / Read 验证**关键文件内容完整性,不能只看构建结果。(补充第 4 条"Edit 静默失败"的编码损坏维度)→ 教训:CASE-20260726-01 H5 白屏。
83. **构建成功 ≠ 运行时正常,白屏优先怀疑 JS 运行时错误**:Vite 构建通过只代表语法 / 类型检查通过,不代表运行时无异常(如未 import 的 `ref`、Pinia `.value` 崩溃、闭包值无法响应)。白屏排查路径:F12 控制台看 `ReferenceError` / `TypeError` → 检查 import → 检查 store 访问方式。→ 教训:CASE-20260726-01 / 03 / 04 三起白屏 / 组件崩溃。
---
## 第二部分 · IT智能服务台项目专属经验
### 服务器与部署
- 正式服务器 `itsupport.servyou.com.cn` (10.90.5.110),出口 IP `218.75.34.87`
- 堡垒机 `sxn@10.212.189.210:2222` (OTP);脚本 `jms_ops.py`
- 项目根 `/opt/wecom-it-desk/`**代码唯一源 `/opt/wecom-it-desk/app/`**(卷挂载),重建镜像只更新依赖;代码变更 `restart`、依赖变更才 `build`
- 后端 mount `./app:/app/app``.py` 变更 `restart backend`env 变更 `up -d backend`);**后端必须 `--workers 1`**ws_manager 进程内单例,多 worker 致约 50% WS 消息静默丢失)
- 前端 dist 只读 bind 到 nginx `/usr/share/nginx/html` 下:`h5` / `itagent` / `itadmin` / `itportal` / `itterminal` / `itdesk`
- 容器名用下划线 `wecom_it_nginx`(非横杠)
- **部署顺序**:后端 → 前端 4 端 → nginx → DB 迁移 → 验收
- 免登录跳转:ITSM 用 `itsm.servyou.com.cn`(企微应用主页域名,从工作台打开免登录);`devops.dc.servyou-it.com` 需扫码
- 蓝绿部署 Blue / Green 共用同一 PG / Redis,切换后用户需重登录、WS 断开;**DB migration 只能逐版降**`alembic downgrade -1` 多次)
### 外部集成(关键端点)
- 企微通讯录 Secret 已废弃(errcode 48009)→ 改用**自建应用 Secret + 全公司可见范围**;`WECOM_CONTACT_SECRET=` 留空触发降级(验证 651 部门 / 7098 员工)
- Dify `http://yw-dify.dc.servyou-it.com/v1/chat-messages`advanced-chat 改工作流后**必须浏览器手动「发布」**,否则 API 400 `Workflow not published`;原生直连优先于代理,compose 须显式声明 `DIFY_NATIVE_*` / `BAIDU_ASR_*` / `WECOM_SSO_CALLBACK_BASE`
- Dify App ID `8f0f3d62-f63d-4cf3-815e-b10529c66f1d`(智能IT支持-员工咨询,85 节点);审批意图 `app-7jkRkAzvX4QM9v9SM3P8mMEO`;分诊 `app-z3S9AEUUAVPbtR2rioxpiIvp`
- RAGFlow 生产 `http://10.80.0.85:8080/` / API `:9380`;映射策略:联软(主) > aTrust(VPN) > eHR(静态)
- **勿硬编码外部系统模板 / ID**:企微审批模板 ID 会失效,已统一改用 ITSM 工单系统(通用教训:外部模板 ID 配置化,勿写死代码)
### 企微登录与扫码
- 企微**扫码登录**:App 无确认 UI,扫码成功 = 直接登录(去 confirm),否则生产走不通
- **iOS WebView 无法解析相对路径回调 URL**,后端须返回绝对 URL(含域名)
- **H5 员工端仍走企微 OAuth**,不进扫码 / MFA 体系,与其他端不同
- OTP **连续 5 次错误锁 5 分钟**、二维码 **120 秒过期**`pyotp` 锁版本 `==2.9.0`
- 后端 **JS-SDK 签名接口已存在**`/api/wecom/jsapi-config`),语音等前端能力无需后端改动
### 企微 JS-SDK
- 双鉴权 `wx.config()`(jsapi_ticket) + `wx.agentConfig()`(agent_config_ticket),签名算法相同但**不可混用**
- `wx.invoke('thirdPartyOpenPage', {oaType:'10001', templateId, thirdNo, extData})` 打开审批表单;后端 `GET /wecom/jsapi-config?url=...&with_agent_config=true`
- 既有 bug`EmergencyDispatcher.vue` 复用 jsapi 签名给 agentConfig(靠 3 秒超时兜底)
### 关键架构决策(锁定,勿随意改)
- 术语:员工端「人工坐席」= 用户呼叫坐席;「摇人」= 坐席呼叫坐席
- UI:企微浅色扁平,accent `#07C160`;统一入口 `/itportal/` → user / agent / adminadmin 需 OTP
- 单通道消息架构:Dify 输出 JSON `{text,action,options}` → 后端发双 WS(`ai_reply` + `dynamic_recommend`) → 文字到气泡 / 卡片到侧边栏
- WS 双连接池:`active_connections`(agent) + `employee_connections`(H5)
- 后端字段 `id` / `sender_type` → 前端 `message_id` / `message_type``conversation.ts``mapMessage()`
- 角色边框色硬编码:主责蓝 `#3b82f6` / 协作绿 `#07C160` / 发起人橙 `#FF9800` / 自己高亮环;H5 超员 N=4、坐席 N=6
### 功能与版本(摘要)
- 群聊(摇人/邀请/四角色) / 审批(12 类型 18 流程 / 三级意图) / 代办 / IT资产推送 / 语音转文字 / 截图拍照 / 复杂场景 P0~P3 / 诊断修复闭环 / D1 路由合并
- 运维手册:`docs/04-运维文档/部署运维/00-标准故障排查手册.md`SOP`docs/07-项目管理/IT智能服务台-标准作业流程SOP.md`