feat: 链接填单增加拼多多订单号/手机号校验并隐藏H5价格
This commit is contained in:
@@ -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,
|
||||
|
||||
22
orders/migrations/0015_merchantorderext_bossphone.py
Normal file
22
orders/migrations/0015_merchantorderext_bossphone.py
Normal file
@@ -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='老板手机号(链接填单)',
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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'
|
||||
|
||||
@@ -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'})
|
||||
|
||||
86
utils/pdd_order_validator.py
Normal file
86
utils/pdd_order_validator.py
Normal file
@@ -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, ''
|
||||
Reference in New Issue
Block a user