企业微信集成方案.md 20 KB


tags: [企业微信, 集成, 配置]

date: 2026-07-29

凭证

参数
企业 ID (CorpID) wwa979fc0ebcce602a
应用 ID (AgentID) 1000008
应用 Secret 3QIxJQfqFgACBBcybmyc_I-mOoFvicH-Y6hdQ09ldLk
Token(回调验证) dc56c68beba4c06984ce17c6342cacb1
EncodingAESKey(回调加密) WngnBCzsdXlPcdNBVG6l7auYMokMvuM94jMunTdSEHM
回调 URL http://api.zilanlife.com/wecom/callback
服务端 IP 59.51.140.250
可信域名 api.zilanlife.com
DeepSeek API Key sk-8a62671966e1470bb1a6b223a9a549fa
回调服务端口 18790(仅 127.0.0.1)

一、配置步骤

1. 创建自建应用

登录 work.weixin.qq.com → 应用管理 → 创建应用

2. 创建子域名并配置服务器

DNS(用户操作):A 记录 api.zilanlife.com47.109.158.218

⚠️ api.zilanlife.com 走 Nginx 80 端口(HTTP),不需要额外开放端口。/wecom/callback 路径反代到内网 127.0.0.1:18790

Nginx(Hermes 在 aliyun2 执行)

cat > /www/server/panel/vhost/nginx/api.zilanlife.com.conf << 'EOF'
server {
    listen 80;
    server_name api.zilanlife.com;
    root /www/wwwroot/api.zilanlife.com;
    index index.html;

    location /wecom/callback {
        proxy_pass http://127.0.0.1:18790;
        proxy_set_header Host $host;
        proxy_read_timeout 60s;
    }
}
EOF
mkdir -p /www/wwwroot/api.zilanlife.com
nginx -t && nginx -s reload

3. 验证域名归属

  1. 企业微信应用详情页 → 网页授权及JS-SDK → 可信域名 → 输入 api.zilanlife.com
  2. 下载验证文件 WW_verify_j5y70gNGnVqhnH4d.txt
  3. 上传到服务器:

    echo 'j5y70gNGnVqhnH4d' > /www/wwwroot/api.zilanlife.com/WW_verify_j5y70gNGnVqhnH4d.txt
    
  4. 确认 http://api.zilanlife.com/WW_verify_j5y70gNGnVqhnH4d.txt 可访问

  5. 点击验证通过

4. 配置 IP 白名单

应用详情页 → 企业可信 IP → 添加 59.51.140.250

⚠️ 必须先完成域名验证才能操作。

5. 配置接收消息

应用详情页 → 接收消息 → 设置API接收:

字段
URL http://api.zilanlife.com/wecom/callback
Token dc56c68beba4c06984ce17c6342cacb1
EncodingAESKey WngnBCzsdXlPcdNBVG6l7auYMokMvuM94jMunTdSEHM

消息事件勾选:用户发送的普通消息

6. 部署回调服务

Python 脚本路径:/data/wecom_callback.py(aliyun2)

依赖安装:

pip3 install pycryptodome

启动/重启(systemd 管理,崩溃 5 秒自愈):

systemctl restart wecom-callback
journalctl -u wecom-callback -f

二、回调服务架构

员工在企微发消息
    ↓
企微 POST 加密 XML → api.zilanlife.com/wecom/callback
    ↓
Nginx 反代 → 127.0.0.1:18790(Python 服务,systemd 托管)
    ↓
1. URL 验证(GET):解密 echostr → 返回明文
2. 接收消息(POST):秒回 200 → 解密消息体 → 后台异步处理
    ↓
Dify 工作流「企微AI回复」(多轮会话)→ AI 生成
    ↓
send_msg 主动推送回复到企微

核心功能

  1. URL 验证:GET 请求,解密 echostr 参数并返回明文(企业微信首次配置时调用)
  2. 消息接收:POST XML 加密体,解密后得到用户消息,先秒回 200 再异步处理(企微 5 秒超时)
  3. AI 回复:调 Dify 工作流(app-6ax5fzZvsOuQvSDiQ0bcCgwa)生成回复,15 秒未完成先发「正在处理」
  4. 主动推送send_msg 调企微 API 把回复发回用户

