facc04aa65
本提交为 .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-*/
170 lines
18 KiB
Markdown
170 lines
18 KiB
Markdown
# 项目经验与教训 · 可复用规则手册
|
||
|
||
> **用途**:作为每个项目 / 任务启动时的预设提示(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 / network;API 用 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 栈跑 E2E:Vite 代理 target 用 compose 服务名(`backend:8000`)非 `localhost`(容器内 localhost 是容器自身 → ECONNREFUSED)。
|
||
10. 测试脚本(单元 / 集成)是最有效验证手段;改完先跑测试再部署。
|
||
11. pytest fixture 用 generator 须 **`yield` 非 `return`**,否则 setup ERROR。
|
||
12. mock patch 路径须是**被测模块实际 import 的模块**,否则 patch 无效。
|
||
13. 外部依赖(Neo4j / PG)fixture 自动**降级内存 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 会换新 inode,bind 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` 防刷新 404;vite `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**;内嵌浏览器可能受限须 fallback(html2canvas)。
|
||
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 / admin(admin 需 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`
|