first commit

This commit is contained in:
2026-06-25 17:41:06 +08:00
commit b2f23a933b
370 changed files with 30526 additions and 0 deletions
+461
View File
@@ -0,0 +1,461 @@
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()