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 落痕,杜绝静默失败
87 lines
3.4 KiB
Python
87 lines
3.4 KiB
Python
"""AI获客 - 客户咨询接口"""
|
||
from fastapi import APIRouter, HTTPException
|
||
from pydantic import BaseModel
|
||
import json, os, datetime, urllib.request
|
||
|
||
router = APIRouter(prefix="/api/cma/lead", tags=["AI获客"])
|
||
|
||
# 客户咨询通知唯一通道:系统总线中继 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
|
||
phone: str
|
||
requirement: str = ""
|
||
source: str = "sxbh.ltd"
|
||
|
||
def _log_notify(ok, detail):
|
||
"""通知结果落痕(旧实现 except: pass 静默吞异常,导致线索漏通知无人知晓)"""
|
||
try:
|
||
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):
|
||
"""接收客户咨询并保存"""
|
||
record = data.model_dump()
|
||
record["timestamp"] = datetime.datetime.now().isoformat()
|
||
record["status"] = "new"
|
||
|
||
log_dir = "/root/leads"
|
||
os.makedirs(log_dir, exist_ok=True)
|
||
with open(f"{log_dir}/leads.json", "a") as f:
|
||
f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
||
|
||
# 客户咨询通知唯一通道:推任总 DM(经 ai-cs 分流);不再推 8800 全员群
|
||
# (2026-10-22 裁定:8800 仅限内容类群发,客户线索含个人信息,不得进全员群)
|
||
notify_boss(data.name, data.phone, data.requirement)
|
||
|
||
return {"success": True, "message": "咨询已提交,我们将在30分钟内联系您"}
|