404 lines
16 KiB
Python
404 lines
16 KiB
Python
# utils/chat_utils.py
|
||
import json
|
||
import logging
|
||
import re
|
||
import requests
|
||
from django.conf import settings
|
||
from django.db.models import Q
|
||
from orders.models import Order, PlatformOrderExt, MerchantOrderExt
|
||
from users.models import UserDashou, UserBoss, UserShangjia
|
||
from users.business_models import User
|
||
|
||
logger = logging.getLogger('chat_utils')
|
||
|
||
ORDER_STATUS_TEXT = {
|
||
1: '待抢单', 2: '进行中', 3: '已完成', 4: '退款审核中', 5: '已退款',
|
||
7: '指定中', 8: '结算中', 9: '未支付', 14: '支付失败', 25: '已废弃',
|
||
}
|
||
|
||
# ========== 辅助配置获取 ==========
|
||
def _get_self_goeasy_appkey():
|
||
return getattr(settings, 'GOEASY_APPKEY', '')
|
||
|
||
def _get_self_goeasy_secret():
|
||
return getattr(settings, 'GOEASY_SECRET', '')
|
||
|
||
# ========== 头像拼接 ==========
|
||
def _full_local_avatar(relative_url):
|
||
if not relative_url:
|
||
return ''
|
||
if relative_url.startswith('http'):
|
||
return relative_url
|
||
domain = getattr(settings, 'COS_DOMAIN', '') or ''
|
||
if domain:
|
||
return domain.rstrip('/') + '/' + relative_url.lstrip('/')
|
||
return relative_url
|
||
|
||
|
||
def resolve_pair_group_id(order):
|
||
"""打手+商家/老板配对群 ID,与前端 resolveLocalGroupId 一致(同两人共用一个群)"""
|
||
dashou_uid = order.PlayerID or ''
|
||
if not dashou_uid:
|
||
return None
|
||
if order.Platform == 2:
|
||
try:
|
||
sj_uid = order.shangjia_kuozhan.MerchantID
|
||
if sj_uid:
|
||
return f'group_Ds{dashou_uid}_Sj{sj_uid}'
|
||
except Exception as e:
|
||
logger.warning(f'配对群(商家)解析失败: {e}')
|
||
elif order.Platform == 1:
|
||
try:
|
||
boss_uid = order.pingtai_kuozhan.BossID
|
||
if boss_uid:
|
||
return f'group_Ds{dashou_uid}_Boss{boss_uid}'
|
||
except Exception as e:
|
||
logger.warning(f'配对群(老板)解析失败: {e}')
|
||
return None
|
||
|
||
|
||
def parse_pair_from_group_id(group_id):
|
||
"""从配对群 ID 解析打手 uid、对方 uid、对方角色(shangjia/boss)"""
|
||
if not group_id:
|
||
return None, None, None
|
||
m = re.match(r'^group_Ds(\w+)_Sj(\w+)$', str(group_id))
|
||
if m:
|
||
return m.group(1), m.group(2), 'shangjia'
|
||
m = re.match(r'^group_Ds(\w+)_Boss(\w+)$', str(group_id))
|
||
if m:
|
||
return m.group(1), m.group(2), 'boss'
|
||
return None, None, None
|
||
|
||
|
||
def resolve_pair_from_order(order):
|
||
"""从订单解析打手与下单方配对信息"""
|
||
dashou_uid = order.PlayerID or ''
|
||
if not dashou_uid:
|
||
return None, None, None
|
||
if order.Platform == 2:
|
||
try:
|
||
sj_uid = order.shangjia_kuozhan.MerchantID
|
||
if sj_uid:
|
||
return dashou_uid, sj_uid, 'shangjia'
|
||
except Exception:
|
||
pass
|
||
elif order.Platform == 1:
|
||
try:
|
||
boss_uid = order.pingtai_kuozhan.BossID
|
||
if boss_uid:
|
||
return dashou_uid, boss_uid, 'boss'
|
||
except Exception:
|
||
pass
|
||
return dashou_uid, None, None
|
||
|
||
|
||
def check_pair_chat_permission(identity_type, uid, dashou_uid, partner_uid, partner_role):
|
||
if identity_type == 'PlayerID':
|
||
if uid != dashou_uid:
|
||
return False, '你不是该会话的打手'
|
||
return True, ''
|
||
if identity_type == 'shangjia':
|
||
if partner_role != 'shangjia' or uid != partner_uid:
|
||
return False, '你不是该会话的商家'
|
||
return True, ''
|
||
if identity_type == 'boss':
|
||
if partner_role != 'boss' or uid != partner_uid:
|
||
return False, '你不是该会话的老板'
|
||
return True, ''
|
||
return False, '不支持的身份类型'
|
||
|
||
|
||
def order_to_chat_dict(order):
|
||
return {
|
||
'dingdan_id': order.OrderID,
|
||
'jieshao': order.Description or '',
|
||
'jine': str(order.Amount) if order.Amount is not None else '',
|
||
'beizhu': order.Remark or '',
|
||
'zhuangtai': order.Status,
|
||
'zhuangtaiText': ORDER_STATUS_TEXT.get(order.Status, ''),
|
||
'nicheng': order.Nickname or '',
|
||
'create_time': order.CreateTime.strftime('%Y-%m-%d %H:%M') if order.CreateTime else '',
|
||
}
|
||
|
||
|
||
def query_pair_orders(dashou_uid, partner_uid, partner_role, keyword='', limit=30):
|
||
if not dashou_uid or not partner_uid:
|
||
return []
|
||
qs = Order.objects.filter(PlayerID=dashou_uid).select_related('pingtai_kuozhan', 'shangjia_kuozhan')
|
||
if partner_role == 'shangjia':
|
||
qs = qs.filter(Platform=2, shangjia_kuozhan__MerchantID=partner_uid)
|
||
elif partner_role == 'boss':
|
||
qs = qs.filter(Platform=1, pingtai_kuozhan__BossID=partner_uid)
|
||
else:
|
||
return []
|
||
qs = qs.exclude(Status__in=[9, 14, 25])
|
||
if keyword:
|
||
qs = qs.filter(Q(OrderID__icontains=keyword) | Q(Description__icontains=keyword))
|
||
return [order_to_chat_dict(o) for o in qs.order_by('-CreateTime')[:limit]]
|
||
|
||
|
||
def _get_dashou_profile(order):
|
||
dashou_uid = order.PlayerID or ''
|
||
dashou_goeasy_id = f'Ds{dashou_uid}' if dashou_uid else ''
|
||
dashou_nickname = f'打手{dashou_uid[:6]}' if dashou_uid else ''
|
||
dashou_avatar = ''
|
||
if dashou_uid:
|
||
try:
|
||
dashou_user = User.objects.get(UserUID=dashou_uid)
|
||
dashou_avatar = _full_local_avatar(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}')
|
||
return dashou_uid, dashou_goeasy_id, dashou_nickname, dashou_avatar
|
||
|
||
|
||
def build_order_group_chat_payload(order, viewer_backend_identity, viewer_uid):
|
||
"""构建前端 ltdhzb 所需群聊元数据(新群用打手+商家/老板配对 groupId)"""
|
||
group_id = resolve_pair_group_id(order) or f'group_{order.OrderID}'
|
||
order_desc = order.Description or ''
|
||
group_name = (
|
||
(order_desc[:20] + '…') if order_desc and len(order_desc) > 20
|
||
else (order_desc or f'订单{order.OrderID}')
|
||
)
|
||
dashou_uid, dashou_goeasy_id, dashou_name, dashou_avatar = _get_dashou_profile(order)
|
||
partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order)
|
||
partner_uid = ''
|
||
if partner_goeasy_id:
|
||
partner_uid = partner_goeasy_id.replace('Ds', '').replace('Sj', '').replace('Boss', '')
|
||
|
||
counterpart_id = ''
|
||
counterpart_yonghuid = ''
|
||
counterpart_name = ''
|
||
counterpart_avatar = ''
|
||
if viewer_backend_identity == 'PlayerID':
|
||
counterpart_id = partner_goeasy_id or ''
|
||
counterpart_yonghuid = partner_uid
|
||
counterpart_name = partner_name or ''
|
||
counterpart_avatar = partner_avatar or ''
|
||
else:
|
||
counterpart_id = dashou_goeasy_id or ''
|
||
counterpart_yonghuid = dashou_uid
|
||
counterpart_name = dashou_name or ''
|
||
counterpart_avatar = dashou_avatar or ''
|
||
|
||
return {
|
||
'groupId': group_id,
|
||
'orderId': order.OrderID,
|
||
'groupName': counterpart_name or group_name,
|
||
'groupAvatar': counterpart_avatar or partner_avatar or dashou_avatar or '',
|
||
'counterpartName': counterpart_name,
|
||
'counterpartAvatar': counterpart_avatar,
|
||
'counterpartId': counterpart_id,
|
||
'counterpartYonghuid': counterpart_yonghuid,
|
||
'orderDesc': order_desc,
|
||
'orderJine': str(order.Amount) if order.Amount is not None else '',
|
||
'orderZhuangtai': order.Status,
|
||
'dashouGoEasyId': dashou_goeasy_id,
|
||
'partnerGoEasyId': partner_goeasy_id or '',
|
||
'dashouYonghuid': dashou_uid,
|
||
'partnerYonghuid': partner_uid,
|
||
'dashouName': dashou_name,
|
||
'partnerName': partner_name or '',
|
||
'dashouAvatar': dashou_avatar,
|
||
'partnerAvatar': partner_avatar or '',
|
||
'isCross': 0,
|
||
}
|
||
|
||
|
||
def prepare_order_group_chat(order, viewer_backend_identity, viewer_uid, subscribe=True):
|
||
"""进入群聊前:订阅当前用户(及双方)到配对群,不重复发送抢单初始化消息"""
|
||
payload = build_order_group_chat_payload(order, viewer_backend_identity, viewer_uid)
|
||
group_id = payload['groupId']
|
||
appkey = _get_self_goeasy_appkey()
|
||
secret = _get_self_goeasy_secret()
|
||
if not appkey:
|
||
logger.error('GoEasy AppKey 未配置')
|
||
return payload, False
|
||
|
||
prefix_map = {'PlayerID': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'}
|
||
viewer_goeasy_id = prefix_map.get(viewer_backend_identity, 'Ds') + viewer_uid
|
||
user_ids = {viewer_goeasy_id}
|
||
if payload.get('dashouGoEasyId'):
|
||
user_ids.add(payload['dashouGoEasyId'])
|
||
if payload.get('partnerGoEasyId'):
|
||
user_ids.add(payload['partnerGoEasyId'])
|
||
|
||
subscribed = True
|
||
if subscribe and user_ids:
|
||
subscribed = _subscribe_users_to_group(list(user_ids), [group_id], appkey, secret)
|
||
return payload, subscribed
|
||
|
||
# ========== 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 _send_group_message(appkey, secret, group_id, sender_id, sender_name, sender_avatar,
|
||
message_text, custom_payload=None, group_name=None, order_id=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'
|
||
|
||
# to.data 包含群聊展示信息及业务字段
|
||
to_data = {
|
||
"name": group_name or group_id,
|
||
"avatar": sender_avatar or "",
|
||
}
|
||
if order_id:
|
||
to_data["orderId"] = order_id
|
||
|
||
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},
|
||
"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 establish_order_chat(dingdan_id):
|
||
try:
|
||
order = Order.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(OrderID=dingdan_id)
|
||
except Order.DoesNotExist:
|
||
logger.error(f"订单 {dingdan_id} 不存在")
|
||
return False
|
||
|
||
dashou_uid = order.PlayerID
|
||
dashou_goeasy_id = f"Ds{dashou_uid}"
|
||
dashou_nickname = f'打手{dashou_uid[:6]}'
|
||
dashou_avatar_relative = ''
|
||
try:
|
||
dashou_user = User.objects.get(UserUID=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
|
||
|
||
# 本地订单处理
|
||
return _handle_local_order(order, dashou_goeasy_id, dashou_nickname, dashou_avatar, appkey, secret)
|
||
|
||
# ========== 普通订单 / 我方派单 ==========
|
||
def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret):
|
||
group_id = resolve_pair_group_id(order) or f"group_{order.OrderID}"
|
||
group_name = (order.Description[:20] + '…') if order.Description and len(order.Description) > 20 else (order.Description or f"订单{order.OrderID}")
|
||
group_avatar = _full_local_avatar(order.ImageURL) if order.ImageURL else _full_local_avatar('')
|
||
|
||
partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order)
|
||
if not partner_goeasy_id:
|
||
return False
|
||
|
||
# 订阅双方
|
||
if not _subscribe_users_to_group([dashou_goeasy_id, partner_goeasy_id], [group_id], appkey, secret):
|
||
return False
|
||
|
||
# 1. 打手发送初始化消息
|
||
init_msg_text = f"订单已接单,内容:{order.Description},备注:{order.Remark},游戏ID:{order.Nickname}"
|
||
success1 = _send_group_message(
|
||
appkey, secret, group_id, dashou_goeasy_id, dashou_name, group_avatar,
|
||
init_msg_text, None, group_name=group_name, order_id=order.OrderID
|
||
)
|
||
|
||
# 2. 【新增】派单方也发一条消息,确保他也能看到群聊
|
||
partner_msg = f"订单已确认,请开始服务。"
|
||
success2 = _send_group_message(
|
||
appkey, secret, group_id, partner_goeasy_id, partner_name, partner_avatar,
|
||
partner_msg, None, group_name=group_name, order_id=order.OrderID
|
||
)
|
||
|
||
return success1 and success2
|
||
|
||
def _get_local_partner_info(order):
|
||
"""本地订单下单方:老板或商家"""
|
||
if order.Platform == 1: # 老板
|
||
try:
|
||
ext = order.pingtai_kuozhan
|
||
laoban_id = ext.BossID
|
||
avatar_full = _full_local_avatar('')
|
||
nickname = f'老板{laoban_id[:6]}'
|
||
try:
|
||
boss_user = User.objects.get(UserUID=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
|
||
except Exception as e:
|
||
logger.error(f"获取老板信息失败: {e}")
|
||
return None, None, None
|
||
elif order.Platform == 2: # 商家
|
||
try:
|
||
ext = order.shangjia_kuozhan
|
||
shangjia_id = ext.MerchantID
|
||
nickname = ext.MerchantNickname or f'商家{shangjia_id[:6]}'
|
||
avatar_full = _full_local_avatar('')
|
||
try:
|
||
sj_user = User.objects.get(UserUID=shangjia_id)
|
||
avatar_full = _full_local_avatar(sj_user.Avatar or '')
|
||
except Exception:
|
||
pass
|
||
return f"Sj{shangjia_id}", nickname, avatar_full
|
||
except Exception as e:
|
||
logger.error(f"获取商家信息失败: {e}")
|
||
return None, None, None
|
||
else:
|
||
if order.User1ID:
|
||
return order.User1ID, "用户", _full_local_avatar('')
|
||
return None, None, None |