From 432387982e0ec11afac60b1c2ca19731d44a7bc5 Mon Sep 17 00:00:00 2001 From: XingQue Date: Fri, 3 Jul 2026 23:32:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=93=BE=E6=8E=A5=E5=A1=AB=E5=8D=95?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8B=BC=E5=A4=9A=E5=A4=9A=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=8F=B7/=E6=89=8B=E6=9C=BA=E5=8F=B7=E6=A0=A1=E9=AA=8C?= =?UTF-8?q?=E5=B9=B6=E9=9A=90=E8=97=8FH5=E4=BB=B7=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/views.py | 39 +++++++-- .../0015_merchantorderext_bossphone.py | 22 +++++ orders/models.py | 4 + orders/views.py | 20 +++-- utils/pdd_order_validator.py | 86 +++++++++++++++++++ 5 files changed, 160 insertions(+), 11 deletions(-) create mode 100644 orders/migrations/0015_merchantorderext_bossphone.py create mode 100644 utils/pdd_order_validator.py diff --git a/config/views.py b/config/views.py index 462cc97..1767f5f 100644 --- a/config/views.py +++ b/config/views.py @@ -32,6 +32,7 @@ from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCod from backend.utils import update_shangjia_daily from utils.chat_utils import subscribe_merchant_link_chat +from utils.pdd_order_validator import validate_pdd_order_id, validate_cn_mobile from .models import ( Gonggao, Lunbo, Tupianpeizhi, Qunpeizhi, @@ -2239,7 +2240,6 @@ class KehuGetDingdanLianjieView(APIView): # ========== 构建订单信息 ========== dingdan_info = { 'jieshao': dingdan_obj.Description or '', - 'jiage': str(dingdan_obj.Amount) if dingdan_obj.Amount is not None else '0.00', # 金额字段名是 Amount 'shangjia_mingcheng': shangjia_nicheng, 'zhuangtai': dingdan_obj.Status, # 新增配置字段 @@ -2253,11 +2253,17 @@ class KehuGetDingdanLianjieView(APIView): # 如果已使用,返回已填写的信息 if lianjie_obj.is_used: + laoban_shouji = '' + try: + laoban_shouji = dingdan_obj.shangjia_kuozhan.BossPhone or '' + except Exception: + pass dingdan_info.update({ 'youxi_nicheng': dingdan_obj.Nickname or '', 'zhiding_dashou': dingdan_obj.AssignedID or dingdan_obj.PlayerID or '', 'beizhu': dingdan_obj.Remark or '', 'waibu_dingdan_id': dingdan_obj.ExternalOrderID or '', + 'laoban_shouji': laoban_shouji, }) # 获取聊天相关标识(新配对群优先,旧单保留 order 群) @@ -2279,7 +2285,6 @@ class KehuGetDingdanLianjieView(APIView): logger.error(f"订单不存在 - 订单ID: {lianjie_obj.OrderID}") response_data.update({ 'jieshao': '', - 'jiage': '0.00', 'shangjia_mingcheng': '', 'zhuangtai': None, 'dashou_biaoshi': '', @@ -2295,7 +2300,6 @@ class KehuGetDingdanLianjieView(APIView): logger.error(f"获取订单信息异常: {str(e)}", exc_info=True) response_data.update({ 'jieshao': '', - 'jiage': '0.00', 'shangjia_mingcheng': '', 'zhuangtai': None, 'dashou_biaoshi': '', @@ -2312,7 +2316,6 @@ class KehuGetDingdanLianjieView(APIView): logger.warning(f"链接记录中订单ID为空 - 链接ID: {lianjie_obj.id}") response_data.update({ 'jieshao': '', - 'jiage': '0.00', 'shangjia_mingcheng': '', 'zhuangtai': None, 'dashou_biaoshi': '', @@ -2387,6 +2390,7 @@ class KehuTianxieDingdanView(APIView): zhiding_dashou = data.get('zhiding_dashou', '').strip() # 可选 beizhu = data.get('beizhu', '').strip() # 可选 waibu_dingdan_id = str(data.get('waibu_dingdan_id') or data.get('waibuDingdanId') or '').strip()[:64] + laoban_shouji = str(data.get('laoban_shouji') or data.get('bossPhone') or data.get('laobanPhone') or '').strip()[:11] # 4.0 临时关闭指定功能(如需开启,请删除或注释掉下面两行) @@ -2424,6 +2428,24 @@ class KehuTianxieDingdanView(APIView): 'data': None }, status=status.HTTP_400_BAD_REQUEST) + ok_pdd, msg_pdd = validate_pdd_order_id(waibu_dingdan_id) + if not ok_pdd: + logger.warning(f"拼多多订单号校验失败 - IP: {client_ip}, 值: {waibu_dingdan_id[:24]}") + return Response({ + 'code': 400, + 'msg': msg_pdd, + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + + ok_phone, msg_phone = validate_cn_mobile(laoban_shouji) + if not ok_phone: + logger.warning(f"老板手机号校验失败 - IP: {client_ip}") + return Response({ + 'code': 400, + 'msg': msg_phone, + 'data': None + }, status=status.HTTP_400_BAD_REQUEST) + # 6. 验证游戏昵称格式 if len(youxi_nicheng) > 50: logger.warning(f"游戏昵称过长 - IP: {client_ip}, 长度: {len(youxi_nicheng)}") @@ -2453,7 +2475,7 @@ class KehuTianxieDingdanView(APIView): 'data': None }, status=status.HTTP_400_BAD_REQUEST) - # 8. 验证拼多多订单号 + # 8. 验证拼多多订单号长度(格式已在上方校验) if len(waibu_dingdan_id) > 64: return Response({ 'code': 400, @@ -2596,6 +2618,13 @@ class KehuTianxieDingdanView(APIView): logger.info( f"更新订单成功 - 订单ID: {dingdan_obj.OrderID}, 新状态: {zhuangtai_xin}, 指定打手: {zhiding_dashou or '无'}") + try: + shangjia_ext = dingdan_obj.shangjia_kuozhan + shangjia_ext.BossPhone = laoban_shouji + shangjia_ext.save(update_fields=['BossPhone']) + except MerchantOrderExt.DoesNotExist: + logger.warning(f"商家扩展表不存在,无法保存手机号 - 订单: {dingdan_obj.OrderID}") + # 构建通知数据(你的 _get_game_type_name 方法保留在视图里直接用) order_info = { 'dingdan_id': dingdan_obj.OrderID, diff --git a/orders/migrations/0015_merchantorderext_bossphone.py b/orders/migrations/0015_merchantorderext_bossphone.py new file mode 100644 index 0000000..bcec0ce --- /dev/null +++ b/orders/migrations/0015_merchantorderext_bossphone.py @@ -0,0 +1,22 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('orders', '0014_order_assigned_response'), + ] + + operations = [ + migrations.AddField( + model_name='merchantorderext', + name='BossPhone', + field=models.CharField( + blank=True, + db_column='laoban_shouji', + default='', + max_length=11, + verbose_name='老板手机号(链接填单)', + ), + ), + ] diff --git a/orders/models.py b/orders/models.py index 7228efd..0333f2d 100644 --- a/orders/models.py +++ b/orders/models.py @@ -298,6 +298,10 @@ class MerchantOrderExt(QModel): max_length=32, null=True, blank=True, db_column='shangyige_dingdan_id', verbose_name='上一个订单ID', ) + BossPhone = models.CharField( + max_length=11, blank=True, default='', + db_column='laoban_shouji', verbose_name='老板手机号(链接填单)', + ) class Meta: db_table = 'dingdan_shangjia' diff --git a/orders/views.py b/orders/views.py index 586c790..593102e 100644 --- a/orders/views.py +++ b/orders/views.py @@ -2394,6 +2394,7 @@ class ShangjiaDingdanXiangqingView(APIView): # 拼多多订单号(商家/客服可见) 'waibu_dingdan_id': order.ExternalOrderID or '', + 'laoban_shouji': getattr(shangjia_kuozhan, 'BossPhone', '') or '', } try: @@ -3633,16 +3634,23 @@ class ShangjiaLianjieLiuYanZhuanFaView(APIView): permission_classes = [] def post(self, request): - token = request.data.get('token') + token = request.data.get('token', '').strip() + dingdan_id = str(request.data.get('dingdan_id') or request.data.get('dingdanId') or '').strip() text = (request.data.get('text') or '').strip() - if not token or not text: + if not text or (not token and not dingdan_id): return Response({'code': 400, 'msg': '缺少参数'}) - try: - lianjie = ShangjiaLianjie.query.get(LinkToken=token) - except ShangjiaLianjie.DoesNotExist: - return Response({'code': 404, 'msg': '链接不存在或已失效'}) + if token: + try: + lianjie = ShangjiaLianjie.query.get(LinkToken=token) + except ShangjiaLianjie.DoesNotExist: + return Response({'code': 404, 'msg': '链接不存在或已失效'}) + else: + try: + lianjie = ShangjiaLianjie.query.get(OrderID=dingdan_id) + except ShangjiaLianjie.DoesNotExist: + return Response({'code': 404, 'msg': '订单链接不存在或已失效'}) if not lianjie.OrderID: return Response({'code': 400, 'msg': '链接缺少订单ID'}) diff --git a/utils/pdd_order_validator.py b/utils/pdd_order_validator.py new file mode 100644 index 0000000..2966230 --- /dev/null +++ b/utils/pdd_order_validator.py @@ -0,0 +1,86 @@ +"""拼多多订单号校验(商家链接填单)""" +import re +from datetime import date, timedelta + +# 用户复制错误时的引导文案 +PDD_ORDER_ID_HELP = ( + '请在拼多多 App:我的 → 我的订单 → 点开对应订单 → 复制「订单编号」后粘贴,' + '不要粘贴分享链接或网页地址。' +) + +PDD_ORDER_ID_ERROR = '拼多多订单号格式不正确,' + PDD_ORDER_ID_HELP + + +def _parse_date_prefix(prefix: str): + """解析订单号前 6 位 YYMMDD 或前 8 位 YYYYMMDD,返回 date 或 None。""" + if len(prefix) >= 8 and prefix[:8].isdigit(): + y = int(prefix[:4]) + if 2010 <= y <= 2035: + m, d = int(prefix[4:6]), int(prefix[6:8]) + try: + return date(y, m, d) + except ValueError: + pass + if len(prefix) >= 6 and prefix[:6].isdigit(): + yy, m, d = int(prefix[:2]), int(prefix[2:4]), int(prefix[4:6]) + y = 2000 + yy if yy < 100 else yy + try: + return date(y, m, d) + except ValueError: + return None + return None + + +def validate_pdd_order_id(raw: str) -> tuple[bool, str]: + """ + 校验拼多多订单号。 + 允许字母数字组合(含国际单 XP 前缀等),拒绝 URL/链接/token 误填。 + """ + order_id = str(raw or '').strip() + if not order_id: + return False, '拼多多订单号不能为空' + + lower = order_id.lower() + if re.search(r'https?://', lower) or '://' in lower: + return False, '请勿粘贴网页链接,请填写拼多多订单编号。' + PDD_ORDER_ID_HELP + if '/' in order_id or '?' in order_id or '#' in order_id: + return False, PDD_ORDER_ID_ERROR + if re.search(r'(order|dingdan)/[a-z0-9]{8,}', lower): + return False, '检测到派单链接内容,请填写拼多多订单编号。' + PDD_ORDER_ID_HELP + + if len(order_id) < 12 or len(order_id) > 32: + return False, PDD_ORDER_ID_ERROR + if not re.match(r'^[A-Za-z0-9\-]+$', order_id): + return False, PDD_ORDER_ID_ERROR + if not re.search(r'\d', order_id): + return False, PDD_ORDER_ID_ERROR + + # 国际清关等 XP 前缀 + if order_id.upper().startswith('XP'): + if len(order_id) < 10: + return False, PDD_ORDER_ID_ERROR + return True, '' + + # 须以数字开头(常见为年份/日期段) + if not order_id[0].isdigit(): + return False, PDD_ORDER_ID_ERROR + + order_date = _parse_date_prefix(order_id) + if order_date is not None: + today = date.today() + if order_date > today + timedelta(days=1): + return False, '订单号日期异常(不能晚于今天),请核对拼多多订单编号。' + if order_date < today - timedelta(days=730): + return False, '订单号日期过旧,请核对是否为当前拼多多订单编号。' + + return True, '' + + +def validate_cn_mobile(raw: str) -> tuple[bool, str]: + """中国大陆手机号校验。""" + phone = str(raw or '').strip() + if not phone: + return False, '手机号不能为空' + if not re.match(r'^1[3-9]\d{9}$', phone): + return False, '请输入正确的11位中国大陆手机号' + return True, ''