Files
Django/utils/chat_utils.py

451 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 build_merchant_link_chat_meta(order, link_merchant_uid):
"""H5 商家链接页群聊:新单走配对群,旧单或无打手时回落 group_{orderId}"""
pair_id = resolve_pair_group_id(order)
legacy_id = f'group_{order.OrderID}'
group_id = pair_id or legacy_id
dashou_uid = order.PlayerID or ''
self_user_id = f'B{link_merchant_uid}' if link_merchant_uid else ''
shangjia_biaoshi = ''
try:
if order.Platform == 2 and getattr(order, 'shangjia_kuozhan', None):
mid = order.shangjia_kuozhan.MerchantID
if mid:
shangjia_biaoshi = f'Sj{mid}'
except Exception:
pass
return {
'group_id': group_id,
'is_pair_group': bool(pair_id),
'legacy_group_id': legacy_id,
'self_user_id': self_user_id,
'dashou_biaoshi': f'Ds{dashou_uid}' if dashou_uid else '',
'shangjia_biaoshi': shangjia_biaoshi,
'you_dashou': bool(dashou_uid),
}
def subscribe_merchant_link_chat(order, link_merchant_uid):
"""链接页有打手时,预订阅 B{商家} 与打手到对应群(新旧群 ID 均兼容)"""
meta = build_merchant_link_chat_meta(order, link_merchant_uid)
if not meta.get('you_dashou'):
return meta, True
appkey = _get_self_goeasy_appkey()
secret = _get_self_goeasy_secret()
if not appkey:
return meta, False
user_ids = []
if meta.get('self_user_id'):
user_ids.append(meta['self_user_id'])
if meta.get('dashou_biaoshi'):
user_ids.append(meta['dashou_biaoshi'])
group_ids = [meta['group_id']]
if meta.get('legacy_group_id') and meta['legacy_group_id'] != meta['group_id']:
group_ids.append(meta['legacy_group_id'])
ok = _subscribe_users_to_group(user_ids, group_ids, appkey, secret)
return meta, ok
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