多轮会话(2026-08-15 改造 v3)

  • 方案:切换到 Dify Chatflow 应用(「企业微信」advanced-chat,LLM 节点开记忆窗口 10),走 /v1/chat-messages API 原生 conversation_id
  • 踩坑:原「企微AI回复」是 Workflow 应用,/v1/workflows/run 请求模型不含 conversation_id,多轮无效
  • 回调脚本:按 userid 维护 conversation_id(互不串扰),每天凌晨 3:00 全量过期,存 /data/wecom_conversations.json
  • API Keyapp-wecom-chat-20260815(Dify「企业微信」应用)
  • 原版备份/data/wecom_callback.py.bak-20260814;回溯记录见桌面 aliyun2_企微回调多轮会话_回溯记录.md

AI 工具接入(2026-08-15)

  • 「企业微信」应用已升级为 Agent 节点 + 4 个 CordysCRM 售后工具(查询/新增/查看/修改)
  • 认证代理:systemd cordys-api-proxy(端口 8766,仅内网)
  • 完整接入流程 + 踩坑 → 见 [[Dify AI工具接入指南]]

加密/解密

  • 算法:AES-256-CBC,IV 为 AES Key 前 16 字节
  • 明文结构:16字节随机串 + 4字节消息长度(big-endian) + 消息内容 + Corpid
  • PKCS7 填充到 32 字节倍数

三、API 参考

主动发消息

POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={TOKEN}
{
    "touser": "WuXingdeYeKong",
    "msgtype": "text",
    "agentid": 1000008,
    "text": {"content": "消息内容"}
}

获取通讯录

GET https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?access_token={TOKEN}&department_id=1&fetch_child=1
  • 返回 155 人
  • useridWuXingdeYeKong,不一定是姓名拼音
  • 申亮 = WuXingdeYeKong

Access Token

  • 接口:GET /cgi-bin/gettoken?corpid=...&corpsecret=...
  • 有效期:7200 秒(2 小时)
  • 需缓存复用

四、诗词 API

  • 端点:http://47.109.158.218:1279/api/v1/poems/random
  • 搜索:http://47.109.158.218:1279/api/v1/poems/search?q=关键词
  • ⚠️ 搜索按内容不按作者,搜"李白"返回提及的诗,非李白所作
  • 按作者找:用 random 随机抽取直到命中

五、踩坑记录

现象 解决
未配可信域名 IP 白名单无法添加 先验证域名,再配 IP
IP 未加白 通讯录/user API 返回 60020 应用详情 → 企业可信 IP
userIds 参数无效 user/list 返回所有人 拉全量客户端建映射
回调 Key 不一致 URL 验证失败 Token/EncodingAESKey 需与后台严格一致
DeepSeek Key 错误 401 Authorization Required 使用正确的 sk-xxx key
搜索按内容不按作者 搜"李白"返回马致远 random 抽到命中为止

七、消息模板

签单前复盘表 — 设计师通知

数据来源:销帮帮 formId=7539701,2026年,模板创建(text_12=ef4570ef...),排除关联客户已删除的记录

业务术语

  • 模板创建 = 销帮帮 text_12 值为模板创建 UUID
  • 对外展示为「未填写」(业务语境:记录已创建但未完善)

卡片格式(手机端适配,分割线不超过 5 个 ━):

📊 {设计师名} — 2026年签单前复盘表
━━━━━
📝 未填写  {N} 条
━━━━━
请在规定范围时间内登录销帮帮填写完毕!

示例

📊 陈叶婷 — 2026年签单前复盘表
━━━━━
📝 未填写  5 条
━━━━━
请在规定范围时间内登录销帮帮填写完毕!

流程

  1. 销帮帮 API 拉取 formId=7539701,筛选 text_12=模板创建
  2. 过滤 addTime >= 2026-01-01
  3. customer/detail 校验每条 text_2,排除 code=100404
  4. 按 ownerId 分组统计,id2name 查姓名
  5. 姓名映射(销帮帮→企微)后查 userid,逐人发送

API 调取条件

{
  "corpid": "xbb8abca6c277b846dab5e0c8a52fe9506e",
  "formId": 7539701,
  "conditions": [
    {"attr": "text_12", "value": ["ef4570ef-5460-7b62-b888-b3295f475e51"], "symbol": "equal"}
  ],
  "pageSize": 100, "page": 1
}

然后客户端过滤 addTime >= 2026-01-01,再逐条 customer/detail 排除 code=100404

发送规则

  • 按设计师分组,每人一条卡片(只含自己的数量)
  • 姓名映射转换后查企微 userid
  • 赵耀跳过(企微无此人)
  • 每条间隔 ≥ 0.5 秒(避免企微频率限制)

姓名映射

销帮帮 企业微信 userid
胡华玉 胡玉 WeiXiaoXiangYang
陈明先 陈想 ChenXiang
王飞 王习僧 b5ed7c2b00
赵耀 ❌ 不存在

