Files
vue-fastapi-admin/app/controllers/msg.py
T
2026-06-25 17:41:06 +08:00

462 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from tortoise.exceptions import IntegrityError
from app.core.crud import CRUDBase
from app.schemas.msg import (
MsgCreate,
MsgUpdate,
MsgNewOrder,
FollowFormErp,
MsgType
)
import httpx
from app.models.msg import Msg, Follow
from app.models.weixin import WeixinCustomer, WeixinUser
from some_sdk.wk_weixin_sdk.apis.extern_user import get_external_user_chat_info
from some_sdk.services.binder import lintao_client, feishu_client
from some_sdk.lintao_sdk.biz.by_order import get_order_relative_user
from some_sdk.feishu_sdk.apis.doc import batch_create as create_feishu_records
from datetime import datetime, timedelta
from app.utils.common import async_split_generator, split_generator, gen_random_str
from typing import List
import re
import hashlib
import logging
logger = logging.getLogger(__name__)
import asyncio
_sync_to_feishu_lock = asyncio.Lock()
class MsgController(CRUDBase[Msg, MsgCreate, MsgUpdate]):
def __init__(self):
super().__init__(model=Msg)
async def send_to_wexin(self, data:dict ):
# pass
url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=57ba9cd5-2c62-43dd-bfea-78c7b4073128'
async with httpx.AsyncClient() as client:
resp = await client.post(url,
headers={"Content-Type": "application/json"},
json=data,
)
data = resp.json()
print(data)
return data
async def send_order_to_weixin(self, msg: Msg):
"""
极简订单通知:仅展示店铺、旺旺ID、金额、归属客服
"""
url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=57ba9cd5-2c62-43dd-bfea-78c7b4073128'
order_list = msg.detail or []
if not order_list:
logger.warning("订单详情为空,跳过发送")
return
order = order_list[0]
# users = order.get("users", [])
# buyer = next((u for u in users if u.get("role") == "buyer"), None)
# 关键字段
shop_name = order.get("shop_name", "未知店铺")
buyer_name = msg.title
# buyer_name = buyer.get("name") if buyer else ""
# buyer_name = buyer_name or msg.content.split(")", 1)[0].split("(")[1]
is_refund = order.get("is_refund", 0) > 0
msg_title = "💰 用户退款" if is_refund else "🛒 新订单"
real_payment = order.get("real_payment", "0.00")
qiwei_name = msg.owner_name or "未知客服"
# 极简 Markdown
markdown_content = f"""# {msg_title}\n
**店铺**{shop_name}
**旺旺ID**`{buyer_name}`
**金额**:¥{real_payment}
**企微客服**{qiwei_name}
**订单编号**{order.get("trade_no", "未知订单号")}
"""
data = {
"msgtype": "markdown",
"markdown": {
"content": markdown_content
}
}
result = await self.send_to_wexin(data)
logger.info(f"企业微信极简订单通知发送结果: {result}")
async def parse_order_msg(self, msg: Msg, send_set: set, msg_dict: dict):
title = msg.title
try:
if title not in send_set:
send_set.add(title)
# try: await self.send_to_wexin(msg)
owner_name = ''.join([ (name or '').split('')[0].split('vip客服-')[-1].split('-印刷定制')[0] for name in msg_dict[title] ])msg.owner_name = owner_name
try: await self.send_order_to_weixin(msg)
except Exception as e:
logger.error(f'发送订单消息到微信失败,{e}')
return
# await self.send_to_wexin(msg, mentioned_list=msg_dict[title])
logger.info(f'发送消息到微信成功,{msg.content}')
msg.is_send = True
msg.send_at = datetime.now()
await msg.save()
except Exception as e:
logger.error(f'发送消息到微信失败,{e}')
async def new_system_msg(self, title: str, content: str, owner_name: str = None):
await self.create(
dict(
hash_id=hashlib.md5(f'{title}{content}{owner_name}'.encode()).hexdigest(),
title=title,
content=content,
type=MsgType.SYSTEM,
owner_name=owner_name,
)
)
async def send_system_msg(self, msg: Msg):
msg.send_at = datetime.now()
markdown_content = f"""# 📣 系统通知 \n
## {msg.title} \n
{msg.content} \n
{msg.send_at.strftime("%Y-%m-%d %H:%M:%S")}
"""
data = {
"msgtype": "markdown",
"markdown": {
"content": markdown_content
}
}
result = await self.send_to_wexin(data)
logger.info(f"企业微信极简订单通知发送结果: {result}")
msg.is_send = True
await msg.save()
async def sync_msg(self):
msg_list = await self.model.filter(is_send=False)
msg_dict = {}
for msg in msg_list:
title = msg.title
msg_dict[title] = msg_dict.get(title, []) + [msg.owner_name]
send_set = set()
for msg in msg_list:
title = msg.title
if msg.type == MsgType.NEW_ORDER:
await self.parse_order_msg(msg, send_set, msg_dict)
elif msg.type == MsgType.SYSTEM:
await self.send_system_msg(msg)
async def fill_customer_info(self):
msg_orm_list = await self.model.filter(id__gt=306)
update_list = []
for msg in msg_orm_list:
detail = msg.detail or []
taobao_name = msg.title
detail_list = []
for order_item in detail:
customer_orm = await WeixinCustomer.filter(taobao_name=taobao_name).first()
if not customer_orm:
logger.warning(f'未找到淘宝用户{taobao_name}的企微客户')
continue
for user in order_item.get("users", []):
if user.get("role") != "buyer": continue
user.update(customer_orm.to_dict())
break
detail_list.append(order_item)
if detail_list:
msg.detail = detail_list
update_list.append(msg)
if update_list:
await self.model.bulk_update(update_list, fields=['detail'])
async def new_order(self, order_id: str, customer_orm: WeixinCustomer, is_refund: bool = False):
msg_set = set()
msg_list = []
now = datetime.now()
order_list = get_order_relative_user(lintao_client, order_id)
order_unique_list = {}
async for order in order_list:
order_state_string = order.get('order_state_string')
trade_no = order.get('trade_no')
if not is_refund and order_state_string not in ['待领单', '待抢单']:
logger.warning(f'订单{trade_no} 状态为{order_state_string},跳过')
continue
remark = order.get('remark')
if not is_refund and remark and '企微联系' in remark:
logger.warning(f'订单{trade_no} 状态为{order_state_string} 备注:{remark}, 已备注企微联系,跳过')
continue
create_time = order.get('create_time')
if create_time and not is_refund:
create_time = datetime.fromisoformat(create_time.replace("Z", "+00:00"))
if (now - create_time).total_seconds() > 3600:
logger.warning(f'订单{order_id} 创建时间({create_time})与当前时间({now})相差超过60秒,跳过')
continue
if trade_no not in order_unique_list:
order_unique_list[trade_no] = order
logger.info(f'订单{order_id} 订单号{trade_no} 创建时间{create_time} 订单状态:{order_state_string} {order.get("title")}')
# 填充买家信息
for user in order.get("users", []):
if user.get("role") != "buyer": continue
user.update(customer_orm.to_dict())
break
if not order_unique_list:
logger.warning(f'订单{order_id} 没有符合条件的订单,跳过')
return
chat_info = await get_external_user_chat_info(customer_orm.weixin_id)
follow_user_list = chat_info.get('follow_user', [])
order_list = list(order_unique_list.values())
order_info = [
f"在({order.get('shop_name')})下单{order.get('price')}元,单号({order.get('trade_no')})"
for order in order_list
]
for follow_user in follow_user_list:
follow_userid = follow_user.get('userid')
weixin_user_orm: WeixinUser = await WeixinUser.filter(userid=follow_userid).first()
owner_name = '未知'
if weixin_user_orm: owner_name = weixin_user_orm.english_name or weixin_user_orm.username
qiwei_customer_name = customer_orm.taobao_name or customer_orm.weixin_name
content = f"{'退款' if is_refund else ''}订单({qiwei_customer_name}) -({owner_name})- 刚{', '.join(order_info)}"
logger.info(f'新订单通知:{content}')
msg_hash_id = hashlib.md5(content.encode('utf-8')).hexdigest()
if msg_hash_id in msg_set: continue
msg_set.add(msg_hash_id)
msg = self.model(
hash_id=msg_hash_id,
title=qiwei_customer_name,
content=content,
detail=order_list,
type=MsgType.NEW_ORDER,
owner_id=follow_userid,
owner_name=owner_name
)
msg_list.append(msg)
if msg_list:
try: await self.model.bulk_create(msg_list)
except IntegrityError as e:
if "Duplicate entry" in str(e) and "hash_id" in str(e):
logger.info("检测到重复消息,已跳过")
# 忽略或处理重复
else:
# 其他完整性错误(如外键失败),应重新抛出
raise
async def get_order_user_days_before(self, days: int):
now = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
start_date, otherMemo = (now - timedelta(days=days)).strftime("%Y-%m-%d %H:%M:%S"), "企微"
# 琪琪——松屿,小小丽——麦子,小小北——大喜,小泉——苏苏,小洋——东东,蓝莓——小满
index = 0
update_list, add_list = [], []
order_iter_list = [
(get_order_relative_user(lintao_client, start_date=start_date, otherMemo=otherMemo), ''),
(get_order_relative_user(lintao_client, start_date=start_date, customer='琪琪'), '松屿'),
(get_order_relative_user(lintao_client, start_date=start_date, customer='小小丽'), '麦子'),
(get_order_relative_user(lintao_client, start_date=start_date, customer='小小北'), '大喜'),
(get_order_relative_user(lintao_client, start_date=start_date, customer='小泉'), '苏苏'),
(get_order_relative_user(lintao_client, start_date=start_date, customer='小洋'), '东东'),
(get_order_relative_user(lintao_client, start_date=start_date, customer='蓝莓'), '小满'),
]
for order_iter, staff_name in order_iter_list:
async for order_list in async_split_generator(order_iter, 10):
order_dict = {}
for order in order_list:
designer_list = list(filter(lambda x: x.get("role") == "desiger", order.get("users", [])))
designer = designer_list[0] if designer_list else None
if not designer: continue
name = designer.get("name", "")
if not name: continue
key = tuple([order.get("trade_no", ""), name])
if key in order_dict: continue
order_dict[key] = order
index += 1
_update_list, _add_list = await self.process_order_dict(order_dict, staff_name=staff_name)
if _update_list and _add_list:
logger.info(f'{index}个订单 处理 {len(_update_list)} 条更新记录, {len(_add_list)} 条新增记录')
update_list.extend(_update_list)
add_list.extend(_add_list)
print(start_date)
_update_list, _add_list = [], []
async with _sync_to_feishu_lock:
task_id = gen_random_str()
_update_list, _add_list = await self.sync_to_feishu(task_id)
return {
"update_count": len(update_list),
"add_count": len(add_list),
"add_count_to_feishu": len(_add_list),
"update_count_to_feishu": len(_update_list),
"start_date": start_date,
"remark": otherMemo
}
async def process_order_dict(self, order_dict: dict, staff_name: str = ''):
async def to_follow_orm(order_info: dict):
follow_orm = Follow(**FollowFormErp(**order_info).model_dump())
# print(follow_orm.order_id, order_info.get("users", []))
for user in order_info.get("users", []):
if user.get("role") == "desiger":
follow_orm.designer_id = user.get("id", "")
follow_orm.designer_name = user.get("name", "")
elif user.get("role") == "taobao_kefu":
follow_orm.kefu_id = user.get("id", "")
follow_orm.kefu_name = user.get("name", "")
elif user.get("role") == "buyer":
follow_orm.customer_taobao_id = user.get("id", "")
follow_orm.customer_name = user.get("name", "")
CustomerUser = await WeixinCustomer.filter(taobao_id=follow_orm.customer_taobao_id).first()
if CustomerUser:
follow_orm.customer_id = CustomerUser.id
# 企微员工
if staff_name:
follow_orm.staff_name = staff_name
staff_orm = await WeixinUser.filter(english_name=f"{follow_orm.staff_name}-印刷定制").first()
if staff_orm:
follow_orm.staff_id = staff_orm.userid
else:
remark = follow_orm.remark
if remark:
staff_name_match = re.search(r"企微联系(.*?)拉群", remark)
if staff_name_match:
follow_orm.staff_name = staff_name_match.group(1).strip()
if follow_orm.staff_name:
staff_orm = await WeixinUser.filter(english_name=f"{follow_orm.staff_name}-印刷定制").first()
if staff_orm:
follow_orm.staff_id = staff_orm.userid
if not staff_name_match:
follow_orm.staff_name = remark
return follow_orm
update_list = []
follow_list = await Follow.filter(order_id__in=[key[0] for key in order_dict.keys()])
keys, update_keys = Follow.get_all_keys(exclude_fields=['id', 'updated_at', 'created_at', 'feishu_record_id', 'is_update_to_feishu', 'is_add_to_feishu']), set()
for follow in follow_list:
key = tuple([follow.order_id, follow.designer_name])
if key not in order_dict: continue
order_info = order_dict.pop(key, None)
follow_orm = await to_follow_orm(order_info)
# 比较是否一样,不一样的话,更新
updated_dict = {}
for key in keys:
origin_value = str(getattr(follow, key))
value = str(getattr(follow_orm, key))
if origin_value != value:
update_keys.add(key)
setattr(follow, key, value)
updated_dict[key] = f'{origin_value} -> {value}'
if updated_dict:
follow_orm.is_update_to_feishu = False
update_keys.add('is_update_to_feishu')
update_list.append(follow)
logger.info(f'订单 {follow.order_id} 设计员 {follow.designer_name} 更新字段 {updated_dict}')
if update_list:
await Follow.bulk_update(update_list, fields=update_keys)
add_list = []
for key, order_info in order_dict.items():
follow_orm = await to_follow_orm(order_info)
add_list.append(follow_orm)
if add_list:
await Follow.bulk_create(add_list)
return update_list, add_list
async def sync_to_feishu(self, task_id: str):
add_list = []
# 需要追加的记录
follow_list = await Follow.filter(is_update_to_feishu=False, feishu_record_id__isnull=True)
if follow_list:
for item_list in split_generator(follow_list, 100):
result = await create_feishu_records(
feishu_client,
app_token="HPiNbbW3YaYhBjsjLQychK9gnbf",
table_id="tblQGVfmglJgZuBf",
records=[{"fields": item.to_feishu_record(fields={"同步任务号": task_id})["fields"]} for item in item_list],
)
_update_list = []
result_list = result.get("data", {}).get("records", [])
for record, item in zip(result_list, item_list):
item.feishu_record_id = record["record_id"]
item.is_add_to_feishu = True
item.is_update_to_feishu = True
_update_list.append(item)
if _update_list:
await Follow.bulk_update(_update_list, fields=['feishu_record_id', 'is_add_to_feishu', 'is_update_to_feishu'])
logger.info(f'追加 {len(_update_list)} 条记录到飞书')
add_list.extend(_update_list)
update_list = []
# 需要更新的记录
# follow_list = await Follow.filter(is_update_to_feishu=False, feishu_record_id__isnull=False)
# if follow_list:
# for item_list in split_generator(follow_list, 100):
# await create_feishu_records(
# get_feishu_client,
# app_token="JTqnbdjb7aEMm3srrLfcidYcnxc",
# table_id="tblXq8AmG22uK5n1",
# records=item_list,
# )
return update_list, add_list
async def set_read(self, id: str):
await Msg.filter(id=id).update(is_read=True, read_at=datetime.now())
msg_controller = MsgController()