129 lines
4.7 KiB
Python
129 lines
4.7 KiB
Python
import asyncio
|
|
import time
|
|
from datetime import datetime
|
|
|
|
|
|
def fire_and_forget(client, coro):
|
|
"""后台执行协程,不阻塞接收循环;异常会记录到日志。"""
|
|
task = asyncio.create_task(coro)
|
|
|
|
def _done(t):
|
|
if t.cancelled():
|
|
return
|
|
exc = t.exception()
|
|
if exc:
|
|
client.logger.exception(f"后台任务异常: {exc}")
|
|
|
|
task.add_done_callback(_done)
|
|
|
|
|
|
def prune_seen(seen: dict, now_mono: float, ttl_sec: float = 8.0):
|
|
if len(seen) <= 2000:
|
|
return
|
|
stale = [k for k, t in seen.items() if (now_mono - t) > ttl_sec]
|
|
for k in stale:
|
|
seen.pop(k, None)
|
|
|
|
|
|
def log_inbound_once(client, data: dict, chat_log_fn):
|
|
"""统一记录入站消息,短窗口去重,避免多分支重复写库。"""
|
|
try:
|
|
cid = data.get("from_id", "")
|
|
if not cid:
|
|
return
|
|
msg = client.to_chinese(data.get("msg", "") or "")
|
|
acc_id = data.get("acc_id", "")
|
|
mtype = int(data.get("msg_type", 0) or 0)
|
|
now_mono = time.monotonic()
|
|
sig = f"{acc_id}|{cid}|{mtype}|{msg}"
|
|
last = client._inbound_log_seen.get(sig, 0.0)
|
|
if (now_mono - last) < 2.0:
|
|
return
|
|
client._inbound_log_seen[sig] = now_mono
|
|
prune_seen(client._inbound_log_seen, now_mono, ttl_sec=8.0)
|
|
chat_log_fn(
|
|
cid,
|
|
msg,
|
|
"in",
|
|
customer_name=client.to_chinese(data.get("from_name", "") or data.get("cy_name", "")),
|
|
acc_id=acc_id,
|
|
platform=data.get("acc_type", ""),
|
|
msg_type=mtype,
|
|
)
|
|
except Exception:
|
|
client.logger.debug("入站消息写库失败", exc_info=True)
|
|
|
|
|
|
def log_outbound_once(client, original_msg: dict, reply_content: str, chat_log_fn):
|
|
"""统一记录出站消息,短窗口去重,避免重复写库。"""
|
|
try:
|
|
cid = original_msg.get("from_id", "")
|
|
if not cid:
|
|
return
|
|
msg = reply_content or ""
|
|
acc_id = original_msg.get("acc_id", "")
|
|
now_mono = time.monotonic()
|
|
sig = f"{acc_id}|{cid}|0|{msg}"
|
|
last = client._outbound_log_seen.get(sig, 0.0)
|
|
if (now_mono - last) < 2.0:
|
|
return
|
|
client._outbound_log_seen[sig] = now_mono
|
|
prune_seen(client._outbound_log_seen, now_mono, ttl_sec=8.0)
|
|
chat_log_fn(
|
|
cid,
|
|
msg,
|
|
"out",
|
|
customer_name=client.to_chinese(original_msg.get("from_name", "") or original_msg.get("cy_name", "")),
|
|
acc_id=acc_id,
|
|
platform=original_msg.get("acc_type", ""),
|
|
msg_type=0,
|
|
)
|
|
except Exception:
|
|
client.logger.debug("出站消息写库失败", exc_info=True)
|
|
|
|
|
|
def build_customer_message(client, data: dict, customer_message_cls):
|
|
"""把原始消息字典转换为 Agent 输入模型。"""
|
|
return customer_message_cls(
|
|
msg_id=data.get("msg_id", ""),
|
|
acc_id=data.get("acc_id", ""),
|
|
msg=client.to_chinese(data.get("msg", "")),
|
|
from_id=data.get("from_id", ""),
|
|
from_name=client.to_chinese(data.get("from_name", "")),
|
|
cy_id=data.get("cy_id", ""),
|
|
acc_type=data.get("acc_type", ""),
|
|
msg_type=data.get("msg_type", 0),
|
|
cy_name=client.to_chinese(data.get("cy_name", "")),
|
|
goods_name=client.to_chinese(data.get("goods_name", "")) if data.get("goods_name") else None,
|
|
goods_order=client.to_chinese(data.get("goods_order", "")) if data.get("goods_order") else None,
|
|
)
|
|
|
|
|
|
def touch_customer_last_contact(client, customer_id: str, db):
|
|
"""兜底更新客户最后联系时间。"""
|
|
if not customer_id:
|
|
return
|
|
try:
|
|
profile = db.get_customer(customer_id)
|
|
profile.last_contact = datetime.now().isoformat()
|
|
db.save_customer(profile)
|
|
except Exception:
|
|
client.logger.debug("更新客户最后联系时间失败: customer_id=%s", customer_id, exc_info=True)
|
|
|
|
|
|
def push_chat_to_wechat_safe(client, *, data: dict, customer_msg: str, reply_msg: str, tag: str, goods_name: str = ""):
|
|
"""异步推送企微聊天日志,失败不影响主流程。"""
|
|
try:
|
|
from utils.wechat_chat_log import push_chat_to_wechat
|
|
|
|
asyncio.create_task(push_chat_to_wechat(
|
|
customer_name=client.to_chinese(data.get("from_name", "") or data.get("cy_name", "")),
|
|
customer_id=data.get("from_id", ""),
|
|
acc_id=data.get("acc_id", ""),
|
|
customer_msg=client.to_chinese(customer_msg or ""),
|
|
reply_msg=reply_msg or "",
|
|
goods_name=goods_name or client.to_chinese(data.get("goods_name", "") or ""),
|
|
))
|
|
except Exception:
|
|
client.logger.debug("推送企微聊天日志失败(%s)", tag, exc_info=True)
|