# 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