八、已验证功能

  • ✅ Token 获取
  • ✅ 通讯录读取(155 人)
  • ✅ 主动发送消息
  • ✅ 诗词 → 企微发送
  • ✅ 接收消息回调 + URL 验证
  • ✅ 员工消息 → Dify 工作流 → AI 自动回复

九、Dify 工作流集成

9.1 工作流配置

工作流名称:企微AI回复

参数
API Key app-6ax5fzZvsOuQvSDiQ0bcCgwa
端点 http://47.109.158.218:8088/v1/workflows/run
输入 query(文本)
输出 text(文本)

节点结构

  1. 开始节点 → query 输入
  2. LLM 节点 → 提示词 {{#start.query#}},关掉 Thinking
  3. 结束节点 → 输出 text

9.2 回调服务对接

回调路径:api.zilanlife.com/wecom/callback → Nginx → 127.0.0.1:18790

流程:

员工发消息 → 企微加密 POST → 回调服务解密 → Dify API 发送 query → LLM 处理 → 返回 text → 加密回复 → 员工收到

代码要点:

body = {"inputs": {"query": user_msg}, "response_mode": "blocking", "user": "wecom"}
req = Request(DIFY_API_URL, data=json.dumps(body).encode(),
              headers={"Authorization": f"Bearer {DIFY_API_KEY}"})
# 返回 text = data["data"]["outputs"]["text"]

9.3 版本演进

版本 后端 模式 状态
v1 DeepSeek API 直连 同步 ❌ Key 泄露风险
v2 Key 注释 同步 ❌ 不可用
v3 Dify 工作流 同步 ❌ 超时断连 (BrokenPipe)
v4 Dify 工作流 异步 ✅ 当前

9.4 异步架构(解决超时断连)

问题:同步模式下 Dify 处理 15-30 秒,企微 5 秒超时断连 → BrokenPipeError

方案

收到消息 → 立即 200(不等 Dify)→ 后台线程处理 → send_msg 主动推送

代码

def do_POST(self):
    # 先返回 200
    self.send_response(200); self.end_headers()
    # 后台处理
    threading.Thread(target=lambda: send_msg(user_id, ai_reply(msg))).start()

9.5 三层可靠性保护

保护 机制 效果
超时兜底 Dify 15s 无响应 → 先发「正在处理」→ 继续等 45s 不丢消息,用户有反馈
发送重试 send_msg 失败自动重试 3 次,间隔 1s 网络抖动不丢
systemd Restart=always,崩溃 5s 自动复活 进程挂了秒恢复

并发能力

  • Python 线程处理 100+ 人同时提问无压力
  • Dify Worker 池自动排队
  • 真正瓶颈在企微 API 频率限制,不在架构

systemd 配置

[Service]
ExecStart=/usr/bin/python3 -u /data/wecom_callback.py
Restart=always
RestartSec=5
# 服务器重启后自动拉起 → systemctl enable

十、定时任务(签单复盘企微推送)

项目 内容
任务 ID 82990ab2bd98
触发 每天 5:00
执行 本机 wecom_xbb_push.sh → SSH aliyun2 → Python 脚本
监控 deliver: origin,结果推回 Hermes

为什么迁到 aliyun2

  • 本地家宽 IP 会变动(已从 59.51.140.250 变为 1.204.41.232)
  • 服务器 IP 47.109.158.218 固定,企微白名单稳定

脚本路径

  • 本地:~/.hermes/scripts/wecom_xbb_push.sh(SSH 跳板)
  • 远程:/opt/scripts/wecom_xbb_push.py(实际执行)

十一、踩坑记录(新增)

现象 解决
IP 变动 本地出网 IP 从 59.51→1.204,企微 60020 脚本迁 aliyun2,用固定 IP
systemctl stop 卡死 服务持续 deactivating,超时 prod.js SIGTERM 处理有 bug,手动 kill 进程
WAL 删除致数据丢失 部署时删 WAL 文件 → 未刷盘数据永久丢失 先用 wal_checkpoint 强制刷盘,再停服
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.workflowid=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 复现验证命令

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)nameschema(文本)、schema_type_str='openapi'credentials_struser_idtenant_idtools_strdescription

  • schema 例(省略 paths):

    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 加密):

    {"auth_type":"api_key_header","api_key_header":"X-Proxy-Token","api_key_value":"<encrypted>"}
    
  • 加密方式(容器内 python,与 Cordys 存储一致):

    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 节点):

    // 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 或等过期。