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()