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

236 lines
9.8 KiB
Python

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