diff --git a/dingdan/views.py b/dingdan/views.py index 8d2537f..fed8cd0 100644 --- a/dingdan/views.py +++ b/dingdan/views.py @@ -4424,6 +4424,7 @@ from peizhi.models import ClubConfig from utils.chat_utils import ( _send_group_message, _subscribe_users_to_group, resolve_group_id_for_order, prepare_order_group_chat, + build_pair_to_data_for_order, ) logger = logging.getLogger('dingdan') @@ -4495,7 +4496,8 @@ def subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None): def send_group_message( appkey, secret, group_id, sender_id, sender_name, sender_avatar, - message_text='', custom_payload=None, group_name='', order_id='', is_cross=0 + message_text='', custom_payload=None, group_name='', order_id='', is_cross=0, + to_data_extra=None, ): """ 通过 GoEasy REST API 发送群消息。 @@ -4503,12 +4505,20 @@ def send_group_message( custom_payload: 自定义消息体(如订单卡片),此时 type 设为 'order' """ url = 'https://rest-hangzhou.goeasy.io/v2/im/message' - to_data = { - "name": group_name or group_id, - "avatar": sender_avatar or '', - "orderId": order_id, - "isCross": is_cross - } + sender_avatar = sender_avatar or '' + 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 'isCross' not in to_data: + to_data['isCross'] = is_cross + else: + to_data = { + "name": group_name or group_id, + "avatar": sender_avatar, + "orderId": order_id, + "isCross": is_cross + } msg_type = 'order' if custom_payload else 'text' payload = custom_payload if custom_payload else message_text @@ -4707,6 +4717,8 @@ class DaiLiQunLiaoXiaoXiView(APIView): custom_payload = order_payload if message_type == 'order' else None text_content = message_text + pair_to_data = build_pair_to_data_for_order(order, viewer_goeasy_id=sender_id) + # ---------- 先向我方群发送消息(永远执行) ---------- local_ok = send_group_message( appkey=self_appkey, @@ -4719,7 +4731,8 @@ class DaiLiQunLiaoXiaoXiView(APIView): custom_payload=custom_payload, group_name=group_name, order_id=order.dingdan_id, - is_cross=is_cross + is_cross=is_cross, + to_data_extra=pair_to_data, ) if not local_ok: return Response({'code': 500, 'msg': '消息发送失败'}) @@ -6212,6 +6225,11 @@ class AdGengHuanDaShou(APIView): dingdan_obj.zhuangtai = 2 # 进行中状态 dingdan_obj.save() + try: + establish_order_chat_with_retry(dingdan_obj.dingdan_id, max_rounds=3) + except Exception as chat_err: + logger.error(f"更换打手后建聊失败: {chat_err}", exc_info=True) + # 13. 返回成功响应 return Response({ 'code': 0, diff --git a/utils/chat_utils.py b/utils/chat_utils.py index 0537e3e..6ad1348 100644 --- a/utils/chat_utils.py +++ b/utils/chat_utils.py @@ -148,10 +148,85 @@ def _subscribe_users_to_group(user_ids, group_ids, appkey=None, secret=None): 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): + list_avatar=None, order_desc=None, to_data_extra=None): if not appkey: appkey = _get_self_goeasy_appkey() if not secret: @@ -162,23 +237,32 @@ def _send_group_message(appkey, secret, group_id, sender_id, sender_name, sender url = 'https://rest-hangzhou.goeasy.io/v2/im/message' - # to.data 包含群聊展示信息及业务字段 - to_data = { - "name": group_name or group_id, - "avatar": list_avatar or sender_avatar or "", - } - if order_id: - to_data["orderId"] = order_id - if order_desc: - to_data["orderDesc"] = order_desc[:300] - to_data["isCross"] = is_cross + 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}, + "senderData": {"avatar": sender_avatar, "name": sender_name or sender_id}, "to": { "type": "group", "id": group_id, @@ -368,12 +452,12 @@ def establish_order_chat_with_retry(dingdan_id, max_rounds=3): # ========== 普通订单 / 我方派单 ========== def _push_order_card_message(appkey, secret, order, group_id, sender_id, sender_name, sender_avatar, - group_name, list_avatar, order_desc): + 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, group_name=group_name, order_id=order.dingdan_id, - is_cross=order.is_cross or 0, list_avatar=list_avatar, order_desc=order_desc + card_text, None, order_id=order.dingdan_id, + is_cross=order.is_cross or 0, to_data_extra=to_data_extra, ) @@ -423,6 +507,13 @@ def prepare_order_group_chat(dingdan_id, caller_goeasy_id, push_order_card=False 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( @@ -436,7 +527,7 @@ def prepare_order_group_chat(dingdan_id, caller_goeasy_id, push_order_card=False card_ok = _retry_call( _push_order_card_message, 3, 0.4, appkey, secret, order, group_id, caller_goeasy_id, caller_name, caller_avatar, - display_group_name, counterpart_avatar, order_desc + pair_to_data, ) _ensure_liaotian_record(group_id, partner_goeasy_id, dashou_goeasy_id) @@ -445,10 +536,19 @@ def prepare_order_group_chat(dingdan_id, caller_goeasy_id, push_order_card=False 'groupId': group_id, 'orderId': order.dingdan_id, 'groupName': display_group_name, - 'groupAvatar': counterpart_avatar or '', + 'groupAvatar': counterpart_avatar or _full_local_avatar(''), 'counterpartId': counterpart_id, 'counterpartName': counterpart_name, - 'counterpartAvatar': counterpart_avatar or '', + '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, @@ -467,21 +567,40 @@ def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, app 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, group_name=group_name, + "订单已接单,请等待对方上线。", None, order_id=order.dingdan_id, is_cross=order.is_cross, - list_avatar=partner_avatar, order_desc=order_desc + 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 @@ -491,22 +610,22 @@ def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, app success1 = _retry_call( _send_group_message, 3, 0.4, appkey, secret, group_id, dashou_goeasy_id, dashou_name, dashou_avatar, - init_msg_text, None, group_name=group_name, order_id=order.dingdan_id, - is_cross=order.is_cross, list_avatar=partner_avatar, order_desc=order_desc + 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, group_name=group_name, order_id=order.dingdan_id, - is_cross=order.is_cross, list_avatar=dashou_avatar, order_desc=order_desc + 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, - group_name, partner_avatar, order_desc + pair_to_data, ) liaotian_ok = _ensure_liaotian_record(group_id, partner_goeasy_id, dashou_goeasy_id) @@ -695,6 +814,14 @@ def _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar 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 @@ -702,7 +829,7 @@ def _create_local_group_only(order, dashou_goeasy_id, dashou_name, dashou_avatar msg_ok = _retry_call( _send_group_message, 3, 0.4, appkey, secret, group_id, dashou_goeasy_id, dashou_name, dashou_avatar, - "订单已接单。", None, group_name=group_name, order_id=order.dingdan_id, - is_cross=order.is_cross, list_avatar=partner_avatar, order_desc=order_desc + "订单已接单。", None, order_id=order.dingdan_id, + is_cross=order.is_cross, to_data_extra=to_data, ) return subscribe_ok or msg_ok \ No newline at end of file