- 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:
+52
-19
@@ -1,16 +1,17 @@
|
||||
"""AI获客 - 客户咨询接口"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
import json, os, datetime, subprocess
|
||||
import json, os, datetime, urllib.request
|
||||
|
||||
router = APIRouter(prefix="/api/cma/lead", tags=["AI获客"])
|
||||
|
||||
# ai-cs 实盘分流脚本(跨 profile 调用)
|
||||
# 必须以 ai-cs 身份 + 其 HERMES_HOME/HOME 运行:脚本内用 `hermes send` 复用该 profile 的企微凭据;
|
||||
# cma-backend.service 的 Environment 为空,不显式传入会解析到非 ai-cs 的 profile(root 默认 profile)。
|
||||
AI_CS_USER = "ai-cs"
|
||||
AI_CS_HOME = "/var/lib/wecom-ai-cs/profile"
|
||||
AI_CS_ROUTING_SCRIPT = AI_CS_HOME + "/scripts/routing-live.py"
|
||||
# 客户咨询通知唯一通道:系统总线中继 8660 → 任总 DM。
|
||||
# 2026-10-22 起取代旧的 ai-cs routing-live.py(该 profile/user 已下线,runuser 必然失败且异常被吞,
|
||||
# 导致线索静默丢失通知)。中继类别按契约取 fallback(无会话兜底),company 类被中继直接拒绝。
|
||||
BUS_RELAY_URL = "http://127.0.0.1:8660/bus/send"
|
||||
BUS_KEY_FILE = "/srv/hermes/shared/.secrets/bus_relay.key"
|
||||
BOSS_USERID = "200653946" # 任富海
|
||||
NOTIFY_LOG = "/root/leads/notify.log"
|
||||
|
||||
class LeadRequest(BaseModel):
|
||||
name: str
|
||||
@@ -18,22 +19,54 @@ class LeadRequest(BaseModel):
|
||||
requirement: str = ""
|
||||
source: str = "sxbh.ltd"
|
||||
|
||||
def notify_boss(name, phone, requirement):
|
||||
"""额外推给任总(经 ai-cs 实盘分流脚本;失败不影响主流程)"""
|
||||
def _log_notify(ok, detail):
|
||||
"""通知结果落痕(旧实现 except: pass 静默吞异常,导致线索漏通知无人知晓)"""
|
||||
try:
|
||||
subprocess.run(
|
||||
["runuser", "-u", AI_CS_USER, "--",
|
||||
"env", f"HOME={AI_CS_HOME}", f"HERMES_HOME={AI_CS_HOME}",
|
||||
f"USER={AI_CS_USER}", f"LOGNAME={AI_CS_USER}",
|
||||
"/usr/bin/python3", AI_CS_ROUTING_SCRIPT,
|
||||
requirement or "(未填需求)",
|
||||
f"{name} {phone}",
|
||||
"website:sxbh.ltd"],
|
||||
timeout=30, capture_output=True,
|
||||
)
|
||||
os.makedirs(os.path.dirname(NOTIFY_LOG), exist_ok=True)
|
||||
with open(NOTIFY_LOG, "a", encoding="utf-8") as f:
|
||||
f.write("%s %s %s\n" % (datetime.datetime.now().isoformat(timespec="seconds"),
|
||||
"OK" if ok else "FAIL", detail))
|
||||
except Exception:
|
||||
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("")
|
||||
def create_lead(data: LeadRequest):
|
||||
"""接收客户咨询并保存"""
|
||||
|
||||
Reference in New Issue
Block a user