import asyncio from typing import List, Dict, Any import concurrent # 添加这行 import concurrent.futures from app.core.crud import CRUDBase from app.schemas.crm import ( CrmCustomerCreate, CrmCustomerUpdate, CrmCustomerCreate, CrmBindInfoCreate, CrmBindInfoUpdate, ) from some_sdk.services import binder as binder_service from some_sdk.lintao_sdk.biz.by_order import get_order_relative_user from app.models.weixin import CrmCustomer, CrmBindInfo from tortoise.expressions import Subquery from tortoise.functions import Count # 导入 Count 函数 import logging logger = logging.getLogger(__name__) def split_generator(iterable, chunk_size=50): """ 将可迭代对象拆分为多个子列表 """ chunk = [] for item in iterable: chunk.append(item) if len(chunk) == chunk_size: yield chunk chunk = [] if chunk: yield chunk class CrmCustomerController(CRUDBase[CrmCustomer, CrmCustomerCreate, CrmCustomerUpdate]): def __init__(self): super().__init__(model=CrmCustomer) async def get_unbound_customers_method_four_alt(self, limit: int = 50): # 创建子查询 bound_ids_subquery = CrmBindInfo.all().values_list('customer_id', flat=True) # 使用正确的子查询语法 unbound_customers = await self.model.filter( cid__not_in=Subquery(bound_ids_subquery) ).limit(limit) return unbound_customers async def bind_crm_all_customer_by_db(self): # 定义一个函数来执行单个同步的 bind_shop_info 调用 def sync_fetch_bind_info(user_data: Dict[str, Any]): # 同步调用 binder_service all_users = [] for crm_user, user_list_from_bind in binder_service.bind_shop_info([user_data]): all_users.extend(user_list_from_bind) return all_users parsed_set = set() while True: # 获取一批未绑定的用户 ORM 对象 unbound_orm_objects = await self.get_unbound_customers_method_four_alt(limit=50) if not unbound_orm_objects: break print(f'获取到 {len(unbound_orm_objects)} 个待绑定的 ORM 对象', flush=True) expected_cids = {obj.cid for obj in unbound_orm_objects if obj.cid is not None and obj.cid not in parsed_set} if not expected_cids: break user_data_list = [await obj.to_dict() for obj in unbound_orm_objects if obj.cid not in parsed_set] [parsed_set.add(cid) for cid in expected_cids] print(f'待绑定用户数(转换后):{len(user_data_list)}', flush=True) await self._bind_crm_all_customer_by_db(user_data_list, expected_cids, sync_fetch_bind_info) async def _bind_crm_all_customer_by_db(self, not_in_db_list: List[Dict[str, Any]], expected_cids: set, sync_fetch_bind_info): print(f'待绑定用户数(传入列表):{len(not_in_db_list)}', flush=True) # --- 并发执行所有同步的 bind_shop_info 调用 --- # 使用 asyncio.to_thread (Python 3.9+) 或 run_in_executor 将同步函数移到线程池执行 # 限制并发线程数很重要,避免创建过多线程 max_workers = 10 # 限制线程池大小,根据系统性能调整 loop = asyncio.get_event_loop() # 创建任务列表 tasks = [] with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: for user_data in not_in_db_list: # 将同步函数提交到线程池执行,并返回一个 Future # asyncio.run_in_executor 将 Future 包装成 awaitable 的协程 task = loop.run_in_executor(executor, sync_fetch_bind_info, user_data) tasks.append(task) # 等待所有线程池任务完成 all_bind_lists_results = await asyncio.gather(*tasks, return_exceptions=True) # 处理结果,将异常和正常结果分开 all_users_to_process = [] for result in all_bind_lists_results: if isinstance(result, Exception): logger.exception(f"Thread pool task failed: {result}") # 可以选择跳过或记录错误 else: all_users_to_process.extend(result) # --- 去重逻辑 --- unique_users_to_process = {} for user in all_users_to_process: customer_id = user.get("customer_id", 0) tenant_name = user.get("tenant_name", 0) platform = user.get("platform", 0) key = f"{customer_id}_{tenant_name}_{platform}" if key not in unique_users_to_process: unique_users_to_process[key] = user print(f"去重后待处理用户数: {len(unique_users_to_process)}", flush=True) # --- 并发处理去重后的用户 --- # 这部分可以保持原有逻辑,因为它已经是并发的了 # 但要注意,如果同时有多个任务尝试插入相同的 customer_id,可能会有并发问题 # 可以考虑使用数据库的 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE 等特性 async def process_user(user_obj: Dict[str, Any]): try: user_type = user_obj.get("type", 0) tenant_name = user_obj.get("tenant_name", 0) customer_id = user_obj.get("customer_id", 0) platform = user_obj.get("platform", 0) if user_type == 'staff': print(f'绑定员工:{user_obj}', flush=True) else: print(f'绑定客户:{user_obj}, Customer ID: {customer_id}', flush=True) # --- 检查数据库中是否已存在绑定 --- existing_bind = await CrmBindInfo.filter(customer_id=customer_id, platform=platform, tenant_name=tenant_name).first() if not existing_bind: bind_instance = CrmBindInfo.create_bind(user_obj) await bind_instance.save() print(f"客户 {customer_id} 绑定成功", flush=True) else: print(f"客户 {customer_id} 已存在绑定", flush=True) logger.warning(f"Attempted to bind customer_id {customer_id} which already exists in CrmBindInfo.") except Exception as e: logger.exception(e) print(f"处理用户 {user_obj.get('customer_id', 'unknown')} 时出错: {e}", flush=True) max_concurrent_db_tasks = 10 # 可以独立控制数据库操作的并发数 semaphore = asyncio.Semaphore(max_concurrent_db_tasks) async def process_user_with_semaphore(user_obj): async with semaphore: return await process_user(user_obj) tasks = [process_user_with_semaphore(user_obj) for user_obj in unique_users_to_process.values()] await asyncio.gather(*tasks, return_exceptions=True) print("当前批次所有用户绑定任务完成", flush=True) async def bind_crm_all_customer(self): """ 绑定CRM客户和微信用户 """ await self.bind_crm_all_customer_by_db() for shop in binder_service.iter_shop(): shopId = shop.get("shopId", "") shopName = shop.get("shopName", "") print(f"开始处理店铺:{shopName}", flush=True) not_in_db_list = [] shop_info = { 'shopId': shopId, 'shopName': shopName, } is_done = False user_list_iter = binder_service.iter_list_trade_user(binder_service.xy_client, shopId=shopId) for user_list in split_generator(user_list_iter): if is_done: break # 提取所有待检查的 cid all_cids = [str(user.get("cid")) for user in user_list] # print(user_list[0]) # break # 只查询 user_list 中存在的 cid in_objs = await self.model.filter(cid__in=all_cids) # 提取已存在的 cid in_db_cid_set = {user.cid for user in in_objs} print(f'已处理用户数:{len(in_db_cid_set)}', flush=True) # 筛选待处理用户 not_in_db_list = [user for user in user_list if user.get("cid") not in in_db_cid_set] for user in not_in_db_list: id = user.pop("id", None) # print('user', user) # if not not_in_db_list: # is_done = True # break print(f'待处理用户数:{len(not_in_db_list)}', flush=True) if not_in_db_list: model_list = [self.model(**CrmCustomerCreate(**user).model_dump(exclude_unset=True)) for user in not_in_db_list] await self.model.bulk_create(model_list) if not not_in_db_list: print(f'店铺:{shopName},无待处理用户', flush=True) continue await self.bind_crm_all_customer_by_db() async def bind_user(self, bind_info: CrmBindInfoCreate): """ 绑定用户 """ bind_info_dict = bind_info.model_dump(exclude_unset=True) platform = bind_info_dict.get("platform", None) platform_id = bind_info_dict.get("platform_id", None) bind_info_dict.pop("id", None) existing_bind = await CrmBindInfo.filter(platform=platform, platform_id=platform_id).first() if existing_bind: await CrmBindInfo.filter(id=existing_bind.id).update(**bind_info_dict) else: obj = CrmBindInfo(**bind_info_dict) await obj.save() return {"message": "用户绑定成功"} crm_customer_controller = CrmCustomerController()