835 lines
33 KiB
Python
835 lines
33 KiB
Python
# 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 |