Просмотр исходного кода

vault backup: 2026-09-06 16:00:49

zhensolid 4 часов назад
Родитель
Сommit
7a52418acd
1 измененных файлов с 110 добавлено и 0 удалено
  1. 110 0
      09-子兰工作/子兰AI/企业微信集成方案.md

+ 110 - 0
09-子兰工作/子兰AI/企业微信集成方案.md

@@ -389,3 +389,113 @@ RestartSec=5
 | Dify LLM Thinking 输出 | 回复含 `<think>` 标签 | 关掉 LLM 节点的 Thinking 模式 |
 | Dify 变量引用错误 | `#sys.query#` not found | 改为 `#start.query#` |
 | Cron 本地跑不通 | `no_agent` 脚本依赖本地 IP 白名单 | 改 shell 脚本 SSH 远端执行 |
+
+---
+
+## 十二、2026-09-06:企业微信对话建档 → 云装天下 ERP(Dify 全链路打通实录)
+
+> 目标:员工在企业微信里发"登记客户 姓名 手机号 楼盘 来源",AI 自动查重→复述→确认→写入云装天下 ERP(信息客户 type2)。本节为**复现级实录**,照做可重演。
+> 状态:✅ 已端到端实测成功(见 §12.5)。生产上线前待办见 §12.7。
+
+### 12.1 最终链路(现状,与售后共用同一入口)
+```
+员工在企微「子兰企业助手」(AgentID 1000008) 发文字
+ → https://api.zilanlife.com/wecom/callback   (nginx → 127.0.0.1:18790)
+ → /data/wecom_callback.py(现有售后回调,未改动;解密后 POST 给 Dify)
+ → Dify http://127.0.0.1:8088/v1/chat-messages(Bearer app-wecom-chat-20260815)
+ → Dify「企业微信」Chatflow(deepseek-v4-flash,FunctionCalling)
+     ├─ 建档意图(登记客户/建档/…) → erp_checknumber → 复述待确认 → erp_add_project → 回执
+     └─ 售后/装修问答 → 原路(Cordys 工具)
+ → 回复经回调脚本加密推送回企微
+```
+分流由 Dify Agent 系统提示词内「客户登记规则」完成,**回调脚本零改动**(本地草稿 wecom_callback_v2_jd.py 已作废)。
+
+### 12.2 服务器新增物(aliyun2 = 47.109.158.218)
+| 项 | 内容 |
+|---|---|
+| 建档桥 | `/data/erp_bridge.py`,systemd `erp-bridge.service`,监听 `0.0.0.0:8767` |
+| 桥功能 | md5 签名、from/type=2/source 枚举硬校验、幂等(sqlite 24h)、频控、审计(jsonl);端点 /health /sources /checknumber /add_project /movetospare |
+| 桥鉴权 | 请求头 `X-Proxy-Token`,兼容 `Basic ` 前缀(Dify api_key_header 固定带 Basic):`t=header.strip(); t=t[6:] if t.startswith("Basic ")` |
+| 桥配置 | ERP_BASE=https://gzzlsh.cloudcubic.net;ERP_TOKEN=14EDCFA9BA50B0DD4768187EC07D6F59;CHANNEL=云装天下(占位);branch=1;projecttype=2;BUILDING_MODE=free(测试期) |
+| 启动 | `systemctl start erp-bridge`;健康 `curl -H 'X-Proxy-Token: Basic zilan-jd-proxy-2026' http://127.0.0.1:8767/health` |
+
+### 12.3 Dify 侧改动(容器/DB 层,勿用浏览器画布)
+1. 自定义工具:表 `tool_api_providers`「云装ERP建档桥」id=`03333ad2-b2b8-4000-b23a-9a7a389874fe`;schema servers=`http://172.20.0.1:8767`(Dify 容器网络宿主机网关);credentials_str 对齐 Cordys:`{"auth_type":"api_key_header","api_key_header":"X-Proxy-Token","api_key_value":"<SECRET_KEY+Fernet 加密后的 zilan-jd-proxy-2026>"}`,不带 prefix 键。
+2. 线上 Agent 直改 DB:apps.workflow_id=`38326b5d-43d2-43b0-8c24-ea4a43e6e7fb`(草稿 `8ed1dbb8-…` 同步)——agent 节点 `agent_parameters.tools.value` 追加 4 个 erp_* 工具(provider 同上);`instruction.value` 末尾追加「客户登记规则」全文(见本地 agent-追加指令-客户登记.md)。改前备份 /tmp/workflow_*.20260906072931.json。
+3. 网络:compose `.env` `SSRF_PROXY_ALLOW_PRIVATE_IPS=172.17.0.0/16,172.20.0.0/16,172.21.0.0/16,172.30.0.0/16,172.31.0.0/16,192.168.0.0/20,127.0.0.0/8`(备份 .env.bak-20260906)→ `docker-compose up -d` 重建;**重建后 api 容器换 IP → `docker restart dify_v1110_chns-nginx-1` 重新解析,否则 502**。
+
+### 12.4 复现验证命令
+```bash
+curl -s -m 120 -X POST http://127.0.0.1:8088/v1/chat-messages \
+  -H 'Authorization: Bearer app-wecom-chat-20260815' -H 'Content-Type: application/json' \
+  -d '{"inputs":{},"query":"登记客户 张三 13812345678 观山云墅 信息流—小红书","response_mode":"blocking","user":"probe"}'
+# Agent 应查重(未建档)→复述待确认;同会话发"确认"→真实写入(仅假号测试)
+```
+
+### 12.5 2026-09-06 实测结果
+- 企微 WuXingdeYeKong:「登记客户 张三 13700000009 观山云墅 其他」→ Dify 会话 b025f057;
+- Agent 查重"未建档"→复述待确认→回「确认」→ `erp_add_project` 成功;
+- 桥审计 `{"mobile":"13700000009","client_id":12771,"project_id":12967,"status":"ok","msg":"成功!"}`;ERP 信息客户池可见(KH 开头编号)。
+
+### 12.6 本次踩坑(务必看)
+1. Dify api_key_header 固定发 `X-Proxy-Token: Basic <token>` → 桥须剥离 Basic 前缀否则 401;
+2. SSRF 私网拦截 + squid 对公网 http 也 MITM 成 https(自签证书失败)→ 最终"私网网关 http + 白名单放行";
+3. docker-compose up 后 api 换 IP → dify nginx 502 → restart nginx;
+4. 浏览器画布改 Dify 编排易误删节点/清空指令(已发生)→ 一律容器/DB+备份;
+5. 工具凭据落库为 SECRET_KEY+Fernet 加密,直写库须同算法。
+
+### 12.7 生产上线前待办(非阻塞联调)
+- ERP 方:正式 from 渠道标识、source 字典口径、楼盘查询接口/清单、员工查询接口;
+- 我方:企微身份→手机号映射(clerkPhone 归属)、BUILDING_MODE=whitelist、来源对话选项映射、申请测试环境。
+
+### 12.8 相关文件
+- 空间 /Users/shenliang/Documents/云装天下/:erp_bridge.py、erp_bridge_openapi.yaml、agent-追加指令-客户登记.md、cloudcubic_api.py、CLAUDE.md(「Dify 接入进度(2026-09-06)」)
+
+### 12.9 如何新增/复现一个 Dify 自定义工具(Swagger API)并挂到 Agent
+
+> 通用流程,不限于建档桥。两种方式任选:A=网页 UI(直观但慢);B=直接写库(快、可脚本化,推荐给 AI 自动执行)。
+
+#### 方式 A:网页 UI(人肉操作路径)
+1. Dify 控制台 → **集成 → 工具 → Swagger API 作为工具 → +添加/创建**;
+2. 填名称(如「云装ERP建档桥」);Schema 粘贴 OpenAPI yaml/json;
+   - **servers.url 必须写 Dify 容器能访问到的地址**:宿主机桥用 `http://172.20.0.1:8767`(网关,需 SSRF 白名单放行,见 §12.3.3);
+   - security: `X-Proxy-Token` header(apiKey)声明在 components.securitySchemes;
+3. **鉴权方法**:选「请求头」→ 键 `X-Proxy-Token` → 值填真实 token(如 `zilan-jd-proxy-2026`)。
+   - ⚠️ 即使选了 Basic 前缀也无所谓:Dify 运行时恒发 `X-Proxy-Token: Basic <token>`,**后端接口需自己剥掉 Basic 前缀**;
+4. 保存 → 工具出现在"工具插件/Swagger API"列表 → 勾选/添加全部工具;
+5. 到 Agent(应用编排 → AI助手节点)→ **工具列表 +添加** → 搜索该工具名 →「添加全部」→ 保存/发布。
+
+#### 方式 B:直接写库(AI 推荐路径)
+表:`dify.tool_api_providers`,关键列:`id(uuid)`、`name`、`schema`(文本)、`schema_type_str='openapi'`、`credentials_str`、`user_id`、`tenant_id`、`tools_str`、`description`。
+- schema 例(省略 paths):
+```yaml
+openapi: 3.0.1
+info: {title: 云装ERP建档桥, version: 0.1.0}
+servers: [{url: http://172.20.0.1:8767}]
+security: [{ProxyToken: []}]
+paths: { ... }   # operationId 即工具名,如 erp_checknumber
+components:
+  securitySchemes: {ProxyToken: {type: apiKey, in: header, name: X-Proxy-Token}}
+```
+- credentials_str(值必须用 Dify SECRET_KEY 加密):
+```json
+{"auth_type":"api_key_header","api_key_header":"X-Proxy-Token","api_key_value":"<encrypted>"}
+```
+- 加密方式(容器内 python,与 Cordys 存储一致):
+```python
+import os, hashlib, base64
+from cryptography.fernet import Fernet
+key = base64.urlsafe_b64encode(hashlib.sha256(os.environ["SECRET_KEY"].encode()).digest())
+enc = Fernet(key).encrypt(b"zilan-jd-proxy-2026").decode()
+```
+- 挂到 Agent(改 `workflows.graph` 的 llm 节点):
+```json
+// nodes[] 里 data.type=="agent" 的节点:
+"agent_parameters": { "tools": { "value": [
+  {"provider_name":"03333ad2-b2b8-4000-b23a-9a7a389874fe",
+   "provider_id":"03333ad2-b2b8-4000-b23a-9a7a389874fe","type":"api",
+   "tool_name":"erp_add_project","tool_label":"erp_add_project",
+   "tool_configurations":{},"enabled":true}
+]}}
+```
+- ⚠️ 直写库前先备份行(SELECT→存 json);agent 运行读 apps.workflow_id 绑定的那行,改完即时生效(无需点发布);redis 有缓存则重启 api 或等过期。