Files
Django/orders/views/dashou.py
XingQue 37bc8f960a feat: 总会员(包含式)模型、迁移、抢单校验与后台配置
- Huiyuan.is_bundle + club_huiyuan_bundle_include 表
- 购买/到期逻辑不变,抢单时总会员覆盖子会员类型
- dshyhq/clumber 返回 is_bundle 与 included_huiyuan_ids

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-05 18:12:57 +08:00

1518 lines
64 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""orders.views.dashou - auto-generated by split script."""
# ==================== 标准库 ====================
import hmac
import os
import json
import time
import random
import string
import traceback
import threading
import requests
import hashlib
import xml.etree.ElementTree as ET
import xmltodict
import logging
from decimal import Decimal
from django.conf import settings
from django.db import models, transaction
from gvsdsdk.fluent import db, func, FQ
from django.db.models import F, Q, OuterRef, Subquery
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponse
from django.utils import timezone
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import permissions, status
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.parsers import JSONParser
from rest_framework.throttling import AnonRateThrottle
from rest_framework_simplejwt.authentication import JWTAuthentication
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.sts.v20180813 import sts_client, models
from utils.weixin_broadcast import WeixinBroadcastSender
from utils.money import yuan_to_fen
from utils.chat_utils import (
_send_group_message, _subscribe_users_to_group, establish_order_chat,
prepare_order_group_chat, resolve_pair_group_id,
parse_pair_from_group_id, resolve_pair_from_order, check_pair_chat_permission,
query_pair_orders, order_to_chat_dict,
)
from utils.fadan_utils import check_fadan_qiangdan_eligible
from orders.utils import (
calc_shangjia_order_fencheng,
update_daily_payout,
settle_shangjia_order_guanshi_fenhong
)
from shop.utils import calculate_pingtai_and_dianpu_shouyi, validate_shangpin_and_dianpu, update_dianpu_daily_stat
from products.utils import update_shangpin_daily_stat
from backend.utils import update_shangjia_daily, update_dashou_daily_by_action, pick_leixing_id, datetime_aliases, fmt_datetime
from rank.services import record_dashou_biaoxian
from rank.utils import check_dashou_biaoqian_required
from orders.notice_tasks import dingdan_guangbo
from ..models import (
Order, MerchantOrderExt, PlatformOrderExt, PlayerDeliveryImage,
PenaltyRecord, PenaltyEvidenceImage, Penalty, PenaltyAppealImage, PenaltyBonus, PenaltyBonusRate,
CommissionRate, PlayerRating, RefundRecord,
)
from jituan.services.club_config import get_commission_rate, get_commission_rate_object
from jituan.services.club_context import resolve_club_id_from_request
from jituan.services.club_user import get_payment_openid
from jituan.services.club_penalty import resolve_penalty_club_id, resolve_penalty_record_club_id
from users.fadan_fenhong_utils import lock_penalty_bonus
from users.fadan_fenhong_utils import lock_penalty_bonus
from products.models import Shangpin, ShangpinLeixing, Huiyuangoumai
from users.models import UserDashou, UserShangjia, UserBoss
from users.business_models import User
from rank.models import DashouBiaoxian, Chenghao, DingdanBiaoqian, YonghuChenghao
from config.models import (
ShangjiaLianjie
)
logger = logging.getLogger(__name__)
ZHIDING_HF_TEXT = {1: '想接', 2: '不想接', 3: '等会接'}
class DashouDingdanHuoquView(APIView):
"""
获取可抢订单列表(支持标签筛选)
POST /dingdan/ddhq
需要JWT Token认证
"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
from orders.services.fake_order_pool import (
dashou_should_use_fake_pool,
build_fake_order_pool_response,
)
if dashou_should_use_fake_pool(request.user):
return build_fake_order_pool_response(request)
# 1. 获取请求参数
data = request.data
leixing_id = pick_leixing_id(data)
page = int(data.get('page', 1))
page_size = int(data.get('page_size', 10))
biaoqian_id = data.get('biaoqian_id')
# 2. 构建基础查询条件
base_query = Q(Status__in=[1, 7])
if leixing_id is not None:
base_query &= Q(ProductTypeID=leixing_id)
# 3. 标签筛选先查出符合条件的订单ID避免跨表 JOIN 排序冲突
if biaoqian_id and int(biaoqian_id) > 0:
biaoqian_order_ids = DingdanBiaoqian.query.filter(
chenghao_id=int(biaoqian_id)
).values_list('dingdan_id', flat=True)
base_query &= Q(OrderID__in=list(biaoqian_order_ids))
# 4. 构建基础查询集
base_qs = Order.query.filter(base_query).order_by('-CreateTime').distinct()
# 5. 获取总记录数
total = base_qs.count()
# 6. 计算分页偏移量
offset = (page - 1) * page_size
limit = page_size
# 7. 获取当前页数据
page_qs = base_qs[offset:offset + limit]
# 8. 使用子查询获取商家昵称Subquery 须用 Django 原生 QuerySet不能用 .query.values()
shangjia_sub = MerchantOrderExt.objects.filter(
Order__OrderID=OuterRef('OrderID')
).values('MerchantNickname')[:1]
# 9. 获取当前页所有需要的字段
dingdan_values = page_qs.annotate(
MerchantNickname=Subquery(shangjia_sub)
).values(
'OrderID', 'Status', 'Platform', 'PlayerCommission',
'AssignedID', 'AssignedResponse', 'AssignedResponseAt', 'AssignedLaterNote',
'ProductTypeID', 'ImageURL', 'GrabRequirement',
'MembershipID', 'CommissionReq', 'Description', 'Remark', 'CreateTime',
'MerchantNickname'
)
# 10. 转换为列表
dingdan_list = list(dingdan_values)
# 11. 批量获取指定打手的头像和昵称
zhiding_ids = {item['AssignedID'] for item in dingdan_list if item.get('AssignedID')}
zhiding_info = {}
if zhiding_ids:
dashou_query = User.query.filter(
UserUID__in=zhiding_ids
).select_related('DashouProfile').annotate(
Nickname=F('DashouProfile__nicheng')
).values('UserUID', 'Avatar', 'Nickname')
zhiding_info = {
item['UserUID']: {
'avatar': item['Avatar'] or '',
'nicheng': item['Nickname'] or ''
}
for item in dashou_query
}
# ============ 批量获取三层标签 ============
order_ids = [item['OrderID'] for item in dingdan_list]
# --- 订单需求标签 ---
xuqiu_tags_map = {}
if order_ids:
try:
dbq_qs = DingdanBiaoqian.query.filter(
dingdan_id__in=order_ids
).select_related('chenghao')
for dbq in dbq_qs:
oid = dbq.dingdan_id
if oid not in xuqiu_tags_map:
xuqiu_tags_map[oid] = []
ch = dbq.chenghao
xuqiu_tags_map[oid].append({
'id': ch.id,
'mingcheng': ch.mingcheng,
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
})
except Exception as e:
logger.error(f"查询订单需求标签失败: {e}", exc_info=True)
# --- 所有需要查询标签的用户ID集合指定打手 + 商家) ---
yonghuid_set = set(zhiding_ids)
shangjia_order_ids = [item['OrderID'] for item in dingdan_list if item['Platform'] == 2]
shangjia_id_map = {}
if shangjia_order_ids:
# 使用跨表查询 Order__OrderID__in 匹配字符串订单ID
sj_exts = MerchantOrderExt.query.filter(
Order__OrderID__in=shangjia_order_ids
).values('Order__OrderID', 'MerchantID')
for sj in sj_exts:
shangjia_id_map[sj['Order__OrderID']] = sj['MerchantID']
if sj['MerchantID']:
yonghuid_set.add(sj['MerchantID'])
# 将 yonghuid 字符串转换为 User 的主键 UserUUID
user_id_map = {}
user_avatar_map = {}
if yonghuid_set:
users = User.query.filter(UserUID__in=yonghuid_set).values('UserUUID', 'UserUID', 'Avatar')
for u in users:
user_id_map[u['UserUID']] = u['UserUUID']
user_avatar_map[u['UserUID']] = (u['Avatar'] or '').strip()
user_main_ids = list(user_id_map.values())
user_tags_map = {}
if user_main_ids:
uc_qs = YonghuChenghao.query.filter(
yonghu_id__in=user_main_ids
).select_related('chenghao')
for uc in uc_qs:
uid = uc.yonghu_id
if uid not in user_tags_map:
user_tags_map[uid] = []
user_tags_map[uid].append(uc.chenghao)
dashou_tags_map = {}
shangjia_tags_map = {}
for item in dingdan_list:
oid = item['OrderID']
# 指定打手标签
zhid = item['AssignedID']
dashou_tags = []
if zhid and zhid in user_id_map:
uid = user_id_map[zhid]
if uid in user_tags_map:
for ch in user_tags_map[uid]:
if ch.leixing == 'dashou':
dashou_tags.append({
'id': ch.id,
'mingcheng': ch.mingcheng,
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
})
dashou_tags_map[oid] = dashou_tags
# 商家标签
sj_tags = []
if item['Platform'] == 2 and oid in shangjia_id_map:
sj_yonghuid = shangjia_id_map[oid]
if sj_yonghuid and sj_yonghuid in user_id_map:
uid = user_id_map[sj_yonghuid]
if uid in user_tags_map:
for ch in user_tags_map[uid]:
if ch.leixing == 'shangjia':
sj_tags.append({
'id': ch.id,
'mingcheng': ch.mingcheng,
'texiao_json': self._parse_texiao_json(ch.texiao_miaoshu)
})
shangjia_tags_map[oid] = sj_tags
# --- 身份装饰标签 + 优质商家 ---
from jituan.services.identity_tag import (
SHENFEN_DASHOU,
SHENFEN_SHANGJIA,
batch_identity_tags,
)
from jituan.services.club_context import resolve_effective_club_id
from users.models import UserShangjia
identity_pairs = []
youzhi_map = {}
merchant_uids = set(shangjia_id_map.values())
if merchant_uids:
for sj in UserShangjia.query.filter(
user__UserUID__in=merchant_uids
).select_related('user').values('user__UserUID', 'is_youzhi_shangjia'):
youzhi_map[sj['user__UserUID']] = bool(sj['is_youzhi_shangjia'])
for item in dingdan_list:
oid = item['OrderID']
zhid = item.get('AssignedID')
if zhid:
identity_pairs.append((zhid, SHENFEN_DASHOU))
if item['Platform'] == 2 and oid in shangjia_id_map:
sj_uid = shangjia_id_map[oid]
if sj_uid:
identity_pairs.append((sj_uid, SHENFEN_SHANGJIA))
club_id = resolve_effective_club_id(request, getattr(request, 'user', None))
identity_tag_map = batch_identity_tags(club_id, identity_pairs) if identity_pairs else {}
# 12. 格式化返回数据
formatted_list = []
for order in dingdan_list:
oid = order['OrderID']
zid = order.get('AssignedID')
info = zhiding_info.get(zid, {})
sj_uid = shangjia_id_map.get(oid) if order['Platform'] == 2 else None
formatted_list.append({
'dingdan_id': order['OrderID'],
'zhuangtai': order['Status'],
'pingtai': order['Platform'],
'dashou_fencheng': float(order['PlayerCommission']) if order['PlayerCommission'] else 0.0,
'leixing_id': order['ProductTypeID'] or 0,
'tupian': order['ImageURL'] or '',
'yaoqiuleixing': order['GrabRequirement'] or 0,
'huiyuan_id': order['MembershipID'] or '',
'yajin': float(order['CommissionReq']) if order['CommissionReq'] else 0.0,
'jieshao': order['Description'] or '',
'beizhu': order['Remark'] or '',
'creat_time': fmt_datetime(order['CreateTime']),
'create_time': fmt_datetime(order['CreateTime']),
'sjnicheng': order['MerchantNickname'] or '',
'sj_avatar': user_avatar_map.get(sj_uid, '') if sj_uid else '',
'zhiding_uid': order['AssignedID'] or '',
'zhiding_avatar': info.get('avatar', ''),
'zhiding_nicheng': info.get('nicheng', ''),
'zhiding_hf': order.get('AssignedResponse'),
'zhiding_hf_text': ZHIDING_HF_TEXT.get(order.get('AssignedResponse'), ''),
'zhiding_dhj_sm': order.get('AssignedLaterNote') or '',
'zhiding_hf_sj': fmt_datetime(order['AssignedResponseAt']) if order.get('AssignedResponseAt') else '',
# 三层标签
'xuqiu_biaoqian': xuqiu_tags_map.get(oid, []),
'dashou_biaoqian': dashou_tags_map.get(oid, []),
'shangjia_biaoqian': shangjia_tags_map.get(oid, []),
# 身份装饰标签 + 优质商家
'shangjia_identity_biaoqian': identity_tag_map.get((sj_uid, SHENFEN_SHANGJIA), []) if sj_uid else [],
'zhiding_identity_biaoqian': identity_tag_map.get((zid, SHENFEN_DASHOU), []) if zid else [],
'shangjia_youzhi': bool(youzhi_map.get(sj_uid)) if sj_uid else False,
})
has_more = (page * page_size) < total
return Response({
'code': 200,
'msg': '获取成功',
'data': {
'list': formatted_list,
'has_more': has_more,
'total': total,
'current_page': page,
'page_size': page_size,
'total_pages': (total + page_size - 1) // page_size,
}
})
except Exception as e:
logger.error(f"订单列表查询失败: {e}", exc_info=True)
return Response({'code': 500, 'msg': f'服务器内部错误: {str(e)}'}, status=500)
def _parse_texiao_json(self, texiao_miaoshu):
if not texiao_miaoshu:
return {}
try:
return json.loads(texiao_miaoshu)
except:
return {}
class ShangpinLeixingHuoquView(APIView):
"""
获取商品类型列表
POST /dingdan/ddlxhq
需要JWT Token认证
"""
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
# 1. 查询商品类型表
# 假设你的商品类型模型字段为id, jieshao, tupian_url
leixing_list = ShangpinLeixing.query.all().values(
'id', # 商品类型ID
'jieshao', # 类型介绍
'tupian_url' # 类型图片URL相对URL
).order_by('id') # 按ID排序可根据需要调整
# 2. 格式化返回数据
formatted_list = []
for leixing in leixing_list:
formatted_leixing = {
'id': leixing['id'],
'jieshao': leixing['jieshao'] or '', # 处理空值
'tupian_url': leixing['tupian_url'] or '' # 处理空值
}
formatted_list.append(formatted_leixing)
# 3. 返回响应
return Response({
'code': 200,
'msg': '获取成功',
'data': {
'list': formatted_list,
'total': len(formatted_list)
}
})
# ----------------- 基础配置获取 -----------------
class QiangdanView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def post(self, request):
data = request.data
dingdan_id = data.get('dingdan_id') or data.get('OrderID')
if not dingdan_id:
return Response({'code': 400, 'msg': '订单ID不能为空'}, status=400)
dingdan_id = str(dingdan_id).strip()
if dingdan_id.upper().startswith('JD'):
return Response({'code': 400, 'msg': '您没充值会员或保证金不可接单'})
try:
with transaction.atomic():
# 1. 验证打手身份和状态
dashou_profile = self._validate_dashou_status(request.user)
if not dashou_profile:
return Response({'code': 403, 'msg': '您不是打手或账号状态异常'}, status=403)
# ==================== 【新增】罚单检查 ====================
fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(request.user.UserUID,1)
if not fadan_ok:
return Response({'code': 400, 'msg': fadan_msg})
# =========================================================
# ========== 标签快速校验(提前拒绝,避免锁资源) ==========
biaoqian_ok, biaoqian_msg = check_dashou_biaoqian_required(dingdan_id, request.user.UserUID)
if not biaoqian_ok:
return Response({'code': 400, 'msg': biaoqian_msg})
# ===========================================================
# 2. 锁定并验证订单(行锁)
order_result = self._validate_and_lock_order(dingdan_id, request.user.UserUID)
if isinstance(order_result, str):
return Response({'code': 400, 'msg': order_result})
order = order_result
validation_result = self._validate_order_requirements(order, dashou_profile, request.user.UserUID)
if not validation_result['success']:
return Response({'code': 400, 'msg': validation_result['message']})
self._execute_qiangdan(order, dashou_profile, request.user.UserUID)
# ========== 【唯一改动】聊天建立方式 ==========
chat_success = establish_order_chat(dingdan_id)
try:
update_dashou_daily_by_action(
yonghuid=order.PlayerID,
amount=order.PlayerCommission,
action=1
)
logger.info(f"打手 {order.PlayerID} 每日统计更新成功")
except Exception as e:
logger.error(f"打手每日统计更新失败: {e}")
# 在 QiangdanView.post 里_execute_qiangdan 之后添加:
# 记录抢单表现(不影响主流程)
# 如果订单表里有 bankuai 信息,可以用 order.bankuai 代替 None
record_dashou_biaoxian(
dingdan_id=dingdan_id,
dashou_id=order.PlayerID,
xingwei=1,
jiedan_time=timezone.now(), # 直接取当前时间
bankuai=None # 暂不填,后续提交时补上
)
return Response({
'code': 200,
'msg': '抢单成功',
'data': {
'dingdan_id': dingdan_id,
'chat_established': chat_success
}
})
except Exception as e:
logger.error(f"抢单异常: {str(e)}", exc_info=True)
return Response({'code': 500, 'msg': '系统异常,请稍后重试'}, status=500)
# ---------- 以下所有方法原封不动照抄旧版 ----------
def _validate_dashou_status(self, user):
try:
dashou_profile = user.DashouProfile
if (dashou_profile.zhuangtai != 1 or
dashou_profile.zhanghaozhuangtai != 1 or
dashou_profile.jifen < 5):
return None
return dashou_profile
except UserDashou.DoesNotExist:
return None
def _validate_and_lock_order(self, dingdan_id, dashou_yonghuid):
try:
order = Order.query.select_for_update().get(OrderID=dingdan_id)
if order.Status not in [1, 7]:
return "订单状态不可抢"
if order.Status == 7:
if not order.AssignedID or order.AssignedID != dashou_yonghuid:
return "此订单为指定单,您不是指定打手"
if order.PlayerID:
return "订单已被其他打手抢走"
return order
except Order.DoesNotExist:
return "订单不存在"
def _validate_order_requirements(self, order, dashou_profile, dashou_yonghuid):
if order.GrabRequirement == 1:
return self._validate_huiyuan_requirement(order, dashou_yonghuid)
elif order.GrabRequirement == 2:
return self._validate_yajin_requirement(order, dashou_profile)
return {'success': True, 'message': '验证通过'}
def _validate_huiyuan_requirement(self, order, dashou_yonghuid):
from jituan.services.huiyuan_bundle import user_has_huiyuan_access
from jituan.services.club_context import resolve_effective_club_id
club_id = None
try:
club_id = resolve_effective_club_id(self.request, self.request.user)
except Exception:
pass
if user_has_huiyuan_access(dashou_yonghuid, order.MembershipID, club_id=club_id):
return {'success': True, 'message': '会员验证通过'}
return {'success': False, 'message': '未开通对应会员,无法抢此订单'}
def _validate_yajin_requirement(self, order, dashou_profile):
yajin_required = order.CommissionReq or 0
dashou_yajin = dashou_profile.yajin or 0
if dashou_yajin < yajin_required:
return {'success': False, 'message': f'押金不足,需要{yajin_required}元,当前有{dashou_yajin}'}
return {'success': True, 'message': '押金验证通过'}
def _execute_qiangdan(self, order, dashou_profile, dashou_yonghuid):
order.Status = 2
order.PlayerID = dashou_yonghuid
order.User2ID = f"Ds{dashou_yonghuid}"
order.save()
dashou_profile.zhuangtai = 0
dashou_profile.jiedanzongliang = F('jiedanzongliang') + 1
dashou_profile.jinrijiedan = F('jinrijiedan') + 1
dashou_profile.jinyuejiedan = F('jinyuejiedan') + 1
dashou_profile.save()
class DashouDingdanHuoquView1(APIView):
"""
打手订单获取接口
支持筛选条件:
- zhuangtai_list: 订单状态列表(可选,空数组或不传表示全部状态)
- leixing_id: 商品类型ID可选
- keyword: 搜索关键词模糊匹配订单ID和订单介绍可选
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# 1. 获取当前登录用户的 yonghuid
try:
current_user = request.user
yonghuid = current_user.UserUID
except AttributeError:
return Response({
'code': 1,
'msg': '用户认证失败',
'data': None
}, status=status.HTTP_401_UNAUTHORIZED)
# 2. 获取并校验前端参数
try:
data = request.data
zhuangtai_list = data.get('zhuangtai_list', []) # 可选,默认空列表(全部)
page = max(1, int(data.get('page', 1)))
page_size = min(100, max(1, int(data.get('page_size', 10))))
leixing_id = pick_leixing_id(data) # 可选商品类型ID
keyword = data.get('keyword', '').strip() # 可选,搜索关键词
except (ValueError, TypeError):
return Response({
'code': 2,
'msg': '参数格式错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 参数默认值与类型修正
if not isinstance(zhuangtai_list, list):
zhuangtai_list = [] # 非列表则视为全部
# 4. 构建查询条件
try:
# 基础条件:当前打手接单
query = Q(PlayerID=yonghuid)
# 状态筛选:如果 zhuangtai_list 不为空列表,则加入筛选;否则查询全部状态
if zhuangtai_list:
query &= Q(Status__in=zhuangtai_list)
# 商品类型筛选
if leixing_id is not None:
query &= Q(ProductTypeID=leixing_id)
# 关键词搜索模糊匹配订单ID和订单介绍
if keyword:
keyword_query = Q(OrderID__icontains=keyword) | Q(Description__icontains=keyword)
query &= keyword_query
# 5. 执行查询,按创建时间倒序
dingdan_query = Order.query.filter(query).order_by('-CreateTime')
# 只选择需要的字段,提高性能
dingdan_query = dingdan_query.only(
'OrderID',
'Status',
'PlayerCommission', # 打手分成金额
'Description',
'ImageURL',
'Nickname',
'CreateTime'
)
total_count = dingdan_query.count()
# 6. 分页处理
start_index = (page - 1) * page_size
end_index = start_index + page_size
current_page_orders = dingdan_query[start_index:end_index]
current_page_count = len(current_page_orders)
# 7. 构建返回数据列表
order_list = []
for order in current_page_orders:
order_data = {
'dingdan_id': order.OrderID,
'zhuangtai': order.Status,
'jine': float(order.PlayerCommission) if order.PlayerCommission else 0.0,
'jieshao': order.Description or '',
'tupian': order.ImageURL or '',
'nicheng': order.Nickname or '',
**datetime_aliases(order.CreateTime),
}
order_list.append(order_data)
# 8. 判断是否还有更多数据
has_more = (current_page_count == page_size) and (end_index < total_count)
if total_count == 0:
has_more = False
return Response({
'code': 0,
'msg': '成功',
'data': {
'list': order_list,
'has_more': has_more,
'total': total_count
}
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"打手订单获取失败: {str(e)}", exc_info=True)
return Response({
'code': 4,
'msg': '服务器内部错误',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class DashouCOSZhengshuView(APIView):
"""打手获取COS上传凭证接口调试版本"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 1. 接收参数(兼容 OrderID / dingdan_id
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
yongtu = request.data.get('yongtu', 'dingdan')
# 2. 条件判断修正:除了 fakuan 之外,其他用途均需提供 dingdan_id
if yongtu != 'fakuan' and not dingdan_id:
return Response(
{'code': 400, 'msg': '订单ID不能为空', 'error': 'dingdan_id is required when yongtu is not fakuan'},
status=status.HTTP_400_BAD_REQUEST
)
# 3. 业务验证逻辑(根据用途分别验证)
if yongtu == 'chufa':
try:
chufa_record = PenaltyRecord.query.filter(
OrderID=dingdan_id,
PlayerID=request.user.UserUID,
ApplyStatus__in=[0, 3]
).first()
if not chufa_record:
return Response(
{'code': 403, 'msg': '处罚记录无权限或状态不对',
'error': f'No punishment record found for user {request.user.UserUID} on order {dingdan_id}'},
status=status.HTTP_403_FORBIDDEN
)
except Exception as e:
logger.error(f"查处罚记录异常: {str(e)}")
return Response(
{'code': 500, 'msg': '服务器内部错误', 'error': str(e)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
elif yongtu == 'fakuan':
# 罚款申诉不需要订单验证
pass
elif yongtu == 'fakuan_evidence':
# 商家上传罚单证据图Purpose=1
fadan_id = request.data.get('fadan_id')
if not dingdan_id or not fadan_id:
return Response(
{'code': 400, 'msg': '缺少订单ID或罚单ID'},
status=status.HTTP_400_BAD_REQUEST,
)
try:
shangjia_profile = request.user.ShopProfile
if shangjia_profile.zhuangtai != 1:
return Response({'code': 403, 'msg': '商家状态异常'}, status=403)
except Exception:
return Response({'code': 403, 'msg': '非商家无权上传'}, status=403)
fadan = Penalty.query.filter(
id=fadan_id,
RelatedOrderID=dingdan_id,
ApplicantID=request.user.UserUID,
).first()
if not fadan:
return Response({'code': 403, 'msg': '罚单不存在或无权操作'}, status=403)
elif yongtu == 'fakuan_evidence_draft':
# 商家申请罚款前上传证据图(仅需订单归属校验)
if not dingdan_id:
return Response({'code': 400, 'msg': '缺少订单ID'}, status=400)
try:
shangjia_profile = request.user.ShopProfile
if shangjia_profile.zhuangtai != 1:
return Response({'code': 403, 'msg': '商家状态异常'}, status=403)
except Exception:
return Response({'code': 403, 'msg': '非商家无权上传'}, status=403)
try:
MerchantOrderExt.query.get(Order__OrderID=dingdan_id, MerchantID=request.user.UserUID)
except MerchantOrderExt.DoesNotExist:
return Response({'code': 403, 'msg': '订单不存在或无权操作'}, status=403)
else:
# 默认为订单相关,需要验证订单且打手必须接单
try:
dingdan = Order.query.get(
OrderID=dingdan_id,
PlayerID=request.user.UserUID,
Status__in=[2, 8]
)
except Order.DoesNotExist:
return Response(
{'code': 403, 'msg': '此功能暂未开放', 'error': f'Order {dingdan_id} not found or not assigned to user {request.user.UserUID}'},
status=status.HTTP_403_FORBIDDEN
)
# 4. 获取腾讯云STS临时密钥
try:
# 初始化固定凭证
cred = credential.Credential(
settings.COS_SECRET_ID,
settings.COS_SECRET_KEY
)
# 创建STS客户端
http_profile = HttpProfile()
http_profile.endpoint = "sts.tencentcloudapi.com"
client_profile = ClientProfile()
client_profile.httpProfile = http_profile
client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
# 构建策略资源
resources = []
if yongtu == 'chufa':
resources.append(
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/dssstp/*"
)
elif yongtu == 'fakuan':
resources.append(
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/dashoufakuan/dashoufakuanshensu/*"
)
elif yongtu == 'fakuan_evidence':
resources.append(
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/shangjiafakuan/zhengju/*"
)
elif yongtu == 'fakuan_evidence_draft':
resources.append(
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/fakuan/shangjiafakuan/zhengju/*"
)
else:
resources.append(
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/order/{dingdan_id}/*"
)
policy = {
"version": "2.0",
"statement": [{
"effect": "allow",
"action": [
"cos:PutObject",
"cos:PostObject",
"cos:InitiateMultipartUpload",
"cos:UploadPart",
"cos:CompleteMultipartUpload"
],
"resource": resources
}]
}
req = models.GetFederationTokenRequest()
req.Name = f"dashou_{dingdan_id}_{int(time.time())}"
req.Policy = json.dumps(policy)
req.DurationSeconds = 7200
resp = client.GetFederationToken(req)
credentials = resp.Credentials
except TencentCloudSDKException as e:
logger.error(f"获取STS临时密钥失败: {str(e)}")
return Response({
'code': 500,
'msg': f'获取上传凭证失败: {str(e)}',
'error_detail': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 5. 构建响应
current_time = int(time.time())
upload_dir = (
'fakuan/shangjiafakuan/zhengju/' if yongtu == 'fakuan_evidence' else
'fakuan/dashoufakuan/dashoufakuanshensu/' if yongtu == 'fakuan' else (
'a_long/chfajltp/dssstp/' if yongtu == 'chufa' else f'order/{dingdan_id}/'
)
)
response_data = {
'credentials': {
'tmpSecretId': credentials.TmpSecretId,
'tmpSecretKey': credentials.TmpSecretKey,
'sessionToken': credentials.Token,
},
'startTime': current_time,
'expiredTime': current_time + 7200,
'bucket': settings.COS_BUCKET,
'region': settings.COS_REGION,
'upload_dir': upload_dir,
}
return Response({'code': 0, 'msg': '成功', 'data': response_data})
except Exception as e:
# 捕捉所有未预期的异常,返回完整 traceback
logger.error(f'COS凭证接口未知错误: {traceback.format_exc()}')
return Response({
'code': 500,
'msg': f'服务器内部错误: {str(e)}',
'error_detail': traceback.format_exc()
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class ShangjiaCOSZhengshuView(APIView):
"""商家获取COS上传凭证接口用于处罚申诉图片上传"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 1. 接收参数
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
yongtu = request.data.get('yongtu', 'chufa') # 用途chufa=处罚申诉
if not dingdan_id:
return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
# 2. 🔴【修改】验证商家身份 - 通过反向查询shop_profile判断是否为商家
try:
# 检查是否有商家扩展表
shangjia_profile = request.user.ShopProfile
# 验证商家状态是否为1正常
if shangjia_profile.zhuangtai != 1:
return Response({'code': 3, 'msg': '商家账号状态异常,无法操作', 'data': None},
status=status.HTTP_403_FORBIDDEN)
# 🔴【重要】获取商家ID从商家扩展表中获取
shangjia_id = request.user.UserUID # 假设商家扩展表中有yonghuid字段
except AttributeError:
# 没有shop_profile属性说明不是商家
return Response({'code': 3, 'msg': '非商家用户无权限', 'data': None},
status=status.HTTP_403_FORBIDDEN)
# 3. 🔴【修改】验证订单权限 - 通过商家订单扩展表验证
try:
# 获取订单
dingdan = Order.query.get(OrderID=dingdan_id)
# 🔴【重要】通过反向查询商家扩展表验证订单是否属于当前商家
try:
shangjia_kuozhan = dingdan.shangjia_kuozhan # 使用反向关系名
# 验证商家ID是否匹配
if str(shangjia_kuozhan.MerchantID) != str(shangjia_id):
return Response({'code': 2, 'msg': '订单不属于当前商家', 'data': None},
status=status.HTTP_403_FORBIDDEN)
# 验证订单状态是否允许申请处罚(根据前端逻辑)
allowed_zhuangtai = [2, 4, 5, 6, 8]
if dingdan.Status not in allowed_zhuangtai:
return Response({'code': 4, 'msg': '当前订单状态不允许申请处罚', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
# 🔴【新增】检查是否已存在处罚申请通过Chufajilu模型
# 注意这里假设Chufajilu模型存在并且有shangjia_id字段
try:
existing_chufa = PenaltyRecord.query.filter(
OrderID=dingdan_id,
ApplicantID=shangjia_id,
ApplyStatus__in=[0, 1] # 审核中或已处罚状态
).exists()
if existing_chufa:
return Response({'code': 5, 'msg': '该订单已有处罚申请,请勿重复上传', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
except ImportError:
# 如果没有Chufajilu模型跳过这个检查
pass
except MerchantOrderExt.DoesNotExist:
# 没有商家扩展记录,说明这不是商家订单
return Response({'code': 2, 'msg': '非商家订单无法操作', 'data': None},
status=status.HTTP_403_FORBIDDEN)
except Order.DoesNotExist:
return Response({'code': 2, 'msg': '订单不存在', 'data': None},
status=status.HTTP_404_NOT_FOUND)
except Exception as e:
logger.error(f"订单验证异常: {str(e)}")
return Response({'code': 500, 'msg': '服务器验证异常', 'data': None},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 4. 获取腾讯云STS临时密钥
try:
# 初始化固定凭证
cred = credential.Credential(
settings.COS_SECRET_ID,
settings.COS_SECRET_KEY
)
# 创建STS客户端
http_profile = HttpProfile()
http_profile.endpoint = "sts.tencentcloudapi.com"
client_profile = ClientProfile()
client_profile.httpProfile = http_profile
client = sts_client.StsClient(cred, "ap-shanghai", client_profile)
# 🔴【修改】构建策略 - 只允许上传到商家处罚证据图片目录
resources = [
f"qcs::cos:{settings.COS_REGION}:uid/{settings.COS_APPID}:{settings.COS_BUCKET}/a_long/chfajltp/sjzjtp/*"
]
policy = {
"version": "2.0",
"statement": [
{
"effect": "allow",
"action": [
"cos:PutObject",
"cos:PostObject",
"cos:InitiateMultipartUpload",
"cos:UploadPart",
"cos:CompleteMultipartUpload"
],
"resource": resources
}
]
}
# 请求STS临时凭证
req = models.GetFederationTokenRequest()
# 🔴【修改】命名包含商家ID
req.Name = f"shangjia_{shangjia_id}_{dingdan_id}_{int(time.time())}"
req.Policy = json.dumps(policy)
req.DurationSeconds = 7200 # 2小时有效期
resp = client.GetFederationToken(req)
# 提取临时密钥
credentials = resp.Credentials
except TencentCloudSDKException as e:
logger.error(f"商家获取STS临时密钥失败: {str(e)}")
return Response({
'code': 500,
'msg': f'获取上传凭证失败: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# 5. 构建响应数据
current_time = int(time.time())
response_data = {
'credentials': {
'tmpSecretId': credentials.TmpSecretId,
'tmpSecretKey': credentials.TmpSecretKey,
'sessionToken': credentials.Token,
},
'startTime': current_time,
'expiredTime': current_time + 7200,
'bucket': settings.COS_BUCKET,
'region': settings.COS_REGION,
# 🔴【修改】商家专用上传目录
'upload_dir': 'a_long/chfajltp/sjzjtp/',
# 返回商家ID和订单ID便于前端生成文件名
'dingdan_id': dingdan_id,
'shangjia_id': shangjia_id,
# 返回商家昵称,便于前端显示
'shangjia_nicheng': shangjia_profile.nicheng if hasattr(shangjia_profile, 'nicheng') else ''
}
# 6. 返回成功
return Response({
'code': 0,
'msg': '成功',
'data': response_data
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f'商家COS凭证接口未知错误: {str(e)}', exc_info=True)
return Response({
'code': 500,
'msg': f'服务器内部错误: {str(e)}',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class DashouTijiaoView(APIView):
"""打手提交订单完成接口"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 1. 获取参数
data = request.data
dingdan_id = str(data.get('OrderID') or data.get('dingdan_id') or '').strip()
liuyan = data.get('liuyan', '')
jiaofu_tupian_urls = data.get('jiaofu_tupian_urls', [])
# 2. 参数验证
if not dingdan_id:
return Response({
'code': 1,
'msg': '订单ID不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
if not isinstance(jiaofu_tupian_urls, list):
return Response({
'code': 2,
'msg': '图片URL列表格式错误',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 限制图片数量最多9张最少1张
if len(jiaofu_tupian_urls) == 0:
return Response({
'code': 3,
'msg': '至少需要上传一张图片',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
if len(jiaofu_tupian_urls) > 9:
return Response({
'code': 4,
'msg': '最多只能上传9张图片',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 留言长度限制50字
if liuyan and len(liuyan) > 50:
return Response({
'code': 5,
'msg': '留言最多50个字',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 3. 获取当前用户并验证打手身份
current_user = request.user
yonghuid = current_user.UserUID
try:
dashou_profile = current_user.DashouProfile
except UserDashou.DoesNotExist:
return Response({
'code': 6,
'msg': '用户不是打手身份',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 4. 验证订单状态和权限
try:
dingdan = Order.query.get(
OrderID=dingdan_id,
PlayerID=yonghuid,
Status=2 # 必须是进行中的订单
)
except Order.DoesNotExist:
return Response({
'code': 7,
'msg': '订单不存在或没有提交权限',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 5. 存储图片到Dashoutupian表
for tupian_url in jiaofu_tupian_urls:
# 创建打手提交图片记录
PlayerDeliveryImage.query.create(
OrderID=dingdan_id,
PlayerID=yonghuid,
ImageURL=tupian_url,
CreateTime=timezone.now()
)
# 6. 更新订单主表
dingdan.PlayerRemark = liuyan
dingdan.Status = 8 # 状态改为结算中
dingdan.UpdateTime = timezone.now()
dingdan.save()
# 7. 更新打手扩展表状态
dashou_profile.zhuangtai = 1 # 改为数字1根据你之前说的
dashou_profile.save()
# ========== 新增:记录打手提交时间 ==========
record_dashou_biaoxian(
dingdan_id=dingdan_id,
dashou_id=yonghuid,
xingwei=2, # 提交
tijiao_time=timezone.now()
)
# ===========================================
# 8. 返回成功响应
return Response({
'code': 0,
'msg': '订单提交成功',
'data': {
'dingdan_id': dingdan_id,
'new_zhuangtai': 8, # 新的订单状态
'dashou_zhuangtai': 1 # 打手新的状态
}
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"打手提交订单失败: {str(e)}", exc_info=True)
return Response({
'code': 8,
'msg': '服务器内部错误',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class DashouDingdanXiangqingView(APIView):
"""打手订单详情获取接口"""
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# 1. 获取订单ID
dingdan_id = str(request.data.get('OrderID') or request.data.get('dingdan_id') or '').strip()
if not dingdan_id:
return Response({
'code': 1,
'msg': '订单ID不能为空',
'data': None
}, status=status.HTTP_400_BAD_REQUEST)
# 2. 获取当前用户并验证权限
current_user = request.user
yonghuid = current_user.UserUID
try:
dashou_profile = current_user.DashouProfile
except UserDashou.DoesNotExist:
return Response({
'code': 2,
'msg': '用户不是打手身份',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 3. 查询订单主表
try:
dingdan = Order.query.get(OrderID=dingdan_id)
except Order.DoesNotExist:
return Response({
'code': 3,
'msg': '订单不存在',
'data': None
}, status=status.HTTP_404_NOT_FOUND)
# 4. 验证订单是否属于当前打手
if dingdan.PlayerID != yonghuid:
return Response({
'code': 4,
'msg': '没有权限查看此订单',
'data': None
}, status=status.HTTP_403_FORBIDDEN)
# 5. 构建基础订单数据
dingdan_data = {
'dingdan_id': dingdan.OrderID,
'zhuangtai': dingdan.Status,
'jine': float(dingdan.PlayerCommission) if dingdan.PlayerCommission else 0.0,
'jieshao': dingdan.Description or '',
'tupian': dingdan.ImageURL or '',
'nicheng': dingdan.Nickname or '',
'beizhu': dingdan.Remark or '',
**datetime_aliases(dingdan.CreateTime),
'tuikuan_liyou':dingdan.RefundReason or '',
'dashou_liuyan': dingdan.PlayerRemark or '',
'fuwudashou_id': dingdan.PlayerID or '',
'fadanpingtai': dingdan.Platform or ''
}
# 6. 查询打手提交的图片
dashou_tupian_list = PlayerDeliveryImage.query.filter(
OrderID=dingdan_id,
PlayerID=yonghuid
).order_by('CreateTime')
jiaofu_tupian_urls = []
for tupian_record in dashou_tupian_list:
if tupian_record.ImageURL:
jiaofu_tupian_urls.append(tupian_record.ImageURL)
dingdan_data['dashou_jiaofu'] = jiaofu_tupian_urls
# 7. 查询商家扩展表信息
shangjia_info = {}
try:
shangjia_kuozhan = dingdan.shangjia_kuozhan
shangjia_info = {
'shangjia_id': shangjia_kuozhan.MerchantID or '',
'shangjia_nicheng': shangjia_kuozhan.MerchantNickname or '',
'shangjia_liuyan': shangjia_kuozhan.MerchantReview or '',
#'tuikuan_liyou': '',
'chufa_liyou': shangjia_kuozhan.PenaltyReason or '',
'chufa_zhuangtai': shangjia_kuozhan.PenaltyApplyStatus or 0,
#'chufa_jieguo': shangjia_kuozhan.chufa_jieguo or '',
'bohui_liyou': shangjia_kuozhan.RejectReason or ''
}
except MerchantOrderExt.DoesNotExist:
# 没有商家扩展信息,返回空值
shangjia_info = {
'shangjia_id': '',
'shangjia_nicheng': '',
'shangjia_liuyan': '',
'tuikuan_liyou': '',
'chufa_liyou': '',
'chufa_zhuangtai': 0,
'chufa_jieguo': '',
'bohui_liyou': ''
}
# 8. 查询平台扩展表信息(如果有的话)
pingtai_info = {}
try:
pingtai_kuozhan = dingdan.pingtai_kuozhan
pingtai_info = {
'laoban_id': pingtai_kuozhan.BossID or '',
'laoban_pingjia': pingtai_kuozhan.BossReview or ''
}
except PlatformOrderExt.DoesNotExist:
pingtai_info = {
'laoban_id': '',
'laoban_pingjia': ''
}
# 9. 合并所有数据
response_data = {
**dingdan_data,
**shangjia_info,
**pingtai_info
}
return Response({
'code': 0,
'msg': '成功',
'data': response_data
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"获取打手订单详情失败: {str(e)}", exc_info=True)
return Response({
'code': 5,
'msg': '服务器内部错误',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class DashouHuoquLeixingView(APIView):
"""
打手获取商品类型接口 - 抢单页面使用
接口地址POST /dingdan/dsqdhqddlx
功能:返回所有正常的商品类型,已按排序权重排序
返回格式(与前端原数据格式完全一致):
{
"code": 200,
"msg": "成功",
"data": {
"list": [
{
"id": 1,
"jieshao": "王者荣耀",
"tupian_url": "leixing/wzry.jpg",
"yaoqiuleixing": null,
"huiyuan_id": null,
"yongjin": null,
"paixu": 100,
"shenhezhuangtai": 1
},
// ... 更多类型
]
}
}
"""
# 🔴 使用JWT Token认证
permission_classes = [IsAuthenticated]
def post(self, request):
"""
处理POST请求返回商品类型列表
请求参数:无特殊参数,只验证用户身份
安全验证:
1. JWT Token验证用户身份
2. 只返回 shenhezhuangtai=1正常状态的类型
3. 数据已排序paixu降序 → id升序
"""
try:
logger.info(f"打手获取商品类型请求 - 用户ID: {request.user.UserUID}")
from jituan.services.club_context import resolve_effective_club_id
from jituan.services.club_shangpin_leixing import build_club_leixing_list_for_api
club_id = resolve_effective_club_id(request, request.user)
leixing_list = build_club_leixing_list_for_api(club_id)
logger.info(f"返回商品类型数据:{len(leixing_list)}条,俱乐部:{club_id} 用户: {request.user.UserUID}")
# 🔴 返回成功响应
# 注意:保持与前端原数据格式完全一致
return Response({
"code": 200,
"msg": "成功",
"data": {
"list": leixing_list # 🔴 保持原字段名 "list"
}
}, status=status.HTTP_200_OK)
except Exception as e:
# 🔴 异常处理
logger.error(f"打手获取商品类型失败 - 用户ID: {request.user.UserUID}, 错误: {str(e)}", exc_info=True)
return Response({
"code": 500,
"msg": f"服务器内部错误: {str(e)}",
"data": None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
class DashouXiugaiView(APIView):
"""
打手修改订单交付内容接口
请求方式POST
请求路径:/dingdan/dsxiugaidd
权限需携带有效JWT Token且用户为打手身份
请求参数:
dingdan_id : 订单ID必填
liuyan : 新的留言内容可选最长50字
deleted_images : 要删除的图片URL列表数组每个元素为完整COS URL
new_images : 新增的图片URL列表数组每个元素为完整COS URL
返回:
成功:{"code":0,"msg":"修改成功","data":null}
失败:返回相应错误码和提示
"""
permission_classes = [IsAuthenticated]
def post(self, request):
# 获取请求数据
data = request.data
dingdan_id = str(data.get('OrderID') or data.get('dingdan_id') or '').strip()
liuyan = data.get('liuyan', '')
deleted_images = data.get('deleted_images', [])
new_images = data.get('new_images', [])
# ---------- 参数校验 ----------
if not dingdan_id:
return Response({'code': 1, 'msg': '订单ID不能为空', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
if not isinstance(deleted_images, list):
return Response({'code': 2, 'msg': 'deleted_images 必须为数组', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
if not isinstance(new_images, list):
return Response({'code': 3, 'msg': 'new_images 必须为数组', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
if liuyan and len(liuyan) > 50:
return Response({'code': 4, 'msg': '留言最多50个字', 'data': None},
status=status.HTTP_400_BAD_REQUEST)
# ---------- 用户身份验证 ----------
current_user = request.user
yonghuid = current_user.UserUID
# 检查是否为打手
try:
dashou_profile = current_user.DashouProfile # User 模型反向 related_name='DashouProfile'
except UserDashou.DoesNotExist:
return Response({'code': 5, 'msg': '用户不是打手身份', 'data': None},
status=status.HTTP_403_FORBIDDEN)
# ---------- 订单查询及权限验证 ----------
try:
# 订单必须属于该打手且状态为4退款中或8结算中
dingdan = Order.query.get(
OrderID=dingdan_id,
PlayerID=yonghuid,
Status__in=[4, 8] # 只允许修改退款中或结算中的订单
)
except Order.DoesNotExist:
return Response({'code': 6, 'msg': '订单不存在或没有修改权限', 'data': None},
status=status.HTTP_403_FORBIDDEN)
# ---------- 执行修改(使用事务保证原子性) ----------
try:
with transaction.atomic():
# 1. 处理删除的图片:从 Dashoutupian 表中删除记录
if deleted_images:
# 根据订单ID、打手ID和图片URL删除记录
delete_count, _ = PlayerDeliveryImage.query.filter(
OrderID=dingdan_id,
PlayerID=yonghuid,
ImageURL__in=deleted_images
).delete()
# 可选:记录删除数量
logger.info(f"订单 {dingdan_id} 删除图片 {delete_count}")
# 2. 处理新增的图片:插入新记录
if new_images:
# 批量创建
new_records = []
for url in new_images:
new_records.append(PlayerDeliveryImage(
OrderID=dingdan_id,
PlayerID=yonghuid,
ImageURL=url,
CreateTime=timezone.now()
))
PlayerDeliveryImage.objects.bulk_create(new_records)
logger.info(f"订单 {dingdan_id} 新增图片 {len(new_records)}")
# 3. 更新订单留言(如果发生变化)
# 注意:即使留言为空字符串,也允许更新,表示清空留言
if 'liuyan' in data: # 判断是否传了liuyan字段可能为空字符串
dingdan.PlayerRemark = liuyan
dingdan.UpdateTime = timezone.now()
dingdan.save(update_fields=['PlayerRemark', 'UpdateTime'])
# 全部成功,返回成功响应
return Response({
'code': 0,
'msg': '修改成功',
'data': None
}, status=status.HTTP_200_OK)
except Exception as e:
logger.error(f"修改订单失败: {str(e)}", exc_info=True)
return Response({
'code': 7,
'msg': '服务器内部错误',
'data': None
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)