fix(lead): 客户咨询通知改走系统总线8660→任总DM,替掉已下线的 ai-cs 通道
ci/woodpecker/push/main Pipeline was successful

- ai-cs 用户/profile 已不存在,旧 runuser 调用必然失败且被 except: pass 吞掉
- 线索只落 /root/leads/leads.json,任总侧零通知(线上活缺陷)
- 新通道:POST 127.0.0.1:8660/bus/send,notice_class=fallback,收件人不变(200653946)
- 新增 /root/leads/notify.log 落痕,杜绝静默失败
This commit is contained in:
项目Bot
2026-10-22 16:47:25 +08:00
parent 1efa6b7df9
commit 4cad2b73c4
+52 -19
View File
@@ -1,16 +1,17 @@
"""AI获客 - 客户咨询接口""" """AI获客 - 客户咨询接口"""
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException
from pydantic import BaseModel from pydantic import BaseModel
import json, os, datetime, subprocess import json, os, datetime, urllib.request
router = APIRouter(prefix="/api/cma/lead", tags=["AI获客"]) router = APIRouter(prefix="/api/cma/lead", tags=["AI获客"])
# ai-cs 实盘分流脚本(跨 profile 调用) # 客户咨询通知唯一通道:系统总线中继 8660 → 任总 DM。
# 必须以 ai-cs 身份 + 其 HERMES_HOME/HOME 运行:脚本内用 `hermes send` 复用该 profile 的企微凭据; # 2026-10-22 起取代旧的 ai-cs routing-live.py(该 profile/user 已下线,runuser 必然失败且异常被吞,
# cma-backend.service 的 Environment 为空,不显式传入会解析到非 ai-cs 的 profile(root 默认 profile)。 # 导致线索静默丢失通知)。中继类别按契约取 fallback(无会话兜底),company 类被中继直接拒绝。
AI_CS_USER = "ai-cs" BUS_RELAY_URL = "http://127.0.0.1:8660/bus/send"
AI_CS_HOME = "/var/lib/wecom-ai-cs/profile" BUS_KEY_FILE = "/srv/hermes/shared/.secrets/bus_relay.key"
AI_CS_ROUTING_SCRIPT = AI_CS_HOME + "/scripts/routing-live.py" BOSS_USERID = "200653946" # 任富海
NOTIFY_LOG = "/root/leads/notify.log"
class LeadRequest(BaseModel): class LeadRequest(BaseModel):
name: str name: str
@@ -18,22 +19,54 @@ class LeadRequest(BaseModel):
requirement: str = "" requirement: str = ""
source: str = "sxbh.ltd" source: str = "sxbh.ltd"
def notify_boss(name, phone, requirement): def _log_notify(ok, detail):
"""额外推给任总(经 ai-cs 实盘分流脚本;失败不影响主流程)""" """通知结果落痕(旧实现 except: pass 静默吞异常,导致线索漏通知无人知晓)"""
try: try:
subprocess.run( os.makedirs(os.path.dirname(NOTIFY_LOG), exist_ok=True)
["runuser", "-u", AI_CS_USER, "--", with open(NOTIFY_LOG, "a", encoding="utf-8") as f:
"env", f"HOME={AI_CS_HOME}", f"HERMES_HOME={AI_CS_HOME}", f.write("%s %s %s\n" % (datetime.datetime.now().isoformat(timespec="seconds"),
f"USER={AI_CS_USER}", f"LOGNAME={AI_CS_USER}", "OK" if ok else "FAIL", detail))
"/usr/bin/python3", AI_CS_ROUTING_SCRIPT,
requirement or "(未填需求)",
f"{name} {phone}",
"website:sxbh.ltd"],
timeout=30, capture_output=True,
)
except Exception: except Exception:
pass pass
def notify_boss(name, phone, requirement):
"""客户咨询通知:经系统总线中继 8660 推任总 DM。失败不阻断主流程,但必须留痕。"""
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
content = (
"## 新客户咨询(sxbh.ltd 官网)\n"
f"> 姓名:{name}\n"
f"> 电话:{phone}\n"
f"> 需求:{requirement or '(未填)'}\n"
f"> 时间:{now}\n\n"
"承诺 30 分钟内联系。"
)
try:
with open(BUS_KEY_FILE, encoding="utf-8") as f:
key = f.read().strip()
if not key:
raise RuntimeError("bus key empty")
payload = json.dumps({
"touser": BOSS_USERID,
"msgtype": "markdown",
"content": content,
"notice_class": "fallback",
}, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(
BUS_RELAY_URL, data=payload,
headers={"Content-Type": "application/json",
"X-API-Key": key,
"X-Notice-Class": "fallback"},
method="POST",
)
with urllib.request.urlopen(req, timeout=10) as resp:
body = resp.read().decode("utf-8", "ignore")[:300]
_log_notify(True, body)
return True
except Exception as e:
_log_notify(False, "%s: %s" % (type(e).__name__, e))
return False
@router.post("") @router.post("")
def create_lead(data: LeadRequest): def create_lead(data: LeadRequest):
"""接收客户咨询并保存""" """接收客户咨询并保存"""