# utils/chat_utils.py import json import logging import time import requests from django.conf import settings from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia, Liaotian from peizhi.models import ClubConfig from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia logger = logging.getLogger('chat_utils') ORDER_CARD_PREFIX = '[ORDER_CARD]' def make_dashou_shangjia_group_id(dashou_yonghuid, shangjia_yonghuid): return f"group_Ds{dashou_yonghuid}_Sj{shangjia_yonghuid}" def make_dashou_boss_group_id(dashou_yonghuid, boss_yonghuid): return f"group_Ds{dashou_yonghuid}_Boss{boss_yonghuid}" def _get_shangjia_id_from_order(order): try: return order.shangjia_kuozhan.shangjia_id except Exception: ext = DingdanShangjia.objects.filter(dingdan__dingdan_id=order.dingdan_id).first() return ext.shangjia_id if ext else None def _get_boss_id_from_order(order): try: return order.pingtai_kuozhan.laoban_id except Exception: ext = DingdanPingtai.objects.filter(dingdan__dingdan_id=order.dingdan_id).first() return ext.laoban_id if ext else None def resolve_group_id_for_order(order, dashou_yonghuid=None): """打手+商家/老板配对群 ID;跨平台或缺信息时回退订单群 ID""" dashou_yonghuid = dashou_yonghuid or order.jiedan_dashou_id if order.is_cross == 1 and order.dispatch_type == 2: return f"group_{order.dingdan_id}" if not dashou_yonghuid: return f"group_{order.dingdan_id}" if order.fadan_pingtai == 2: shangjia_id = _get_shangjia_id_from_order(order) if shangjia_id: return make_dashou_shangjia_group_id(dashou_yonghuid, shangjia_id) elif order.fadan_pingtai == 1: boss_id = _get_boss_id_from_order(order) if boss_id: return make_dashou_boss_group_id(dashou_yonghuid, boss_id) return f"group_{order.dingdan_id}" def _build_order_card_text(order): payload = { 'dingdan_id': order.dingdan_id, 'jieshao': order.jieshao or '', 'beizhu': order.beizhu or '', 'jine': str(order.jine), 'zhuangtai': order.zhuangtai, 'nicheng': order.nicheng or '', } if order.create_time: payload['create_time'] = order.create_time.strftime('%Y-%m-%d %H:%M:%S') return ORDER_CARD_PREFIX + json.dumps(payload, ensure_ascii=False) # ========== 辅助配置获取(从 ClubConfig) ========== def _get_self_club_config(): if not hasattr(_get_self_club_config, '_config'): try: _get_self_club_config._config = ClubConfig.objects.filter(is_self=1).first() except Exception as e: logger.warning(f"获取我方俱乐部配置失败: {e}") _get_self_club_config._config = None return _get_self_club_config._config def _get_self_goeasy_appkey(): cfg = _get_self_club_config() if cfg and cfg.chat_api_key: return cfg.chat_api_key return getattr(settings, 'GOEASY_APPKEY', '') def _get_self_goeasy_secret(): cfg = _get_self_club_config() if cfg and cfg.chat_api_id: return cfg.chat_api_id return getattr(settings, 'GOEASY_SECRET', '') def _get_self_storage_domain(): cfg = _get_self_club_config() if cfg and cfg.storage_bucket_domain: return cfg.storage_bucket_domain return '' def _get_self_default_avatar(): cfg = _get_self_club_config() if cfg and cfg.club_avatar: return cfg.club_avatar return '' # ========== 头像拼接 ========== def _full_local_avatar(relative_url): storage_domain = _get_self_storage_domain() default_avatar = _get_self_default_avatar() if not relative_url: return default_avatar or '' if relative_url.startswith('http'): return relative_url if storage_domain.endswith('/'): return storage_domain + relative_url.lstrip('/') return storage_domain + '/' + relative_url if storage_domain else relative_url def _full_remote_avatar(storage_bucket_domain, relative_url): if not relative_url: return '' if relative_url.startswith('http'): return relative_url if storage_bucket_domain.endswith('/'): return storage_bucket_domain + relative_url.lstrip('/') return storage_bucket_domain + '/' + relative_url if storage_bucket_domain else relative_url # ========== GoEasy 订阅 ========== def _subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None): if not appkey: appkey = _get_self_goeasy_appkey() if not secret: secret = _get_self_goeasy_secret() if not appkey: logger.error("GoEasy AppKey 未配置") return False url = 'https://rest-hangzhou.goeasy.io/v2/im/subscribe-groups' body = {"appkey": appkey, "userIds": user_ids, "groupIds": group_ids} headers = {"Content-Type": "application/json"} if secret: headers["Authorization"] = f"Bearer {secret}" try: resp = requests.post(url, headers=headers, json=body, timeout=10) if resp.status_code == 200: return True logger.error(f"订阅失败: {resp.status_code} {resp.text}") return False except Exception as e: logger.error(f"订阅异常: {e}", exc_info=True) return False def _goeasy_to_yonghuid(goeasy_id): if not goeasy_id: return '' if goeasy_id.startswith('Boss'): return goeasy_id[4:] if goeasy_id.startswith('Ds') or goeasy_id.startswith('Sj'): return goeasy_id[2:] return goeasy_id def _ensure_avatar_url(avatar_url): """头像为空时返回俱乐部默认头像(完整 URL)""" return avatar_url or _full_local_avatar('') def _build_pair_group_to_data(dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar, order_id=None, order_desc=None, is_cross=0, viewer_goeasy_id=None): """群会话 to.data:同时写入打手/对方双方 ID、昵称、头像,前端按当前身份取对方展示""" dashou_avatar = _ensure_avatar_url(dashou_avatar) partner_avatar = _ensure_avatar_url(partner_avatar) dashou_uid = _goeasy_to_yonghuid(dashou_goeasy_id) partner_uid = _goeasy_to_yonghuid(partner_goeasy_id) dashou_name = dashou_name or f'打手{dashou_uid}' partner_name = partner_name or f'用户{partner_uid}' to_data = { 'dashouGoEasyId': dashou_goeasy_id, 'partnerGoEasyId': partner_goeasy_id, 'dashouYonghuid': dashou_uid, 'partnerYonghuid': partner_uid, 'dashouName': dashou_name, 'partnerName': partner_name, 'dashouAvatar': dashou_avatar, 'partnerAvatar': partner_avatar, 'isCross': is_cross, } if order_id: to_data['orderId'] = order_id if order_desc: to_data['orderDesc'] = order_desc[:300] if viewer_goeasy_id == dashou_goeasy_id: to_data['name'] = partner_name to_data['avatar'] = partner_avatar elif viewer_goeasy_id == partner_goeasy_id: to_data['name'] = dashou_name to_data['avatar'] = dashou_avatar else: to_data['name'] = partner_name to_data['avatar'] = partner_avatar return to_data def build_pair_to_data_for_order(order, viewer_goeasy_id=None): dashou_uid = order.jiedan_dashou_id if not dashou_uid: return None dashou_goeasy_id = f'Ds{dashou_uid}' dashou_name, dashou_avatar = _resolve_goeasy_user_display(dashou_goeasy_id) partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order) if not partner_goeasy_id: return None order_desc = (order.jieshao or '').strip() if order.beizhu: order_desc = f"{order_desc} | 备注:{order.beizhu}" if order_desc else f"备注:{order.beizhu}" return _build_pair_group_to_data( dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar, order_id=order.dingdan_id, order_desc=order_desc, is_cross=order.is_cross or 0, viewer_goeasy_id=viewer_goeasy_id, ) # ========== 发送群消息(完整参数) ========== def _send_group_message(appkey, secret, group_id, sender_id, sender_name, sender_avatar, message_text, custom_payload=None, group_name=None, order_id=None, is_cross=0, list_avatar=None, order_desc=None, to_data_extra=None): if not appkey: appkey = _get_self_goeasy_appkey() if not secret: secret = _get_self_goeasy_secret() if not appkey: logger.error("GoEasy AppKey 未配置") return False url = 'https://rest-hangzhou.goeasy.io/v2/im/message' sender_avatar = _ensure_avatar_url(sender_avatar) # to.data:优先使用配对群完整元数据(双方头像/昵称) if to_data_extra: to_data = dict(to_data_extra) if order_id and 'orderId' not in to_data: to_data['orderId'] = order_id if order_desc and 'orderDesc' not in to_data: to_data['orderDesc'] = order_desc[:300] else: to_data = { "name": group_name or group_id, "avatar": _ensure_avatar_url(list_avatar or sender_avatar), } if order_id: to_data["orderId"] = order_id if order_desc: to_data["orderDesc"] = order_desc[:300] to_data["isCross"] = is_cross payload_str = custom_payload.get("text", message_text) if custom_payload else message_text request_body = { "appkey": appkey, "senderId": sender_id, "senderData": {"avatar": sender_avatar, "name": sender_name or sender_id}, "to": { "type": "group", "id": group_id, "data": to_data }, "type": "text", "payload": payload_str } headers = {"Content-Type": "application/json"} if secret: headers["Authorization"] = f"Bearer {secret}" try: resp = requests.post(url, headers=headers, json=request_body, timeout=10) if resp.status_code == 200: return True logger.error(f"群聊消息发送失败,状态码:{resp.status_code},响应:{resp.text}") return False except Exception as e: logger.error(f"发送群聊消息异常: {e}", exc_info=True) return False def _retry_call(func, max_retries=5, delay=0.4, *args, **kwargs): """对 GoEasy 等外部调用做短重试,提高抢单后建聊成功率""" for attempt in range(1, max_retries + 1): try: if func(*args, **kwargs): return True except Exception as e: logger.warning(f"{func.__name__} 第{attempt}次失败: {e}") if attempt < max_retries: time.sleep(delay * attempt) return False def _send_private_init_message(appkey, secret, sender_id, sender_name, sender_avatar, receiver_id, receiver_name, receiver_avatar, message_text): if not appkey: appkey = _get_self_goeasy_appkey() if not secret: secret = _get_self_goeasy_secret() if not appkey: return False url = 'https://rest-hangzhou.goeasy.io/v2/im/message' request_body = { "appkey": appkey, "senderId": sender_id, "senderData": {"avatar": sender_avatar or "", "name": sender_name or sender_id}, "to": { "type": "private", "id": receiver_id, "data": {"avatar": receiver_avatar or "", "name": receiver_name or receiver_id} }, "type": "text", "payload": message_text } headers = {"Content-Type": "application/json"} if secret: headers["Authorization"] = f"Bearer {secret}" try: resp = requests.post(url, headers=headers, json=request_body, timeout=10) if resp.status_code == 200: return True logger.error(f"私聊初始化消息失败: {resp.status_code} {resp.text}") return False except Exception as e: logger.error(f"私聊初始化消息异常: {e}", exc_info=True) return False def _ensure_liaotian_record(dingdan_id, partner_goeasy_id, dashou_goeasy_id): try: Liaotian.objects.update_or_create( dingdan_id=dingdan_id, defaults={ 'user1_id': partner_goeasy_id, 'user2_id': dashou_goeasy_id, 'zhuangtai': 1, } ) return True except Exception as e: logger.error(f"写入 Liaotian 记录失败 order={dingdan_id}: {e}", exc_info=True) return False def _establish_private_chat(appkey, secret, dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar): msg_dashou = "订单已接单,可在此处沟通。" msg_partner = "打手已接单,可在此处沟通。" ok1 = _retry_call( _send_private_init_message, 3, 0.4, appkey, secret, dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar, msg_dashou ) ok2 = _retry_call( _send_private_init_message, 3, 0.4, appkey, secret, partner_goeasy_id, partner_name, partner_avatar, dashou_goeasy_id, dashou_name, dashou_avatar, msg_partner ) return ok1 or ok2 def _resolve_goeasy_user_display(goeasy_id): if not goeasy_id: return "用户", _full_local_avatar('') avatar = _full_local_avatar('') name = f"用户{goeasy_id[-6:]}" try: if goeasy_id.startswith('Boss'): uid = goeasy_id[4:] boss_user = UserMain.objects.get(yonghuid=uid) avatar = _full_local_avatar(boss_user.avatar or '') boss_profile = UserBoss.objects.get(user=boss_user) if boss_profile.nickname: name = boss_profile.nickname elif goeasy_id.startswith('Sj'): uid = goeasy_id[2:] sj_user = UserMain.objects.get(yonghuid=uid) avatar = _full_local_avatar(sj_user.avatar or '') try: sj_profile = UserShangjia.objects.get(user=sj_user) if sj_profile.nicheng: name = sj_profile.nicheng except Exception: pass elif goeasy_id.startswith('Ds'): uid = goeasy_id[2:] ds_user = UserMain.objects.get(yonghuid=uid) avatar = _full_local_avatar(ds_user.avatar or '') ds_profile = UserDashou.objects.get(user=ds_user) if ds_profile.nicheng: name = ds_profile.nicheng except Exception as e: logger.warning(f"解析 GoEasy 用户展示信息失败 {goeasy_id}: {e}") return name, avatar # ========== 核心入口 ========== def establish_order_chat(dingdan_id): try: order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: logger.error(f"订单 {dingdan_id} 不存在") return False dashou_uid = order.jiedan_dashou_id dashou_goeasy_id = f"Ds{dashou_uid}" dashou_nickname = f'打手{dashou_uid[:6]}' dashou_avatar_relative = '' try: dashou_user = UserMain.objects.get(yonghuid=dashou_uid) dashou_avatar_relative = dashou_user.avatar or '' dashou_profile = UserDashou.objects.get(user=dashou_user) if dashou_profile.nicheng: dashou_nickname = dashou_profile.nicheng except Exception as e: logger.warning(f"获取打手信息失败: {e}") dashou_avatar = _full_local_avatar(dashou_avatar_relative) appkey = _get_self_goeasy_appkey() secret = _get_self_goeasy_secret() if not appkey: logger.error("GoEasy AppKey 未配置") return False # 判断是否跨平台 if order.is_cross != 1: return _handle_local_order(order, dashou_goeasy_id, dashou_nickname, dashou_avatar, appkey, secret) else: if order.dispatch_type == 1: # 我方派单,与普通订单处理一致 return _handle_local_order(order, dashou_goeasy_id, dashou_nickname, dashou_avatar, appkey, secret) elif order.dispatch_type == 2: return _handle_cross_partner_order(order, dashou_goeasy_id, dashou_nickname, dashou_avatar, appkey, secret) else: return False def establish_order_chat_with_retry(dingdan_id, max_rounds=3): """抢单后建聊入口:多轮重试,尽量保证建立成功""" for round_num in range(1, max_rounds + 1): try: if establish_order_chat(dingdan_id): logger.info(f"订单 {dingdan_id} 建聊成功(第{round_num}轮)") return True except Exception as e: logger.error(f"订单 {dingdan_id} 建聊第{round_num}轮异常: {e}", exc_info=True) if round_num < max_rounds: time.sleep(0.8 * round_num) logger.error(f"订单 {dingdan_id} 建聊最终失败") return False # ========== 普通订单 / 我方派单 ========== def _push_order_card_message(appkey, secret, order, group_id, sender_id, sender_name, sender_avatar, to_data_extra): card_text = _build_order_card_text(order) return _send_group_message( appkey, secret, group_id, sender_id, sender_name, sender_avatar, card_text, None, order_id=order.dingdan_id, is_cross=order.is_cross or 0, to_data_extra=to_data_extra, ) def prepare_order_group_chat(dingdan_id, caller_goeasy_id, push_order_card=False): """ 订单详情进入聊天前:确保配对群已订阅,可选推送订单卡片。 返回前端跳转所需字段 dict,失败返回 None。 """ try: order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: logger.error(f"prepare_order_group_chat: 订单 {dingdan_id} 不存在") return None dashou_uid = order.jiedan_dashou_id if not dashou_uid: logger.error(f"prepare_order_group_chat: 订单 {dingdan_id} 无接单打手") return None dashou_goeasy_id = f"Ds{dashou_uid}" partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order) if not partner_goeasy_id: logger.error(f"prepare_order_group_chat: 订单 {dingdan_id} 无下单方") return None group_id = resolve_group_id_for_order(order, dashou_uid) dashou_name, dashou_avatar = _resolve_goeasy_user_display(dashou_goeasy_id) if caller_goeasy_id == dashou_goeasy_id: counterpart_id = partner_goeasy_id counterpart_name = partner_name counterpart_avatar = partner_avatar elif caller_goeasy_id == partner_goeasy_id: counterpart_id = dashou_goeasy_id counterpart_name = dashou_name counterpart_avatar = dashou_avatar else: logger.warning(f"prepare_order_group_chat: caller {caller_goeasy_id} 不属于订单 {dingdan_id}") return None appkey = _get_self_goeasy_appkey() secret = _get_self_goeasy_secret() if not appkey: return None order_desc = (order.jieshao or '').strip() if order.beizhu: order_desc = f"{order_desc} | 备注:{order.beizhu}" if order_desc else f"备注:{order.beizhu}" display_group_name = counterpart_name or '聊天' pair_to_data = _build_pair_group_to_data( dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar, order_id=order.dingdan_id, order_desc=order_desc, is_cross=1 if order.is_cross == 1 else 0, viewer_goeasy_id=caller_goeasy_id, ) user_ids = list({dashou_goeasy_id, partner_goeasy_id, caller_goeasy_id}) subscribe_ok = _retry_call( _subscribe_users_to_group, 5, 0.4, user_ids, [group_id], appkey, secret ) card_ok = False if push_order_card: caller_name, caller_avatar = _resolve_goeasy_user_display(caller_goeasy_id) card_ok = _retry_call( _push_order_card_message, 3, 0.4, appkey, secret, order, group_id, caller_goeasy_id, caller_name, caller_avatar, pair_to_data, ) _ensure_liaotian_record(group_id, partner_goeasy_id, dashou_goeasy_id) return { 'groupId': group_id, 'orderId': order.dingdan_id, 'groupName': display_group_name, 'groupAvatar': counterpart_avatar or _full_local_avatar(''), 'counterpartId': counterpart_id, 'counterpartName': counterpart_name, 'counterpartAvatar': counterpart_avatar or _full_local_avatar(''), 'counterpartYonghuid': _goeasy_to_yonghuid(counterpart_id), 'dashouGoEasyId': dashou_goeasy_id, 'partnerGoEasyId': partner_goeasy_id, 'dashouYonghuid': dashou_uid, 'partnerYonghuid': _goeasy_to_yonghuid(partner_goeasy_id), 'dashouName': dashou_name, 'partnerName': partner_name, 'dashouAvatar': dashou_avatar or _full_local_avatar(''), 'partnerAvatar': partner_avatar or _full_local_avatar(''), 'orderDesc': order_desc, 'orderJine': str(order.jine), 'orderZhuangtai': order.zhuangtai, 'isCross': 1 if order.is_cross == 1 else 0, 'subscribeOk': subscribe_ok, 'orderCardPushed': card_ok, } def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret): dashou_uid = dashou_goeasy_id[2:] if dashou_goeasy_id.startswith('Ds') else dashou_goeasy_id group_id = resolve_group_id_for_order(order, dashou_uid) partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order) group_name = partner_name or f"用户{partner_goeasy_id[-6:] if partner_goeasy_id else ''}" order_desc = (order.jieshao or '').strip() if order.beizhu: order_desc = f"{order_desc} | 备注:{order.beizhu}" if order_desc else f"备注:{order.beizhu}" if not partner_goeasy_id: logger.error(f"订单 {order.dingdan_id} 无法获取下单方 GoEasy 标识") subscribe_ok = _retry_call( _subscribe_users_to_group, 5, 0.4, [dashou_goeasy_id], [group_id], appkey, secret ) fallback_to_data = { 'name': dashou_name, 'avatar': _ensure_avatar_url(dashou_avatar), 'dashouGoEasyId': dashou_goeasy_id, 'dashouYonghuid': dashou_uid, 'dashouName': dashou_name, 'dashouAvatar': _ensure_avatar_url(dashou_avatar), 'orderId': order.dingdan_id, 'orderDesc': order_desc, 'isCross': order.is_cross or 0, } msg_ok = _retry_call( _send_group_message, 3, 0.4, appkey, secret, group_id, dashou_goeasy_id, dashou_name, dashou_avatar, "订单已接单,请等待对方上线。", None, order_id=order.dingdan_id, is_cross=order.is_cross, to_data_extra=fallback_to_data, ) return subscribe_ok or msg_ok pair_to_data = _build_pair_group_to_data( dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar, order_id=order.dingdan_id, order_desc=order_desc, is_cross=order.is_cross or 0, ) subscribe_ok = _retry_call( _subscribe_users_to_group, 5, 0.4, [dashou_goeasy_id, partner_goeasy_id], [group_id], appkey, secret ) init_msg_text = f"订单已接单,内容:{order.jieshao},备注:{order.beizhu},游戏ID:{order.nicheng}" success1 = _retry_call( _send_group_message, 3, 0.4, appkey, secret, group_id, dashou_goeasy_id, dashou_name, dashou_avatar, init_msg_text, None, order_id=order.dingdan_id, is_cross=order.is_cross, to_data_extra=pair_to_data, ) partner_msg = "订单已确认,请开始服务。" success2 = _retry_call( _send_group_message, 3, 0.4, appkey, secret, group_id, partner_goeasy_id, partner_name, partner_avatar, partner_msg, None, order_id=order.dingdan_id, is_cross=order.is_cross, to_data_extra=pair_to_data, ) card_ok = _retry_call( _push_order_card_message, 3, 0.4, appkey, secret, order, group_id, dashou_goeasy_id, dashou_name, dashou_avatar, pair_to_data, ) liaotian_ok = _ensure_liaotian_record(group_id, partner_goeasy_id, dashou_goeasy_id) private_ok = _establish_private_chat( appkey, secret, dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar ) result = subscribe_ok or success1 or success2 or card_ok or liaotian_ok or private_ok if not result: logger.error(f"订单 {order.dingdan_id} 群聊/私聊均未建立成功") return result def _get_local_partner_info(order): """本地订单下单方:老板或商家""" if order.fadan_pingtai == 1: try: ext = order.pingtai_kuozhan laoban_id = ext.laoban_id except Exception: ext = DingdanPingtai.objects.filter(dingdan__dingdan_id=order.dingdan_id).first() laoban_id = ext.laoban_id if ext else None if laoban_id: avatar_full = _full_local_avatar('') nickname = f'老板{laoban_id[:6]}' try: boss_user = UserMain.objects.get(yonghuid=laoban_id) avatar_full = _full_local_avatar(boss_user.avatar or '') boss_profile = UserBoss.objects.get(user=boss_user) if boss_profile.nickname: nickname = boss_profile.nickname except Exception: pass return f"Boss{laoban_id}", nickname, avatar_full elif order.fadan_pingtai == 2: try: ext = order.shangjia_kuozhan shangjia_id = ext.shangjia_id nickname = ext.sjnicheng or f'商家{shangjia_id[:6]}' except Exception: ext = DingdanShangjia.objects.filter(dingdan__dingdan_id=order.dingdan_id).first() if not ext: shangjia_id = None nickname = '商家' else: shangjia_id = ext.shangjia_id nickname = ext.sjnicheng or f'商家{shangjia_id[:6]}' if shangjia_id: avatar_full = _full_local_avatar('') try: sj_user = UserMain.objects.get(yonghuid=shangjia_id) avatar_full = _full_local_avatar(sj_user.avatar or '') except Exception: pass return f"Sj{shangjia_id}", nickname, avatar_full if order.user1_id: name, avatar = _resolve_goeasy_user_display(order.user1_id) return order.user1_id, name, avatar return None, None, None # ========== 跨平台 + 对方派单 ========== def _handle_cross_partner_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret): partner_club_id = order.partner_club_id if not partner_club_id: logger.warning("缺少 partner_club_id") return _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret) try: partner_config = ClubConfig.objects.get(club_id=partner_club_id, is_self=0) except ClubConfig.DoesNotExist: logger.error(f"未找到对方俱乐部配置: {partner_club_id}") return _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret) partner_order_id = order.partner_order_id if not partner_order_id: logger.error("partner_order_id 为空") return _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret) # 推断对方下单方标识 target_partner_goeasy_id = _get_cross_partner_identity(order, partner_order_id) if not target_partner_goeasy_id: logger.error("无法推断对方下单方标识") return _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret) # 对方默认头像 partner_default_avatar = _full_remote_avatar(partner_config.storage_bucket_domain or '', partner_config.club_avatar or '') or _full_local_avatar('') # ---- 1. 在我方建群 ---- local_group_id = f"group_{order.dingdan_id}" local_group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"跨平台订单{order.dingdan_id}" local_group_avatar = _full_local_avatar(order.tupian) if order.tupian else _full_local_avatar('') proxy_partner_id = f"{partner_order_id}_{target_partner_goeasy_id}" proxy_partner_name = f"对方用户({partner_club_id})" # 对方代理头像使用对方默认头像 proxy_partner_avatar = partner_default_avatar if not _subscribe_users_to_group([dashou_goeasy_id, proxy_partner_id], [local_group_id], appkey, secret): return False # 1. 打手发送初始化消息 init_msg_text = f"跨平台订单已接单,内容:{order.jieshao},备注:{order.beizhu}" _send_group_message( appkey, secret, local_group_id, dashou_goeasy_id, dashou_name, local_group_avatar, init_msg_text, None, group_name=local_group_name, order_id=order.dingdan_id, is_cross=1 ) # 2. 【新增】对方代理也发一条消息,确保对方能看到群聊 partner_msg = f"订单已确认,请等待服务。" _send_group_message( appkey, secret, local_group_id, proxy_partner_id, proxy_partner_name, proxy_partner_avatar, partner_msg, None, group_name=local_group_name, order_id=order.dingdan_id, is_cross=1 ) # ---- 2. 在对方建群 ---- partner_appkey = partner_config.chat_api_key partner_secret = partner_config.chat_api_id or '' if not partner_appkey: return True # 无对方 key,仅完成我方建群 partner_group_id = f"group_{partner_order_id}" partner_group_name = f"跨平台订单-我方打手已接单" # 代理我方打手 ID our_agent_id = f"{order.dingdan_id}_{dashou_goeasy_id}" our_agent_name = f"[我方]{dashou_name}" # 对方频道头像用对方俱乐部默认头像 partner_group_avatar = partner_default_avatar if not _subscribe_users_to_group([our_agent_id, target_partner_goeasy_id], [partner_group_id], partner_appkey, partner_secret): logger.error("向对方频道订阅失败") return True # 我方打手在对方频道发消息 cross_msg_text = f"我方打手已接单,订单:{order.jieshao},请确认" _send_group_message( partner_appkey, partner_secret, partner_group_id, our_agent_id, our_agent_name, partner_group_avatar, cross_msg_text, None, group_name=partner_group_name, order_id=partner_order_id, is_cross=1 ) # 【新增】对方用户在对方频道也发一条消息(以对方原始身份) target_partner_name = f"用户{target_partner_goeasy_id}" # 简单命名,可按需优化 _send_group_message( partner_appkey, partner_secret, partner_group_id, target_partner_goeasy_id, target_partner_name, partner_default_avatar, "订单信息已收到。", None, group_name=partner_group_name, order_id=partner_order_id, is_cross=1 ) return True def _get_cross_partner_identity(order, partner_order_id): """ 获取对方下单方的 GoEasy 标识。 优先从商家扩展表取 partner_yonghu_id,否则按 partner_order_id 前缀推断。 """ # 尝试从商家扩展表取(仅当发单平台为商家且存在 partner_yonghu_id) if order.fadan_pingtai == 2 and hasattr(order, 'shangjia_kuozhan'): ext = order.shangjia_kuozhan if ext and ext.partner_yonghu_id: # 直接根据订单前缀构造标识 prefix = partner_order_id[:2].upper() if prefix == 'SJ': return f"Sj{ext.partner_yonghu_id}" elif prefix == 'PT': return f"Boss{ext.partner_yonghu_id}" else: return f"User{ext.partner_yonghu_id}" # 后备:根据 partner_order_id 前缀推断 prefix = partner_order_id[:2].upper() uid = partner_order_id[2:] if len(partner_order_id) > 2 else '' if prefix == 'SJ': return f"Sj{uid}" elif prefix == 'PT': return f"Boss{uid}" else: return f"User{uid}" def _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret): dashou_uid = dashou_goeasy_id[2:] if dashou_goeasy_id.startswith('Ds') else dashou_goeasy_id group_id = resolve_group_id_for_order(order, dashou_uid) partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order) group_name = partner_name or (order.jieshao or f"订单{order.dingdan_id}") order_desc = (order.jieshao or '').strip() if order.beizhu: order_desc = f"{order_desc} | 备注:{order.beizhu}" if order_desc else f"备注:{order.beizhu}" to_data = None if partner_goeasy_id: to_data = _build_pair_group_to_data( dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar, order_id=order.dingdan_id, order_desc=order_desc, is_cross=order.is_cross or 0, ) subscribe_ok = _retry_call( _subscribe_users_to_group, 5, 0.4, [dashou_goeasy_id], [group_id], appkey, secret ) msg_ok = _retry_call( _send_group_message, 3, 0.4, appkey, secret, group_id, dashou_goeasy_id, dashou_name, dashou_avatar, "订单已接单。", None, order_id=order.dingdan_id, is_cross=order.is_cross, to_data_extra=to_data, ) return subscribe_ok or msg_ok