Files
Django/utils/chat_utils.py
2026-06-12 03:43:54 +08:00

374 lines
15 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 requests
from django.conf import settings
from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia
from peizhi.models import ClubConfig
from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia
logger = logging.getLogger('chat_utils')
# ========== 辅助配置获取(从 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 _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):
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
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},
"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 = 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 _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, appkey, secret):
group_id = f"group_{order.dingdan_id}"
group_name = (order.jieshao[:20] + '') if order.jieshao and len(order.jieshao) > 20 else (order.jieshao or f"订单{order.dingdan_id}")
group_avatar = _full_local_avatar(order.tupian) if order.tupian 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.jieshao},备注:{order.beizhu}游戏ID{order.nicheng}"
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.dingdan_id,
is_cross=order.is_cross
)
# 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.dingdan_id,
is_cross=order.is_cross
)
return success1 and success2
def _get_local_partner_info(order):
"""本地订单下单方:老板或商家"""
if order.fadan_pingtai == 1: # 老板
try:
ext = order.pingtai_kuozhan
laoban_id = ext.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
except Exception as e:
logger.error(f"获取老板信息失败: {e}")
return None, None, None
elif order.fadan_pingtai == 2: # 商家
try:
ext = order.shangjia_kuozhan
shangjia_id = ext.shangjia_id
nickname = ext.sjnicheng or f'商家{shangjia_id[:6]}'
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
except Exception as e:
logger.error(f"获取商家信息失败: {e}")
return None, None, None
else:
if order.user1_id:
return order.user1_id, "用户", _full_local_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):
group_id = f"group_{order.dingdan_id}"
_subscribe_users_to_group([dashou_goeasy_id], [group_id], appkey, secret)
return False