From 2db394b2f9c322e24b659a90ccf4c6137b5cbecc Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 23 Jun 2026 22:49:28 +0800 Subject: [PATCH] =?UTF-8?q?=E8=81=8A=E5=A4=A9=E9=85=8D=E5=AF=B9=E7=BE=A4?= =?UTF-8?q?=E7=BB=84ID=E4=B8=8E=E5=87=86=E5=A4=87=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=9A=E6=89=93=E6=89=8B=E5=95=86=E5=AE=B6=E5=90=8C=E7=BE=A4?= =?UTF-8?q?=E3=80=81=E8=AE=A2=E5=8D=95=E5=8D=A1=E7=89=87=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- dingdan/urls.py | 3 +- dingdan/views.py | 45 +++++++++- utils/chat_utils.py | 201 ++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 229 insertions(+), 20 deletions(-) diff --git a/dingdan/urls.py b/dingdan/urls.py index ffe5b76..142569b 100644 --- a/dingdan/urls.py +++ b/dingdan/urls.py @@ -7,7 +7,7 @@ from .views import CreateOrderView, WechatPayNotifyView, \ ShangjiaChexiaoView, DashouDingdanHuoquView, ShangpinLeixingHuoquView, QiangdanView, \ DashouDingdanHuoquView1, DashouCOSZhengshuView, DashouTijiaoView, DashouDingdanXiangqingView, AdGetDingdanXiangQing, \ AdTongYiTuiKuanPingTai, AdTongYiTuiKuanShangJia, AdQiangZhiJieDan, AdJuJueTuiKuan,\ - AdGengHuanDaShou,ShangjiaCOSZhengshuView, DashouHuoquLeixingView,AdJuJueJieSuan, AdZhuanYiDaTing, AdQuXiaoZhiDing,PartnerNotifyClaimView, PartnerSyncOrderView, PartnerCheckClaimView, PartnerRefundNotifyView,DashouXiugaiView,VirtualCreateOrderView, VirtualPayNotifyView, VirtualPaymentVerifyView,VirtualPaymentFailView,DaiLiQunLiaoXiaoXiView,\ + AdGengHuanDaShou,ShangjiaCOSZhengshuView, DashouHuoquLeixingView,AdJuJueJieSuan, AdZhuanYiDaTing, AdQuXiaoZhiDing,PartnerNotifyClaimView, PartnerSyncOrderView, PartnerCheckClaimView, PartnerRefundNotifyView,DashouXiugaiView,VirtualCreateOrderView, VirtualPayNotifyView, VirtualPaymentVerifyView,VirtualPaymentFailView,DaiLiQunLiaoXiaoXiView,DingdanLiaotianZhunbeiView,\ ShangjiaLianjieLiuYanZhuanFaView, ZxsjghdsView, \ ShangjiaFakuanApplyView, ShangjiaFakuaiXiugaiView @@ -78,6 +78,7 @@ urlpatterns = [ path('dsxiugaidd', DashouXiugaiView.as_view(), name='打手修改订单详情'), path('kptxxfs', DaiLiQunLiaoXiaoXiView.as_view(), name='前端聊天转发'), # 群聊消息代理发送 + path('ltdhzb', DingdanLiaotianZhunbeiView.as_view(), name='订单聊天准备'), path('virtual_xiadan', VirtualCreateOrderView.as_view(),name='虚拟支付下单'), diff --git a/dingdan/views.py b/dingdan/views.py index 4e3688f..8d2537f 100644 --- a/dingdan/views.py +++ b/dingdan/views.py @@ -4421,7 +4421,10 @@ from rest_framework import status, permissions from .models import Dingdan, DingdanPingtai, DingdanShangjia from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia from peizhi.models import ClubConfig -from utils.chat_utils import _send_group_message, _subscribe_users_to_group +from utils.chat_utils import ( + _send_group_message, _subscribe_users_to_group, + resolve_group_id_for_order, prepare_order_group_chat, +) logger = logging.getLogger('dingdan') @@ -4593,6 +4596,44 @@ def get_partner_club_config(partner_club_id): # ----------------- 主视图 ----------------- +class DingdanLiaotianZhunbeiView(APIView): + """订单详情进入聊天前:确保配对群订阅并返回 groupId""" + permission_classes = [permissions.IsAuthenticated] + + def post(self, request): + dingdan_id = request.data.get('dingdan_id') or request.data.get('orderId') + identity_type = request.data.get('identityType') + push_order_card = request.data.get('push_order_card', True) + if isinstance(push_order_card, str): + push_order_card = push_order_card.lower() in ('1', 'true', 'yes') + + if not dingdan_id: + return Response({'code': 400, 'msg': '缺少 dingdan_id'}) + if not identity_type: + return Response({'code': 400, 'msg': '缺少 identityType'}) + if identity_type not in ('dashou', 'shangjia', 'boss'): + return Response({'code': 400, 'msg': 'identityType 无效'}) + + uid = request.user.yonghuid + try: + order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=dingdan_id) + except Dingdan.DoesNotExist: + return Response({'code': 404, 'msg': '订单不存在'}) + + valid, err_msg = check_user_permission(identity_type, uid, order) + if not valid: + return Response({'code': 403, 'msg': err_msg}) + + prefix_map = {'dashou': 'Ds', 'shangjia': 'Sj', 'boss': 'Boss'} + caller_goeasy_id = prefix_map[identity_type] + uid + + result = prepare_order_group_chat(dingdan_id, caller_goeasy_id, push_order_card=push_order_card) + if not result: + return Response({'code': 500, 'msg': '准备群聊失败,请稍后重试'}) + + return Response({'code': 0, 'msg': 'ok', 'data': result}) + + class DaiLiQunLiaoXiaoXiView(APIView): """ 代理发送群聊消息 @@ -4648,7 +4689,7 @@ class DaiLiQunLiaoXiaoXiView(APIView): return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'}) # ===== 6. 公共群信息 ===== - real_group_id = f"group_{order.dingdan_id}" + real_group_id = resolve_group_id_for_order(order) group_name = (order.jieshao[:20] + '…') if order.jieshao and len(order.jieshao) > 20 else f"订单{order.dingdan_id}" sender_avatar = full_avatar(sender_avatar_raw) if sender_avatar_raw else full_avatar('') diff --git a/utils/chat_utils.py b/utils/chat_utils.py index a377dc5..0537e3e 100644 --- a/utils/chat_utils.py +++ b/utils/chat_utils.py @@ -10,6 +10,65 @@ from yonghu.models import UserMain, UserDashou, UserBoss, UserShangjia logger = logging.getLogger('chat_utils') +ORDER_CARD_PREFIX = '[ORDER_CARD]' + + +def make_dashou_shangjia_group_id(dashou_yonghuid, shangjia_yonghuid): + return f"group_Ds{dashou_yonghuid}_Sj{shangjia_yonghuid}" + + +def make_dashou_boss_group_id(dashou_yonghuid, boss_yonghuid): + return f"group_Ds{dashou_yonghuid}_Boss{boss_yonghuid}" + + +def _get_shangjia_id_from_order(order): + try: + return order.shangjia_kuozhan.shangjia_id + except Exception: + ext = DingdanShangjia.objects.filter(dingdan__dingdan_id=order.dingdan_id).first() + return ext.shangjia_id if ext else None + + +def _get_boss_id_from_order(order): + try: + return order.pingtai_kuozhan.laoban_id + except Exception: + ext = DingdanPingtai.objects.filter(dingdan__dingdan_id=order.dingdan_id).first() + return ext.laoban_id if ext else None + + +def resolve_group_id_for_order(order, dashou_yonghuid=None): + """打手+商家/老板配对群 ID;跨平台或缺信息时回退订单群 ID""" + dashou_yonghuid = dashou_yonghuid or order.jiedan_dashou_id + if order.is_cross == 1 and order.dispatch_type == 2: + return f"group_{order.dingdan_id}" + if not dashou_yonghuid: + return f"group_{order.dingdan_id}" + if order.fadan_pingtai == 2: + shangjia_id = _get_shangjia_id_from_order(order) + if shangjia_id: + return make_dashou_shangjia_group_id(dashou_yonghuid, shangjia_id) + elif order.fadan_pingtai == 1: + boss_id = _get_boss_id_from_order(order) + if boss_id: + return make_dashou_boss_group_id(dashou_yonghuid, boss_id) + return f"group_{order.dingdan_id}" + + +def _build_order_card_text(order): + payload = { + 'dingdan_id': order.dingdan_id, + 'jieshao': order.jieshao or '', + 'beizhu': order.beizhu or '', + 'jine': str(order.jine), + 'zhuangtai': order.zhuangtai, + 'nicheng': order.nicheng or '', + } + if order.create_time: + payload['create_time'] = order.create_time.strftime('%Y-%m-%d %H:%M:%S') + return ORDER_CARD_PREFIX + json.dumps(payload, ensure_ascii=False) + + # ========== 辅助配置获取(从 ClubConfig) ========== def _get_self_club_config(): if not hasattr(_get_self_club_config, '_config'): @@ -91,7 +150,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=None, order_id=None, is_cross=0): + message_text, custom_payload=None, group_name=None, order_id=None, is_cross=0, + list_avatar=None, order_desc=None): if not appkey: appkey = _get_self_goeasy_appkey() if not secret: @@ -105,10 +165,12 @@ def _send_group_message(appkey, secret, group_id, sender_id, sender_name, sender # to.data 包含群聊展示信息及业务字段 to_data = { "name": group_name or group_id, - "avatar": sender_avatar or "", + "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 payload_str = custom_payload.get("text", message_text) if custom_payload else message_text @@ -305,12 +367,106 @@ def establish_order_chat_with_retry(dingdan_id, max_rounds=3): return False # ========== 普通订单 / 我方派单 ========== +def _push_order_card_message(appkey, secret, order, group_id, sender_id, sender_name, sender_avatar, + group_name, list_avatar, order_desc): + 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 + ) + + +def prepare_order_group_chat(dingdan_id, caller_goeasy_id, push_order_card=False): + """ + 订单详情进入聊天前:确保配对群已订阅,可选推送订单卡片。 + 返回前端跳转所需字段 dict,失败返回 None。 + """ + try: + order = Dingdan.objects.select_related('pingtai_kuozhan', 'shangjia_kuozhan').get(dingdan_id=dingdan_id) + except Dingdan.DoesNotExist: + logger.error(f"prepare_order_group_chat: 订单 {dingdan_id} 不存在") + return None + + dashou_uid = order.jiedan_dashou_id + if not dashou_uid: + logger.error(f"prepare_order_group_chat: 订单 {dingdan_id} 无接单打手") + return None + + dashou_goeasy_id = f"Ds{dashou_uid}" + partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order) + if not partner_goeasy_id: + logger.error(f"prepare_order_group_chat: 订单 {dingdan_id} 无下单方") + return None + + group_id = resolve_group_id_for_order(order, dashou_uid) + dashou_name, dashou_avatar = _resolve_goeasy_user_display(dashou_goeasy_id) + + if caller_goeasy_id == dashou_goeasy_id: + counterpart_id = partner_goeasy_id + counterpart_name = partner_name + counterpart_avatar = partner_avatar + elif caller_goeasy_id == partner_goeasy_id: + counterpart_id = dashou_goeasy_id + counterpart_name = dashou_name + counterpart_avatar = dashou_avatar + else: + logger.warning(f"prepare_order_group_chat: caller {caller_goeasy_id} 不属于订单 {dingdan_id}") + return None + + appkey = _get_self_goeasy_appkey() + secret = _get_self_goeasy_secret() + if not appkey: + 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}" + display_group_name = counterpart_name or '聊天' + + user_ids = list({dashou_goeasy_id, partner_goeasy_id, caller_goeasy_id}) + subscribe_ok = _retry_call( + _subscribe_users_to_group, 5, 0.4, + user_ids, [group_id], appkey, secret + ) + + card_ok = False + if push_order_card: + caller_name, caller_avatar = _resolve_goeasy_user_display(caller_goeasy_id) + 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 + ) + + _ensure_liaotian_record(group_id, partner_goeasy_id, dashou_goeasy_id) + + return { + 'groupId': group_id, + 'orderId': order.dingdan_id, + 'groupName': display_group_name, + 'groupAvatar': counterpart_avatar or '', + 'counterpartId': counterpart_id, + 'counterpartName': counterpart_name, + 'counterpartAvatar': counterpart_avatar or '', + 'orderDesc': order_desc, + 'orderJine': str(order.jine), + 'orderZhuangtai': order.zhuangtai, + 'isCross': 1 if order.is_cross == 1 else 0, + 'subscribeOk': subscribe_ok, + 'orderCardPushed': card_ok, + } + + 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('') + dashou_uid = dashou_goeasy_id[2:] if dashou_goeasy_id.startswith('Ds') else dashou_goeasy_id + group_id = resolve_group_id_for_order(order, dashou_uid) partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order) + group_name = partner_name or f"用户{partner_goeasy_id[-6:] if partner_goeasy_id else ''}" + 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( @@ -319,9 +475,10 @@ def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, app ) msg_ok = _retry_call( _send_group_message, 3, 0.4, - appkey, secret, group_id, dashou_goeasy_id, dashou_name, group_avatar, + 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 + order_id=order.dingdan_id, is_cross=order.is_cross, + list_avatar=partner_avatar, order_desc=order_desc ) return subscribe_ok or msg_ok @@ -333,9 +490,9 @@ def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, app init_msg_text = f"订单已接单,内容:{order.jieshao},备注:{order.beizhu},游戏ID:{order.nicheng}" success1 = _retry_call( _send_group_message, 3, 0.4, - appkey, secret, group_id, dashou_goeasy_id, dashou_name, group_avatar, + 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 + is_cross=order.is_cross, list_avatar=partner_avatar, order_desc=order_desc ) partner_msg = "订单已确认,请开始服务。" @@ -343,16 +500,22 @@ def _handle_local_order(order, dashou_goeasy_id, dashou_name, dashou_avatar, app _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 + is_cross=order.is_cross, list_avatar=dashou_avatar, order_desc=order_desc ) - liaotian_ok = _ensure_liaotian_record(order.dingdan_id, partner_goeasy_id, dashou_goeasy_id) + 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 + ) + + liaotian_ok = _ensure_liaotian_record(group_id, partner_goeasy_id, dashou_goeasy_id) private_ok = _establish_private_chat( appkey, secret, dashou_goeasy_id, dashou_name, dashou_avatar, partner_goeasy_id, partner_name, partner_avatar ) - result = subscribe_ok or success1 or success2 or liaotian_ok or private_ok + result = subscribe_ok or success1 or success2 or card_ok or liaotian_ok or private_ok if not result: logger.error(f"订单 {order.dingdan_id} 群聊/私聊均未建立成功") return result @@ -525,17 +688,21 @@ def _get_cross_partner_identity(order, partner_order_id): 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}" - 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('') + dashou_uid = dashou_goeasy_id[2:] if dashou_goeasy_id.startswith('Ds') else dashou_goeasy_id + group_id = resolve_group_id_for_order(order, dashou_uid) + partner_goeasy_id, partner_name, partner_avatar = _get_local_partner_info(order) + group_name = partner_name or (order.jieshao or f"订单{order.dingdan_id}") + order_desc = (order.jieshao or '').strip() + if order.beizhu: + order_desc = f"{order_desc} | 备注:{order.beizhu}" if order_desc else f"备注:{order.beizhu}" subscribe_ok = _retry_call( _subscribe_users_to_group, 5, 0.4, [dashou_goeasy_id], [group_id], appkey, secret ) msg_ok = _retry_call( _send_group_message, 3, 0.4, - appkey, secret, group_id, dashou_goeasy_id, dashou_name, group_avatar, + 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 + is_cross=order.is_cross, list_avatar=partner_avatar, order_desc=order_desc ) return subscribe_ok or msg_ok \ No newline at end of file