# yonghu/views.py import time import random import hashlib import requests from django.db import transaction, IntegrityError from django.conf import settings from utils.xcx_sys_config import wx_cfg from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework_simplejwt.tokens import RefreshToken from .models import UserMain, UserBoss, UserDashou, UserShangjia, UserGuanshi from dingdan.models import Dingdan, DingdanPingtai, Lilubiao from peizhi.models import Qunpeizhi # 假设群配置表在这个模块 from rest_framework.throttling import AnonRateThrottle from rest_framework.permissions import AllowAny # 新增这行 from django.conf import settings from utils.xcx_sys_config import wx_cfg from django.utils import timezone from django.db import transaction, IntegrityError from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.throttling import AnonRateThrottle from rest_framework.permissions import AllowAny from rest_framework_simplejwt.tokens import RefreshToken from .models import UserMain, AdminProfile # 获取客户端真实IP的函数 def huoquKehuduanIP(request): """ 获取客户端的真实IP地址 考虑反向代理(如Nginx)的情况 """ # 优先从HTTP_X_FORWARDED_FOR获取(经过代理的情况) x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: # 可能有多个IP,取第一个 ip = x_forwarded_for.split(',')[0].strip() if ip: return ip # 如果没有代理,直接从REMOTE_ADDR获取 ip = request.META.get('REMOTE_ADDR', '') # 如果还是获取不到,返回默认值 if not ip: ip = '0.0.0.0' return ip # 获取用户代理信息(用于更详细的追踪) def huoquYonghuDaili(request): """ 获取用户浏览器/设备信息 """ user_agent = request.META.get('HTTP_USER_AGENT', '') # 这里可以添加更复杂的用户代理解析 # 为简化,我们只返回原始字符串的前100个字符 return user_agent[:100] if user_agent else '' def shengcheng_yonghu_id(): """生成7位数字用户ID""" for _ in range(10): timestamp_part = str(int(time.time()))[-5:].zfill(5) random_part = str(random.randint(0, 99)).zfill(2) user_id = timestamp_part + random_part if len(user_id) == 7 and user_id.isdigit(): if not UserMain.objects.filter(yonghuid=user_id).exists(): return user_id raise Exception('生成用户ID失败') def get_or_create_wechat_user(openid, user_type='normal'): """ 按 openid 获取或创建微信用户,兼容并发注册导致的主键/唯一键冲突。 返回 (user_main, created) """ for attempt in range(3): try: with transaction.atomic(): user_main = UserMain.objects.select_for_update().filter(openid=openid).first() if user_main: if not user_main.yonghuid: yonghuid = shengcheng_yonghu_id() UserMain.objects.filter(pk=user_main.pk).update(yonghuid=yonghuid) user_main.refresh_from_db() return user_main, False user_main = UserMain.objects.create( openid=openid, yonghuid=shengcheng_yonghu_id(), user_type=user_type, ) user_main.refresh_from_db() return user_main, True except IntegrityError: if attempt == 2: user_main = UserMain.objects.filter(openid=openid).first() if user_main: return user_main, False raise user_main = UserMain.objects.get(openid=openid) return user_main, False def update_wechat_user_login_info(user_main, ip, unionid=None): """更新登录信息,使用 update 避免 save() 误触发 INSERT。""" update_fields = { 'ip': ip, 'last_login_time': timezone.now(), } if unionid and unionid != user_main.unionid: update_fields['unionid'] = unionid UserMain.objects.filter(pk=user_main.pk).update(**update_fields) user_main.refresh_from_db() class WechatMiniProgramLoginView(APIView): """ 微信小程序登录接口 """ throttle_classes = [AnonRateThrottle] permission_classes = [AllowAny] def post(self, request): try: code = request.data.get('code', '').strip() if not code: return Response({'code': 1, 'msg': '微信授权码不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST) # 获取微信openid和unionid wechat_data = self.get_wechat_openid(code) if not wechat_data or 'openid' not in wechat_data: error_msg = wechat_data.get('errmsg', '微信授权失败') return Response({'code': 2, 'msg': f'微信登录失败: {error_msg}', 'data': None}, status=status.HTTP_400_BAD_REQUEST) openid = wechat_data['openid'] session_key = wechat_data.get('session_key', '') unionid = wechat_data.get('unionid', '') # 🆕【新增】获取unionid # 获取客户端真实IP kehuduan_ip = self.huoquKehuduanIP(request) with transaction.atomic(): user_main, created = get_or_create_wechat_user(openid, user_type='normal') update_wechat_user_login_info(user_main, kehuduan_ip, unionid=unionid or None) if created: UserBoss.objects.create(user=user_main, nickname='板板大人') # 检查扩展表 dashou_status = 0 shangjia_status = 0 guanshi_status = 0 boss_nickname = '微信用户' try: boss_profile = UserBoss.objects.get(user=user_main) boss_nickname = boss_profile.nickname if boss_profile.nickname else '微信用户' except UserBoss.DoesNotExist: pass try: UserDashou.objects.get(user=user_main) dashou_status = 1 except UserDashou.DoesNotExist: dashou_status = 0 try: UserShangjia.objects.get(user=user_main) shangjia_status = 1 except UserShangjia.DoesNotExist: shangjia_status = 0 try: UserGuanshi.objects.get(user=user_main) guanshi_status = 1 except UserGuanshi.DoesNotExist: guanshi_status = 0 try: UserZuzhang.objects.get(user=user_main) zuzhang_status = 1 except UserZuzhang.DoesNotExist: zuzhang_status = 0 # 生成JWT token refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) # 计算订单数量 order_counts = self.jisuanDingdanShuliang(user_main.yonghuid) # 获取群配置 group_info = self.huoquQunPeizhi() # 返回数据 response_data = { 'token': token, 'nicheng': boss_nickname, 'uid': user_main.yonghuid, 'touxiang': user_main.avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, 'zuzhangstatus': zuzhang_status, 'dingdantiaoshu': order_counts, 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''), 'guanshiqunid': group_info.get('guanshiqunid', '') } return Response({'code': 0, 'msg': '登录成功', 'data': response_data}) except Exception as e: import logging logger = logging.getLogger(__name__) error_openid = locals().get('openid', 'N/A') logger.error(f"微信登录异常 - openid: {error_openid}, 错误类型: {type(e).__name__}, 错误详情: {str(e)}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def get_wechat_openid(self, code): """ 调用微信接口获取openid和unionid """ try: appid = wx_cfg.WEIXIN_APPID secret = wx_cfg.WEIXIN_SECRET if not appid or not secret: raise ValueError('微信配置未设置') url = 'https://api.weixin.qq.com/sns/jscode2session' params = {'appid': appid, 'secret': secret, 'js_code': code, 'grant_type': 'authorization_code'} response = requests.get(url, params=params, timeout=10) result = response.json() if 'openid' in result: return {'openid': result['openid'], 'session_key': result.get('session_key', ''), 'unionid': result.get('unionid', '')} else: errcode = result.get('errcode', 'unknown') errmsg = result.get('errmsg', '未知错误') return {'errmsg': f'[{errcode}]{errmsg}'} except requests.exceptions.Timeout: return {'errmsg': '请求微信接口超时'} except Exception as e: return {'errmsg': f'请求微信接口异常: {str(e)}'} def shengchengYonghuID(self): """ 生成7位数字用户ID """ for _ in range(10): timestamp_part = str(int(time.time()))[-5:].zfill(5) random_part = str(random.randint(0, 99)).zfill(2) user_id = timestamp_part + random_part if len(user_id) == 7 and user_id.isdigit(): if not UserMain.objects.filter(yonghuid=user_id).exists(): return user_id raise Exception('生成用户ID失败') def huoquKehuduanIP(self, request): """ 获取客户端真实IP地址 """ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() if ip: return ip ip = request.META.get('REMOTE_ADDR', '') return ip if ip else '0.0.0.0' def jisuanDingdanShuliang(self, yonghuid): """ 统计订单数量 """ try: if not DingdanPingtai.objects.filter(laoban_id=yonghuid).exists(): return {'daifuwu': 0, 'fuwuzhong': 0, 'yiwancheng': 0, 'yituikuan': 0} dingdan_ids = DingdanPingtai.objects.filter(laoban_id=yonghuid).values_list('dingdan__id', flat=True) from django.db.models import Q, Count from django.db.models import Case, When, IntegerField results = Dingdan.objects.filter(id__in=dingdan_ids).aggregate( daifuwu=Count(Case(When(Q(zhuangtai=1) | Q(zhuangtai=7), then=1), output_field=IntegerField())), fuwuzhong=Count(Case(When(zhuangtai=2, then=1), output_field=IntegerField())), yiwancheng=Count(Case(When(zhuangtai=3, then=1), output_field=IntegerField())), yituikuan=Count(Case(When(zhuangtai=5, then=1), output_field=IntegerField())) ) return { 'daifuwu': results['daifuwu'] or 0, 'fuwuzhong': results['fuwuzhong'] or 0, 'yiwancheng': results['yiwancheng'] or 0, 'yituikuan': results['yituikuan'] or 0 } except Exception: return {'daifuwu': 0, 'fuwuzhong': 0, 'yiwancheng': 0, 'yituikuan': 0} def huoquQunPeizhi(self): """ 获取群配置信息 """ try: qun_configs = Qunpeizhi.objects.filter(id__in=[1, 2]) result = { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } for config in qun_configs: if config.id == 1: result['dashouqun'] = config.neirong or '' result['dashouqunid'] = config.qunid or '' elif config.id == 2: result['guanshiqun'] = config.neirong or '' result['guanshiqunid'] = config.qunid or '' return result except Exception: return { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } # views/weixin_official.py - 完整文件 import hashlib import time import logging from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse from django.utils import timezone from xml.etree import ElementTree as ET from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from django.conf import settings from utils.xcx_sys_config import wx_cfg from .models import OfficialAccountUser logger = logging.getLogger(__name__) class WeixinOfficialCallbackView(APIView): """ 微信服务号回调接口 处理用户关注、取消关注等事件 核心:有关注事件就存,不管有没有UnionID """ throttle_classes = [] permission_classes = [AllowAny] authentication_classes = [] def get(self, request): """验证服务器配置(微信官方要求)""" try: signature = request.GET.get('signature', '') timestamp = request.GET.get('timestamp', '') nonce = request.GET.get('nonce', '') echostr = request.GET.get('echostr', '') token = wx_cfg.WEIXIN_OFFICIAL_TOKEN if not token: logger.error("服务号Token未配置") return HttpResponse('Token not configured', status=500) # 验证签名 tmp_list = sorted([token, timestamp, nonce]) tmp_str = ''.join(tmp_list) hash_str = hashlib.sha1(tmp_str.encode()).hexdigest() if hash_str == signature: return HttpResponse(echostr) else: logger.error(f"签名验证失败") return HttpResponse('Signature verification failed', status=403) except Exception as e: logger.error(f"验证服务器异常: {str(e)}") return HttpResponse('Internal Server Error', status=500) def post(self, request): """处理事件推送(关注、取关)""" try: xml_str = request.body.decode('utf-8') logger.info(f"收到服务号事件,原始数据(前200字符): {xml_str[:200]}") root = ET.fromstring(xml_str) msg_type = root.find('MsgType').text from_user = root.find('FromUserName').text # 服务号OpenID if msg_type == 'event': event = root.find('Event').text if event == 'subscribe': return self.handle_subscribe_event(root, from_user) elif event == 'unsubscribe': return self.handle_unsubscribe_event(root, from_user) else: # 其他事件(如菜单点击)直接返回success return HttpResponse('success') # 非事件消息也直接返回success return HttpResponse('success') except Exception as e: logger.error(f"处理服务号事件异常: {str(e)}", exc_info=True) # 即使异常也返回success,避免微信服务器重试 return HttpResponse('success') def handle_subscribe_event(self, root, from_user): """处理用户关注事件 - 简化版:有就存,没有拉倒""" try: # 尝试获取UnionID(有就有,没有也没关系) unionid_elem = root.find('UnionId') unionid = unionid_elem.text if unionid_elem is not None else None logger.info(f"用户关注,OpenID: {from_user}, UnionID: {unionid}") # 核心逻辑:更新或创建记录 obj, created = OfficialAccountUser.objects.update_or_create( official_openid=from_user, defaults={ 'unionid': unionid, 'is_subscribed': True, 'subscribe_time': timezone.now() } ) action = "新建" if created else "更新" logger.info(f"{action}用户记录成功: OpenID={from_user}") # 回复欢迎消息(可选) return self.generate_text_response(root, "感谢关注文赫电竞!平台有新订单时您会收到通知。") except Exception as e: logger.error(f"处理关注事件异常: {str(e)}", exc_info=True) # 依然返回success,不让微信重试 return HttpResponse('success') def handle_unsubscribe_event(self, root, from_user): """处理用户取消关注事件 - 简化版:标记为未订阅""" try: updated_count = OfficialAccountUser.objects.filter( official_openid=from_user ).update(is_subscribed=False) if updated_count > 0: logger.info(f"用户取消关注,已标记为未订阅: {from_user}") else: logger.info(f"用户取消关注,但记录不存在(无需处理): {from_user}") return HttpResponse('success') except Exception as e: logger.error(f"处理取消关注事件异常: {str(e)}", exc_info=True) return HttpResponse('success') def generate_text_response(self, root, content): """生成文本消息响应""" try: to_user = root.find('ToUserName').text from_user = root.find('FromUserName').text response_xml = f""" {int(time.time())} """ return HttpResponse(response_xml, content_type='application/xml') except Exception as e: logger.error(f"生成回复消息异常: {str(e)}") # 即使生成回复失败,也返回一个空的success return HttpResponse('success') # yonghu/views.py from rest_framework.parsers import MultiPartParser, FormParser,JSONParser from rest_framework import permissions from django.db import transaction, IntegrityError from rest_framework.response import Response from rest_framework import status from rest_framework.views import APIView from .models import UserMain, UserBoss, UserDashou, UserShangjia, UserGuanshi from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss import os import uuid class UserInfoUpdateView(APIView): """ 用户个人信息修改接口 功能: - 上传 / 更新头像 - 修改昵称 - 更新手机号(支持明文 phone 或微信 getPhoneNumber 组件返回的 shoujihao_code) 当传 shoujihao_code 时,会通过微信接口解密得到真实手机号并自动设置 shoujihao_renzheng=True """ permission_classes = [permissions.IsAuthenticated] def post(self, request): """ 处理 POST 请求,支持 multipart/form-data 格式 """ try: # ---------- 获取当前用户 ---------- user_main = request.user # ---------- 获取或创建老板扩展表记录 ---------- try: user_boss = UserBoss.objects.get(user=user_main) except UserBoss.DoesNotExist: user_boss = UserBoss.objects.create(user=user_main, nickname='微信用户') response_data = {} # ==================== 1. 处理头像上传 ==================== avatar_file = request.FILES.get('avatar') if avatar_file: # 验证图片格式 / 大小 is_valid, error_msg = validate_image(avatar_file) if not is_valid: return Response({ 'code': 1, 'msg': f'图片验证失败: {error_msg}', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 上传头像到 OSS / COS avatar_url = self.handle_avatar_upload(user_main, avatar_file) if avatar_url: response_data['touxiang'] = avatar_url user_main.avatar = avatar_url else: return Response({ 'code': 2, 'msg': '头像上传失败', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # ==================== 2. 处理昵称更新 ==================== nicheng = request.data.get('nicheng') if nicheng is not None: nicheng = str(nicheng).strip() if nicheng: if len(nicheng) > 20: return Response({ 'code': 3, 'msg': '昵称不能超过20个字符', 'data': None }, status=status.HTTP_400_BAD_REQUEST) user_boss.nickname = nicheng response_data['nicheng'] = nicheng # ==================== 3. 处理手机号更新 ==================== # 明文手机号(传统方式) phone =None shoujihao_code = request.data.get('shoujihao_code') # 微信手机号快速验证组件返回的临时 code if shoujihao_code: # 如果前端传了 shoujihao_code,则通过微信接口解密得到真实手机号 try: phone = self._get_phone_by_code(shoujihao_code) logger.info(f"手机号解密成功: {phone}") except Exception as e: logger.error(f"手机号解密失败: {str(e)}") return Response({ 'code': 6, 'msg': f'获取手机号失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) if phone is not None: phone = str(phone).strip() if phone: # 验证手机号格式(11 位数字) if not phone.isdigit() or len(phone) != 11: return Response({ 'code': 4, 'msg': '请输入11位有效的手机号码', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 更新主表手机号,并标记手机号已认证 user_main.phone = phone user_main.shoujihao_renzheng = True response_data['phone'] = phone # ==================== 4. 检查是否有任何更新 ==================== if not response_data: return Response({ 'code': 5, 'msg': '没有需要更新的数据', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # ==================== 5. 事务保存 ==================== with transaction.atomic(): user_main.save() user_boss.save() # ==================== 6. 返回成功 ==================== return Response({ 'code': 0, 'msg': '更新成功', 'data': response_data }) except Exception as e: logger.exception("用户信息更新异常") return Response({ 'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # ==================== 原有方法:头像上传 ==================== def handle_avatar_upload(self, user_main, avatar_file): """ 上传头像到 OSS / COS,并返回存储的相对路径 """ try: yonghuid = user_main.yonghuid if not yonghuid: logger.warning("用户 ID 为空,无法上传头像") return None # 获取旧头像,用于后续删除 old_avatar_url = user_main.avatar # 生成新文件名 file_extension = os.path.splitext(avatar_file.name)[1].lower() if not file_extension: file_extension = '.jpg' unique_filename = f"{uuid.uuid4().hex}{file_extension}" # OSS 文件路径:avatar/{yonghuid}/{filename} avatar_folder = 'avatar' oss_file_path = f"{avatar_folder}/{yonghuid}/{unique_filename}" # 删除旧头像(如果存在) if old_avatar_url and old_avatar_url.strip(): delete_from_oss(old_avatar_url) # 上传新头像 new_avatar_url = upload_to_oss(avatar_file, oss_file_path) if new_avatar_url: logger.info(f"头像上传成功: {oss_file_path}") return oss_file_path else: logger.error("头像上传失败,upload_to_oss 返回 None") return None except Exception as e: logger.exception("处理头像上传失败") return None # ==================== 新增方法:微信接口相关 ==================== def _get_access_token(self): """ 获取微信小程序全局 access_token,带缓存 access_token 有效期 7200 秒,提前 300 秒过期 """ import requests from django.core.cache import cache # 尝试从缓存中读取 token = cache.get('wechat_access_token') if token: return token # 从 settings 读取 AppID 和 AppSecret appid = wx_cfg.WEIXIN_APPID or None secret = wx_cfg.WEIXIN_SECRET or None if not appid or not secret: raise ValueError( 'settings.py 中缺少 WECHAT_APPID 或 WECHAT_APPSECRET,' '请添加:\n' "WECHAT_APPID = 'wx0e4be86faac4a8d1'\n" "WECHAT_APPSECRET = '你的小程序密钥'" ) url = 'https://api.weixin.qq.com/cgi-bin/token' params = { 'grant_type': 'client_credential', 'appid': appid, 'secret': secret } try: resp = requests.get(url, params=params) data = resp.json() except Exception as e: raise Exception(f'请求微信 access_token 接口异常: {str(e)}') if 'access_token' in data: token = data['access_token'] # 缓存时间设 7100 秒,提前 5 分钟过期 cache.set('wechat_access_token', token, timeout=7100) logger.info("成功获取微信 access_token") return token else: errcode = data.get('errcode') errmsg = data.get('errmsg') logger.error(f"获取 access_token 失败: errcode={errcode}, errmsg={errmsg}") raise Exception(f"获取 access_token 失败: {errmsg}") def _get_phone_by_code(self, code): """ 通过微信手机号快速验证组件返回的 code 换取真实手机号 接口文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html """ import requests # 先获取有效的 access_token access_token = self._get_access_token() # 构建请求 URL url = f'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={access_token}' # 请求体:code 必须放在 JSON 中 payload = {'code': code} try: resp = requests.post(url, json=payload, timeout=10) data = resp.json() except Exception as e: raise Exception(f'请求微信手机号解密接口异常: {str(e)}') errcode = data.get('errcode') if errcode == 0: phone_info = data.get('phone_info', {}) pure_phone = phone_info.get('purePhoneNumber') if not pure_phone: raise Exception('手机号为空,可能用户未授权真实手机号') return pure_phone else: errmsg = data.get('errmsg') logger.error(f"手机号解密失败: errcode={errcode}, errmsg={errmsg}") raise Exception(f'微信接口错误 (errcode={errcode}): {errmsg}') # yonghu/views.py 或 yonghu/api_views.py from django.utils import timezone from django.shortcuts import get_object_or_404 from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework import status from .models import UserMain, UserDashou from shangpin.models import Huiyuangoumai # 需要导入会员模型 import logging class DashouXinxiAPIView(APIView): """ 获取打手信息接口 请求方式:POST 认证方式:JWT Token 返回:打手基本信息、资产信息、会员信息,金牌打手额外返回专属字段 """ permission_classes = [IsAuthenticated] def post(self, request): try: # 1. 获取当前登录用户 current_user = request.user yonghuid = current_user.yonghuid # 2. 查询打手扩展表 try: dashou_profile = current_user.dashou_profile except UserDashou.DoesNotExist: return Response({ 'code': 400, 'message': '您不是打手身份', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 3. 构建基础信息响应(原有字段完全保留) data = { 'dashounicheng': dashou_profile.nicheng or '', 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, 'yongjin': str(dashou_profile.yue) if dashou_profile.yue is not None else '0.00', 'zonge': str(dashou_profile.zonge) if dashou_profile.zonge is not None else '0.00', 'yajin': str(dashou_profile.yajin) if dashou_profile.yajin is not None else '0.00', 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'jiedanZongliang': dashou_profile.jiedanzongliang if dashou_profile.jiedanzongliang is not None else '0', 'jifen': dashou_profile.jifen, 'chenghao': dashou_profile.chenghao or '', } # 4. 查询会员信息(原有逻辑) huiyuan_list = [] try: huiyuan_records = Huiyuangoumai.objects.filter( yonghu_id=yonghuid ).select_related() for record in huiyuan_records: # 检查会员是否过期 is_expired = record.jiance_shifou_daoqi() # 只返回未过期的会员(huiyuan_zhuangtai = 1) if record.huiyuan_zhuangtai == 1: huiyuan_list.append({ 'huiyuanid': record.huiyuan_id, 'huiyuanming': record.jieshao or f"会员{record.huiyuan_id}", 'daoqi': record.daoqi_time }) except Exception as e: # 会员信息查询失败不影响主要功能,返回空列表 huiyuan_list = [] data['clumber'] = huiyuan_list # 5. 如果是金牌打手,添加专属字段 if dashou_profile.chenghao == "金牌打手": data.update({ 'jiedanZongliang': dashou_profile.jiedanzongliang, # 接单总量 'jinriJiedan': str(dashou_profile.jinrishouyi) if dashou_profile.jinrishouyi is not None else '0.00', # 今日接单金额 'zuijinTixian': str(dashou_profile.jinritixian_jine) if dashou_profile.jinritixian_jine is not None else '0.00', # 最近提现金额(今日已提现) }) # 6. 返回成功响应 return Response({ 'code': 200, 'message': '获取成功', 'data': data }, status=status.HTTP_200_OK) except Exception as e: # 生产环境应使用 logging 记录错误,而不是 print # logger.error(f"获取打手信息失败: {e}", exc_info=True) return Response({ 'code': 500, 'message': f'服务器内部错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # yonghu/views.py 或 yonghu/api_views.py class ZaixianZhuangtaiAPIView(APIView): """ 更改打手在线状态接口 请求方式:POST 认证方式:JWT Token 请求参数:status(0=离线,1=在线) 返回:更新后的状态信息 """ permission_classes = [IsAuthenticated] def post(self, request): try: # 1. 获取请求参数 new_status = request.data.get('status') if new_status is None: return Response({ 'code': 400, 'message': '缺少状态参数', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 转换为整数,确保是0或1 try: new_status = int(new_status) if new_status not in [0, 1]: return Response({ 'code': 400, 'message': '状态参数必须是0或1', 'data': None }, status=status.HTTP_400_BAD_REQUEST) except ValueError: return Response({ 'code': 400, 'message': '状态参数必须是整数', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 2. 获取当前登录用户 current_user = request.user yonghuid = current_user.yonghuid # 3. 查询打手扩展表 try: dashou_profile = current_user.dashou_profile except UserDashou.DoesNotExist: return Response({ 'code': 400, 'message': '您不是打手身份', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 4. 检查账号状态(是否被封禁) if dashou_profile.zhanghaozhuangtai != 1: return Response({ 'code': 403, 'message': '账号状态异常,无法更改在线状态', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 5. 更新在线状态 old_status = dashou_profile.zaixianzhuangtai # 如果状态没有变化,直接返回成功 if old_status == new_status: return Response({ 'code': 200, 'message': '状态未变化', 'data': { 'zaixianzhuangtai': new_status, 'status_text': '在线' if new_status == 1 else '离线' } }, status=status.HTTP_200_OK) # 更新状态 dashou_profile.zaixianzhuangtai = new_status dashou_profile.save() # 6. 返回成功响应 return Response({ 'code': 200, 'message': '状态更新成功', 'data': { 'zaixianzhuangtai': new_status, 'status_text': '在线' if new_status == 1 else '离线' } }, status=status.HTTP_200_OK) except Exception as e: return Response({ 'code': 500, 'message': f'服务器内部错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) from shangpin.models import Gsfenhong # 需要导入会员模型 class GuanshiXinxiView(APIView): """ 获取管事信息接口 路径:/yonghu/guanshixinxi 方法:POST 权限:JWT认证 功能:获取当前管事的详细信息,包括基本信息、分红信息以及已充值打手数量 """ permission_classes = [IsAuthenticated] def post(self, request): try: # 获取当前用户(通过JWT认证) current_user = request.user # 查询管事扩展表 try: guanshi_profile = UserGuanshi.objects.get(user=current_user) # 🔥【新增】查询已充值打手数量(从分红表中统计) # Gsfenhong 表中 guanshi 字段存储管事ID(yonghuid) yichongzhi_count = Gsfenhong.objects.filter(guanshi=current_user.yonghuid).count() # 返回管事信息 return Response({ 'code': 200, 'msg': '获取成功', 'data': { 'gszhstatus': guanshi_profile.zhuangtai, # 管事账号状态 'yaoqingzongshu': guanshi_profile.yaogingshuliang, # 邀请打手总数 'fenyongzonge': float(guanshi_profile.chongzhifenrun), # 充值分佣总额 'fenyongtixian': float(guanshi_profile.yue), # 可提现余额 'yichongzhiDashou': yichongzhi_count, # 已充值打手数量 } }) except UserGuanshi.DoesNotExist: # 用户不是管事,返回错误 return Response({ 'code': 400, 'msg': '您还不是管事,请先注册', 'data': None }, status=400) except Exception as e: # 捕获所有异常,返回错误信息并记录日志 import logging logger = logging.getLogger(__name__) logger.error(f"获取管事信息异常: {str(e)}", exc_info=True) return Response({ 'code': 500, 'msg': f'获取信息失败: {str(e)}', 'data': None }, status=500) '''class GuanshiXinxiView(APIView): """ 获取管事信息接口 路径:/yonghu/guanshixinxi 方法:POST 权限:JWT认证 功能:获取当前管事的详细信息 """ permission_classes = [IsAuthenticated] def post(self, request): try: # 获取当前用户(通过JWT认证) current_user = request.user # 查询管事扩展表 try: guanshi_profile = UserGuanshi.objects.get(user=current_user) # 返回管事信息 return Response({ 'code': 200, 'msg': '获取成功', 'data': { 'gszhstatus': guanshi_profile.zhuangtai, # 管事账号状态 'yaoqingzongshu': guanshi_profile.yaogingshuliang, # 邀请打手总数 'fenyongzonge': float(guanshi_profile.chongzhifenrun), # 充值分佣总额 'fenyongtixian': float(guanshi_profile.yue), # 可提现余额 # 注意:这里不返回guanshistatus,因为这是缓存字段 # 前端需要的管事状态就是gszhstatus } }) except UserGuanshi.DoesNotExist: # 用户不是管事,返回错误 return Response({ 'code': 400, 'msg': '您还不是管事,请先注册', 'data': None }, status=400) except Exception as e: # 捕获所有异常,返回错误信息 return Response({ 'code': 500, 'msg': f'获取信息失败: {str(e)}', 'data': None }, status=500)''' # yonghu/views.py from rest_framework import status from django.core.exceptions import ObjectDoesNotExist class ShangJiaXinXiView(APIView): """ 获取商家信息接口 (POST /yonghu/shangjiaxinxi) 权限:需JWT认证 """ permission_classes = [IsAuthenticated] def post(self, request): try: # 关键:使用 get() 方法。如果反向关联不存在,会抛出 UserShangjia.DoesNotExist 异常。 shangjia = request.user.shop_profile # 如果反向关系存在,但你想更显式地查询,也可以这样写(效果相同): # shangjia = UserShangjia.objects.get(user=request.user) except ObjectDoesNotExist: # 捕获“不存在”异常,说明当前用户还没有商家扩展资料 return Response({ 'code': 4001, # 你可以定义特定的业务错误码 'msg': '您还未注册成为商家或商家资料不存在', 'data': None }, status=status.HTTP_200_OK) # 业务错误,HTTP状态码通常仍用200 # 构建返回给前端的数据 response_data = { 'sjzhzhuangtai': shangjia.zhuangtai, # 商家账号状态 'fadanzong': shangjia.fabu, # 发单总量 'tuikuanzong': shangjia.tuikuan, # 退款总量 'sjyue': float(shangjia.yue), # 商家余额 'riliushui': float(shangjia.jinriliushui), # 今日流水 'yueliushui': float(shangjia.jinyueliushui), # 今月流水 } return Response({ 'code': 200, 'msg': '获取商家信息成功', 'data': response_data }) class BossBiaoshiView(APIView): """ 获取用户的GoEasy标识 - 极简实现 POST /yonghu/bossbiaoshi 需要JWT Token认证 """ permission_classes = [IsAuthenticated] def post(self, request): # 1. 直接从request.user获取用户 user = request.user # 2. 获取yonghuid(确保用户已登录) if not user or not hasattr(user, 'yonghuid'): return Response({ 'code': 401, 'msg': '用户未认证或用户信息不完整' }, status=401) # 3. 生成标识:Boss + yonghuid(与前端约定) boss_biaoshi = f"Boss{user.yonghuid}" # 4. 返回给前端 return Response({ 'code': 200, 'msg': '获取成功', 'data': { 'boss_biaoshi': boss_biaoshi, 'yonghuid': user.yonghuid, 'user_type': user.user_type } }) """ 阿龙电竞陪玩平台 - 邀请码相关API接口 """ from utils.yaoqingma_utils import chuangjianYaoqingma, yanzhengYaoqingmaWeiyixing class YaoqingmaView(APIView): """ 获取或生成管事邀请码 访问路径: /api/yonghu/yaoqingma/ 请求方法: GET 权限要求: JWT Token认证通过的用户 """ permission_classes = [IsAuthenticated] def post(self, request): """ 获取当前用户的邀请码 逻辑: 1. 通过JWT获取当前用户 (request.user) 2. 反向查询UserGuanshi扩展表 3. 验证管事状态(zhuangtai=1) 4. 已有邀请码直接返回,没有则生成并保存 """ # 获取当前认证用户 current_user = request.user # 1. 使用反向查询获取管事扩展信息 (性能最优方式) try: guanshi_profile = current_user.guanshi_profile except UserGuanshi.DoesNotExist: # 用户不是管事,没有扩展表记录 return Response({ 'code': 403, 'message': '当前用户不是管事身份,无法生成邀请码', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 2. 验证管事状态 (zhuangtai 必须等于 1) if guanshi_profile.zhuangtai != 1: return Response({ 'code': 403, 'message': '管事账号状态异常,无法生成邀请码', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 3. 检查是否已存在邀请码 existing_yaoqingma = guanshi_profile.yaoqingma # 如果存在且非空,直接返回 if existing_yaoqingma and existing_yaoqingma.strip() != '': return Response({ 'code': 200, 'message': '成功获取现有邀请码', 'data': { 'yaoqingma': existing_yaoqingma, 'is_new': False # 标记是否为新生成的 } }) # 4. 生成新的邀请码 (使用用户ID字符串) # 注意: 这里使用yonghuid,不是主键id,如你要求的"123456"这种形式 yonghuid_str = str(current_user.yonghuid) new_yaoqingma = chuangjianYaoqingma(yonghuid_str) # 5. 验证生成的邀请码格式 if not yanzhengYaoqingmaWeiyixing(new_yaoqingma): return Response({ 'code': 500, 'message': '邀请码生成失败,格式验证错误', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 6. 保存到数据库 (使用事务确保数据一致性) try: with transaction.atomic(): guanshi_profile.yaoqingma = new_yaoqingma guanshi_profile.save() except Exception as e: # 这里可以记录日志 return Response({ 'code': 500, 'message': f'保存邀请码到数据库失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 7. 返回新生成的邀请码 return Response({ 'code': 200, 'message': '成功生成新的邀请码', 'data': { 'yaoqingma': new_yaoqingma, 'is_new': True # 标记是新生成的 } }) """ 阿龙电竞陪玩平台 - 打手注册及相关API接口 """ from shangpin.models import Huiyuangoumai # 导入会员购买模型 from utils.yaoqingma_utils import chuangjianYaoqingma # 导入之前写的邀请码生成算法 from shangpin.models import Huiyuangoumai # 导入会员购买模型 from utils.yaoqingma_utils import chuangjianYaoqingma # 导入之前写的邀请码生成算法 import traceback _dashou_zhuce_logger = logging.getLogger('yonghu.dashouzhuce') class DashouZhuceView(APIView): """ 打手注册接口 (通过管事邀请码) 访问路径: /api/yonghu/dashouzhuce/ 请求方法: POST 权限要求: JWT Token认证通过的用户 请求体: { "inviteCode": "AbC123..." } """ permission_classes = [IsAuthenticated] def _log_dashou_zhuce_error(self, invite_code, yonghuid, exc): """500 时打印完整堆栈,便于排查(gunicorn/1panel 可抓 stderr)。""" err_text = traceback.format_exc() _dashou_zhuce_logger.exception( '打手注册失败 inviteCode=%s yonghuid=%s error=%s', invite_code, yonghuid, exc, ) print( f'[DashouZhuce ERROR] inviteCode={invite_code} yonghuid={yonghuid} error={exc}\n{err_text}', flush=True, ) def post(self, request): yaoqingma = '' current_user = request.user yonghuid = getattr(current_user, 'yonghuid', '') try: from utils.dashou_register_service import ( create_dashou_for_user, resolve_guanshi_for_dashou_register, ) # 1. 解析邀请码(支持无邀请码走默认码) yaoqingma = request.data.get('inviteCode', '') guanshi_profile, invite_err = resolve_guanshi_for_dashou_register(yaoqingma) if invite_err: return Response({ 'code': 404, 'message': invite_err, 'data': None }, status=status.HTTP_404_NOT_FOUND) # 2. 检查用户是否已是打手 (使用反向查询,性能最优) try: dashou_profile = current_user.dashou_profile # 用户已是打手,直接返回现有信息 return self.fanhuiXianyouDashouXinxi(dashou_profile, current_user) except UserDashou.DoesNotExist: # 用户不是打手,继续注册流程 pass # 3. 创建打手身份 dashou_profile = create_dashou_for_user(current_user, guanshi_profile) # 4. 注册成功,返回信息 fanhui_data = self.zhuangbeiFanhuiShuju(dashou_profile, current_user, is_new=True) return Response({ 'code': 200, 'message': '注册成功!已开通打手、商家、管事身份。', 'data': fanhui_data }) except Exception as e: self._log_dashou_zhuce_error(yaoqingma, yonghuid, e) return Response({ 'code': 500, 'message': f'注册过程中发生系统错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def fanhuiXianyouDashouXinxi(self, dashou_profile, current_user): """处理已是打手的用户,返回其现有信息""" fanhui_data = self.zhuangbeiFanhuiShuju(dashou_profile, current_user, is_new=False) return Response({ 'code': 200, 'message': '您已是打手,返回当前信息。', 'data': fanhui_data }) def zhuangbeiFanhuiShuju(self, dashou_profile, current_user, is_new=False): """准备返回给前端的打手数据,字段名严格匹配前端""" # 构建基础信息 data = { 'dashounicheng': dashou_profile.nicheng, 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'yongjin': str(dashou_profile.yue), # Decimal转字符串 'zonge': str(dashou_profile.zonge), 'yajin': str(dashou_profile.yajin), 'chenghao': dashou_profile.chenghao, 'jinfen': dashou_profile.jifen, 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, } # 查询并处理会员列表 (clumber) # 使用当前用户的yonghuid进行查询 huiyuan_goumai_list = [] if not is_new: # 如果是新用户,直接返回空列表,无需查询 goumai_records = Huiyuangoumai.objects.filter(yonghu_id=current_user.yonghuid) for record in goumai_records: # 调用方法检查是否到期,此方法内部会更新状态 shifou_daoqi = record.jiance_shifou_daoqi() huiyuan_goumai_list.append({ 'huiyuan_id': record.huiyuan_id, 'huiyuan_zhuangtai': record.huiyuan_zhuangtai, 'daoqi_time': record.daoqi_time.isoformat() if record.daoqi_time else None, 'shifou_daoqi': shifou_daoqi, 'jieshao': record.jieshao }) data['clumber'] = huiyuan_goumai_list data['dashoustatus'] = 1 data['guanshistatus'] = 1 if UserGuanshi.objects.filter(user=current_user).exists() else 0 data['shangjiastatus'] = 1 if UserShangjia.objects.filter(user=current_user).exists() else 0 return data from django.core.paginator import Paginator, EmptyPage class GuanshiYaoqingDashouListView(APIView): """ 管事获取已邀请打手列表(支持筛选、搜索、分页) POST /api/yonghu/gshqyqds/ Headers: Authorization: Bearer Body: { "page": 1, // 页码,从1开始 "pageSize": 30, // 每页条数,最大100 "keyword": "", // 搜索关键词(用户ID或昵称) "zhanghaozhuangtai": 1, // 账号状态:1=正常,2=封禁(可选) "zaixianzhuangtai": 1 // 在线状态:1=在线,2=离线(可选) } """ permission_classes = [IsAuthenticated] def post(self, request): try: current_user = request.user guanshi_yonghuid = current_user.yonghuid # 1. 分页参数 page = int(request.data.get('page', 1)) page_size = int(request.data.get('pageSize', 30)) if page < 1 or page_size < 1: return Response({'code': 400, 'message': '分页参数必须大于0'}, status=400) if page_size > 100: page_size = 100 # 2. 筛选参数 keyword = request.data.get('keyword', '').strip() zhanghaozhuangtai = request.data.get('zhanghaozhuangtai') # 可选:1=正常,2=封禁 zaixianzhuangtai = request.data.get('zaixianzhuangtai') # 可选:1=在线,2=离线 # 3. 基础查询:当前管事邀请的所有打手 base_qs = UserDashou.objects.select_related('user').filter( yaoqingren=guanshi_yonghuid ) # 4. 全局统计(不受筛选影响,直接基于 base_qs) total_all = base_qs.count() zaixian_all = base_qs.filter(zaixianzhuangtai=1).count() zhengchang_all = base_qs.filter(zhanghaozhuangtai=1).count() fengjin_all = total_all - zhengchang_all # 5. 应用筛选条件 if keyword: base_qs = base_qs.filter( Q(nicheng__icontains=keyword) | Q(user__yonghuid__icontains=keyword) ) if zhanghaozhuangtai is not None: try: val = int(zhanghaozhuangtai) if val == 1: base_qs = base_qs.filter(zhanghaozhuangtai=1) else: base_qs = base_qs.exclude(zhanghaozhuangtai=1) except (ValueError, TypeError): pass if zaixianzhuangtai is not None: try: val = int(zaixianzhuangtai) if val == 1: base_qs = base_qs.filter(zaixianzhuangtai=1) else: base_qs = base_qs.exclude(zaixianzhuangtai=1) except (ValueError, TypeError): pass # 6. 排序和分页 base_qs = base_qs.order_by('-create_time') paginator = Paginator(base_qs, page_size) try: page_obj = paginator.page(page) except EmptyPage: page_obj = [] has_more = False filtered_total = paginator.count if paginator.count else 0 else: has_more = page_obj.has_next() filtered_total = paginator.count # 7. 构建打手列表 dashou_list = [] for dashou in page_obj: user_main = dashou.user dashou_list.append({ 'uid': user_main.yonghuid, 'nicheng': dashou.nicheng or '打手', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'touxiang': user_main.avatar or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, 'tuikuanliang': dashou.tuikuanliang, 'yue': str(dashou.zonge), }) # 8. 获取管事邀请总数(来自管事扩展表) try: yaoqingzongshu = current_user.guanshi_profile.yaogingshuliang except Exception: yaoqingzongshu = total_all response_data = { 'dashouList': dashou_list, 'yaoqingzongshu': yaoqingzongshu, # 邀请总数(全局) 'totalCount': filtered_total, # 当前筛选条件下的总数 'zaixianCount': zaixian_all, # 全局在线 'zhengchangCount': zhengchang_all, # 全局正常 'fengjinCount': fengjin_all, # 全局封禁 'currentPage': page, 'pageSize': page_size, 'hasMore': has_more, } return Response({'code': 200, 'message': '获取成功', 'data': response_data}) except ValueError: return Response({'code': 400, 'message': '分页参数格式错误'}, status=400) except Exception as e: logger.error(f'获取打手列表失败: {str(e)}', exc_info=True) return Response({'code': 500, 'message': '系统错误'}, status=500) from rest_framework.views import APIView from rest_framework import permissions, status from rest_framework.response import Response from django.db import transaction, IntegrityError from .models import UserMain, UserDashou, UserGuanshi, Tixianjilu import os import uuid from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss class TixianXinxiHuoquView(APIView): """ 获取用户提现收款信息接口 返回用户已设置的手机号、收款码、收款账号 """ permission_classes = [permissions.IsAuthenticated] def post(self, request): """ 处理获取收款信息的POST请求 直接返回用户当前的收款设置 """ try: # 从request.user获取当前用户 user_main = request.user # 🔥【修改开始 - 使用Decimal,字段名是lilu】 # 查询打手提现费率 (fadanpingtai='5') dashou_rate_obj = Lilubiao.objects.filter(fadanpingtai='5').first() dashou_rate = decimal.Decimal(str(dashou_rate_obj.lilu)) if dashou_rate_obj else decimal.Decimal('0.00') # 查询管事提现费率 (fadanpingtai='6') guanshi_rate_obj = Lilubiao.objects.filter(fadanpingtai='6').first() guanshi_rate = decimal.Decimal(str(guanshi_rate_obj.lilu)) if guanshi_rate_obj else decimal.Decimal('0.00') # 🔥【修改结束】 # 构建返回数据 response_data = { 'txdianhua': user_main.phone if user_main.phone else '', 'txtupian': user_main.zhifu if user_main.zhifu else '', 'txzh': user_main.skzhanghao if user_main.skzhanghao else '', # 🔥【修改开始】 'dashou_rate': dashou_rate, # 打手提现费率 'guanshi_rate': guanshi_rate, # 管事提现费率 # 🔥【修改结束】 } # 返回成功响应 return Response({ 'code': 0, 'msg': '获取成功', 'data': response_data }) except Exception as e: return Response({ 'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class ShoukuanXinxiShangchuanView(APIView): """ 上传用户收款信息接口 支持同时上传手机号、收款账号、收款码图片 使用multipart/form-data格式 """ permission_classes = [permissions.IsAuthenticated] parser_classes = (MultiPartParser, FormParser,JSONParser) def post(self, request): """ 处理上传收款信息的POST请求 验证并保存用户的收款设置 """ try: # 获取当前用户 user_main = request.user yonghuid = user_main.yonghuid # 准备响应数据 response_data = {} # 1. 验证必填字段:手机号必须要有 txdianhua = request.data.get('txdianhua', '').strip() if not txdianhua: return Response({ 'code': 1, 'msg': '收款人电话不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 验证手机号格式(11位数字) if not txdianhua.isdigit() or len(txdianhua) != 11: return Response({ 'code': 2, 'msg': '请输入11位有效的手机号码', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 2. 获取收款账号(可能为空) txzh = request.data.get('txzh', '').strip() # 3. 处理收款码图片上传(如果有) txtupian_file = request.FILES.get('file') # 注意:前端upload.js使用'file'作为字段名 txtupian_url = '' if txtupian_file: # 验证图片 is_valid, error_msg = validate_image(txtupian_file) if not is_valid: return Response({ 'code': 3, 'msg': f'图片验证失败: {error_msg}', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 上传收款码图片 txtupian_url = self.handle_shoukuanma_upload(user_main, txtupian_file) if txtupian_url: response_data['txtupian'] = txtupian_url else: return Response({ 'code': 4, 'msg': '收款码上传失败', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) else: response_data['txtupian'] = '' # 4. 验证收款账号和收款码至少有一个 if not txzh and not txtupian_url: return Response({ 'code': 5, 'msg': '请填写收款账号或上传收款码', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 5. 保存所有信息到数据库 with transaction.atomic(): user_main.phone = txdianhua user_main.skzhanghao = txzh if txzh else '' # 只有上传了图片才更新zhifu字段 if txtupian_url: user_main.zhifu = txtupian_url user_main.save() # 6. 构建完整的返回数据 response_data['txdianhua'] = txdianhua response_data['txzh'] = txzh if txzh else '' # 7. 返回成功响应 return Response({ 'code': 0, 'msg': '收款信息设置成功', 'data': response_data }) except Exception as e: import traceback traceback.print_exc() # 打印详细错误信息到控制台 return Response({ 'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def handle_shoukuanma_upload(self, user_main, image_file): """ 处理收款码图片上传到腾讯云COS 存储路径: tixian/{yonghuid}/{yonghuid}.扩展名 """ try: yonghuid = user_main.yonghuid if not yonghuid: return None # 获取旧收款码URL old_shoukuanma_url = user_main.zhifu # 生成文件扩展名 file_name = image_file.name file_extension = os.path.splitext(file_name)[1].lower() if not file_extension: file_extension = '.jpg' # 构建OSS文件路径: tixian/{yonghuid}/{yonghuid}{扩展名} # 使用用户ID作为文件名,确保同一用户多次上传会覆盖 oss_file_name = f"{yonghuid}{file_extension}" oss_file_path = f"tixian/{yonghuid}/{oss_file_name}" # 删除旧收款码(如果存在) if old_shoukuanma_url and old_shoukuanma_url.strip(): delete_from_oss(old_shoukuanma_url) # 上传新收款码到OSS new_shoukuanma_url = upload_to_oss(image_file, oss_file_path) if new_shoukuanma_url: # 返回相对路径(从tixian文件夹开始) return oss_file_path else: return None except Exception as e: print(f"处理收款码上传失败: {e}") return None from rest_framework.views import APIView from rest_framework import permissions, status from rest_framework.response import Response from django.db import transaction, IntegrityError from django.db.models import F import decimal import traceback from .models import UserMain, UserDashou, UserGuanshi, Tixianjilu from rest_framework.views import APIView from rest_framework import permissions, status from rest_framework.response import Response from django.db import transaction, IntegrityError from django.db.models import F import decimal import traceback from .models import UserMain, UserDashou, UserGuanshi, Tixianjilu,UserShenheguan from utils.fadan_utils import check_fadan_qiangdan_eligible # ========== 订单状态常量(请根据您的实际业务修改)========== # 假设状态值:已完成=9,已退款=10(示例,实际以您的项目为准) ORDER_STATUS_COMPLETED = 3 # 订单已完成 ORDER_STATUS_REFUNDED = 5 # 订单已退款 # 进行中状态(不可提现)例如:待接单、已接单、进行中等,您需要定义排除列表 FORBIDDEN_STATUS_FOR_WITHDRAW = [1, 2, 3, 4, 5, 6, 7, 8] # 除已完成和已退款外的所有状态 class TixianShenqingView(APIView): """ 用户提现申请接口(完整版) 支持类型: 1-打手佣金提现 2-管事分红提现 3-组长分红提现 4-审核官分佣提现 5-打手押金提现(退出平台) 6-商家余额提现(退出平台) """ permission_classes = [IsAuthenticated] def post(self, request): try: user_main = request.user yonghuid = user_main.yonghuid # 1. 获取并验证参数 leixing = request.data.get('leixing') jine = request.data.get('jine') fangshi = request.data.get('fangshi') txskfs = request.data.get('txskfs', 0) # 提前解析 leixing:商家提现(leixing=6)不校验罚单 raw_leixing = request.data.get('leixing') try: leixing_preview = int(raw_leixing) if raw_leixing is not None else None except (ValueError, TypeError): leixing_preview = None if leixing_preview != 6: fadan_ok, fadan_msg = check_fadan_qiangdan_eligible(yonghuid, 2) if not fadan_ok: return Response({'code': 400, 'msg': fadan_msg}) # 参数完整性 missing = [] if leixing is None: missing.append('leixing') if jine is None: missing.append('jine') if fangshi is None: missing.append('fangshi') if missing: return Response({'code': 1, 'msg': f'缺少参数: {", ".join(missing)}'}, status=status.HTTP_400_BAD_REQUEST) try: leixing = int(leixing) jine = decimal.Decimal(str(jine)) fangshi = int(fangshi) txskfs = int(txskfs) except (ValueError, TypeError, decimal.InvalidOperation) as e: return Response({'code': 2, 'msg': f'参数格式错误: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST) if jine <= decimal.Decimal('0'): return Response({'code': 3, 'msg': '提现金额必须大于0'}, status=status.HTTP_400_BAD_REQUEST) # 支持的提现类型(1-6) if leixing not in [1, 2, 3, 4, 5, 6]: return Response({'code': 4, 'msg': '无效的提现类型'}, status=status.HTTP_400_BAD_REQUEST) if fangshi not in [1, 2]: return Response({'code': 5, 'msg': '无效的收款方式,只能是1(微信)或2(支付宝)'}, status=status.HTTP_400_BAD_REQUEST) # 2. 获取对应费率及手续费 current_rate = decimal.Decimal('0.00') rate_key = { 1: '5', # 打手佣金 2: '6', # 管事分红 3: '8', # 组长分红 4: '9', # 审核官分佣 5: '11', # 打手押金提现 6: '10', # 商家余额提现 }.get(leixing) if rate_key: rate_obj = Lilubiao.objects.filter(fadanpingtai=rate_key).first() if rate_obj: current_rate = decimal.Decimal(str(rate_obj.lilu)) else: return Response({'code': 41, 'msg': f'提现费率未配置(类型{leixing})'}, status=status.HTTP_400_BAD_REQUEST) shouxufei = jine * current_rate shijidaozhang = jine - shouxufei if shijidaozhang <= decimal.Decimal('0'): return Response({'code': 6, 'msg': '提现金额过低,扣除手续费后无实际到账'}, status=status.HTTP_400_BAD_REQUEST) # 3. 根据提现类型执行校验和扣款 nicheng = '' if leixing == 1: result = self._check_dashou_commission_withdraw(user_main, jine) if result['code'] != 0: return Response(result, status=status.HTTP_400_BAD_REQUEST) nicheng = result['data']['nicheng'] # 扣款(佣金) with transaction.atomic(): profile = UserDashou.objects.select_for_update().get(user=user_main) if profile.yue < jine: return Response({'code': 13, 'msg': '余额不足'}) profile.yue = F('yue') - jine profile.save() elif leixing == 2: result = self._check_guanshi_dividend_withdraw(user_main, jine) if result['code'] != 0: return Response(result, status=status.HTTP_400_BAD_REQUEST) with transaction.atomic(): profile = UserGuanshi.objects.select_for_update().get(user=user_main) if profile.yue < jine: return Response({'code': 22, 'msg': '余额不足'}) profile.yue = F('yue') - jine profile.save() nicheng = "管事用户" elif leixing == 3: result = self._check_zuzhang_withdraw(user_main, jine) if result['code'] != 0: return Response(result, status=status.HTTP_400_BAD_REQUEST) with transaction.atomic(): profile = UserZuzhang.objects.select_for_update().get(user=user_main) if profile.ketixian_jine < jine: return Response({'code': 32, 'msg': '可提现金额不足'}) profile.ketixian_jine = F('ketixian_jine') - jine profile.save() nicheng = user_main.nicheng if hasattr(user_main, 'nicheng') else '' elif leixing == 4: result = self._check_shenheguan_withdraw(user_main, jine) if result['code'] != 0: return Response(result, status=status.HTTP_400_BAD_REQUEST) with transaction.atomic(): profile = UserShenheguan.objects.select_for_update().get(user=user_main) if profile.yue < jine: return Response({'code': 42, 'msg': '可提现金额不足'}) profile.yue = F('yue') - jine profile.save() nicheng = "审核官" elif leixing == 5: # 打手押金提现(退出) result = self._check_dashou_yajin_withdraw(user_main, jine) if result['code'] != 0: return Response(result, status=status.HTTP_400_BAD_REQUEST) with transaction.atomic(): profile = UserDashou.objects.select_for_update().get(user=user_main) if profile.yajin < jine: return Response({'code': 53, 'msg': '押金余额不足'}) profile.yajin = F('yajin') - jine profile.save() nicheng = profile.nicheng or '' elif leixing == 6: # 商家余额提现(退出) result = self._check_shangjia_withdraw(user_main, jine) if result['code'] != 0: return Response(result, status=status.HTTP_400_BAD_REQUEST) with transaction.atomic(): profile = UserShangjia.objects.select_for_update().get(user=user_main) if profile.yue < jine: return Response({'code': 63, 'msg': '商家余额不足'}) profile.yue = F('yue') - jine # 注意:是否封禁商家账号?根据需求,用户未明确要求封禁,暂时只扣款,可后续扩展。 profile.save() nicheng = profile.nicheng or '' # 4. 创建提现记录(先扣款后创建,确保资金安全) try: avatar = user_main.avatar or '' phone = user_main.phone or '' zhifu = user_main.zhifu or '' skzhanghao = user_main.skzhanghao or '' tixian_record = Tixianjilu.objects.create( yonghuid=yonghuid, avatar=avatar, phone=phone, nicheng=nicheng, leixing=leixing, zhifu=zhifu, skzhanghao=skzhanghao, jine=shijidaozhang, shenqing_jine=jine, shouxufei=shouxufei, zhuangtai=1, fangshi=fangshi, dakuan_mode=1, shenheid=None, bhliyou='', ) except Exception as e: return Response({'code': 99, 'msg': f'创建提现记录失败: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 5. 返回成功响应 return Response({ 'code': 0, 'msg': '提现申请提交成功', 'data': { 'tixian_id': tixian_record.id, 'leixing': leixing, 'jine': str(jine.quantize(decimal.Decimal('0.01'))), 'fangshi': fangshi, 'zhuangtai': 1, 'create_time': tixian_record.create_time.strftime('%Y-%m-%d %H:%M:%S'), 'current_rate': str(current_rate.quantize(decimal.Decimal('0.0000'))), 'shouxufei': str(shouxufei.quantize(decimal.Decimal('0.01'))), 'shijidaozhang': str(shijidaozhang.quantize(decimal.Decimal('0.01'))), 'tip': f'本次提现手续费率{current_rate * 100}%,手续费{shouxufei}元,实际到账{shijidaozhang}元' } }) except Exception as e: traceback.print_exc() return Response({'code': 99, 'msg': f'系统错误: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # ==================== 校验函数 ==================== def _check_dashou_commission_withdraw(self, user_main, jine): """打手佣金提现校验""" try: dashou = UserDashou.objects.get(user=user_main) except ObjectDoesNotExist: return {'code': 10, 'msg': '您不是打手身份,无法提现佣金'} if dashou.zhanghaozhuangtai != 1: return {'code': 11, 'msg': '打手账号已被封禁,无法提现'} if dashou.zhuangtai != 1: return {'code': 12, 'msg': '您有订单进行中,请完成后提现'} if dashou.yue < jine: return {'code': 13, 'msg': f'余额不足,当前余额: {dashou.yue}'} return {'code': 0, 'msg': 'ok', 'data': {'nicheng': dashou.nicheng or ''}} def _check_guanshi_dividend_withdraw(self, user_main, jine): """管事分红提现校验""" try: guanshi = UserGuanshi.objects.get(user=user_main) except ObjectDoesNotExist: return {'code': 20, 'msg': '您不是管事身份,无法提现分红'} if guanshi.zhuangtai != 1: return {'code': 21, 'msg': '管事账号已被封禁,无法提现'} if guanshi.yue < jine: return {'code': 22, 'msg': f'余额不足,当前余额: {guanshi.yue}'} return {'code': 0, 'msg': 'ok', 'data': {}} def _check_zuzhang_withdraw(self, user_main, jine): """组长分红提现校验""" try: zuzhang = UserZuzhang.objects.get(user=user_main) except ObjectDoesNotExist: return {'code': 30, 'msg': '您不是组长身份,无法提现组长分红'} if zuzhang.zhuangtai != 1: return {'code': 31, 'msg': '组长账号已被封禁,无法提现'} if zuzhang.ketixian_jine < jine: return {'code': 32, 'msg': f'可提现金额不足,当前可提: {zuzhang.ketixian_jine}'} return {'code': 0, 'msg': 'ok', 'data': {}} def _check_shenheguan_withdraw(self, user_main, jine): """审核官分佣提现校验""" try: shenheguan = UserShenheguan.objects.get(user=user_main) except ObjectDoesNotExist: return {'code': 40, 'msg': '您不是审核官身份,无法提现分佣'} if shenheguan.zhuangtai != 1: return {'code': 41, 'msg': '审核官账号已被封禁,无法提现'} if shenheguan.yue < jine: return {'code': 42, 'msg': f'可提现金额不足,当前可提: {shenheguan.yue}'} return {'code': 0, 'msg': 'ok', 'data': {}} from django.db.models import Q def _check_shangjia_withdraw(self, user_main, jine): """商家余额提现校验:身份、账号状态、余额。""" try: shangjia = UserShangjia.objects.get(user=user_main) except ObjectDoesNotExist: return {'code': 60, 'msg': '您不是商家身份,无法提现商家余额'} if shangjia.zhuangtai != 1: return {'code': 61, 'msg': '商家账号已被禁用,无法提现'} if shangjia.yue < jine: return {'code': 62, 'msg': f'商家余额不足,当前余额: {shangjia.yue}'} return {'code': 0, 'msg': 'ok', 'data': {'nicheng': shangjia.nicheng or ''}} def _check_dashou_yajin_withdraw(self, user_main, jine): """ 打手押金提现校验(退出平台) 要求:无进行中的订单,无未结算订单,自身罚单需已处理 """ try: dashou = UserDashou.objects.get(user=user_main) except ObjectDoesNotExist: return {'code': 50, 'msg': '您不是打手身份,无法提取押金'} if dashou.zhanghaozhuangtai != 1: return {'code': 51, 'msg': '打手账号已禁用,无法提取押金'} if dashou.zhuangtai != 1: return {'code': 52, 'msg': '您有订单进行中,请完成后提取押金'} if dashou.yajin < jine: return {'code': 53, 'msg': f'押金余额不足,当前押金: {dashou.yajin}'} # 订单检查(使用 exclude + __in) unfinished_orders = Dingdan.objects.filter( jiedan_dashou_id=user_main.yonghuid, ).exclude( zhuangtai__in=[ORDER_STATUS_COMPLETED, ORDER_STATUS_REFUNDED] ).exists() if unfinished_orders: return {'code': 54, 'msg': '您还有未完成或未退款的订单,请先处理完毕再提取押金'} # 自身罚单检查 self_fadan = Fadan.objects.filter( beichufa_id=user_main.yonghuid, ).exclude( zhuangtai__in=[2, 4] ).exists() if self_fadan: return {'code': 55, 'msg': '您有未处理完毕的自身罚单(待缴纳/申诉中),请先处理'} return {'code': 0, 'msg': 'ok', 'data': {'nicheng': dashou.nicheng or ''}} class TixianJiluHuoquViewV2(APIView): """ 提现记录获取接口 mode=1:手动提现记录(默认,兼容旧版) mode=2:自动提现审核记录(含 shenhe_jilu_id / shenhe_danhao,支持 zhuangtai 筛选) """ permission_classes = [permissions.IsAuthenticated] def post(self, request): try: from .models import TixianAutoRecord, TixianShenheJilu user_main = request.user yonghuid = user_main.yonghuid page = request.data.get('page', 1) limit = request.data.get('limit', 50) mode = int(request.data.get('mode', 1) or 1) zhuangtai_filter = int(request.data.get('zhuangtai', 0) or 0) try: page = int(page) limit = int(limit) if page < 1 or limit < 1: return Response({'code': 1, 'msg': '参数错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST) except (TypeError, ValueError): return Response({'code': 1, 'msg': '参数格式错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST) if limit > 100: limit = 100 offset = (page - 1) * limit qs = Tixianjilu.objects.filter(yonghuid=yonghuid) if mode == 2: # 自动提现:含新审核关联记录;zhuangtai>0 时按状态筛选 if zhuangtai_filter > 0: qs = qs.filter(zhuangtai=zhuangtai_filter) qs = qs.order_by('-create_time') records_queryset = qs.values( 'id', 'leixing', 'jine', 'zhuangtai', 'create_time', 'fangshi', 'phone', 'skzhanghao', 'zhifu', 'bhliyou', 'shenhe_jilu_id', 'shenhe_danhao', )[offset:offset + limit + 1] records_list = list(records_queryset) has_more = len(records_list) > limit records_to_return = records_list[:limit] if has_more else records_list # 批量取审核表 fail_reason audit_ids = [r['shenhe_jilu_id'] for r in records_to_return if r.get('shenhe_jilu_id')] audit_map = {} if audit_ids: for a in TixianShenheJilu.objects.filter(id__in=audit_ids).values( 'id', 'fail_reason', 'shenqing_jine', 'shenhe_danhao' ): audit_map[a['id']] = a # 批量取自动打款失败原因 danhao_list = [r['shenhe_danhao'] for r in records_to_return if r.get('shenhe_danhao')] auto_fail_map = {} if danhao_list: for ar in TixianAutoRecord.objects.filter( shenhe_danhao__in=danhao_list, zhuangtai=2 ).order_by('-create_time').values('shenhe_danhao', 'fail_reason'): if ar['shenhe_danhao'] not in auto_fail_map: auto_fail_map[ar['shenhe_danhao']] = ar['fail_reason'] formatted_records = [] for record in records_to_return: audit = audit_map.get(record.get('shenhe_jilu_id')) if record.get('shenhe_jilu_id') else None fail_reason = '' if audit and audit.get('fail_reason'): fail_reason = audit['fail_reason'] elif record.get('shenhe_danhao'): fail_reason = auto_fail_map.get(record['shenhe_danhao'], '') or '' jine_val = float(record['jine']) if record['jine'] else 0.00 if mode == 2 and audit and audit.get('shenqing_jine') is not None: jine_val = float(audit['shenqing_jine']) shenhe_danhao_val = record.get('shenhe_danhao') or (audit.get('shenhe_danhao') if audit else '') formatted_records.append({ 'id': record['id'], 'tixianjilu_id': record['id'], 'tixian_id': record['id'], 'leixing': record['leixing'], 'jine': jine_val, 'zhuangtai': record['zhuangtai'], 'create': record['create_time'].strftime('%Y-%m-%d %H:%M:%S'), 'fangshi': record['fangshi'], 'txdianhua': record['phone'] or '', 'txzh': record['skzhanghao'] or '', 'txtupian': record['zhifu'] or '', 'shibaiyuanyin': record['bhliyou'] or '', 'bhliyou': record['bhliyou'] or '', 'fail_reason': fail_reason, 'shenhe_jilu_id': record.get('shenhe_jilu_id'), 'shenhe_danhao': shenhe_danhao_val, }) return Response({ 'code': 0, 'msg': '获取成功', 'data': { 'list': formatted_records, 'has_more': has_more, 'page': page, 'limit': limit, 'current_count': len(formatted_records), }, }) except Exception as e: return Response({ 'code': 99, 'msg': f'系统错误: {str(e)}', 'data': None, }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # yonghu/views.py import jwt import datetime class AdminLoginView(APIView): """ 管理员登录接口 路径:/yonghu/adlog/ 方法:POST 字段:zhanghao(账号), mima(密码), ip(二次验证码) """ # 限制匿名用户访问频率,防止暴力破解 throttle_classes = [AnonRateThrottle] # 允许任何人访问(登录接口不需要认证) permission_classes = [AllowAny] def post(self, request): """ 处理管理员登录请求 安全策略:任何错误都返回401,不透露具体信息 """ try: # 1. 获取客户端真实IP和用户代理信息 kehuduan_ip = huoquKehuduanIP(request) yonghu_daili = huoquYonghuDaili(request) # 2. 获取前端传递的数据 data = request.data # 检查必需字段是否存在 bixu_ziduan = ['zhanghao', 'mima', 'ip'] for ziduan in bixu_ziduan: if ziduan not in data: # 字段缺失,返回401 return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 提取并清理数据 zhanghao = data.get('zhanghao', '').strip() mima = data.get('mima', '').strip() erci_yanzhengma = data.get('ip', '').strip() # 二次验证码(前端叫ip) # 简单验证字段非空 if not zhanghao or not mima or not erci_yanzhengma: return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 3. 使用事务确保数据一致性 with transaction.atomic(): # 【优化点1】使用select_related一次性获取管理员扩展表 # 只查询管理员类型的用户 user = UserMain.objects.filter( phone=zhanghao, # 手机号作为账号 password=mima, # 密码 user_type='admin' # 必须是管理员类型 ).select_related('admin_profile').first() # 如果用户不存在或不是管理员,返回401 if not user: return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 【优化点2】检查管理员扩展表是否存在 if not hasattr(user, 'admin_profile'): return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 【优化点3】验证二次密码(管理员扩展表中的password字段) admin_profile = user.admin_profile if admin_profile.password != erci_yanzhengma: return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 更新用户登录信息 # 获取当前时间 now_time = timezone.now() # 处理IP地址长度问题 # 如果原ip字段长度不够,截取前11个字符(保持兼容性) if len(kehuduan_ip) > 11: # 如果ip字段长度限制是11,我们截取前11个字符 # 建议在数据库中修改ip字段为50长度 cunchu_ip = kehuduan_ip[:11] else: cunchu_ip = kehuduan_ip # 更新用户信息 user.ip = cunchu_ip # 存储客户端IP user.last_login_time = now_time # 更新最后登录时间 user.save() # 【优化点4】生成JWT token(使用和微信登录相同的方式) # 使用djangorestframework-simplejwt生成token refresh = RefreshToken.for_user(user) token = str(refresh.access_token) # 5. 准备返回数据 # 返回用户基本信息(可根据需要调整) response_data = { 'token': token, 'yonghuid': user.yonghuid, 'phone': user.phone or '', 'user_type': user.user_type, 'last_login_time': now_time.strftime('%Y-%m-%d %H:%M:%S') if now_time else '', } # 可以添加管理员特定信息 if hasattr(user, 'admin_profile'): response_data['wechat'] = user.admin_profile.wechat or '' return Response({ 'code': 0, 'msg': '登录成功', 'data': response_data }) except Exception as e: # 【安全点】任何异常都返回401,不暴露具体错误信息 # 记录日志用于排查问题,但前端只收到通用错误信息 # 这里可以添加日志记录,比如: # import logging # logging.error(f'管理员登录异常: {str(e)}', exc_info=True) return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # yonghu/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from django.db import transaction, IntegrityError from django.core.exceptions import ObjectDoesNotExist import json # 导入模型 from yonghu.models import UserMain, AdminProfile from peizhi.models import Szjilu class AdminFinancialDataView(APIView): """ 管理员财务数据接口 路径:/yonghu/adhq/ 方法:POST 认证:JWT Token 权限:仅管理员 返回:收支记录数据 """ # 使用JWT认证 permission_classes = [IsAuthenticated] def post(self, request): """ 处理管理员财务数据请求 安全策略:任何错误都返回401,不透露具体信息 """ try: # 1. 获取前端传递的数据 data = request.data # 检查必需字段 if 'zhanghao' not in data: return Response( {'code': 401, 'msg': '请求参数错误', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) qianzhanghao = data.get('zhanghao', '').strip() # 简单验证字段非空 if not qianzhanghao: return Response( {'code': 401, 'msg': '请求参数错误', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 2. 获取当前登录用户(JWT认证后,request.user就是UserMain实例) dangqianyonghu = request.user # 3. 验证用户类型是否为admin if not hasattr(dangqianyonghu, 'user_type') or dangqianyonghu.user_type != 'admin': return Response( {'code': 401, 'msg': '权限不足', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证前端传递的账号与当前用户的phone字段是否一致 if not dangqianyonghu.phone or dangqianyonghu.phone != qianzhanghao: return Response( {'code': 401, 'msg': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在(反向查询) # 使用高效的ORM查询,避免额外的数据库查询 try: # 使用select_related一次性获取管理员扩展表 # 注意:这里假设user_type='admin'的用户都有admin_profile yonghu_xiangxi = UserMain.objects.select_related('admin_profile').get( pk=dangqianyonghu.pk, user_type='admin' ) # 检查管理员扩展表是否存在 if not hasattr(yonghu_xiangxi, 'admin_profile'): return Response( {'code': 401, 'msg': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) except UserMain.DoesNotExist: return Response( {'code': 401, 'msg': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 查询收支记录表(Szjilu) # 使用高效的ORM查询,获取最新的一条记录 # 注意:根据你的业务需求,这里获取最新的一条记录 # 如果需要汇总多条记录,可以使用aggregate进行聚合查询 # 方法1:获取最新的一条记录(假设表中只有一条汇总记录) shouzhijilu = Szjilu.objects.order_by('-create_time').first() # 方法2:如果表中可能有多条记录,需要汇总,可以使用以下方式: # from django.db.models import Sum # zonghe = Szjilu.objects.aggregate( # zongliushui=Sum('zongls'), # zongshouyi=Sum('zongsy'), # zongzhichu=Sum('zongzc'), # jrls=Sum('jrls'), # jrzc=Sum('jrzc') # ) # 准备返回数据 if shouzhijilu: # 有记录的情况 huizongshuju = { 'zongliushui': float(shouzhijilu.zongls or 0), 'zongshouyi': float(shouzhijilu.zongsy or 0), 'zongzhichu': float(shouzhijilu.zongzc or 0), 'jrls': float(shouzhijilu.jrls or 0), 'jrzc': float(shouzhijilu.jrzc or 0) } else: # 没有记录的情况,返回默认值 huizongshuju = { 'zongliushui': 0.00, 'zongshouyi': 0.00, 'zongzhichu': 0.00, 'jrls': 0.00, 'jrzc': 0.00 } # 7. 返回成功响应 return Response({ 'code': 0, 'msg': '获取成功', 'data': huizongshuju }) except Exception as e: # 【安全策略】任何异常都返回401,不暴露具体错误信息 # 这里可以记录日志,但不返回给前端 # import logging # logging.error(f'管理员财务数据接口异常: {str(e)}', exc_info=True) return Response( {'code': 401, 'msg': '获取失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # shangpin/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser import logging logger = logging.getLogger(__name__) class AdGetOrderTypes(APIView): """ 后台获取订单类型列表接口 前端调用地址: /shangpin/getOrderTypes 前端需要字段: id, biaoti, tupian_url """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # 1. 获取参数 qianzhanghao = request.data.get('zhanghao', '').strip() if not qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 2. 身份验证(按你说的方式) dangqianyonghu = request.user # 验证手机号 if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 验证管理员身份 if dangqianyonghu.user_type != 'admin': return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 3. 验证管理员扩展表 try: guanliyuan = dangqianyonghu.admin_profile except AttributeError: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 4. 查询订单类型(商品类型)表 # 注意:这里需要根据你实际的模型名调整 # 如果模型名不是ShangpinType,请告诉我正确的模型名 # 假设模型名是ShangpinLeixing(因为我之前看到你有商品类型表) # 你需要告诉我确切的模型名 try: # 请替换为你的实际模型名 from shangpin.models import ShangpinLeixing # 或者 ShangpinType # 只查询前端需要的三个字段 shangpin_types = ShangpinLeixing.objects.all().only('id', 'jieshao', 'tupian_url') # 构建响应数据 type_list = [ { 'id': item.id, 'biaoti': item.jieshao or '', 'tupian_url': item.tupian_url or '' } for item in shangpin_types ] return Response({ 'code': 0, 'message': '获取成功', 'data': type_list }) except Exception as e: logger.error(f"查询商品类型表失败: {str(e)}") # 如果表不存在,返回空数组 return Response({ 'code': 0, 'message': '获取成功', 'data': [] }) except Exception as e: logger.error(f"获取订单类型异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) # yonghu/views.py 或 dingdan/views.py from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser import logging from django.db.models import Q, Count import time logger = logging.getLogger(__name__) class AdGetDingdanList(APIView): """ 后台订单列表获取接口 前端调用地址: /yonghu/adddhq 前端需要字段: dingdan_id, jieshao, zhuangtai, leixing, fadanpingtai, jiage, tupian_url, fadanshijian 统计字段: stats.platform.total, stats.platform.completed, stats.merchant.total, stats.merchant.completed """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # 1. 获取参数(严格按前端传的字段名) qianzhanghao = request.data.get('zhanghao', '').strip() page = int(request.data.get('page', 1)) page_size = int(request.data.get('pageSize', 30)) # 搜索相关参数 dingdan_id = request.data.get('dingdan_id', '').strip() # 筛选相关参数(严格按前端字段名) fadanpingtai = request.data.get('fadanpingtai') # 前端叫 fadanpingtai leixing = request.data.get('leixing') # 前端叫 leixing zhuangtai = request.data.get('zhuangtai', '') # 前端叫 zhuangtai if not qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 2. 身份验证(与你说的完全一样) dangqianyonghu = request.user # 验证手机号 if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 验证管理员身份 if dangqianyonghu.user_type != 'admin': return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 3. 验证管理员扩展表 try: guanliyuan = dangqianyonghu.admin_profile except AttributeError: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 4. 导入订单模型 from dingdan.models import Dingdan # 你的订单主表模型 # 5. 构建查询条件(严格按前端字段名映射到数据库字段) query_conditions = Q() # 判断是否为搜索模式 is_search_mode = bool(dingdan_id) if is_search_mode: # 搜索模式:只按订单ID查询 query_conditions &= Q(dingdan_id=dingdan_id) else: # 筛选模式 # 发单平台筛选(前端: fadanpingtai -> 数据库: fadan_pingtai) if fadanpingtai is not None: try: fadanpingtai_int = int(fadanpingtai) if fadanpingtai_int in [1, 2]: query_conditions &= Q(fadan_pingtai=fadanpingtai_int) except (ValueError, TypeError): pass # 订单类型筛选(前端: leixing -> 数据库: leixing_id) if leixing is not None: try: leixing_int = int(leixing) query_conditions &= Q(leixing_id=leixing_int) except (ValueError, TypeError): pass # 订单状态筛选(前端: zhuangtai -> 数据库: zhuangtai) if zhuangtai and zhuangtai != 'all': try: # 处理多个状态,如 "1,7" status_list = [int(s.strip()) for s in zhuangtai.split(',') if s.strip()] if status_list: query_conditions &= Q(zhuangtai__in=status_list) except (ValueError, TypeError): pass # 6. 获取统计数据(平台订单和商家订单) # 注意:这里统计所有订单,不考虑筛选条件 try: # 平台订单统计 (fadan_pingtai=1) platform_total = Dingdan.objects.filter(fadan_pingtai=1).count() platform_completed = Dingdan.objects.filter(fadan_pingtai=1, zhuangtai=3).count() # 商家订单统计 (fadan_pingtai=2) merchant_total = Dingdan.objects.filter(fadan_pingtai=2).count() merchant_completed = Dingdan.objects.filter(fadan_pingtai=2, zhuangtai=3).count() platform_stats = { 'total': platform_total, 'completed': platform_completed } merchant_stats = { 'total': merchant_total, 'completed': merchant_completed } except Exception as e: logger.warning(f"统计数据查询失败: {str(e)}") platform_stats = {'total': 0, 'completed': 0} merchant_stats = {'total': 0, 'completed': 0} # 7. 执行分页查询(只查询主表,不查扩展表) start_time = time.time() # 计算分页 offset = (page - 1) * page_size # 查询符合条件的订单总数 total_count = Dingdan.objects.filter(query_conditions).count() # 查询当前页的数据(只查询主表,只查询前端需要的字段) dingdan_list = Dingdan.objects.filter(query_conditions).only( 'dingdan_id', 'jieshao', 'zhuangtai', 'leixing_id', 'fadan_pingtai', 'jine', 'tupian', 'create_time' ).order_by('-create_time')[offset:offset + page_size] query_time = time.time() - start_time logger.info(f"订单查询完成,耗时: {query_time:.3f}s,查询条数: {len(dingdan_list)}") # 8. 构建响应数据(严格按前端需要的字段) result_list = [] for dingdan in dingdan_list: # 注意:这里字段名必须与前端完全一致 order_data = { 'dingdan_id': dingdan.dingdan_id or '', 'jieshao': dingdan.jieshao or '', 'zhuangtai': dingdan.zhuangtai or 1, 'leixing': dingdan.leixing_id or 0, # 前端需要 leixing 'fadanpingtai': dingdan.fadan_pingtai or 1, # 前端需要 fadanpingtai 'jiage': float(dingdan.jine) if dingdan.jine else 0.00, # 价格对应 jine 字段 'tupian_url': dingdan.tupian or '', # 图片对应 tupian 字段 'fadanshijian': dingdan.create_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan.create_time else '' } result_list.append(order_data) # 9. 判断是否有更多数据 current_loaded_count = offset + len(result_list) has_more = len(result_list) >= page_size and current_loaded_count < total_count # 10. 构建完整响应(严格按前端需要的结构) response_data = { 'stats': { 'platform': platform_stats, 'merchant': merchant_stats }, 'list': result_list, 'hasMore': has_more, 'total': total_count, 'currentPage': page, 'pageSize': page_size } return Response({ 'code': 0, 'message': '获取成功', 'data': response_data }) except Exception as e: logger.error(f"获取订单列表异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from rest_framework import status from django.db import transaction, IntegrityError import logging # 获取日志器 logger = logging.getLogger(__name__) class AdYaoQingDaShou(APIView): """ 后台获取邀请码接口(用于邀请打手) 权限: JWT Token认证 + 管理员身份验证 前端调用: POST /yonghu/adyqds 参数: { "zhanghao": "管理员账号" } 返回: { "code": 0, "message": "成功", "data": { "yaoqingma": "邀请码" } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # 1. 获取前端参数 qianzhanghao = request.data.get('zhanghao', '').strip() if not qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 2. JWT Token身份验证 dangqianyonghu = request.user # 3. 验证手机号是否匹配 if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型是否为管理员 if dangqianyonghu.user_type != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.admin_profile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 获取用户ID(yonghuid),如果获取不到就用'123456' yonghuid_str = getattr(dangqianyonghu, 'yonghuid', '123456') # 7. 尝试获取或创建管事扩展表 try: # 尝试获取现有的管事扩展表 guanshi_profile = dangqianyonghu.guanshi_profile except AttributeError: # 管事扩展表不存在,创建一个 try: from yonghu.models import UserGuanshi # 创建默认的管事扩展表 guanshi_profile = UserGuanshi.objects.create( user=dangqianyonghu, yaoqingma='', # 暂时为空,后面生成 dianhua='', # 默认空 wechat='', # 默认空 yaogingshuliang=0, # 邀请数量为0 zhuangtai=1, # 正常状态 jinrichongzhi=0, # 今日充值0 jinyuechongzhi=0, # 今月充值0 chongzhifenrun=0, # 充值分佣0 yue=0 # 余额0 ) #logger.info(f"为管理员{dangqianyonghu.yonghuid}创建了管事扩展表") except Exception as e: logger.error(f"创建管事扩展表失败: {str(e)}") return Response( {'code': 500, 'message': '创建用户信息失败', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) # 8. 检查是否已有邀请码 existing_yaoqingma = guanshi_profile.yaoqingma if existing_yaoqingma and existing_yaoqingma.strip(): # 已有邀请码,直接返回 return Response({ 'code': 0, 'message': '成功获取邀请码', 'data': { 'yaoqingma': existing_yaoqingma } }) # 9. 生成新的邀请码 try: # 导入邀请码生成工具 from utils.yaoqingma_generator import chuangjianYaoqingma # 生成邀请码 new_yaoqingma = chuangjianYaoqingma(str(yonghuid_str)) # 验证邀请码格式 from utils.yaoqingma_generator import yanzhengYaoqingmaWeiyixing if not yanzhengYaoqingmaWeiyixing(new_yaoqingma): raise Exception('邀请码格式验证失败') except ImportError: # 如果导入失败,使用简单方法生成邀请码 import time import random import string timestamp = str(int(time.time())) random_str = ''.join(random.choices(string.ascii_uppercase + string.digits, k=8)) new_yaoqingma = f"GL{timestamp[-6:]}{random_str}" logger.warning(f"使用简单方法生成邀请码: {new_yaoqingma}") except Exception as e: logger.error(f"生成邀请码失败: {str(e)}") return Response( {'code': 500, 'message': '生成邀请码失败', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) # 10. 保存邀请码到数据库 try: with transaction.atomic(): guanshi_profile.yaoqingma = new_yaoqingma guanshi_profile.save() logger.info(f"管理员{dangqianyonghu.yonghuid}生成邀请码: {new_yaoqingma}") except Exception as e: logger.error(f"保存邀请码失败: {str(e)}") return Response( {'code': 500, 'message': '保存邀请码失败', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) # 11. 返回成功响应 return Response({ 'code': 0, 'message': '成功生成邀请码', 'data': { 'yaoqingma': new_yaoqingma } }) except Exception as e: logger.error(f"获取邀请码接口异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from rest_framework import status from django.db.models import Q import logging logger = logging.getLogger(__name__) class AdGuanLiYongHu(APIView): """ 后台用户管理列表接口 权限: JWT Token认证 + 管理员身份验证 前端调用: POST /yonghu/adgl 参数: { "zhanghao": "管理员账号", "user_type": "用户类型(1-4)", "keyword": "搜索关键词(可选)", "page": "页码(从1开始)", "page_size": "每页数量" } 返回: { "code": 0, "message": "成功", "data": { "list": [用户列表], "stats": {"1": 总数, "2": 总数, "3": 总数, "4": 总数}, "has_more": true/false } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # 1. 获取前端参数 qianzhanghao = request.data.get('zhanghao', '').strip() user_type = request.data.get('user_type') keyword = request.data.get('keyword', '').strip() page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 30)) # 验证参数 if not qianzhanghao or not user_type: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 2. JWT Token身份验证 dangqianyonghu = request.user # 3. 验证手机号是否匹配 if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型是否为管理员 if dangqianyonghu.user_type != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.admin_profile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 根据用户类型选择查询的模型 user_type_int = int(user_type) if user_type_int not in [1, 2, 3, 4]: return Response( {'code': 400, 'message': '用户类型错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 7. 统计四种用户类型的总数 stats = self._get_user_stats() # 8. 根据用户类型查询用户列表 user_list, has_more = self._query_users_by_type( user_type_int, keyword, page, page_size ) # 9. 构建返回数据 return Response({ 'code': 0, 'message': '成功', 'data': { 'list': user_list, 'stats': stats, 'has_more': has_more } }) except ValueError: logger.error("参数类型错误") return Response( {'code': 400, 'message': '参数类型错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"用户管理列表接口异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) def _get_user_stats(self): """ 获取四种用户类型的统计数量 """ try: from yonghu.models import UserBoss, UserDashou, UserGuanshi, UserShangjia stats = { '1': UserBoss.objects.count(), '2': UserDashou.objects.count(), '3': UserGuanshi.objects.count(), '4': UserShangjia.objects.count() } return stats except Exception as e: logger.error(f"获取用户统计失败: {str(e)}") return {'1': 0, '2': 0, '3': 0, '4': 0} def _query_users_by_type(self, user_type_int, keyword, page, page_size): """ 根据用户类型查询用户列表 返回: (用户列表, 是否还有更多数据) """ try: # 计算偏移量 offset = (page - 1) * page_size if user_type_int == 1: return self._query_boss_users(keyword, offset, page_size) elif user_type_int == 2: return self._query_dashou_users(keyword, offset, page_size) elif user_type_int == 3: return self._query_guanshi_users(keyword, offset, page_size) elif user_type_int == 4: return self._query_shangjia_users(keyword, offset, page_size) else: return [], False except Exception as e: logger.error(f"查询用户列表失败: {str(e)}") return [], False def _query_boss_users(self, keyword, offset, limit): """查询普通用户(老板)""" try: from yonghu.models import UserBoss queryset = UserBoss.objects.select_related('user') if keyword: queryset = queryset.filter( Q(user__yonghuid__icontains=keyword) | Q(nickname__icontains=keyword) ) # 获取总数 total_count = queryset.count() # 获取当前页的数据 users = list(queryset.order_by('-create_time')[offset:offset + limit]) # 计算是否有更多数据 current_count = len(users) has_more = (offset + current_count) < total_count # 构建返回数据 user_list = [] for boss in users: user_main = boss.user user_data = { 'yonghuid': user_main.yonghuid, 'avatar': user_main.avatar or '', 'nicheng': boss.nickname or '', } user_list.append(user_data) return user_list, has_more except Exception as e: logger.error(f"查询老板用户失败: {str(e)}") return [], False def _query_dashou_users(self, keyword, offset, limit): """查询打手用户""" try: from yonghu.models import UserDashou queryset = UserDashou.objects.select_related('user') if keyword: queryset = queryset.filter( Q(user__yonghuid__icontains=keyword) | Q(nicheng__icontains=keyword) | Q(chenghao__icontains=keyword) ) # 获取总数 total_count = queryset.count() # 获取当前页的数据 dashou_list = list(queryset.order_by('-create_time')[offset:offset + limit]) # 计算是否有更多数据 current_count = len(dashou_list) has_more = (offset + current_count) < total_count # 构建返回数据 user_list = [] for dashou in dashou_list: user_main = dashou.user user_data = { 'yonghuid': user_main.yonghuid, 'avatar': user_main.avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'nicheng': dashou.nicheng or '', 'zhuangtai': dashou.zhuangtai, } user_list.append(user_data) return user_list, has_more except Exception as e: logger.error(f"查询打手用户失败: {str(e)}") return [], False def _query_guanshi_users(self, keyword, offset, limit): """查询管事用户""" try: from yonghu.models import UserGuanshi queryset = UserGuanshi.objects.select_related('user') if keyword: queryset = queryset.filter( Q(user__yonghuid__icontains=keyword) ) # 获取总数 total_count = queryset.count() # 获取当前页的数据 guanshi_list = list(queryset.order_by('-create_time')[offset:offset + limit]) # 计算是否有更多数据 current_count = len(guanshi_list) has_more = (offset + current_count) < total_count # 构建返回数据 user_list = [] for guanshi in guanshi_list: user_main = guanshi.user user_data = { 'yonghuid': user_main.yonghuid, 'avatar': user_main.avatar or '', 'zhanghaozhuangtai': guanshi.zhuangtai, 'nicheng': f'管事{user_main.yonghuid}', } user_list.append(user_data) return user_list, has_more except Exception as e: logger.error(f"查询管事用户失败: {str(e)}") return [], False def _query_shangjia_users(self, keyword, offset, limit): """查询商家用户""" try: from yonghu.models import UserShangjia queryset = UserShangjia.objects.select_related('user') if keyword: queryset = queryset.filter( Q(user__yonghuid__icontains=keyword) | Q(nicheng__icontains=keyword) ) # 获取总数 total_count = queryset.count() # 获取当前页的数据 shangjia_list = list(queryset.order_by('-create_time')[offset:offset + limit]) # 计算是否有更多数据 current_count = len(shangjia_list) has_more = (offset + current_count) < total_count # 构建返回数据 user_list = [] for shangjia in shangjia_list: user_main = shangjia.user user_data = { 'yonghuid': user_main.yonghuid, 'avatar': user_main.avatar or '', 'zhanghaozhuangtai': shangjia.zhuangtai, 'nicheng': shangjia.nicheng or '', } user_list.append(user_data) return user_list, has_more except Exception as e: logger.error(f"查询商家用户失败: {str(e)}") return [], False from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from rest_framework import status from django.db.models import Q import logging logger = logging.getLogger(__name__) class CfGuanLi(APIView): """ 后台处罚管理列表接口 权限: JWT Token认证 + 管理员身份验证 前端调用: POST /yonghu/cfgl 参数: { "zhanghao": "管理员账号", "qingqiu_tongji": true/false, # 是否只请求统计信息 "sqzhuangtai": 0 或 [1,2], # 处罚申请状态筛选(可选) "page": "页码(从1开始)", # 分页参数 "page_size": "每页数量" # 分页参数 } 返回: { "code": 0, "message": "成功", "data": { "list": [处罚记录列表], "zongshu": 总数, "daichuli": 待处理数量, "yichuli": 已处理数量, "haiyougengduo": true/false } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # 1. 获取前端参数 qianzhanghao = request.data.get('zhanghao', '').strip() qingqiu_tongji = request.data.get('qingqiu_tongji', False) sqzhuangtai = request.data.get('sqzhuangtai') page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 30)) # 验证参数 if not qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 2. JWT Token身份验证 dangqianyonghu = request.user # 3. 验证手机号是否匹配 if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型是否为管理员 if dangqianyonghu.user_type != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.admin_profile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 导入模型 from dingdan.models import Chufajilu # 7. 获取统计信息(总是需要统计) zongshu = Chufajilu.objects.count() daichuli = Chufajilu.objects.filter(sqzhuangtai__in=[0, 3]).count() yichuli = Chufajilu.objects.filter(sqzhuangtai__in=[1, 2]).count() # 8. 如果只请求统计信息,直接返回 if qingqiu_tongji: return Response({ 'code': 0, 'message': '成功', 'data': { 'zongshu': zongshu, 'daichuli': daichuli, 'yichuli': yichuli } }) # 9. 构建查询集 queryset = Chufajilu.objects.all() # 10. 根据状态筛选 if sqzhuangtai is not None: if isinstance(sqzhuangtai, list): # 如果是数组,表示多个状态 queryset = queryset.filter(sqzhuangtai__in=sqzhuangtai) else: # 单个状态 queryset = queryset.filter(sqzhuangtai=sqzhuangtai) # 11. 获取总数(用于分页计算) total_count = queryset.count() # 12. 计算分页偏移量 offset = (page - 1) * page_size # 13. 分页查询 chufa_list = list(queryset.order_by('-create_time')[offset:offset + page_size]) # 🔴【新增】高效获取图片信息 if chufa_list: # 收集所有需要查询的组合 dingdan_ids = [] yonghu_combinations = [] # 保存 (dingdan_id, yonghuid) 组合 for chufa in chufa_list: if chufa.dingdan_id: dingdan_ids.append(chufa.dingdan_id) # 商家图片组合 if chufa.qingqiuid: yonghu_combinations.append((chufa.dingdan_id, chufa.qingqiuid)) # 打手图片组合 if chufa.dashouid: yonghu_combinations.append((chufa.dingdan_id, chufa.dashouid)) # 去重 dingdan_ids = list(set(dingdan_ids)) yonghu_combinations = list(set(yonghu_combinations)) # 🔴【新增】批量查询图片表(高效查询,避免循环) tupian_mapping = {} if yonghu_combinations: # 导入图片模型 from dingdan.models import Chufatupian # 构建查询条件 from django.db.models import Q query_conditions = Q() for dingdan_id, yonghuid in yonghu_combinations: query_conditions |= Q(dingdan_id=dingdan_id, yonghuid=yonghuid) # 批量查询 tupian_queryset = Chufatupian.objects.filter(query_conditions).values( 'dingdan_id', 'yonghuid', 'tupian' ) # 构建映射:key为"dingdan_id_yonghuid",value为图片URL列表 for item in tupian_queryset: key = f"{item['dingdan_id']}_{item['yonghuid']}" if key not in tupian_mapping: tupian_mapping[key] = [] if item['tupian']: tupian_mapping[key].append(item['tupian']) else: tupian_mapping = {} # 🔴【修改】14. 构建返回数据 - 添加图片和申诉理由字段 chufa_data = [] for chufa in chufa_list: # 🔴【新增】获取商家证据图片 zhengju_tupian = [] if chufa.dingdan_id and chufa.qingqiuid: key = f"{chufa.dingdan_id}_{chufa.qingqiuid}" zhengju_tupian = tupian_mapping.get(key, []) # 🔴【新增】获取打手申诉图片 shensu_tupian = [] if chufa.dingdan_id and chufa.dashouid: key = f"{chufa.dingdan_id}_{chufa.dashouid}" shensu_tupian = tupian_mapping.get(key, []) chufa_data.append({ 'dashouid': chufa.dashouid, 'qingqiuid': chufa.qingqiuid, 'chuliid': chufa.chuliid, 'cfliyou': chufa.cfliyou, 'sqzhuangtai': chufa.sqzhuangtai, 'bhliyou': chufa.bhliyou, 'jifen': chufa.jifen, 'dingdan_id': chufa.dingdan_id, 'create_time': chufa.create_time, 'update_time': chufa.update_time, # 🔴【新增】申诉理由字段 'ssliyou': chufa.ssliyou or '', # 🔴【新增】商家证据图片 'zhengju_tupian': zhengju_tupian, # 🔴【新增】打手申诉图片 'shensu_tupian': shensu_tupian, }) # 15. 计算是否还有更多数据 current_count = len(chufa_list) haiyougengduo = (offset + current_count) < total_count return Response({ 'code': 0, 'message': '成功', 'data': { 'list': chufa_data, 'zongshu': zongshu, 'daichuli': daichuli, 'yichuli': yichuli, 'haiyougengduo': haiyougengduo } }) except ValueError: return Response( {'code': 400, 'message': '参数类型错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"处罚管理列表接口异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) '''from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from rest_framework import status from django.db.models import Q import logging logger = logging.getLogger(__name__) class CfGuanLi(APIView): """ 后台处罚管理列表接口 权限: JWT Token认证 + 管理员身份验证 前端调用: POST /yonghu/cfgl 参数: { "zhanghao": "管理员账号", "qingqiu_tongji": true/false, # 是否只请求统计信息 "sqzhuangtai": 0 或 [1,2], # 处罚申请状态筛选(可选) "page": "页码(从1开始)", # 分页参数 "page_size": "每页数量" # 分页参数 } 返回: { "code": 0, "message": "成功", "data": { "list": [处罚记录列表], "zongshu": 总数, "daichuli": 待处理数量, "yichuli": 已处理数量, "haiyougengduo": true/false } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # 1. 获取前端参数 qianzhanghao = request.data.get('zhanghao', '').strip() qingqiu_tongji = request.data.get('qingqiu_tongji', False) sqzhuangtai = request.data.get('sqzhuangtai') page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 30)) # 验证参数 if not qianzhanghao: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 2. JWT Token身份验证 dangqianyonghu = request.user # 3. 验证手机号是否匹配 if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型是否为管理员 if dangqianyonghu.user_type != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.admin_profile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 导入模型 from dingdan.models import Chufajilu # 7. 获取统计信息(总是需要统计) zongshu = Chufajilu.objects.count() daichuli = Chufajilu.objects.filter(sqzhuangtai=0).count() yichuli = Chufajilu.objects.filter(sqzhuangtai__in=[1, 2]).count() # 8. 如果只请求统计信息,直接返回 if qingqiu_tongji: return Response({ 'code': 0, 'message': '成功', 'data': { 'zongshu': zongshu, 'daichuli': daichuli, 'yichuli': yichuli } }) # 9. 构建查询集 queryset = Chufajilu.objects.all() # 10. 根据状态筛选 if sqzhuangtai is not None: if isinstance(sqzhuangtai, list): # 如果是数组,表示多个状态 queryset = queryset.filter(sqzhuangtai__in=sqzhuangtai) else: # 单个状态 queryset = queryset.filter(sqzhuangtai=sqzhuangtai) # 11. 获取总数(用于分页计算) total_count = queryset.count() # 12. 计算分页偏移量 offset = (page - 1) * page_size # 13. 分页查询 chufa_list = list(queryset.order_by('-create_time')[offset:offset + page_size]) # 14. 构建返回数据 chufa_data = [] for chufa in chufa_list: chufa_data.append({ 'dashouid': chufa.dashouid, 'qingqiuid': chufa.qingqiuid, 'chuliid': chufa.chuliid, 'cfliyou': chufa.cfliyou, 'sqzhuangtai': chufa.sqzhuangtai, 'bhliyou': chufa.bhliyou, 'jifen': chufa.jifen, 'dingdan_id': chufa.dingdan_id, 'create_time': chufa.create_time, 'update_time': chufa.update_time }) # 15. 计算是否还有更多数据 current_count = len(chufa_list) haiyougengduo = (offset + current_count) < total_count return Response({ 'code': 0, 'message': '成功', 'data': { 'list': chufa_data, 'zongshu': zongshu, 'daichuli': daichuli, 'yichuli': yichuli, 'haiyougengduo': haiyougengduo } }) except ValueError: return Response( {'code': 400, 'message': '参数类型错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"处罚管理列表接口异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR )''' logger = logging.getLogger(__name__) class AdTongYiChuFa(APIView): """ 后台同意/驳回处罚接口(已修复重复记录问题) 权限: JWT Token认证 + 管理员身份验证 前端调用: POST /yonghu/adtycf 参数: { "zhanghao": "管理员账号", "dingdan_id": "订单ID", "caozuo": 1或2, # 1=同意处罚,2=拒绝处罚 "bohui_liyou": "拒绝理由" # 拒绝处罚时必填 } 返回: { "code": 0, "message": "成功", "data": null } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # 1. 获取前端参数 qianzhanghao = request.data.get('zhanghao', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() caozuo = request.data.get('caozuo') # 1=同意,2=拒绝 bohui_liyou = request.data.get('bohui_liyou', '').strip() # 验证参数 if not qianzhanghao or not dingdan_id or caozuo is None: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 验证操作类型 if caozuo not in [1, 2]: return Response( {'code': 400, 'message': '操作类型错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 如果是拒绝处罚,需要拒绝理由 if caozuo == 2 and not bohui_liyou: return Response( {'code': 400, 'message': '拒绝处罚需要填写理由', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 2. JWT Token身份验证 dangqianyonghu = request.user # 3. 验证手机号是否匹配 if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型是否为管理员 if dangqianyonghu.user_type != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.admin_profile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 使用事务保证数据一致性 with transaction.atomic(): # 7. 导入模型 from dingdan.models import Chufajilu, Dingdan, DingdanShangjia from yonghu.models import UserDashou # 8. 🔴 修复:查询处罚记录(处理重复记录问题) # 使用 filter().first() 而不是 get(),以防出现多条记录 chufajilu = Chufajilu.objects.filter( dingdan_id=dingdan_id, sqzhuangtai__in=[0, 3] # 🔴【修改】包括待处理和申诉中状态 ).order_by('-create_time').first() # 取最新的一条 if not chufajilu: # 如果没有待处理的处罚记录,检查是否有已处理的 exists_processed = Chufajilu.objects.filter(dingdan_id=dingdan_id).exists() if exists_processed: return Response( {'code': 400, 'message': '该处罚记录已处理', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) else: return Response( {'code': 404, 'message': '处罚记录不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND ) # 9. 验证处罚记录状态(必须是待处理或申诉中状态才能处理) if chufajilu.sqzhuangtai not in [0, 3]: # 🔴【修改】包括待处理(0)和申诉中(3) return Response( {'code': 400, 'message': '该处罚记录已处理', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 10. 🔴 修复:检查是否有重复的待处理/申诉中记录,并记录警告 duplicate_count = Chufajilu.objects.filter( dingdan_id=dingdan_id, sqzhuangtai__in=[0, 3] # 🔴【修改】包括待处理和申诉中 ).count() if duplicate_count > 1: # 记录警告日志 logger.warning(f"订单ID {dingdan_id} 存在 {duplicate_count} 条待处理/申诉中处罚记录,处理最新的一条") # 🔴 可选:将其他重复的待处理/申诉中记录标记为已拒绝 # 防止用户重复提交相同订单的处罚申请 duplicate_records = Chufajilu.objects.filter( dingdan_id=dingdan_id, sqzhuangtai__in=[0, 3] # 🔴【修改】包括待处理和申诉中 ).exclude(id=chufajilu.id) for dup in duplicate_records: dup.sqzhuangtai = 2 # 已拒绝 dup.chuliid = qianzhanghao dup.bhliyou = "系统自动拒绝:存在重复处罚记录" dup.save() # 11. 根据操作类型处理 if caozuo == 1: # 同意处罚 - 不扣分,只改状态 # 11.1 检查打手是否存在 if not chufajilu.dashouid: return Response( {'code': 400, 'message': '被处罚打手ID不存在', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 11.2 查询打手扩展表(只是验证存在性) try: dashou = UserDashou.objects.get(user__yonghuid=chufajilu.dashouid) except UserDashou.DoesNotExist: return Response( {'code': 404, 'message': '被处罚打手不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND ) # 🔴【修改】11.3 同意处罚时不扣分(因为申请时已经扣了),只更新状态 chufajilu.sqzhuangtai = 1 # 已处罚 chufajilu.chuliid = qianzhanghao # 处理人ID为管理员账号 chufajilu.save() # 11.4 更新商家订单扩展表 try: dingdan = Dingdan.objects.get(dingdan_id=dingdan_id) if hasattr(dingdan, 'shangjia_kuozhan'): shangjia_kuozhan = dingdan.shangjia_kuozhan shangjia_kuozhan.sqzhuangtai = 1 # 已处罚 shangjia_kuozhan.save() except (Dingdan.DoesNotExist, AttributeError): # 如果订单或扩展表不存在,继续执行,不中断 pass logger.info(f"同意处罚成功:订单ID={dingdan_id},打手ID={chufajilu.dashouid}") elif caozuo == 2: # 拒绝处罚 - 需要返还积分(加分) # 🔴【新增】11.5 返还打手积分(加5分) if chufajilu.jifen > 0: try: dashou = UserDashou.objects.get(user__yonghuid=chufajilu.dashouid) # 🔴【新增】安全验证:必须积分小于等于5分才允许加分 if dashou.jifen <= 5: # 判断积分是否小于等于5分 dashou.jifen += chufajilu.jifen # 加5分 dashou.save() logger.info( f"拒绝处罚,给打手{dashou.user.yonghuid}返还积分{chufajilu.jifen}分,当前积分{dashou.jifen}") else: # 积分已超过5分,可能是异常情况,记录警告但不中断处理 logger.warning( f"拒绝处罚时打手积分异常:打手ID={chufajilu.dashouid},当前积分{dashou.jifen}已超过5分,不再加分") except UserDashou.DoesNotExist: # 打手不存在,记录错误,但继续处理状态 logger.error(f"拒绝处罚时打手不存在:打手ID={chufajilu.dashouid}") # 11.6 更新处罚记录 chufajilu.sqzhuangtai = 2 # 已拒绝 chufajilu.chuliid = qianzhanghao # 处理人ID为管理员账号 chufajilu.bhliyou = bohui_liyou # 拒绝理由 chufajilu.save() # 11.7 更新商家订单扩展表 try: dingdan = Dingdan.objects.get(dingdan_id=dingdan_id) if hasattr(dingdan, 'shangjia_kuozhan'): shangjia_kuozhan = dingdan.shangjia_kuozhan shangjia_kuozhan.sqzhuangtai = 2 # 已拒绝 shangjia_kuozhan.bhliyou = bohui_liyou # 拒绝理由 shangjia_kuozhan.save() except (Dingdan.DoesNotExist, AttributeError): # 如果订单或扩展表不存在,继续执行,不中断 pass logger.info(f"拒绝处罚成功:订单ID={dingdan_id},拒绝理由={bohui_liyou}") # 12. 返回成功响应 return Response({ 'code': 0, 'message': '处理成功', 'data': None }) except ValueError: return Response( {'code': 400, 'message': '参数类型错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"处理处罚接口异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) '''logger = logging.getLogger(__name__) class AdTongYiChuFa(APIView): """ 后台同意/驳回处罚接口(已修复重复记录问题) 权限: JWT Token认证 + 管理员身份验证 前端调用: POST /yonghu/adtycf 参数: { "zhanghao": "管理员账号", "dingdan_id": "订单ID", "caozuo": 1或2, # 1=同意处罚,2=驳回处罚 "bohui_liyou": "驳回理由" # 驳回处罚时必填 } 返回: { "code": 0, "message": "成功", "data": null } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): try: # 1. 获取前端参数 qianzhanghao = request.data.get('zhanghao', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() caozuo = request.data.get('caozuo') # 1=同意,2=驳回 bohui_liyou = request.data.get('bohui_liyou', '').strip() # 验证参数 if not qianzhanghao or not dingdan_id or caozuo is None: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 验证操作类型 if caozuo not in [1, 2]: return Response( {'code': 400, 'message': '操作类型错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 如果是驳回处罚,需要驳回理由 if caozuo == 2 and not bohui_liyou: return Response( {'code': 400, 'message': '驳回处罚需要填写理由', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 2. JWT Token身份验证 dangqianyonghu = request.user # 3. 验证手机号是否匹配 if not hasattr(dangqianyonghu, 'phone') or str(dangqianyonghu.phone) != qianzhanghao: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 4. 验证用户类型是否为管理员 if dangqianyonghu.user_type != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.admin_profile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 使用事务保证数据一致性 with transaction.atomic(): # 7. 导入模型 from dingdan.models import Chufajilu, Dingdan, DingdanShangjia from yonghu.models import UserDashou # 8. 🔴 修复:查询处罚记录(处理重复记录问题) # 使用 filter().first() 而不是 get(),以防出现多条记录 chufajilu = Chufajilu.objects.filter( dingdan_id=dingdan_id, sqzhuangtai=0 # 只查询待处理状态的记录 ).order_by('-create_time').first() # 取最新的一条 if not chufajilu: # 如果没有待处理的处罚记录,检查是否有已处理的 exists_processed = Chufajilu.objects.filter(dingdan_id=dingdan_id).exists() if exists_processed: return Response( {'code': 400, 'message': '该处罚记录已处理', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) else: return Response( {'code': 404, 'message': '处罚记录不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND ) # 9. 验证处罚记录状态(必须是待处理状态才能处理) if chufajilu.sqzhuangtai != 0: return Response( {'code': 400, 'message': '该处罚记录已处理', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 10. 🔴 修复:检查是否有重复的待处理记录,并记录警告 duplicate_count = Chufajilu.objects.filter( dingdan_id=dingdan_id, sqzhuangtai=0 ).count() if duplicate_count > 1: # 记录警告日志 logger.warning(f"订单ID {dingdan_id} 存在 {duplicate_count} 条待处理处罚记录,处理最新的一条") # 🔴 可选:将其他重复的待处理记录标记为已驳回 # 防止用户重复提交相同订单的处罚申请 duplicate_records = Chufajilu.objects.filter( dingdan_id=dingdan_id, sqzhuangtai=0 ).exclude(id=chufajilu.id) for dup in duplicate_records: dup.sqzhuangtai = 2 # 已驳回 dup.chuliid = qianzhanghao dup.bhliyou = "系统自动驳回:存在重复处罚记录" dup.save() # 11. 根据操作类型处理 if caozuo == 1: # 同意处罚 # 11.1 检查打手是否存在 if not chufajilu.dashouid: return Response( {'code': 400, 'message': '被处罚打手ID不存在', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 11.2 查询打手扩展表 try: dashou = UserDashou.objects.get(user__yonghuid=chufajilu.dashouid) except UserDashou.DoesNotExist: return Response( {'code': 404, 'message': '被处罚打手不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND ) # 11.3 扣除打手积分(确保积分不为负数) jifen_kouchu = chufajilu.jifen if jifen_kouchu > 0: # 检查积分是否足够 if dashou.jifen < jifen_kouchu: # 如果积分不足,设置为0 dashou.jifen = 0 else: dashou.jifen -= jifen_kouchu dashou.save() # 11.4 更新处罚记录 chufajilu.sqzhuangtai = 1 # 已处罚 chufajilu.chuliid = qianzhanghao # 处理人ID为管理员账号 chufajilu.save() # 11.5 更新商家订单扩展表 try: dingdan = Dingdan.objects.get(dingdan_id=dingdan_id) if hasattr(dingdan, 'shangjia_kuozhan'): shangjia_kuozhan = dingdan.shangjia_kuozhan shangjia_kuozhan.sqzhuangtai = 1 # 已处罚 shangjia_kuozhan.save() except (Dingdan.DoesNotExist, AttributeError): # 如果订单或扩展表不存在,继续执行,不中断 pass #logger.info(f"同意处罚成功:订单ID={dingdan_id},打手ID={chufajilu.dashouid},扣除积分={jifen_kouchu}") elif caozuo == 2: # 驳回处罚 # 11.6 更新处罚记录 chufajilu.sqzhuangtai = 2 # 已驳回 chufajilu.chuliid = qianzhanghao # 处理人ID为管理员账号 chufajilu.bhliyou = bohui_liyou # 驳回理由 chufajilu.save() # 11.7 更新商家订单扩展表 try: dingdan = Dingdan.objects.get(dingdan_id=dingdan_id) if hasattr(dingdan, 'shangjia_kuozhan'): shangjia_kuozhan = dingdan.shangjia_kuozhan shangjia_kuozhan.sqzhuangtai = 2 # 已驳回 shangjia_kuozhan.bhliyou = bohui_liyou # 驳回理由 shangjia_kuozhan.save() except (Dingdan.DoesNotExist, AttributeError): # 如果订单或扩展表不存在,继续执行,不中断 pass #logger.info(f"驳回处罚成功:订单ID={dingdan_id},驳回理由={bohui_liyou}") # 12. 返回成功响应 return Response({ 'code': 0, 'message': '处理成功', 'data': None }) except ValueError: return Response( {'code': 400, 'message': '参数类型错误', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) except Exception as e: logger.error(f"处理处罚接口异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR )''' # yonghu/views.py from django.utils import timezone from datetime import timedelta from decimal import Decimal from django.db import transaction, IntegrityError from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework import status from .models import ( UserMain, UserBoss, UserDashou, UserShangjia, UserGuanshi, AdminProfile, Xiugaijilu ) from shangpin.models import Huiyuan, Huiyuangoumai class AdckyhxqView(APIView): """ 获取用户详情接口 前端需要传递: zhanghao, uid, user_type 返回用户信息和会员列表 """ permission_classes = [IsAuthenticated] def post(self, request): try: # ==================== 管理员身份验证 ==================== # 1. 验证JWT Token if not request.user.is_authenticated: return Response({ 'code': 401, 'message': '未认证', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) # 2. 验证用户类型为管理员 if request.user.user_type != 'admin': return Response({ 'code': 403, 'message': '权限不足', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 3. 验证管理员扩展表存在 admin_profile = getattr(request.user, 'admin_profile', None) if not admin_profile: return Response({ 'code': 403, 'message': '管理员信息不完整', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 4. 验证管理员账号(phone)与前端传递的zhanghao一致 zhanghao = request.data.get('zhanghao') if not zhanghao or request.user.phone != zhanghao: return Response({ 'code': 401, 'message': '账号验证失败', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) # ==================== 身份验证结束 ==================== # 获取业务参数 uid = request.data.get('uid') # 用户ID (yonghuid) user_type = request.data.get('user_type') # 用户类型 (1,2,3,4) if not uid or not user_type: return Response({ 'code': 400, 'message': '参数缺失', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 验证用户类型 try: user_type = int(user_type) if user_type not in [1, 2, 3, 4]: raise ValueError except ValueError: return Response({ 'code': 400, 'message': '用户类型无效', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 查询用户主表 - 使用select_related优化查询 user_main = UserMain.objects.filter(yonghuid=uid).first() if not user_main: return Response({ 'code': 404, 'message': '用户不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 构建响应数据 response_data = { 'user_info': {}, 'member_list': [], 'all_member_list': [] } # 通用字段(所有用户都有) user_info = { 'yonghuid': user_main.yonghuid, 'avatar': user_main.avatar, 'phone': user_main.phone, 'ip': user_main.ip, 'create_time': user_main.create_time, 'update_time': user_main.update_time, 'user_type': user_type } # 根据用户类型查询扩展信息 if user_type == 1: # 老板 # 使用select_related预取boss_profile user_main = UserMain.objects.filter(yonghuid=uid).select_related('boss_profile').first() if user_main.boss_profile: boss_profile = user_main.boss_profile user_info.update({ 'nickname': boss_profile.nickname, 'zonge': boss_profile.zonge, 'alldingdan': boss_profile.alldingdan, 'alltui': boss_profile.alltui, 'create_time_boss': boss_profile.create_time }) elif user_type == 2: # 打手 # 使用select_related预取dashou_profile user_main = UserMain.objects.filter(yonghuid=uid).select_related('dashou_profile').first() if user_main.dashou_profile: dashou_profile = user_main.dashou_profile # 构建打手信息 user_info.update({ 'nicheng': dashou_profile.nicheng, 'chenghao': dashou_profile.chenghao, 'zhuangtai': dashou_profile.zhuangtai, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'zhanghaozhuangtai': dashou_profile.zhanghaozhuangtai, 'jieshao': dashou_profile.jieshao, 'jiedanzongliang': dashou_profile.jiedanzongliang, 'chengjiaozongliang': dashou_profile.chengjiaozongliang, 'tuikuanliang': dashou_profile.tuikuanliang, 'yue': dashou_profile.yue, # 打手可提现余额 'zonge': dashou_profile.zonge, # 打手赚取总额 'dianhua': dashou_profile.dianhua, 'wechat': dashou_profile.wechat, 'yaoqingren': dashou_profile.yaoqingren, 'jinrijiedan': dashou_profile.jinrijiedan, 'jinrishouyi': dashou_profile.jinrishouyi, 'jinyuejiedan': dashou_profile.jinyuejiedan, 'jinyueshouyi': dashou_profile.jinyueshouyi, 'jifen': dashou_profile.jifen, 'yajin': dashou_profile.yajin, 'create_time_dashou': dashou_profile.create_time }) # 查询打手的会员信息(只返回未过期的) # 使用select_related预取huiyuan信息(如果有关联的话) member_records = Huiyuangoumai.objects.filter( yonghu_id=uid ).order_by('-create_time') member_list = [] for record in member_records: # 调用方法检查是否过期,并更新状态 is_expired = record.jiance_shifou_daoqi() if not is_expired: # 只返回未过期的 # 查询会员详情 huiyuan = Huiyuan.objects.filter( huiyuan_id=record.huiyuan_id ).first() if huiyuan: member_list.append({ 'huiyuan_id': record.huiyuan_id, 'huiyuan_name': huiyuan.jieshao, 'daoqi_time': record.daoqi_time, 'is_active': record.huiyuan_zhuangtai == 1, 'create_time': record.create_time }) response_data['member_list'] = member_list elif user_type == 3: # 管事 # 使用select_related预取guanshi_profile user_main = UserMain.objects.filter(yonghuid=uid).select_related('guanshi_profile').first() if user_main.guanshi_profile: guanshi_profile = user_main.guanshi_profile user_info.update({ 'nicheng': user_main.phone, # 管事昵称用手机号 'yaoqingma': guanshi_profile.yaoqingma, 'dianhua': guanshi_profile.dianhua, 'wechat': guanshi_profile.wechat, 'yaogingshuliang': guanshi_profile.yaogingshuliang, 'zhuangtai': guanshi_profile.zhuangtai, 'jinrichongzhi': guanshi_profile.jinrichongzhi, 'jinyuechongzhi': guanshi_profile.jinyuechongzhi, 'chongzhifenrun': guanshi_profile.chongzhifenrun, 'yue': guanshi_profile.yue, # 管事可提现余额 'create_time_guanshi': guanshi_profile.create_time }) elif user_type == 4: # 商家 # 使用select_related预取shop_profile user_main = UserMain.objects.filter(yonghuid=uid).select_related('shop_profile').first() if user_main.shop_profile: shop_profile = user_main.shop_profile user_info.update({ 'nicheng': shop_profile.nicheng, 'zhuangtai': shop_profile.zhuangtai, 'dianhua': shop_profile.dianhua, 'wechat': shop_profile.wechat, 'fabu': shop_profile.fabu, 'tuikuan': shop_profile.tuikuan, 'yue': shop_profile.yue, # 商家余额 'chengjiao': shop_profile.chengjiao, 'jinridingdan': shop_profile.jinridingdan, 'jinriliushui': shop_profile.jinriliushui, 'jinyuedingdan': shop_profile.jinyuedingdan, 'jinyueliushui': shop_profile.jinyueliushui, 'create_time_shop': shop_profile.create_time }) # 获取所有会员列表(用于前端下拉选择) all_huiyuan = Huiyuan.objects.all().order_by('jiage') all_member_list = [ { 'id': huiyuan.huiyuan_id, 'nicheng': huiyuan.jieshao, 'jiage': huiyuan.jiage, 'jieshao': huiyuan.jtjieshao } for huiyuan in all_huiyuan ] response_data['user_info'] = user_info response_data['all_member_list'] = all_member_list return Response({ 'code': 0, 'message': '获取成功', 'data': response_data }, status=status.HTTP_200_OK) except Exception as e: return Response({ 'code': 500, 'message': f'获取用户详情失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class AdcjxgView(APIView): """ 修改用户信息接口 前端需要传递: zhanghao, uid, user_type, 以及其他修改的字段 支持打手会员添加功能 敏感字段修改会记录到修改记录表 """ permission_classes = [IsAuthenticated] def post(self, request): try: # ==================== 管理员身份验证 ==================== if not request.user.is_authenticated: return Response({ 'code': 401, 'message': '未认证', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) if request.user.user_type != 'admin': return Response({ 'code': 403, 'message': '权限不足', 'data': None }, status=status.HTTP_403_FORBIDDEN) admin_profile = getattr(request.user, 'admin_profile', None) if not admin_profile: return Response({ 'code': 403, 'message': '管理员信息不完整', 'data': None }, status=status.HTTP_403_FORBIDDEN) zhanghao = request.data.get('zhanghao') if not zhanghao or request.user.phone != zhanghao: return Response({ 'code': 401, 'message': '账号验证失败', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) # ==================== 身份验证结束 ==================== # 获取业务参数 uid = request.data.get('uid') user_type = request.data.get('user_type') if not uid or not user_type: return Response({ 'code': 400, 'message': '参数缺失', 'data': None }, status=status.HTTP_400_BAD_REQUEST) try: user_type = int(user_type) if user_type not in [1, 2, 3, 4]: raise ValueError except ValueError: return Response({ 'code': 400, 'message': '用户类型无效', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 查询用户主表 user_main = UserMain.objects.filter(yonghuid=uid).first() if not user_main: return Response({ 'code': 404, 'message': '用户不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 使用事务确保数据一致性 with transaction.atomic(): # 存储修改记录的数据 modify_record_data = { 'yonghuid': uid, 'xiugaiid': zhanghao, 'leixing': user_type, 'qitashuoming': '', 'create_time': timezone.now() } # 标记是否有敏感字段被修改 has_sensitive_change = False # 🔒 判断当前管理员是否有增加权限 from django.conf import settings allow_increase = zhanghao in getattr(settings, 'ALLOW_INCREASE_ADMINS', []) if user_type == 1: # 老板 - 不允许修改 return Response({ 'code': 403, 'message': '老板信息不允许修改', 'data': None }, status=status.HTTP_403_FORBIDDEN) elif user_type == 2: # 打手 # 获取或创建打手扩展记录 dashou_profile, created = UserDashou.objects.get_or_create( user=user_main, defaults={'nicheng': user_main.phone or f'打手{uid}'} ) # 记录敏感字段的旧值 old_yue = dashou_profile.yue # 打手可提现余额 old_zonge = dashou_profile.zonge # 打手提现金额 old_jifen = dashou_profile.jifen # 打手积分 old_yajin = dashou_profile.yajin # 打手押金 # 需要更新的字段列表 update_fields = [ 'chenghao', 'dianhua', 'yue', 'zonge', 'jifen', 'yajin', 'yaoqingren', 'jieshao', 'zhuangtai', 'zaixianzhuangtai', 'zhanghaozhuangtai' ] # 记录被修改的字段 modified_fields = [] for field in update_fields: if field in request.data: new_value = request.data.get(field) old_value = getattr(dashou_profile, field) # 检查值是否改变 if str(new_value) != str(old_value): # 🔒 权限控制:仅允许增加的管理员可增加 if field in ['yue', 'zonge', 'jifen', 'yajin']: try: # 转换为对应类型以便比较 if field in ['yue', 'zonge', 'yajin']: new_num = Decimal(str(new_value)) old_num = Decimal(str(old_value)) else: # jifen new_num = int(new_value) old_num = int(old_value) # 如果不允许增加,且新值大于旧值,则拒绝 if not allow_increase and new_num > old_num: return Response({ 'code': 403, 'message': f'您无权增加{field},只能减少或保持不变', 'data': None }, status=status.HTTP_403_FORBIDDEN) except Exception: # 类型转换失败则拒绝修改 return Response({ 'code': 400, 'message': f'{field}值格式错误', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 处理特殊字段类型 if field in ['yue', 'zonge', 'yajin']: new_value = Decimal(str(new_value)) setattr(dashou_profile, field, new_value) elif field in ['jifen']: new_value = int(new_value) setattr(dashou_profile, field, new_value) elif field in ['zhuangtai', 'zaixianzhuangtai', 'zhanghaozhuangtai']: new_value = int(new_value) setattr(dashou_profile, field, new_value) else: setattr(dashou_profile, field, new_value) modified_fields.append(field) # 记录敏感字段修改 if field == 'yue': has_sensitive_change = True modify_record_data['xiugaitijiao'] = new_value # 使用xiugaitijiao字段记录打手余额修改 modify_record_data['xiugaitijiaoq'] = old_yue elif field == 'zonge': has_sensitive_change = True # zonge字段在修改记录表中可能没有直接对应,可以记录在备注中 modify_record_data['qitashuoming'] += f' 打手总额从{old_zonge}修改为{new_value}' elif field == 'jifen': has_sensitive_change = True modify_record_data['jifen'] = new_value modify_record_data['yjifen'] = old_jifen elif field == 'yajin': has_sensitive_change = True modify_record_data['yajin'] = new_value modify_record_data['yyajin'] = old_yajin # 保存打手信息 dashou_profile.save() # 记录被修改的非敏感字段 non_sensitive_fields = [f for f in modified_fields if f not in ['yue', 'zonge', 'jifen', 'yajin']] if non_sensitive_fields: modify_record_data['qitashuoming'] += f' 修改了字段: {",".join(non_sensitive_fields)}' elif user_type == 3: # 管事 # 获取或创建管事扩展记录 guanshi_profile, created = UserGuanshi.objects.get_or_create( user=user_main, defaults={'yaoqingma': f'GS{uid[-4:]}'} ) # 记录敏感字段的旧值 old_yue = guanshi_profile.yue # 管事可提现余额 update_fields = [ 'yaogingshuliang', 'chongzhifenrun', 'yue', 'zhuangtai', 'dianhua', 'wechat' ] modified_fields = [] for field in update_fields: if field in request.data: new_value = request.data.get(field) old_value = getattr(guanshi_profile, field) if str(new_value) != str(old_value): # 🔒 权限控制:仅允许增加的管理员可增加 if field in ['yue', 'chongzhifenrun']: try: new_num = Decimal(str(new_value)) old_num = Decimal(str(old_value)) if not allow_increase and new_num > old_num: return Response({ 'code': 403, 'message': f'您无权增加{field},只能减少或保持不变', 'data': None }, status=status.HTTP_403_FORBIDDEN) except Exception: return Response({ 'code': 400, 'message': f'{field}值格式错误', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if field in ['chongzhifenrun', 'yue']: new_value = Decimal(str(new_value)) setattr(guanshi_profile, field, new_value) elif field in ['yaogingshuliang']: new_value = int(new_value) setattr(guanshi_profile, field, new_value) elif field in ['zhuangtai']: new_value = int(new_value) setattr(guanshi_profile, field, new_value) else: setattr(guanshi_profile, field, new_value) modified_fields.append(field) # 记录敏感字段修改 if field == 'yue': has_sensitive_change = True modify_record_data['guanshiyue'] = new_value modify_record_data['guanshiyueq'] = old_yue elif field == 'chongzhifenrun': has_sensitive_change = True modify_record_data['qitashuoming'] += f' 管事分佣从{old_value}修改为{new_value}' guanshi_profile.save() # 记录非敏感字段 non_sensitive_fields = [f for f in modified_fields if f not in ['yue', 'chongzhifenrun']] if non_sensitive_fields: modify_record_data['qitashuoming'] += f' 修改了字段: {",".join(non_sensitive_fields)}' elif user_type == 4: # 商家 # 获取或创建商家扩展记录 shop_profile, created = UserShangjia.objects.get_or_create( user=user_main, defaults={'nicheng': user_main.phone or f'商家{uid}'} ) # 记录敏感字段的旧值 old_yue = shop_profile.yue # 商家余额 update_fields = ['yue', 'zhuangtai', 'dianhua', 'wechat'] modified_fields = [] for field in update_fields: if field in request.data: new_value = request.data.get(field) old_value = getattr(shop_profile, field) if str(new_value) != str(old_value): # 🔒 权限控制:仅允许增加的管理员可增加 if field == 'yue': try: new_num = Decimal(str(new_value)) old_num = Decimal(str(old_value)) if not allow_increase and new_num > old_num: return Response({ 'code': 403, 'message': '您无权增加商家余额,只能减少或保持不变', 'data': None }, status=status.HTTP_403_FORBIDDEN) except Exception: return Response({ 'code': 400, 'message': '余额值格式错误', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if field == 'yue': new_value = Decimal(str(new_value)) setattr(shop_profile, field, new_value) elif field == 'zhuangtai': new_value = int(new_value) setattr(shop_profile, field, new_value) else: setattr(shop_profile, field, new_value) modified_fields.append(field) # 记录敏感字段修改 if field == 'yue': has_sensitive_change = True modify_record_data['shangjiayue'] = new_value modify_record_data['shangjiayueq'] = old_yue shop_profile.save() # 记录非敏感字段 non_sensitive_fields = [f for f in modified_fields if f != 'yue'] if non_sensitive_fields: modify_record_data['qitashuoming'] += f' 修改了字段: {",".join(non_sensitive_fields)}' # 处理打手会员添加(支持正负数) if user_type == 2 and 'huiyuan_id' in request.data and 'days' in request.data: huiyuan_id = request.data.get('huiyuan_id') days = int(request.data.get('days', 0)) # 🔥 修改:允许正负数,但不能为0 if huiyuan_id and days != 0: # 🔥 修改:检查绝对值范围 if abs(days) < 1 or abs(days) > 10000: return Response({ 'code': 400, 'message': '会员天数必须在1-10000之间(正数增加,负数减少)', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 检查会员是否存在 huiyuan = Huiyuan.objects.filter(huiyuan_id=huiyuan_id).first() if not huiyuan: return Response({ 'code': 404, 'message': '会员不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 检查是否已购买该会员 existing_record = Huiyuangoumai.objects.filter( yonghu_id=uid, huiyuan_id=huiyuan_id ).first() # 🔒 权限控制:仅允许增加的管理员可增加会员天数 if not allow_increase and days > 0: return Response({ 'code': 403, 'message': '您无权增加会员天数,只能减少或保持不变', 'data': None }, status=status.HTTP_403_FORBIDDEN) if existing_record: # 重要修改:支持减少天数 # 计算新的到期时间 new_daoqi_time = existing_record.daoqi_time + timedelta(days=days) # 安全验证:减少天数后不能小于当前时间前90天(防止过度减少) min_allowed_time = timezone.now() - timedelta(days=90) if new_daoqi_time < min_allowed_time: return Response({ 'code': 400, 'message': f'减少天数过多,调整后到期时间不能早于 {(min_allowed_time + timedelta(days=90)).strftime("%Y-%m-%d")}', 'data': None }, status=status.HTTP_400_BAD_REQUEST) existing_record.daoqi_time = new_daoqi_time existing_record.save() # 记录会员操作 has_sensitive_change = True modify_record_data['huiyuants'] = days modify_record_data['huiyuan_id'] = huiyuan_id if days > 0: modify_record_data['qitashuoming'] += f' 续费会员 {huiyuan.jieshao} {days}天' # 只有增加天数时才增加购买次数 huiyuan.goumai_cishu += 1 huiyuan.save() else: modify_record_data[ 'qitashuoming'] += f' 减少会员 {huiyuan.jieshao} {abs(days)}天' # 减少天数时不增加购买次数 else: # 新购买时只能增加天数 if days > 0: daoqi_time = timezone.now() + timedelta(days=days) new_record = Huiyuangoumai.objects.create( huiyuan_id=huiyuan_id, yonghu_id=uid, jieshao=huiyuan.jieshao, daoqi_time=daoqi_time ) # 记录会员购买 has_sensitive_change = True modify_record_data['huiyuants'] = days modify_record_data['huiyuan_id'] = huiyuan_id modify_record_data['qitashuoming'] += f' 购买会员 {huiyuan.jieshao} {days}天' # 更新会员购买次数 huiyuan.goumai_cishu += 1 huiyuan.save() else: # 不能为不存在的会员减少天数 return Response({ 'code': 400, 'message': '用户未购买该会员,无法减少天数', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 如果有敏感字段被修改或会员操作,创建修改记录 if has_sensitive_change or 'huiyuan_id' in request.data: # 创建修改记录 Xiugaijilu.objects.create(**modify_record_data) elif modify_record_data.get('qitashuoming', '').strip(): # 只有非敏感字段修改,也记录但qitashuoming不同 Xiugaijilu.objects.create(**modify_record_data) return Response({ 'code': 0, 'message': '修改成功', 'data': None }, status=status.HTTP_200_OK) except Exception as e: return Response({ 'code': 500, 'message': f'修改失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) '''class AdcjxgView(APIView): """ 修改用户信息接口 前端需要传递: zhanghao, uid, user_type, 以及其他修改的字段 支持打手会员添加功能 敏感字段修改会记录到修改记录表 """ permission_classes = [IsAuthenticated] def post(self, request): try: # ==================== 管理员身份验证 ==================== if not request.user.is_authenticated: return Response({ 'code': 401, 'message': '未认证', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) if request.user.user_type != 'admin': return Response({ 'code': 403, 'message': '权限不足', 'data': None }, status=status.HTTP_403_FORBIDDEN) admin_profile = getattr(request.user, 'admin_profile', None) if not admin_profile: return Response({ 'code': 403, 'message': '管理员信息不完整', 'data': None }, status=status.HTTP_403_FORBIDDEN) zhanghao = request.data.get('zhanghao') if not zhanghao or request.user.phone != zhanghao: return Response({ 'code': 401, 'message': '账号验证失败', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) # ==================== 身份验证结束 ==================== # 获取业务参数 uid = request.data.get('uid') user_type = request.data.get('user_type') if not uid or not user_type: return Response({ 'code': 400, 'message': '参数缺失', 'data': None }, status=status.HTTP_400_BAD_REQUEST) try: user_type = int(user_type) if user_type not in [1, 2, 3, 4]: raise ValueError except ValueError: return Response({ 'code': 400, 'message': '用户类型无效', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 查询用户主表 user_main = UserMain.objects.filter(yonghuid=uid).first() if not user_main: return Response({ 'code': 404, 'message': '用户不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 使用事务确保数据一致性 with transaction.atomic(): # 存储修改记录的数据 modify_record_data = { 'yonghuid': uid, 'xiugaiid': zhanghao, 'leixing': user_type, 'qitashuoming': '', 'create_time': timezone.now() } # 标记是否有敏感字段被修改 has_sensitive_change = False if user_type == 1: # 老板 - 不允许修改 return Response({ 'code': 403, 'message': '老板信息不允许修改', 'data': None }, status=status.HTTP_403_FORBIDDEN) elif user_type == 2: # 打手 # 获取或创建打手扩展记录 dashou_profile, created = UserDashou.objects.get_or_create( user=user_main, defaults={'nicheng': user_main.phone or f'打手{uid}'} ) # 记录敏感字段的旧值 old_yue = dashou_profile.yue # 打手可提现余额 old_zonge = dashou_profile.zonge # 打手提现金额 old_jifen = dashou_profile.jifen # 打手积分 old_yajin = dashou_profile.yajin # 打手押金 # 需要更新的字段列表 update_fields = [ 'chenghao', 'dianhua', 'yue', 'zonge', 'jifen', 'yajin', 'yaoqingren', 'jieshao', 'zhuangtai', 'zaixianzhuangtai', 'zhanghaozhuangtai' ] # 记录被修改的字段 modified_fields = [] for field in update_fields: if field in request.data: new_value = request.data.get(field) old_value = getattr(dashou_profile, field) # 检查值是否改变 if str(new_value) != str(old_value): # 处理特殊字段类型 if field in ['yue', 'zonge', 'yajin']: new_value = Decimal(str(new_value)) setattr(dashou_profile, field, new_value) elif field in ['jifen']: new_value = int(new_value) setattr(dashou_profile, field, new_value) elif field in ['zhuangtai', 'zaixianzhuangtai', 'zhanghaozhuangtai']: new_value = int(new_value) setattr(dashou_profile, field, new_value) else: setattr(dashou_profile, field, new_value) modified_fields.append(field) # 记录敏感字段修改 if field == 'yue': has_sensitive_change = True modify_record_data['xiugaitijiao'] = new_value # 使用xiugaitijiao字段记录打手余额修改 modify_record_data['xiugaitijiaoq'] = old_yue elif field == 'zonge': has_sensitive_change = True # zonge字段在修改记录表中可能没有直接对应,可以记录在备注中 modify_record_data['qitashuoming'] += f' 打手总额从{old_zonge}修改为{new_value}' elif field == 'jifen': has_sensitive_change = True modify_record_data['jifen'] = new_value modify_record_data['yjifen'] = old_jifen elif field == 'yajin': has_sensitive_change = True modify_record_data['yajin'] = new_value modify_record_data['yyajin'] = old_yajin # 保存打手信息 dashou_profile.save() # 记录被修改的非敏感字段 non_sensitive_fields = [f for f in modified_fields if f not in ['yue', 'zonge', 'jifen', 'yajin']] if non_sensitive_fields: modify_record_data['qitashuoming'] += f' 修改了字段: {",".join(non_sensitive_fields)}' elif user_type == 3: # 管事 # 获取或创建管事扩展记录 guanshi_profile, created = UserGuanshi.objects.get_or_create( user=user_main, defaults={'yaoqingma': f'GS{uid[-4:]}'} ) # 记录敏感字段的旧值 old_yue = guanshi_profile.yue # 管事可提现余额 update_fields = [ 'yaogingshuliang', 'chongzhifenrun', 'yue', 'zhuangtai', 'dianhua', 'wechat' ] modified_fields = [] for field in update_fields: if field in request.data: new_value = request.data.get(field) old_value = getattr(guanshi_profile, field) if str(new_value) != str(old_value): if field in ['chongzhifenrun', 'yue']: new_value = Decimal(str(new_value)) setattr(guanshi_profile, field, new_value) elif field in ['yaogingshuliang']: new_value = int(new_value) setattr(guanshi_profile, field, new_value) elif field in ['zhuangtai']: new_value = int(new_value) setattr(guanshi_profile, field, new_value) else: setattr(guanshi_profile, field, new_value) modified_fields.append(field) # 记录敏感字段修改 if field == 'yue': has_sensitive_change = True modify_record_data['guanshiyue'] = new_value modify_record_data['guanshiyueq'] = old_yue elif field == 'chongzhifenrun': has_sensitive_change = True modify_record_data['qitashuoming'] += f' 管事分佣从{old_value}修改为{new_value}' guanshi_profile.save() # 记录非敏感字段 non_sensitive_fields = [f for f in modified_fields if f not in ['yue', 'chongzhifenrun']] if non_sensitive_fields: modify_record_data['qitashuoming'] += f' 修改了字段: {",".join(non_sensitive_fields)}' elif user_type == 4: # 商家 # 获取或创建商家扩展记录 shop_profile, created = UserShangjia.objects.get_or_create( user=user_main, defaults={'nicheng': user_main.phone or f'商家{uid}'} ) # 记录敏感字段的旧值 old_yue = shop_profile.yue # 商家余额 update_fields = ['yue', 'zhuangtai', 'dianhua', 'wechat'] modified_fields = [] for field in update_fields: if field in request.data: new_value = request.data.get(field) old_value = getattr(shop_profile, field) if str(new_value) != str(old_value): if field == 'yue': new_value = Decimal(str(new_value)) setattr(shop_profile, field, new_value) elif field == 'zhuangtai': new_value = int(new_value) setattr(shop_profile, field, new_value) else: setattr(shop_profile, field, new_value) modified_fields.append(field) # 记录敏感字段修改 if field == 'yue': has_sensitive_change = True modify_record_data['shangjiayue'] = new_value modify_record_data['shangjiayueq'] = old_yue shop_profile.save() # 记录非敏感字段 non_sensitive_fields = [f for f in modified_fields if f != 'yue'] if non_sensitive_fields: modify_record_data['qitashuoming'] += f' 修改了字段: {",".join(non_sensitive_fields)}' # 处理打手会员添加(只有打手可以添加会员) # 🔥🔥🔥 后端修改:支持正负数会员天数 # 修改会员处理逻辑部分 if user_type == 2 and 'huiyuan_id' in request.data and 'days' in request.data: huiyuan_id = request.data.get('huiyuan_id') days = int(request.data.get('days', 0)) # 🔥 修改:允许正负数,但不能为0 if huiyuan_id and days != 0: # 🔥 修改:检查绝对值范围 if abs(days) < 1 or abs(days) > 10000: return Response({ 'code': 400, 'message': '会员天数必须在1-10000之间(正数增加,负数减少)', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 检查会员是否存在 huiyuan = Huiyuan.objects.filter(huiyuan_id=huiyuan_id).first() if not huiyuan: return Response({ 'code': 404, 'message': '会员不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 检查是否已购买该会员 existing_record = Huiyuangoumai.objects.filter( yonghu_id=uid, huiyuan_id=huiyuan_id ).first() if existing_record: # 🔥🔥🔥 重要修改:支持减少天数 # 计算新的到期时间 new_daoqi_time = existing_record.daoqi_time + timedelta(days=days) # 🔥 安全验证:减少天数后不能小于当前时间前90天(防止过度减少) # 这个限制可以根据业务调整 min_allowed_time = timezone.now() - timedelta(days=90) if new_daoqi_time < min_allowed_time: return Response({ 'code': 400, 'message': f'减少天数过多,调整后到期时间不能早于 {(min_allowed_time + timedelta(days=90)).strftime("%Y-%m-%d")}', 'data': None }, status=status.HTTP_400_BAD_REQUEST) existing_record.daoqi_time = new_daoqi_time existing_record.save() # 记录会员操作 has_sensitive_change = True modify_record_data['huiyuants'] = days modify_record_data['huiyuan_id'] = huiyuan_id if days > 0: modify_record_data['qitashuoming'] += f' 续费会员 {huiyuan.jieshao} {days}天' # 只有增加天数时才增加购买次数 huiyuan.goumai_cishu += 1 huiyuan.save() else: modify_record_data[ 'qitashuoming'] += f' 减少会员 {huiyuan.jieshao} {abs(days)}天' # 减少天数时不增加购买次数 else: # 🔥 修改:新购买时只能增加天数 if days > 0: daoqi_time = timezone.now() + timedelta(days=days) new_record = Huiyuangoumai.objects.create( huiyuan_id=huiyuan_id, yonghu_id=uid, jieshao=huiyuan.jieshao, daoqi_time=daoqi_time ) # 记录会员购买 has_sensitive_change = True modify_record_data['huiyuants'] = days modify_record_data['huiyuan_id'] = huiyuan_id modify_record_data['qitashuoming'] += f' 购买会员 {huiyuan.jieshao} {days}天' # 更新会员购买次数 huiyuan.goumai_cishu += 1 huiyuan.save() else: # 🔥 不能为不存在的会员减少天数 return Response({ 'code': 400, 'message': '用户未购买该会员,无法减少天数', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 如果有敏感字段被修改或会员操作,创建修改记录 if has_sensitive_change or 'huiyuan_id' in request.data: # 创建修改记录 Xiugaijilu.objects.create(**modify_record_data) elif modify_record_data.get('qitashuoming', '').strip(): # 只有非敏感字段修改,也记录但qitashuoming不同 Xiugaijilu.objects.create(**modify_record_data) return Response({ 'code': 0, 'message': '修改成功', 'data': None }, status=status.HTTP_200_OK) except Exception as e: return Response({ 'code': 500, 'message': f'修改失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)''' # yonghu/views.py from django.utils import timezone from django.db.models import Q from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework import status from .models import UserMain, AdminProfile, Tixianjilu # 管理员认证装饰器 def admin_required(view_func): """管理员权限验证装饰器""" def wrapped_view(self, request, *args, **kwargs): try: # 1. 验证JWT Token if not request.user.is_authenticated: return Response({ 'code': 401, 'message': '未认证', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) # 2. 验证用户类型为管理员 if request.user.user_type != 'admin': return Response({ 'code': 403, 'message': '权限不足', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 3. 验证管理员扩展表存在 admin_profile = getattr(request.user, 'admin_profile', None) if not admin_profile: return Response({ 'code': 403, 'message': '管理员信息不完整', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 4. 验证管理员账号(phone)与前端传递的zhanghao一致 zhanghao = request.data.get('zhanghao') if not zhanghao or request.user.phone != zhanghao: return Response({ 'code': 401, 'message': '账号验证失败', 'data': None }, status=status.HTTP_401_UNAUTHORIZED) return view_func(self, request, *args, **kwargs) except Exception as e: return Response({ 'code': 500, 'message': f'权限验证异常: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return wrapped_view from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.utils import timezone from django.db.models import Q from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework import status from yonghu.models import Tixianjilu class AddtxshView(APIView): """ 获取提现审核列表接口 与用户管理页面保持一致的分页逻辑 支持按用户ID搜索,排序改为按申请时间升序(最早申请排最前) """ permission_classes = [IsAuthenticated] @admin_required def post(self, request): try: # 获取参数 zhanghao = request.data.get('zhanghao') page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 5)) zhuangtai = int(request.data.get('zhuangtai', 1)) leixing = int(request.data.get('leixing', 1)) fangshi = request.data.get('fangshi') # 🔴【新增】获取搜索参数(用户ID) search_uid = request.data.get('search_uid', '').strip() if page < 1: page = 1 # 计算偏移量(与用户管理页面完全一致) offset = (page - 1) * page_size # 构建查询条件 query = Q() # 状态筛选 if zhuangtai == 1: query &= Q(zhuangtai=1) elif zhuangtai == 2: query &= Q(zhuangtai__in=[2, 3]) # 类型筛选 query &= Q(leixing=leixing) # 收款方式筛选(可选) if fangshi: query &= Q(fangshi=int(fangshi)) # 🔴【新增】如果搜索了用户ID,添加条件 if search_uid: query &= Q(yonghuid=search_uid) # 🔴【修改】排序改为按申请时间升序(最早申请排最前) queryset = Tixianjilu.objects.filter(query).order_by('create_time') # 🔴【修改】分页逻辑:如果是搜索模式,不分页,返回所有匹配记录 if search_uid: records = list(queryset) total_count = len(records) has_more = False # 搜索模式没有更多数据 else: total_count = queryset.count() records = list(queryset[offset:offset + page_size]) current_count = len(records) has_more = (offset + current_count) < total_count # 获取状态统计(统计与搜索无关,依然按筛选条件统计) status_counts = { 'awaiting': Tixianjilu.objects.filter( zhuangtai=1, leixing=leixing ).count(), 'processed': Tixianjilu.objects.filter( zhuangtai__in=[2, 3], leixing=leixing ).count() } # 构建响应数据 withdrawal_list = [] for record in records: withdrawal_list.append({ 'id': record.id, 'yonghuid': record.yonghuid, 'avatar': record.avatar, 'phone': record.phone, 'nicheng': record.nicheng, 'leixing': record.leixing, 'zhifu': record.zhifu, 'skzhanghao': record.skzhanghao, 'jine': str(record.jine) if record.jine else '0.00', 'zhuangtai': record.zhuangtai, 'fangshi': record.fangshi, 'shenheid': record.shenheid, 'bhliyou': record.bhliyou, 'create_time': record.create_time, 'update_time': record.update_time }) return Response({ 'code': 0, 'message': '获取成功', 'data': { 'list': withdrawal_list, 'total_count': total_count, 'status_counts': status_counts, 'has_more': has_more } }, status=status.HTTP_200_OK) except Exception as e: return Response({ 'code': 500, 'message': f'获取提现列表失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) '''class AddtxshView(APIView): """ 获取提现审核列表接口 与用户管理页面保持一致的分页逻辑 """ permission_classes = [IsAuthenticated] @admin_required def post(self, request): try: # 获取参数 zhanghao = request.data.get('zhanghao') page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 5)) zhuangtai = int(request.data.get('zhuangtai', 1)) leixing = int(request.data.get('leixing', 1)) fangshi = request.data.get('fangshi') if page < 1: page = 1 # 计算偏移量(与用户管理页面完全一致) offset = (page - 1) * page_size # 构建查询条件 query = Q() # 状态筛选 if zhuangtai == 1: query &= Q(zhuangtai=1) elif zhuangtai == 2: query &= Q(zhuangtai__in=[2, 3]) # 类型筛选 query &= Q(leixing=leixing) # 收款方式筛选(可选) if fangshi: query &= Q(fangshi=int(fangshi)) # 获取查询集(与用户管理页面一致) queryset = Tixianjilu.objects.filter(query).order_by('-update_time', '-create_time') # 获取总数 total_count = queryset.count() # 分页数据(与用户管理页面完全一致) records = list(queryset[offset:offset + page_size]) # ✅ 计算是否有更多数据(与用户管理页面完全一致) current_count = len(records) has_more = (offset + current_count) < total_count # 获取状态统计 status_counts = { 'awaiting': Tixianjilu.objects.filter( zhuangtai=1, leixing=leixing ).count(), 'processed': Tixianjilu.objects.filter( zhuangtai__in=[2, 3], leixing=leixing ).count() } # 构建响应数据 withdrawal_list = [] for record in records: withdrawal_list.append({ 'id': record.id, 'yonghuid': record.yonghuid, 'avatar': record.avatar, 'phone': record.phone, 'nicheng': record.nicheng, 'leixing': record.leixing, 'zhifu': record.zhifu, 'skzhanghao': record.skzhanghao, 'jine': str(record.jine) if record.jine else '0.00', 'zhuangtai': record.zhuangtai, 'fangshi': record.fangshi, 'shenheid': record.shenheid, 'bhliyou': record.bhliyou, 'create_time': record.create_time, 'update_time': record.update_time }) return Response({ 'code': 0, 'message': '获取成功', 'data': { 'list': withdrawal_list, 'total_count': total_count, 'status_counts': status_counts, 'has_more': has_more } }, status=status.HTTP_200_OK) except Exception as e: return Response({ 'code': 500, 'message': f'获取提现列表失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)''' from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from django.db import transaction, IntegrityError from django.utils import timezone from peizhi.models import Szjilu # 添加收支记录表导入 # 假设你的提现记录模型导入 # from your_app.models import Tixianjilu class AdtixianqkView(APIView): """ 处理提现申请接口(已更新:添加收支记录表更新) """ permission_classes = [IsAuthenticated] @admin_required # 假设你有这个装饰器 def post(self, request): try: zhanghao = request.data.get('zhanghao') tixian_id = request.data.get('tixian_id') result = int(request.data.get('result', 0)) reason = request.data.get('reason', '') if not tixian_id: return Response({ 'code': 400, 'message': '提现记录ID不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if result not in [2, 3]: return Response({ 'code': 400, 'message': '操作结果无效', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 如果是拒绝,需要理由 if result == 3 and not reason: return Response({ 'code': 400, 'message': '拒绝提现需要提供理由', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 使用事务确保数据一致性 with transaction.atomic(): try: withdrawal = Tixianjilu.objects.select_for_update().get(id=tixian_id) except Tixianjilu.DoesNotExist: return Response({ 'code': 404, 'message': '提现记录不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) if withdrawal.zhuangtai != 1: return Response({ 'code': 400, 'message': '该提现申请已被处理', 'data': None }, status=status.HTTP_400_BAD_REQUEST) withdrawal.zhuangtai = result withdrawal.shenheid = request.user.yonghuid if result == 3: # 拒绝 user_main = UserMain.objects.select_for_update().get(yonghuid=withdrawal.yonghuid) from yonghu.tixian_shenhe_services import refund_manual_tixianjilu refund_manual_tixianjilu(user_main, withdrawal) withdrawal.bhliyou = reason withdrawal.update_time = timezone.now() withdrawal.save() # 🟢 新增:如果同意提现(result=2),更新收支记录表 if result == 2: try: tixian_jine = withdrawal.jine szjilu_record = Szjilu.objects.get(id=1) from dingdan.utils import update_daily_payout from yonghu.tixian_shenhe_services import record_manual_withdraw_success_stats update_daily_payout(tixian_jine) user_main = UserMain.objects.get(yonghuid=withdrawal.yonghuid) record_manual_withdraw_success_stats(user_main, withdrawal) szjilu_record.zongsy -= tixian_jine szjilu_record.zongzc += tixian_jine # 总支出加上提现金额 szjilu_record.jrzc += tixian_jine # 今日支出加上提现金额 szjilu_record.update_time = timezone.now() szjilu_record.save() except Szjilu.DoesNotExist: # 如果收支记录表不存在id=1的记录,创建一个 szjilu_record = Szjilu.objects.create( id=1, zongsy=-tixian_jine if result == 2 else 0, zongzc=tixian_jine if result == 2 else 0, jrzc=tixian_jine if result == 2 else 0 ) except Exception as e: # 如果更新收支记录失败,抛出异常,事务会回滚 raise Exception(f"更新收支记录失败: {str(e)}") return Response({ 'code': 0, 'message': '处理成功', 'data': { 'id': withdrawal.id, 'zhuangtai': withdrawal.zhuangtai, 'shenheid': withdrawal.shenheid, 'update_time': withdrawal.update_time } }, status=status.HTTP_200_OK) except Exception as e: return Response({ 'code': 500, 'message': f'处理提现失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) '''class AdtixianqkView(APIView): """ 处理提现申请接口(保持不变) """ permission_classes = [IsAuthenticated] @admin_required def post(self, request): try: zhanghao = request.data.get('zhanghao') tixian_id = request.data.get('tixian_id') result = int(request.data.get('result', 0)) reason = request.data.get('reason', '') if not tixian_id: return Response({ 'code': 400, 'message': '提现记录ID不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if result not in [2, 3]: return Response({ 'code': 400, 'message': '操作结果无效', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 如果是拒绝,需要理由 if result == 3 and not reason: return Response({ 'code': 400, 'message': '拒绝提现需要提供理由', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 获取提现记录 try: withdrawal = Tixianjilu.objects.get(id=tixian_id) except Tixianjilu.DoesNotExist: return Response({ 'code': 404, 'message': '提现记录不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 检查状态,只有待审核的可以处理 if withdrawal.zhuangtai != 1: return Response({ 'code': 400, 'message': '该提现申请已被处理', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 更新提现记录 withdrawal.zhuangtai = result withdrawal.shenheid = request.user.yonghuid if result == 3: # 拒绝 withdrawal.bhliyou = reason withdrawal.update_time = timezone.now() withdrawal.save() return Response({ 'code': 0, 'message': '处理成功', 'data': { 'id': withdrawal.id, 'zhuangtai': withdrawal.zhuangtai, 'shenheid': withdrawal.shenheid, 'update_time': withdrawal.update_time } }, status=status.HTTP_200_OK) except Exception as e: return Response({ 'code': 500, 'message': f'处理提现失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR)''' # views.py (yonghu应用) from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status from .models import UserMain, UserDashou import logging logger = logging.getLogger(__name__) class DashouZiliaoHuoquView(APIView): """ 打手资料获取接口 URL: /yonghu/dszjxxhq 方法: POST 认证: JWT Token 返回: 打手扩展表信息 """ permission_classes = [IsAuthenticated] def post(self, request): try: # 使用select_related高效查询,避免N+1问题 dashou = UserDashou.objects.select_related('user').get(user=request.user) # 构建返回数据 data = { 'nicheng': dashou.nicheng if dashou.nicheng else '', 'jieshao': dashou.jieshao if dashou.jieshao else '', 'dianhua': dashou.dianhua if dashou.dianhua else '', 'wechat': dashou.wechat if dashou.wechat else '', 'uid': request.user.yonghuid, # 从主表获取UID 'avatar': request.user.avatar if request.user.avatar else '', # 头像相对URL } return Response({ 'code': 200, 'msg': 'success', 'data': data }, status=status.HTTP_200_OK) except UserDashou.DoesNotExist: logger.warning(f'打手扩展表不存在: user_id={request.user.id}') return Response({ 'code': 404, 'msg': '打手信息不存在' }, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f'获取打手资料异常: {str(e)}', exc_info=True) return Response({ 'code': 500, 'msg': '服务器内部错误' }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # views.py (yonghu应用) from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status from .models import UserDashou import re import logging logger = logging.getLogger(__name__) class DashouGengxinView(APIView): """ 打手信息更新接口 URL: /yonghu/dsgxxx 方法: POST 认证: JWT Token 参数: nicheng(昵称), dianhua(电话), wechat(微信), jieshao(介绍) """ permission_classes = [IsAuthenticated] def post(self, request): try: # 获取请求数据 nicheng = request.data.get('nicheng', '').strip() dianhua = request.data.get('dianhua', '').strip() wechat = request.data.get('wechat', '').strip() jieshao = request.data.get('jieshao', '').strip() # 数据验证 validation_errors = self.validate_data(nicheng, dianhua, wechat, jieshao) if validation_errors: return Response({ 'code': 400, 'msg': validation_errors }, status=status.HTTP_400_BAD_REQUEST) # 获取打手扩展表 try: dashou = UserDashou.objects.get(user=request.user) except UserDashou.DoesNotExist: return Response({ 'code': 404, 'msg': '打手信息不存在' }, status=status.HTTP_404_NOT_FOUND) # 更新数据 update_fields = [] if nicheng is not None: dashou.nicheng = nicheng if nicheng != '' else None update_fields.append('nicheng') if dianhua is not None: dashou.dianhua = dianhua if dianhua != '' else None update_fields.append('dianhua') if wechat is not None: dashou.wechat = wechat if wechat != '' else None update_fields.append('wechat') if jieshao is not None: dashou.jieshao = jieshao if jieshao != '' else None update_fields.append('jieshao') # 保存更新(只更新修改的字段) if update_fields: dashou.save(update_fields=update_fields) return Response({ 'code': 200, 'msg': '更新成功', 'data': { 'nicheng': dashou.nicheng or '', 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', 'jieshao': dashou.jieshao or '' } }, status=status.HTTP_200_OK) except Exception as e: logger.error(f'更新打手信息异常: {str(e)}', exc_info=True) return Response({ 'code': 500, 'msg': '服务器内部错误' }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def validate_data(self, nicheng, dianhua, wechat, jieshao): """数据验证方法""" errors = [] # 1. 昵称验证(必填) if not nicheng: errors.append('昵称不能为空') elif len(nicheng) > 20: errors.append('昵称不能超过20个字符') # 2. 手机号验证(非必填,但填写时必须正确) if dianhua: # 手机号正则:1开头,11位数字 phone_pattern = r'^1[3-9]\d{9}$' if not re.match(phone_pattern, dianhua): errors.append('手机号格式不正确') # 3. 微信号验证(非必填) if wechat and len(wechat) > 30: errors.append('微信号不能超过30个字符') # 4. 个人介绍验证 if jieshao and len(jieshao) > 200: errors.append('个人介绍不能超过200个字符') return '; '.join(errors) if errors else None class ShangjiaZhuceView(APIView): """ 商家注册接口 (通过管理员邀请码) 访问路径: /api/yonghu/shangjiahuce/ 请求方法: POST 权限要求: JWT Token认证通过的用户 请求体: { "inviteCode": "管理员邀请码" } 返回字段说明: - code: 状态码(200成功,其他失败) - message: 提示信息 - data: 商家信息数据,包含以下字段(与前端商家页面完全一致): - nicheng: 商家昵称 - sjyue: 商家余额(字符串,保留两位小数) - fabu: 发布订单总数 - chengjiao: 成交订单总数 - tuikuan: 退款订单总数 - jinridingdan: 今日订单 - jinriliushui: 今日流水(字符串) - jinyuedingdan: 今月订单 - jinyueliushui: 今月流水(字符串) - zhuangtai: 商家状态(1正常) - dianhua: 商家电话 - wechat: 商家微信 """ permission_classes = [IsAuthenticated] def post(self, request): # 1. 获取并验证邀请码参数 yaoqingma = request.data.get('inviteCode') if not yaoqingma: return Response({ 'code': 400, 'message': '邀请码不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) yaoqingma = yaoqingma.strip() if len(yaoqingma) > 100: return Response({ 'code': 400, 'message': '邀请码长度超过限制', 'data': None }, status=status.HTTP_400_BAD_REQUEST) current_user = request.user # 2. 检查用户是否已是商家 try: shangjia_profile = current_user.shop_profile # 用户已是商家,直接返回现有信息 return self.fanhuiXianyouShangjiaXinxi(shangjia_profile, current_user) except UserShangjia.DoesNotExist: # 用户不是商家,继续注册流程 pass # 3. 根据邀请码查找对应的管理员 try: # 从管理员表查找邀请码 admin_profile = AdminProfile.objects.select_related('user').get(yaoqingma=yaoqingma) except AdminProfile.DoesNotExist: return Response({ 'code': 404, 'message': '管理员邀请码无效或不存在', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 4. 核心:在数据库事务中创建商家身份 try: with transaction.atomic(): # 4.1 查询老板扩展表获取老板昵称 boss_nickname = '普通商家' try: boss_profile = current_user.boss_profile if boss_profile.nickname and boss_profile.nickname.strip(): boss_nickname = boss_profile.nickname.strip() except UserBoss.DoesNotExist: # 老板扩展表不存在,使用默认值 pass # 4.2 创建商家扩展表 # 使用老板昵称作为商家昵称的基础 shangjia_nicheng = boss_nickname # 检查昵称是否已存在(安全考虑) if UserShangjia.objects.filter(nicheng=shangjia_nicheng).exists(): # 如果存在,添加随机数确保唯一 import random shangjia_nicheng = f"{boss_nickname}{random.randint(1000, 9999)}" # 4.3 创建商家记录 shop_profile = UserShangjia.objects.create( user=current_user, nicheng=shangjia_nicheng, zhuangtai=1, # 正常状态 yue=0.00, # 商家余额 fabu=0, # 发布订单总数 tuikuan=0, # 退款订单总数 chengjiao=0, # 成交订单总数 jinridingdan=0, # 今日订单 jinriliushui=0.00, # 今日流水 jinyuedingdan=0, # 今月订单 jinyueliushui=0.00, # 今月流水 dianhua='', # 电话 wechat='', # 微信 ) except Exception as e: # 事务会自动回滚 import traceback error_detail = traceback.format_exc() #print(f"商家注册错误详情: {error_detail}") return Response({ 'code': 500, 'message': f'商家注册过程中发生系统错误: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 5. 注册成功,返回商家信息 fanhui_data = self.zhuangbeiFanhuiShuju(shop_profile, current_user) return Response({ 'code': 200, 'message': '商家注册成功!', 'data': fanhui_data }) def fanhuiXianyouShangjiaXinxi(self, shangjia_profile, current_user): """处理已是商家的用户,返回其现有信息""" fanhui_data = self.zhuangbeiFanhuiShuju(shangjia_profile, current_user) return Response({ 'code': 200, 'message': '您已是商家,返回当前信息。', 'data': fanhui_data }) def zhuangbeiFanhuiShuju(self, shangjia_profile, current_user): """ 准备返回给前端的商家数据 字段名必须与前端商家页面完全一致 """ data = { # 🟢 商家基本信息 'nicheng': shangjia_profile.nicheng, # 商家昵称 'sjyue': format(shangjia_profile.yue, '.2f'), # 商家余额,保留两位小数 'fabu': shangjia_profile.fabu, # 发布订单总数 'chengjiao': shangjia_profile.chengjiao, # 成交订单总数 'tuikuan': shangjia_profile.tuikuan, # 退款订单总数 'jinridingdan': shangjia_profile.jinridingdan, # 今日订单 'jinriliushui': format(shangjia_profile.jinriliushui, '.2f'), # 今日流水 'jinyuedingdan': shangjia_profile.jinyuedingdan, # 今月订单 'jinyueliushui': format(shangjia_profile.jinyueliushui, '.2f'), # 今月流水 'zhuangtai': shangjia_profile.zhuangtai, # 商家状态 'dianhua': shangjia_profile.dianhua or '', # 电话 'wechat': shangjia_profile.wechat or '', # 微信 # 🟢 可能需要的主表信息 'uid': current_user.yonghuid, # 用户ID } return data # yonghu/ranking_views.py """ 排行榜查询接口 - 管理员专用 安全认证:JWT Token + 管理员权限验证 """ from django.db.models import Q from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from datetime import datetime from .models import RankingRecord import logging logger = logging.getLogger(__name__) # yonghu/ranking_views.py - 修改PaihangbangGuanliQueryView的post方法参数接收 class PaihangbangGuanliQueryView(APIView): """ 排行榜查询接口 - 管理员专用 安全要求: 1. JWT Token认证 2. 管理员权限验证 3. 参数严格验证 4. 返回统一格式 """ # JWT Token认证 permission_classes = [IsAuthenticated] @admin_required def post(self, request): """ POST请求查询排行榜数据 参数: - zhanghao: 管理员账号 - shenfen: 用户身份(必须,2=打手,3=管事,4=商家) - zhouqi: 周期类型(必须,1=日榜,2=月榜) - date: 查询日期(必须,格式:YYYY-MM-DD) 返回格式: { "code": 0, # 0=成功,非0=失败 "message": "成功", "data": { "list": [...], # 排行榜数据列表 "query_info": {...} # 查询信息 } } """ try: # 1. 获取请求数据 data = request.data zhanghao = data.get('zhanghao') shenfen = data.get('shenfen') zhouqi = data.get('zhouqi') date_str = data.get('date') # 2. 参数验证 if not zhanghao: return Response({ 'code': 400, 'message': '管理员账号不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if not shenfen: return Response({ 'code': 400, 'message': '用户身份不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if not zhouqi: return Response({ 'code': 400, 'message': '周期类型不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if not date_str: return Response({ 'code': 400, 'message': '查询日期不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 3. 参数类型和范围验证 try: zhouqi_int = int(zhouqi) shenfen_int = int(shenfen) date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() except ValueError as e: return Response({ 'code': 400, 'message': f'参数格式错误: {str(e)}', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 验证周期类型范围 if zhouqi_int not in [1, 2]: return Response({ 'code': 400, 'message': '周期类型必须为1(日榜)或2(月榜)', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 验证用户身份范围 if shenfen_int not in [2, 3, 4]: return Response({ 'code': 400, 'message': '用户身份必须为2(打手)、3(管事)或4(商家)', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 4. 构建查询条件 query = Q( zhouqi=zhouqi_int, tongji_date=date_obj, shenfen=shenfen_int ) # 5. 执行查询(按排名升序) records = RankingRecord.objects.filter(query).order_by('paiming') # 6. 格式化返回数据 data_list = [] for record in records: # 根据用户身份确定指标值单位 zhizhi_danwei = self._get_zhizhi_danwei(shenfen_int) data_list.append({ 'paiming': record.paiming, 'yonghuid': record.yonghuid, 'nicheng': record.nicheng, 'avatar': record.avatar or '', 'zhizhi': float(record.zhizhi), 'zhizhi_danwei': zhizhi_danwei, }) # 7. 返回成功响应 response_data = { 'code': 0, 'message': '查询成功', 'data': { 'list': data_list, 'total_count': len(data_list), 'query_info': { 'zhouqi': zhouqi_int, 'zhouqi_text': '日榜' if zhouqi_int == 1 else '月榜', 'shenfen': shenfen_int, 'shenfen_text': self._get_shenfen_text(shenfen_int), 'date': date_str, } } } return Response(response_data, status=status.HTTP_200_OK) except Exception as e: logger.error(f"管理员查询排行榜失败: {str(e)}", exc_info=True) return Response({ 'code': 500, 'message': f'查询排行榜失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def _get_shenfen_text(self, shenfen): """获取身份文本""" shenfen_map = {2: '打手', 3: '管事', 4: '商家'} return shenfen_map.get(shenfen, '未知') def _get_zhizhi_danwei(self, shenfen): """获取指标值单位""" danwei_map = {2: '元', 3: '人', 4: '元'} return danwei_map.get(shenfen, '') # yonghu/ranking_views.py - 添加以下代码 class PaihangbangRiqiliebiaoView(APIView): """ 获取排行榜可查询日期列表 - 管理员专用 返回当前排行榜表中存在的日期(日榜和月榜分开) """ # JWT Token认证 permission_classes = [IsAuthenticated] @admin_required def post(self, request): """ POST请求获取可查询日期列表 参数: - zhanghao: 管理员账号(用于验证) 返回格式: { "code": 0, "message": "成功", "data": { "ribang_dates": ["2024-01-15", "2024-01-14", ...], # 日榜日期列表 "yuebang_dates": ["2024-01-01", "2023-12-01", ...], # 月榜日期列表 } } """ try: # 1. 获取请求数据 data = request.data zhanghao = data.get('zhanghao') if not zhanghao: return Response({ 'code': 400, 'message': '管理员账号不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 2. 查询日榜日期(最近3个月内的) three_months_ago = timezone.now().date() - timedelta(days=90) ribang_dates = RankingRecord.objects.filter( zhouqi=1, # 日榜 tongji_date__gte=three_months_ago # 只查询最近3个月 ).values_list('tongji_date', flat=True).distinct().order_by('-tongji_date') # 3. 查询月榜日期(最近12个月内的) twelve_months_ago = timezone.now().date() - timedelta(days=365) yuebang_dates = RankingRecord.objects.filter( zhouqi=2, # 月榜 tongji_date__gte=twelve_months_ago # 只查询最近12个月 ).values_list('tongji_date', flat=True).distinct().order_by('-tongji_date') # 4. 格式化日期 ribang_date_list = [date.strftime('%Y-%m-%d') for date in ribang_dates] yuebang_date_list = [date.strftime('%Y-%m-%d') for date in yuebang_dates] # 5. 返回成功响应 response_data = { 'code': 0, 'message': '获取成功', 'data': { 'ribang_dates': ribang_date_list, 'yuebang_dates': yuebang_date_list, 'ribang_count': len(ribang_date_list), 'yuebang_count': len(yuebang_date_list), } } return Response(response_data, status=status.HTTP_200_OK) except Exception as e: logger.error(f"获取排行榜日期列表失败: {str(e)}", exc_info=True) return Response({ 'code': 500, 'message': f'获取日期列表失败: {str(e)}', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class WechatLoginAndDashouRegisterView(APIView): """ 微信登录并注册打手一体化接口 访问路径: /api/yonghu/wdlyhdl 请求方法: POST 权限要求: 无需登录 请求体: { "code": "微信登录code", "inviteCode": "邀请码" } 功能: 用户扫码进入但未登录时,一次性完成微信登录和打手注册 """ throttle_classes = [AnonRateThrottle] permission_classes = [AllowAny] def post(self, request): """ 处理微信登录并注册打手请求 """ try: from utils.dashou_register_service import ( create_dashou_for_user, ensure_guanshi_profile_for_user, resolve_guanshi_for_dashou_register, ) # 1. 获取并验证参数 code = request.data.get('code', '').strip() yaoqingma = request.data.get('inviteCode', '').strip() if not code: return Response({ 'code': 1, 'msg': '微信授权码不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) guanshi_profile, invite_err = resolve_guanshi_for_dashou_register(yaoqingma) if invite_err: return Response({ 'code': 2, 'msg': invite_err, 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 2. 获取微信openid wechat_data = self.get_wechat_openid(code) if not wechat_data or 'openid' not in wechat_data: error_msg = wechat_data.get('errmsg', '微信授权失败') return Response({ 'code': 4, 'msg': f'微信登录失败: {error_msg}', 'data': None }, status=status.HTTP_400_BAD_REQUEST) openid = wechat_data['openid'] # 3. 获取客户端真实IP kehuduan_ip = self.huoquKehuduanIP(request) # 4. 开始数据库事务(确保登录和注册的原子性) with transaction.atomic(): user_main, created = get_or_create_wechat_user(openid, user_type='normal') update_wechat_user_login_info(user_main, kehuduan_ip) if created: # 新用户:创建老板扩展表 UserBoss.objects.create(user=user_main, nickname='微信用户') # 验证管事状态 if guanshi_profile.zhuangtai != 1: return self.fanhuiZhihouDengluXinxi(user_main, guanshi_jinyong=True) # 4.3 检查用户是否已是打手 dashou_exists = UserDashou.objects.filter(user=user_main).exists() if dashou_exists: dashou_profile = UserDashou.objects.get(user=user_main) return self.fanhuiDengluZhuceshuju(user_main, dashou_profile) # 4.4 创建打手;扫码流程顺带开通管事(已存在则跳过) dashou_profile = create_dashou_for_user(user_main, guanshi_profile) ensure_guanshi_profile_for_user(user_main) # 5. 生成JWT token refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) # 6. 准备返回数据 response_data = self.zhuangbeiFanhuiShuju(user_main, dashou_profile, token) return Response({ 'code': 0, 'msg': '登录并注册成功!已开通打手、管事等身份。', 'data': response_data }) except Exception as e: # 记录错误日志 import logging logger = logging.getLogger(__name__) error_openid = locals().get('openid', 'N/A') logger.error( f"微信登录注册一体化接口异常 - openid: {error_openid}, " f"错误类型: {type(e).__name__}, 错误详情: {str(e)}", exc_info=True ) # 返回友好错误信息 return Response({ 'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def get_wechat_openid(self, code): """ 调用微信接口获取openid """ try: appid = wx_cfg.WEIXIN_APPID secret = wx_cfg.WEIXIN_SECRET if not appid or not secret: raise ValueError('微信配置未设置') url = 'https://api.weixin.qq.com/sns/jscode2session' params = { 'appid': appid, 'secret': secret, 'js_code': code, 'grant_type': 'authorization_code' } response = requests.get(url, params=params, timeout=10) result = response.json() if 'openid' in result: return result else: errcode = result.get('errcode', 'unknown') errmsg = result.get('errmsg', '未知错误') return {'errmsg': f'[{errcode}]{errmsg}'} except requests.exceptions.Timeout: return {'errmsg': '请求微信接口超时'} except Exception as e: return {'errmsg': f'请求微信接口异常: {str(e)}'} def shengchengYonghuID(self): """ 生成7位数字用户ID """ for _ in range(10): timestamp_part = str(int(time.time()))[-5:].zfill(5) random_part = str(random.randint(0, 99)).zfill(2) user_id = timestamp_part + random_part if len(user_id) == 7 and user_id.isdigit(): if not UserMain.objects.filter(yonghuid=user_id).exists(): return user_id raise Exception('生成用户ID失败') def huoquKehuduanIP(self, request): """ 获取客户端真实IP地址 """ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() if ip: return ip ip = request.META.get('REMOTE_ADDR', '') return ip if ip else '0.0.0.0' def huoquQunPeizhi(self): """ 获取群配置信息 """ try: qun_configs = Qunpeizhi.objects.filter(id__in=[1, 2]) result = { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } for config in qun_configs: if config.id == 1: result['dashouqun'] = config.neirong or '' result['dashouqunid'] = config.qunid or '' elif config.id == 2: result['guanshiqun'] = config.neirong or '' result['guanshiqunid'] = config.qunid or '' return result except Exception: return { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } def fanhuiZhihouDengluXinxi(self, user_main, yaoqingma_wuxiao=False, guanshi_jinyong=False): """ 返回只登录成功的信息(当邀请码无效或管事禁用时) """ # 获取老板昵称 boss_nickname = '微信用户' try: boss_profile = UserBoss.objects.get(user=user_main) if boss_profile.nickname: boss_nickname = boss_profile.nickname except UserBoss.DoesNotExist: pass # 检查各身份状态 dashou_status = 1 if UserDashou.objects.filter(user=user_main).exists() else 0 shangjia_status = 1 if UserShangjia.objects.filter(user=user_main).exists() else 0 guanshi_status = 1 if UserGuanshi.objects.filter(user=user_main).exists() else 0 # 生成token refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) # 获取群配置 group_info = self.huoquQunPeizhi() # 构建返回数据 response_data = { 'token': token, 'nicheng': boss_nickname, 'uid': user_main.yonghuid, 'touxiang': user_main.avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, # 🟢 注意:这里没有返回dingdantiaoshu(订单数量),与前端需求一致 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''), 'guanshiqunid': group_info.get('guanshiqunid', '') } # 根据情况设置返回消息 if yaoqingma_wuxiao: msg = '登录成功,但邀请码无效' elif guanshi_jinyong: msg = '登录成功,但邀请码对应的管事账号已被禁用' else: msg = '登录成功' return Response({ 'code': 5, 'msg': msg, 'data': response_data }) def fanhuiDengluZhuceshuju(self, user_main, dashou_profile): """ 返回登录并注册的数据(用户已是打手的情况) """ # 获取老板昵称 boss_nickname = '微信用户' try: boss_profile = UserBoss.objects.get(user=user_main) if boss_profile.nickname: boss_nickname = boss_profile.nickname except UserBoss.DoesNotExist: pass # 检查各身份状态(这里用户已是打手,所以都是1) dashou_status = 1 shangjia_status = 1 if UserShangjia.objects.filter(user=user_main).exists() else 1 # 确保是1 guanshi_status = 1 if UserGuanshi.objects.filter(user=user_main).exists() else 1 # 确保是1 # 生成token refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) # 获取群配置 group_info = self.huoquQunPeizhi() # 准备打手信息 dashou_data = self.zhuangbeiDashouFanhuiShuju(dashou_profile) # 合并所有数据 response_data = { 'token': token, 'nicheng': boss_nickname, 'uid': user_main.yonghuid, 'touxiang': user_main.avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''), 'guanshiqunid': group_info.get('guanshiqunid', ''), # 合并打手信息 **dashou_data } return Response({ 'code': 0, 'msg': '您已是打手,登录成功', 'data': response_data }) def zhuangbeiFanhuiShuju(self, user_main, dashou_profile, token): """ 准备完整的返回数据(登录+注册成功的情况) """ # 获取老板昵称 boss_nickname = '微信用户' try: boss_profile = UserBoss.objects.get(user=user_main) if boss_profile.nickname: boss_nickname = boss_profile.nickname except UserBoss.DoesNotExist: pass # 身份状态(刚注册成功,应该都是1) dashou_status = 1 shangjia_status = 1 guanshi_status = 1 # 获取群配置 group_info = self.huoquQunPeizhi() # 准备打手信息 dashou_data = self.zhuangbeiDashouFanhuiShuju(dashou_profile) # 合并所有数据 response_data = { 'token': token, 'nicheng': boss_nickname, 'uid': user_main.yonghuid, 'touxiang': user_main.avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''), 'guanshiqunid': group_info.get('guanshiqunid', ''), # 合并打手信息 **dashou_data } return response_data def zhuangbeiDashouFanhuiShuju(self, dashou_profile): """ 准备打手返回数据(字段名严格匹配前端) """ # 构建基础信息 data = { 'dashounicheng': dashou_profile.nicheng, 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'yongjin': str(dashou_profile.yue), # Decimal转字符串 'zonge': str(dashou_profile.zonge), 'yajin': str(dashou_profile.yajin), 'chenghao': dashou_profile.chenghao, 'jinfen': dashou_profile.jifen, 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, } # 查询并处理会员列表 (clumber) # 使用当前用户的yonghuid进行查询 huiyuan_goumai_list = [] goumai_records = Huiyuangoumai.objects.filter(yonghu_id=dashou_profile.user.yonghuid) for record in goumai_records: # 调用方法检查是否到期,此方法内部会更新状态 shifou_daoqi = record.jiance_shifou_daoqi() huiyuan_goumai_list.append({ 'huiyuan_id': record.huiyuan_id, 'huiyuan_zhuangtai': record.huiyuan_zhuangtai, 'daoqi_time': record.daoqi_time.isoformat() if record.daoqi_time else None, 'shifou_daoqi': shifou_daoqi, 'jieshao': record.jieshao }) data['clumber'] = huiyuan_goumai_list return data from datetime import datetime # 🔴【关键修改】 class YonghuTixianShenheXiugaiView(APIView): """ 用户修改审核中提现申请接口 访问路径: /api/yonghu/yonghutxshxg 请求方法: POST 权限要求: JWT Token认证通过的用户 请求体: multipart/form-data 格式 支持字段: tixian_id, fangshi, txdianhua, txzh, file (图片文件) 功能: 允许用户修改审核中提现申请的收款方式、电话、账号、收款码等 """ permission_classes = [IsAuthenticated] parser_classes = (MultiPartParser, FormParser, JSONParser) def post(self, request): """ 处理用户修改审核中提现申请的POST请求 """ try: # 1. 获取当前用户 user_main = request.user current_yonghuid = user_main.yonghuid # 2. 验证必填字段:提现记录ID tixian_id = request.data.get('tixian_id') if not tixian_id: return Response({ 'code': 1, 'msg': '提现记录ID不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 3. 查询提现记录(使用select_for_update确保事务安全) with transaction.atomic(): try: tixian_record = Tixianjilu.objects.select_for_update().get( id=tixian_id, yonghuid=current_yonghuid # 确保只能修改自己的记录 ) except Tixianjilu.DoesNotExist: return Response({ 'code': 2, 'msg': '提现记录不存在或无权修改', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 4. 验证提现记录状态(必须是审核中) if tixian_record.zhuangtai != 1: return Response({ 'code': 3, 'msg': '只有审核中的提现申请可以修改', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 5. 获取并验证提现方式 fangshi = request.data.get('fangshi') if fangshi: fangshi = int(fangshi) if fangshi not in [1, 2]: return Response({ 'code': 4, 'msg': '提现方式无效,请选择1(微信)或2(支付宝)', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 检查提现类型与方式的匹配(如果前端提供了leixing) leixing = request.data.get('leixing') if leixing: leixing = int(leixing) # 这里可以根据业务逻辑添加类型与方式的验证 # 例如:某些类型只能使用某些方式 # 6. 验证手机号(必填) txdianhua = request.data.get('txdianhua', '').strip() if not txdianhua: return Response({ 'code': 5, 'msg': '收款人电话不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 验证手机号格式 if not txdianhua.isdigit() or len(txdianhua) != 11: return Response({ 'code': 6, 'msg': '请输入11位有效的手机号码', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 7. 获取收款账号(可选) txzh = request.data.get('txzh', '').strip() # 8. 处理收款码图片上传(如果有) txtupian_file = request.FILES.get('file') new_zhifu_url = None old_zhifu_url = tixian_record.zhifu if txtupian_file: # 验证图片 is_valid, error_msg = validate_image(txtupian_file) if not is_valid: return Response({ 'code': 7, 'msg': f'图片验证失败: {error_msg}', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 上传收款码图片 new_zhifu_url = self.handle_shoukuanma_upload( user_main, txtupian_file, old_zhifu_url, tixian_record.leixing ) if not new_zhifu_url: return Response({ 'code': 8, 'msg': '收款码上传失败', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 9. 验证收款信息(账号和图片至少有一个) if not txzh and not (new_zhifu_url or old_zhifu_url): return Response({ 'code': 9, 'msg': '请填写收款账号或上传收款码', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 10. 更新提现记录 update_fields = [] # 更新提现方式(如果有) if fangshi: tixian_record.fangshi = fangshi update_fields.append('fangshi') # 更新手机号(必填) tixian_record.phone = txdianhua update_fields.append('phone') # 更新收款账号(如果有) if txzh is not None: # 注意:空字符串也要更新 tixian_record.skzhanghao = txzh update_fields.append('skzhanghao') # 更新收款码图片(如果有新图片) if new_zhifu_url: tixian_record.zhifu = new_zhifu_url update_fields.append('zhifu') # 保存更新 if update_fields: tixian_record.save(update_fields=update_fields) # 11. 构建返回数据 response_data = { 'tixian_id': tixian_record.id, 'fangshi': tixian_record.fangshi, 'txdianhua': tixian_record.phone, 'txzh': tixian_record.skzhanghao or '', 'txtupian': tixian_record.zhifu or '' # 返回相对URL } # 12. 返回成功响应 return Response({ 'code': 0, 'msg': '提现申请修改成功', 'data': response_data }) except Exception as e: # 记录详细错误日志 import logging logger = logging.getLogger(__name__) logger.error( f"修改提现申请异常 - 用户ID: {getattr(user_main, 'yonghuid', 'N/A')}, " f"提现ID: {tixian_id}, " f"错误类型: {type(e).__name__}, " f"错误详情: {str(e)}", exc_info=True ) # 返回友好错误信息 return Response({ 'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def handle_shoukuanma_upload(self, user_main, image_file, old_zhifu_url, leixing=None): """ 处理收款码图片上传到腾讯云COS 存储路径: tixian/{yonghuid}/{timestamp_uuid}.扩展名 参数: - user_main: 当前用户对象 - image_file: 图片文件对象 - old_zhifu_url: 旧收款码URL(用于删除) - leixing: 提现类型(可选,用于文件夹分类) 返回: 新的图片相对路径 """ try: yonghuid = user_main.yonghuid if not yonghuid: return None # 删除旧收款码(如果存在且不是空字符串) if old_zhifu_url and old_zhifu_url.strip(): delete_from_oss(old_zhifu_url) # 获取文件扩展名 file_name = image_file.name file_extension = os.path.splitext(file_name)[1].lower() if not file_extension: file_extension = '.jpg' # 生成唯一文件名(使用时间戳+UUID确保不重复) timestamp = datetime.now().strftime('%Y%m%d%H%M%S') unique_id = str(uuid.uuid4())[:8] # 取UUID前8位 unique_file_name = f"{timestamp}_{unique_id}{file_extension}" # 根据提现类型构建文件夹路径(如果提供了leixing) if leixing: # leixing=1:打手佣金, leixing=2:管事分红 type_folder = 'dashou' if leixing == 1 else 'guanshi' oss_file_path = f"tixian/{type_folder}/{yonghuid}/{unique_file_name}" else: # 默认路径 oss_file_path = f"tixian/{yonghuid}/{unique_file_name}" # 上传新收款码到OSS new_shoukuanma_url = upload_to_oss(image_file, oss_file_path) if new_shoukuanma_url: # 返回相对路径(从tixian文件夹开始) return oss_file_path else: return None except Exception as e: print(f"处理收款码上传失败: {e}") return None # views.py from django.db.models import Q, Prefetch from django.db import connection from django.core.paginator import Paginator from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from dingdan.models import Chufajilu, Chufatupian from yonghu.models import UserDashou import logging logger = logging.getLogger(__name__) class DashouPermission: """ 打手权限验证类 确保用户是打手且账号状态正常 """ def has_permission(self, request, view): user = request.user # 检查用户是否已认证 if not user.is_authenticated: return False try: # 通过反向关系获取打手扩展信息 dashou_profile = user.dashou_profile # 检查账号状态是否为1(正常) if dashou_profile.zhanghaozhuangtai != 1: return False return True except UserDashou.DoesNotExist: return False except Exception as e: logger.error(f"打手权限验证异常: {e}") return False from django.db.models import Q, Count, Case, When, IntegerField logger = logging.getLogger(__name__) class ChufaJiluHuoquView(APIView): """ 打手获取处罚记录接口 路径: /yonghu/dshqcfjl 方法: POST 权限: JWT Token + 打手权限验证 """ permission_classes = [IsAuthenticated, DashouPermission] def post(self, request): try: # 获取当前用户信息 user_main = request.user yonghuid = user_main.yonghuid # 通过反向关系获取打手扩展信息 try: dashou_profile = user_main.dashou_profile # 再次确认账号状态 if dashou_profile.zhanghaozhuangtai != 1: return Response({ 'code': 3, 'msg': '账号已被封禁,无法查看处罚记录', 'data': None }, status=status.HTTP_403_FORBIDDEN) except UserDashou.DoesNotExist: return Response({ 'code': 2, 'msg': '用户不是打手身份', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 判断请求类型:统计信息 or 记录列表 qingqiu_tongji = request.data.get('qingqiu_tongji', False) if qingqiu_tongji: # 返回统计信息 return self.get_tongji_info(yonghuid) else: # 返回处罚记录列表 return self.get_chufa_list(yonghuid, request.data) except Exception as e: logger.error(f"获取处罚记录异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True) return Response({ 'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def get_tongji_info(self, yonghuid): """ 获取统计信息(总记录、待处理、已处理) """ try: # 使用单个查询获取所有统计信息 stats = Chufajilu.objects.filter(dashouid=yonghuid).aggregate( # 总记录数 zongshu=Count('id'), # 待处理数:状态为0(待处罚)和3(申诉中) daichuli=Count( Case( When(Q(sqzhuangtai=0) | Q(sqzhuangtai=3), then=1), output_field=IntegerField() ) ), # 已处理数:状态为1(已处罚)和2(已驳回) yichuli=Count( Case( When(Q(sqzhuangtai=1) | Q(sqzhuangtai=2), then=1), output_field=IntegerField() ) ) ) # 如果查询结果为None,则设置为0 tongji_data = { 'zongshu': stats.get('zongshu', 0) or 0, 'daichuli': stats.get('daichuli', 0) or 0, 'yichuli': stats.get('yichuli', 0) or 0 } return Response({ 'code': 0, 'msg': '获取统计信息成功', 'data': tongji_data }) except Exception as e: logger.error(f"获取统计信息异常 - 用户ID: {yonghuid}, 错误: {str(e)}") return Response({ 'code': 1, 'msg': '获取统计信息失败', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def get_chufa_list(self, yonghuid, request_data): """ 获取处罚记录列表(高性能批量查询) 🔴 只修改图片查询逻辑,字段保持原样 """ try: # 获取分页参数 page = int(request_data.get('page', 1)) page_size = int(request_data.get('page_size', 10)) # 限制最大每页数量,防止恶意请求 if page_size > 50: page_size = 50 if page_size < 1: page_size = 10 # 获取状态筛选参数 zhuangtai = request_data.get('zhuangtai', 0) # 🔴 构建查询条件 - 打手端根据dashouid查询 queryset = Chufajilu.objects.filter(dashouid=yonghuid) if zhuangtai == 0: # 待处理:状态为0(待处罚)和3(申诉中) queryset = queryset.filter(Q(sqzhuangtai=0) | Q(sqzhuangtai=3)) elif zhuangtai == 1: # 已处理:状态为1(已处罚)和2(已驳回) queryset = queryset.filter(Q(sqzhuangtai=1) | Q(sqzhuangtai=2)) else: # 默认返回所有状态 pass # 按创建时间倒序排列(最新在前) queryset = queryset.order_by('-create_time') # 使用游标分页,避免传统分页的性能问题 paginator = Paginator(queryset, page_size) try: page_obj = paginator.page(page) except: # 页码超出范围,返回空列表 return Response({ 'code': 0, 'msg': '获取成功', 'data': { 'list': [], 'has_more': False, 'page': page, 'page_size': page_size, 'current_count': 0 } }) # 获取当前页的记录 chufa_records = list(page_obj) if not chufa_records: # 没有数据直接返回 return Response({ 'code': 0, 'msg': '获取成功', 'data': { 'list': [], 'has_more': False, 'page': page, 'page_size': page_size, 'current_count': 0 } }) # 🔴【核心修复】收集所有需要查询图片的组合 dingdan_ids = [] shangjia_ids = [] for record in chufa_records: if record.dingdan_id: dingdan_ids.append(record.dingdan_id) if record.qingqiuid: # 商家ID shangjia_ids.append(record.qingqiuid) # 去重 dingdan_ids = list(set(dingdan_ids)) shangjia_ids = list(set(shangjia_ids)) # 🔴【核心修复】分别查询两种图片(避免if-elif逻辑错误) # 1. 查询证据图片(商家上传的) zhengju_mapping = {} if dingdan_ids and shangjia_ids: # 构建Q对象:dingdan_id在列表中 AND yonghuid在商家ID列表中 zhengju_q = Q(dingdan_id__in=dingdan_ids, yonghuid__in=shangjia_ids) zhengju_queryset = Chufatupian.objects.filter(zhengju_q).values('dingdan_id', 'yonghuid', 'tupian') for item in zhengju_queryset: key = f"{item['dingdan_id']}_{item['yonghuid']}" if key not in zhengju_mapping: zhengju_mapping[key] = [] if item['tupian']: zhengju_mapping[key].append(item['tupian']) # 2. 查询申诉图片(打手上传的) shensu_mapping = {} if dingdan_ids: # 构建Q对象:dingdan_id在列表中 AND yonghuid=当前打手ID shensu_q = Q(dingdan_id__in=dingdan_ids, yonghuid=yonghuid) shensu_queryset = Chufatupian.objects.filter(shensu_q).values('dingdan_id', 'yonghuid', 'tupian') for item in shensu_queryset: key = f"{item['dingdan_id']}_{item['yonghuid']}" if key not in shensu_mapping: shensu_mapping[key] = [] if item['tupian']: shensu_mapping[key].append(item['tupian']) # 🔴【核心修复】组装返回数据 - 字段保持原样! chufa_list = [] for record in chufa_records: dingdan_id = record.dingdan_id shangjia_id = record.qingqiuid # 获取证据图片(商家上传的) zhengju_tupian = [] if dingdan_id and shangjia_id: key = f"{dingdan_id}_{shangjia_id}" zhengju_tupian = zhengju_mapping.get(key, []) # 获取申诉图片(打手上传的) shensu_tupian = [] if dingdan_id: key = f"{dingdan_id}_{yonghuid}" shensu_tupian = shensu_mapping.get(key, []) # 🔴【字段保持原样!】与前端完全对应! chufa_data = { 'id': record.id, 'dingdan_id': record.dingdan_id or '', 'qingqiuid': record.qingqiuid or '', # 商家ID 'cfliyou': record.cfliyou or '', 'sqzhuangtai': record.sqzhuangtai or 0, 'ssliyou': record.ssliyou or '', 'jifen': record.jifen or 0, 'create_time': record.create_time.strftime('%Y-%m-%d %H:%M:%S') if record.create_time else '', 'update_time': record.update_time.strftime('%Y-%m-%d %H:%M:%S') if record.update_time else '', 'zhengju_tupian': zhengju_tupian, # 商家上传的证据图片 'shensu_tupian': shensu_tupian, # 打手上传的申诉图片 } chufa_list.append(chufa_data) # 判断是否还有更多数据 has_more = page_obj.has_next() # 构建响应数据 response_data = { 'list': chufa_list, 'has_more': has_more, 'page': page, 'page_size': page_size, 'current_count': len(chufa_list) } return Response({ 'code': 0, 'msg': '获取成功', 'data': response_data }) except ValueError as e: logger.error(f"参数错误 - 用户ID: {yonghuid}, 错误: {str(e)}") return Response({ 'code': 4, 'msg': '参数格式错误', 'data': None }, status=status.HTTP_400_BAD_REQUEST) except Exception as e: logger.error(f"获取处罚记录列表异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True) return Response({ 'code': 5, 'msg': '获取记录列表失败', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class DashouShensuView(APIView): """ 打手申诉接口 路径: /yonghu/dscfss 方法: POST 权限: JWT Token + 打手权限验证 功能: 打手对处罚进行申诉 """ permission_classes = [IsAuthenticated, DashouPermission] def post(self, request): try: # 获取当前用户信息 user_main = request.user yonghuid = user_main.yonghuid # 🔴🆕【关键】验证打手账号状态 try: dashou_profile = user_main.dashou_profile if dashou_profile.zhanghaozhuangtai != 1: return Response({ 'code': 3, 'msg': '账号已被封禁,无法申诉', 'data': None }, status=status.HTTP_403_FORBIDDEN) except UserDashou.DoesNotExist: return Response({ 'code': 2, 'msg': '用户不是打手身份', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 🔴🆕【关键】获取请求参数 chufa_id = request.data.get('chufa_id') # 处罚记录ID shensu_liyou = request.data.get('shensu_liyou', '').strip() # 申诉理由 shensu_tupian_urls = request.data.get('shensu_tupian_urls', []) # 申诉图片URL数组 # 验证必填字段 if not chufa_id: return Response({ 'code': 6, 'msg': '处罚记录ID不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) if not shensu_liyou: return Response({ 'code': 7, 'msg': '申诉理由不能为空', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 验证申诉理由长度 if len(shensu_liyou) > 500: return Response({ 'code': 8, 'msg': '申诉理由不能超过500字', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 验证图片数量 if len(shensu_tupian_urls) > 9: return Response({ 'code': 9, 'msg': '最多只能上传9张图片', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 🔴🆕【关键】使用事务确保数据一致性 from django.db import transaction, IntegrityError with transaction.atomic(): # 1. 查询处罚记录 try: chufa_record = Chufajilu.objects.select_for_update().get( id=chufa_id, dashouid=yonghuid # 确保只能申诉自己的处罚记录 ) except Chufajilu.DoesNotExist: return Response({ 'code': 10, 'msg': '处罚记录不存在或无权申诉', 'data': None }, status=status.HTTP_404_NOT_FOUND) # 2. 验证处罚记录状态(必须是待处理状态0) if chufa_record.sqzhuangtai != 0: return Response({ 'code': 11, 'msg': '该处罚记录已处理,无法申诉', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 3. 验证订单是否存在(可选,根据需求决定) # 这里可以根据dingdan_id去查询订单表,但根据需求描述,我们已经有了处罚记录 # 处罚记录中包含了dingdan_id,如果dingdan_id不存在,说明订单可能被删除 # 但根据业务逻辑,有处罚记录就应该有订单,所以这里可以跳过这个检查 # 4. 检查是否已有申诉记录(防止重复申诉) if chufa_record.ssliyou or chufa_record.sqzhuangtai == 3: # 检查是否已经有申诉图片 existing_shensu_tupian = Chufatupian.objects.filter( dingdan_id=chufa_record.dingdan_id, yonghuid=yonghuid ).exists() if existing_shensu_tupian: return Response({ 'code': 12, 'msg': '您已经提交过申诉,请等待处理结果', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 5. 更新处罚记录 chufa_record.ssliyou = shensu_liyou chufa_record.sqzhuangtai = 3 # 状态改为申诉中 chufa_record.save() # 6. 保存申诉图片(如果有) saved_tupian_urls = [] if shensu_tupian_urls: for tupian_url in shensu_tupian_urls: if tupian_url: # 确保URL不为空 chufa_tupian = Chufatupian.objects.create( dingdan_id=chufa_record.dingdan_id, yonghuid=yonghuid, tupian=tupian_url ) saved_tupian_urls.append(tupian_url) # 7. 构建返回数据 response_data = { 'chufa_id': chufa_record.id, 'dingdan_id': chufa_record.dingdan_id or '', 'shensu_liyou': shensu_liyou, 'shensu_tupian_urls': saved_tupian_urls, 'sqzhuangtai': chufa_record.sqzhuangtai, 'update_time': chufa_record.update_time.strftime( '%Y-%m-%d %H:%M:%S') if chufa_record.update_time else '' } # 记录操作日志 logger.info(f"打手申诉成功 - 用户ID: {yonghuid}, 处罚ID: {chufa_id}, 订单ID: {chufa_record.dingdan_id}") return Response({ 'code': 0, 'msg': '申诉提交成功', 'data': response_data }) except Exception as e: logger.error(f"打手申诉异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True) return Response({ 'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class ShangjiaChufaJiluHuoquView(APIView): """ 商家获取处罚记录接口 路径: /yonghu/sjcfjlhq 方法: POST 权限: JWT Token + 商家权限验证 """ permission_classes = [IsAuthenticated] def post(self, request): try: # 获取当前用户信息 user_main = request.user yonghuid = user_main.yonghuid # 通过反向关系获取商家扩展信息 try: shangjia_profile = user_main.shop_profile # 再次确认账号状态 if shangjia_profile.zhuangtai != 1: return Response({ 'code': 3, 'msg': '商家账号状态异常,无法查看处罚记录', 'data': None }, status=status.HTTP_403_FORBIDDEN) except UserShangjia.DoesNotExist: return Response({ 'code': 2, 'msg': '用户不是商家身份', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 判断请求类型 qingqiu_tongji = request.data.get('qingqiu_tongji', False) if qingqiu_tongji: return self.get_tongji_info(yonghuid) else: return self.get_chufa_list(yonghuid, request.data) except Exception as e: logger.error(f"商家获取处罚记录异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True) return Response({ 'code': 99, 'msg': '系统繁忙,请稍后重试', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def get_tongji_info(self, yonghuid): """ 获取统计信息 """ try: # 🔴 查询条件:qingqiuid = 当前商家ID stats = Chufajilu.objects.filter(qingqiuid=yonghuid).aggregate( zongshu=Count('id'), daichuli=Count( Case( When(Q(sqzhuangtai=0) | Q(sqzhuangtai=3), then=1), output_field=IntegerField() ) ), yichuli=Count( Case( When(Q(sqzhuangtai=1) | Q(sqzhuangtai=2), then=1), output_field=IntegerField() ) ) ) tongji_data = { 'zongshu': stats.get('zongshu', 0) or 0, 'daichuli': stats.get('daichuli', 0) or 0, 'yichuli': stats.get('yichuli', 0) or 0 } return Response({ 'code': 0, 'msg': '获取统计信息成功', 'data': tongji_data }) except Exception as e: logger.error(f"商家获取统计信息异常 - 用户ID: {yonghuid}, 错误: {str(e)}") return Response({ 'code': 1, 'msg': '获取统计信息失败', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def get_chufa_list(self, yonghuid, request_data): """ 获取处罚记录列表(商家端) 🔴 只修改图片查询逻辑,字段保持原样 """ try: # 获取分页参数 page = int(request_data.get('page', 1)) page_size = int(request_data.get('page_size', 10)) if page_size > 50: page_size = 50 if page_size < 1: page_size = 10 # 获取状态筛选参数 zhuangtai = request_data.get('zhuangtai', 0) # 🔴 查询条件:qingqiuid = 当前商家ID queryset = Chufajilu.objects.filter(qingqiuid=yonghuid) if zhuangtai == 0: queryset = queryset.filter(Q(sqzhuangtai=0) | Q(sqzhuangtai=3)) elif zhuangtai == 1: queryset = queryset.filter(Q(sqzhuangtai=1) | Q(sqzhuangtai=2)) else: pass # 按创建时间倒序排列 queryset = queryset.order_by('-create_time') # 分页逻辑 paginator = Paginator(queryset, page_size) try: page_obj = paginator.page(page) except: return Response({ 'code': 0, 'msg': '获取成功', 'data': { 'list': [], 'has_more': False, 'page': page, 'page_size': page_size, 'current_count': 0 } }) chufa_records = list(page_obj) if not chufa_records: return Response({ 'code': 0, 'msg': '获取成功', 'data': { 'list': [], 'has_more': False, 'page': page, 'page_size': page_size, 'current_count': 0 } }) # 🔴【核心修复】收集所有需要查询图片的组合 dingdan_ids = [] dashou_ids = [] for record in chufa_records: if record.dingdan_id: dingdan_ids.append(record.dingdan_id) if record.dashouid: # 打手ID dashou_ids.append(record.dashouid) # 去重 dingdan_ids = list(set(dingdan_ids)) dashou_ids = list(set(dashou_ids)) # 🔴【核心修复】分别查询两种图片(避免if-elif逻辑错误) # 1. 查询证据图片(商家自己上传的) zhengju_mapping = {} if dingdan_ids: # 构建Q对象:dingdan_id在列表中 AND yonghuid=当前商家ID zhengju_q = Q(dingdan_id__in=dingdan_ids, yonghuid=yonghuid) zhengju_queryset = Chufatupian.objects.filter(zhengju_q).values('dingdan_id', 'yonghuid', 'tupian') for item in zhengju_queryset: key = f"{item['dingdan_id']}_{item['yonghuid']}" if key not in zhengju_mapping: zhengju_mapping[key] = [] if item['tupian']: zhengju_mapping[key].append(item['tupian']) # 2. 查询申诉图片(打手上传的) shensu_mapping = {} if dingdan_ids and dashou_ids: # 构建Q对象:dingdan_id在列表中 AND yonghuid在打手ID列表中 shensu_q = Q(dingdan_id__in=dingdan_ids, yonghuid__in=dashou_ids) shensu_queryset = Chufatupian.objects.filter(shensu_q).values('dingdan_id', 'yonghuid', 'tupian') for item in shensu_queryset: key = f"{item['dingdan_id']}_{item['yonghuid']}" if key not in shensu_mapping: shensu_mapping[key] = [] if item['tupian']: shensu_mapping[key].append(item['tupian']) # 🔴【核心修复】组装返回数据 - 字段保持原样! chufa_list = [] for record in chufa_records: dingdan_id = record.dingdan_id dashou_id = record.dashouid # 获取证据图片(商家自己上传的) zhengju_tupian = [] if dingdan_id: key = f"{dingdan_id}_{yonghuid}" zhengju_tupian = zhengju_mapping.get(key, []) # 获取申诉图片(打手上传的) shensu_tupian = [] if dingdan_id and dashou_id: key = f"{dingdan_id}_{dashou_id}" shensu_tupian = shensu_mapping.get(key, []) # 🔴【字段保持原样!】与前端完全对应! chufa_data = { 'id': record.id, 'dingdan_id': record.dingdan_id or '', 'qingqiuid': record.dashouid or '', # 打手ID 'cfliyou': record.cfliyou or '', 'sqzhuangtai': record.sqzhuangtai or 0, 'ssliyou': record.ssliyou or '', 'jifen': record.jifen or 0, 'create_time': record.create_time.strftime('%Y-%m-%d %H:%M:%S') if record.create_time else '', 'update_time': record.update_time.strftime('%Y-%m-%d %H:%M:%S') if record.update_time else '', 'zhengju_tupian': zhengju_tupian, # 商家自己上传的证据图片 'shensu_tupian': shensu_tupian, # 打手上传的申诉图片 } chufa_list.append(chufa_data) # 判断是否还有更多数据 has_more = page_obj.has_next() response_data = { 'list': chufa_list, 'has_more': has_more, 'page': page, 'page_size': page_size, 'current_count': len(chufa_list) } return Response({ 'code': 0, 'msg': '获取成功', 'data': response_data }) except ValueError as e: logger.error(f"商家端参数错误 - 用户ID: {yonghuid}, 错误: {str(e)}") return Response({ 'code': 4, 'msg': '参数格式错误', 'data': None }, status=status.HTTP_400_BAD_REQUEST) except Exception as e: logger.error(f"商家获取处罚记录列表异常 - 用户ID: {yonghuid}, 错误: {str(e)}", exc_info=True) return Response({ 'code': 5, 'msg': '获取记录列表失败', 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) from .models import UserMain, UserKefu logger = logging.getLogger(__name__) class KefuLoginView(APIView): """ 客服登录接口(支持手机号重复) 请求:POST /yonghu/kefujinru 请求体:{"phone": "13800138000", "password": "xxx", "erjimima": "yyy"} 返回: code=0 成功,data包含token,nicheng,yonghuid code=4 账号被封禁(提示“账号已被封禁”) 其他错误统一 code=1 msg="登录失败" """ throttle_classes = [AnonRateThrottle] # 限制匿名请求频率 permission_classes = [AllowAny] # 任何人都可访问 def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() password = request.data.get('password', '') erjimima = request.data.get('erjimima', '') if not phone or not password or not erjimima: return Response({ 'code': 1, 'msg': '登录失败', # 统一提示,不暴露具体原因 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 2. 获取客户端真实IP client_ip = self.get_client_ip(request) # 3. 查询所有手机号匹配且类型为客服的用户 # 注意:UserMain 中需要有 user_type 字段,且值为 'kefu' 代表客服 user_mains = UserMain.objects.filter( phone=phone, user_type='kefu' # 只查找客服账号 ).select_related('kefu_profile') # 预加载客服扩展表 if not user_mains.exists(): logger.warning(f"登录失败,手机号不存在或非客服: {phone}, IP: {client_ip}") return Response({ 'code': 1, 'msg': '登录失败', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 4. 遍历所有匹配的用户,验证密码和二级密码 target_user = None target_kefu = None for user in user_mains: # 验证主密码(明文比较,按您的要求) if user.password != password: continue # 检查是否存在客服扩展表 try: kefu = user.kefu_profile except UserKefu.DoesNotExist: continue # 验证二级密码(明文比较) if kefu.erjimima != erjimima: continue # 如果账号被禁用,记录但继续查找(可能还有其他正常账号?) if kefu.zhuangtai != 1: # 如果找到被禁用的,记录下来,但继续查找是否有正常账号 if target_user is None: # 暂时记录这个禁用账号,但继续遍历 target_user = user target_kefu = kefu continue # 找到完全匹配且状态正常的账号,立即使用 target_user = user target_kefu = kefu break else: # 循环结束未找到正常账号,但可能找到了禁用账号 if target_user and target_kefu: # 只有被禁用的账号匹配,返回封禁提示 logger.warning(f"登录失败,客服账号已禁用: {phone}, IP: {client_ip}") return Response({ 'code': 4, 'msg': '账号已被封禁', 'data': None }, status=status.HTTP_403_FORBIDDEN) else: # 没有任何账号匹配(密码/二级密码错误) logger.warning(f"登录失败,密码或二级密码错误: {phone}, IP: {client_ip}") return Response({ 'code': 1, 'msg': '登录失败', 'data': None }, status=status.HTTP_400_BAD_REQUEST) # 5. 更新IP和最后登录时间 target_user.ip = client_ip target_user.last_login_time = timezone.now() target_user.save(update_fields=['ip', 'last_login_time']) # 6. 生成JWT token refresh = RefreshToken.for_user(target_user) token = str(refresh.access_token) # 7. 返回成功数据 return Response({ 'code': 0, 'msg': '登录成功', 'data': { 'token': token, 'nicheng': target_kefu.nicheng, 'yonghuid': target_user.yonghuid, } }) def get_client_ip(self, request): """ 获取客户端真实IP """ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() if ip: return ip return request.META.get('REMOTE_ADDR', '0.0.0.0') logger = logging.getLogger(__name__) class KefuStatsView(APIView): """ 客服统计数据接口 请求:POST /yonghu/kfjrhqzl 请求体:{"phone": "13800138000"} 认证:必须携带有效的 JWT token(放在 Authorization 头) 返回: code=0 成功,data 包含 jinrichuli, jinyuechuli, zongchuli 验证失败统一返回 401,不透露具体原因 """ permission_classes = [IsAuthenticated] # 必须登录 def post(self, request): # 1. 获取请求中的 phone phone = request.data.get('phone', '').strip() if not phone: # 缺少参数,但按约定返回 401(可以改为 400,但为了统一,用401) logger.warning(f"统计接口缺少 phone 参数,用户: {request.user.yonghuid}") return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 获取当前登录用户(从 JWT 中解析) current_user = request.user # 3. 验证前端传来的 phone 是否与当前用户的 phone 一致 if current_user.phone != phone: # 不一致,可能 token 被冒用或参数错误 logger.warning(f"统计接口 phone 不匹配: 请求phone={phone}, 用户phone={current_user.phone}") return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 4. 验证用户类型是否为客服 if current_user.user_type != 'kefu': logger.warning(f"统计接口用户类型非客服: {current_user.yonghuid}") return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 5. 尝试获取客服扩展表数据 try: kefu_profile = current_user.kefu_profile # OneToOneField 反向关系 except UserKefu.DoesNotExist: logger.warning(f"统计接口客服扩展表不存在: {current_user.yonghuid}") return Response({'detail': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 6. 返回统计数据(字段名与前端约定一致) return Response({ 'code': 0, 'data': { 'jinrichuli': kefu_profile.jinrichuli, 'jinyuechuli': kefu_profile.jinyuechuli, 'zongchuli': kefu_profile.zongchuli, } }, status=status.HTTP_200_OK) from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser import logging from django.core.exceptions import ObjectDoesNotExist from yonghu.models import UserMain, UserKefu from shangpin.models import ShangpinLeixing # 假设商品类型表名,请根据实际调整 logger = logging.getLogger(__name__) class KefuGetOrderTypesView(APIView): """ 客服获取订单类型列表接口(平台订单用) 请求:POST /yonghu/kfhqptddlx 参数:{"phone": "13800138000"} 认证:JWT 返回:code=0 + 类型列表 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() if not phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) current_user = request.user # 2. 验证手机号一致性 if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: 请求phone={phone}, 用户phone={current_user.phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 验证用户类型及客服状态 if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu_profile = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu_profile.zhuangtai != 1: logger.warning(f"客服账号已被禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 4. 查询订单类型(商品类型) try: # 只查询需要的字段,减少数据传输 types_qs = ShangpinLeixing.objects.all().only('id', 'jieshao', 'tupian_url') type_list = [ { 'id': t.id, 'biaoti': t.jieshao or '', # 假设商品类型的标题字段为 jieshao 'tupian_url': t.tupian_url or '' } for t in types_qs ] return Response({'code': 0, 'data': type_list}) except Exception as e: logger.error(f"查询商品类型表失败: {str(e)}") # 表不存在或查询失败,返回空列表 return Response({'code': 0, 'data': []}) import time import logging from django.db.models import Q, Count from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from yonghu.models import UserMain, UserKefu, UserBoss from dingdan.models import Dingdan, DingdanPingtai logger = logging.getLogger(__name__) from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from django.db.models import Q, Count from django.core.paginator import Paginator class KefuGetOrderListView(APIView): """ 客服获取平台订单列表接口(发单平台=1,非跨平台) 需要权限:002ab(平台订单管理) 请求参数: username: 客服账号(用于权限校验) page: 页码 pageSize: 每页条数 dingdan_id: 订单ID(精确搜索) laoban_id: 老板ID(搜索平台扩展表中的老板) dashou_id: 打手ID(搜索主表中的接单打手) leixing: 商品类型ID zhuangtai: 状态(多个用逗号分隔,如 "1,7") date: 日期(YYYY-MM-DD),筛选订单创建时间 nicheng: 游戏昵称(订单主表的 nicheng 字段) jieshao: 订单介绍(订单主表的 jieshao 字段) clkf: 处理客服(订单主表的 clkf 字段) """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): start_total = time.time() # 1. 获取前端参数 username = request.data.get('phone', '').strip() if not username: return Response({'code': 400, 'msg': '缺少username'}) # 2. 公共权限校验 kefu, permissions = verify_kefu_permission(request, username) if kefu is None: return permissions # 3. 检查平台订单管理权限(002ab) if '002ab' not in permissions: return Response({'code': 403, 'msg': '您无权查看平台订单列表'}) # 4. 获取分页和筛选参数 page = int(request.data.get('page', 1)) page_size = int(request.data.get('pageSize', 20)) dingdan_id = request.data.get('dingdan_id', '').strip() laoban_id = request.data.get('laoban_id', '').strip() dashou_id = request.data.get('dashou_id', '').strip() leixing = request.data.get('leixing') zhuangtai = request.data.get('zhuangtai', '') date = request.data.get('date', '').strip() # 新增筛选条件 nicheng = request.data.get('nicheng', '').strip() jieshao = request.data.get('jieshao', '').strip() clkf = request.data.get('clkf', '').strip() # ---------- 查询条件 ---------- # 基础条件:平台发单 + 非跨平台 base_q = Q(fadan_pingtai=1) & Q(is_cross=0) if dingdan_id: q_conditions = base_q & Q(dingdan_id=dingdan_id) else: q_conditions = base_q if leixing is not None: try: leixing_int = int(leixing) q_conditions &= Q(leixing_id=leixing_int) except (ValueError, TypeError): pass if zhuangtai and zhuangtai != 'all': try: status_list = [int(s.strip()) for s in zhuangtai.split(',') if s.strip()] if status_list: q_conditions &= Q(zhuangtai__in=status_list) except (ValueError, TypeError): pass if dashou_id: q_conditions &= Q(jiedan_dashou_id=dashou_id) if laoban_id: q_conditions &= Q(pingtai_kuozhan__laoban_id=laoban_id) if date: q_conditions &= Q(create_time__date=date) # 新增筛选条件 if nicheng: q_conditions &= Q(nicheng__icontains=nicheng) if jieshao: q_conditions &= Q(jieshao__icontains=jieshao) if clkf: q_conditions &= Q(clkf__icontains=clkf) # ---------- 统计(仅按发单平台,非跨平台) ---------- stats = Dingdan.objects.filter(fadan_pingtai=1, is_cross=0).aggregate( total_orders=Count('id'), completed_orders=Count('id', filter=Q(zhuangtai=3)), refund_orders=Count('id', filter=Q(zhuangtai=5)), pending_orders=Count('id', filter=Q(zhuangtai__in=[4,8])) ) # ---------- 状态计数(所有状态都返回,非跨平台) ---------- status_counts = { 'all': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0).count(), '1,7': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai__in=[1,7]).count(), '2': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=2).count(), '8': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=8).count(), '3': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=3).count(), '4': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=4).count(), '5': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=5).count(), '6': Dingdan.objects.filter(fadan_pingtai=1, is_cross=0, zhuangtai=6).count(), } # ---------- 分页查询 ---------- total_count = Dingdan.objects.filter(q_conditions).count() orders_qs = Dingdan.objects.filter(q_conditions).select_related( 'pingtai_kuozhan' ).only( 'dingdan_id', 'jieshao', 'zhuangtai', 'leixing_id', 'fadan_pingtai', 'jine', 'tupian', 'create_time', 'nicheng', 'jiedan_dashou_id', 'pingtai_kuozhan__laoban_id' ).order_by('-create_time')[(page-1)*page_size : page*page_size] # ---------- 获取老板昵称 ---------- laoban_ids = [o.pingtai_kuozhan.laoban_id for o in orders_qs if hasattr(o, 'pingtai_kuozhan') and o.pingtai_kuozhan.laoban_id] nickname_map = {} if laoban_ids: users = UserMain.objects.filter(yonghuid__in=laoban_ids).select_related('boss_profile').only( 'yonghuid', 'boss_profile__nickname' ) for user in users: nickname_map[user.yonghuid] = user.boss_profile.nickname or '' if hasattr(user, 'boss_profile') else '' result_list = [] for order in orders_qs: ext = getattr(order, 'pingtai_kuozhan', None) laoban_id = ext.laoban_id if ext else '' youxi_nicheng = nickname_map.get(laoban_id, order.nicheng or '') result_list.append({ 'dingdan_id': order.dingdan_id or '', 'jieshao': order.jieshao or '', 'zhuangtai': order.zhuangtai, 'leixing': order.leixing_id, 'fadanpingtai': order.fadan_pingtai, 'jiage': float(order.jine) if order.jine else 0.00, 'tupian_url': order.tupian or '', 'fadanshijian': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else '', 'youxi_nicheng': youxi_nicheng }) response_data = { 'stats': { 'totalOrders': stats['total_orders'] or 0, 'completedOrders': stats['completed_orders'] or 0, 'refundOrders': stats['refund_orders'] or 0, 'pendingOrders': stats['pending_orders'] or 0 }, 'statusCounts': status_counts, 'list': result_list, 'total': total_count, 'page': page, 'pageSize': page_size } logger.info(f"平台订单接口总耗时: {time.time()-start_total:.3f}s") return Response({'code': 0, 'data': response_data}) class KefuGetShangjiaOrderListView(APIView): """ 客服获取商家订单列表接口(发单平台=2,非跨平台) 需要权限:002ac(商家订单管理)—— 如不区分可使用 002ab 请求参数: username: 客服账号(用于权限校验) page: 页码 pageSize: 每页条数 dingdan_id: 订单ID(精确搜索) shangjia_id: 商家ID(搜索商家扩展表中的商家ID) dashou_id: 打手ID(搜索主表中的接单打手) leixing: 商品类型ID zhuangtai: 状态(多个用逗号分隔,如 "1,7") date: 日期(YYYY-MM-DD),筛选订单创建时间 nicheng: 游戏昵称(订单主表的 nicheng 字段) jieshao: 订单介绍(订单主表的 jieshao 字段) clkf: 处理客服(订单主表的 clkf 字段) shangjia_nicheng: 商家昵称(商家扩展表中的昵称) """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): start_total = time.time() # 1. 获取前端参数 username = request.data.get('username', '').strip() if not username: return Response({'code': 400, 'msg': '缺少username'}) # 2. 公共权限校验 kefu, permissions = verify_kefu_permission(request, username) if kefu is None: return permissions # 3. 检查商家订单管理权限(使用 002ac,如果没有可改用 002ab) if '002ac' not in permissions: return Response({'code': 403, 'msg': '您无权查看商家订单列表'}) # 4. 获取分页和筛选参数 page = int(request.data.get('page', 1)) page_size = int(request.data.get('pageSize', 20)) dingdan_id = request.data.get('dingdan_id', '').strip() shangjia_id = request.data.get('shangjia_id', '').strip() dashou_id = request.data.get('dashou_id', '').strip() leixing = request.data.get('leixing') zhuangtai = request.data.get('zhuangtai', '') date = request.data.get('date', '').strip() # 新增筛选条件 nicheng = request.data.get('nicheng', '').strip() jieshao = request.data.get('jieshao', '').strip() clkf = request.data.get('clkf', '').strip() shangjia_nicheng = request.data.get('shangjia_nicheng', '').strip() # ---------- 查询条件 ---------- # 基础条件:商家发单 + 非跨平台 base_q = Q(fadan_pingtai=2) & Q(is_cross=0) if dingdan_id: q_conditions = base_q & Q(dingdan_id=dingdan_id) else: q_conditions = base_q if leixing is not None: try: leixing_int = int(leixing) q_conditions &= Q(leixing_id=leixing_int) except (ValueError, TypeError): pass if zhuangtai and zhuangtai != 'all': try: status_list = [int(s.strip()) for s in zhuangtai.split(',') if s.strip()] if status_list: q_conditions &= Q(zhuangtai__in=status_list) except (ValueError, TypeError): pass if dashou_id: q_conditions &= Q(jiedan_dashou_id=dashou_id) if shangjia_id: q_conditions &= Q(shangjia_kuozhan__shangjia_id=shangjia_id) if date: q_conditions &= Q(create_time__date=date) if nicheng: q_conditions &= Q(nicheng__icontains=nicheng) if jieshao: q_conditions &= Q(jieshao__icontains=jieshao) if clkf: q_conditions &= Q(clkf__icontains=clkf) if shangjia_nicheng: # 通过商家扩展表关联 UserShangjia 的昵称 q_conditions &= Q(shangjia_kuozhan__shangjia_id__in=UserMain.objects.filter( shop_profile__nicheng__icontains=shangjia_nicheng ).values_list('yonghuid', flat=True)) # ---------- 统计(仅按发单平台,非跨平台) ---------- stats = Dingdan.objects.filter(fadan_pingtai=2, is_cross=0).aggregate( total_orders=Count('id'), completed_orders=Count('id', filter=Q(zhuangtai=3)), refund_orders=Count('id', filter=Q(zhuangtai=5)), pending_orders=Count('id', filter=Q(zhuangtai__in=[4,8])) ) # ---------- 状态计数(所有状态都返回,非跨平台) ---------- status_counts = { 'all': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0).count(), '1,7': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai__in=[1,7]).count(), '2': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=2).count(), '8': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=8).count(), '3': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=3).count(), '4': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=4).count(), '5': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=5).count(), '6': Dingdan.objects.filter(fadan_pingtai=2, is_cross=0, zhuangtai=6).count(), } # ---------- 分页查询 ---------- total_count = Dingdan.objects.filter(q_conditions).count() orders_qs = Dingdan.objects.filter(q_conditions).select_related( 'shangjia_kuozhan' ).only( 'dingdan_id', 'jieshao', 'zhuangtai', 'leixing_id', 'fadan_pingtai', 'jine', 'tupian', 'create_time', 'nicheng', 'jiedan_dashou_id', 'shangjia_kuozhan__shangjia_id' ).order_by('-create_time')[(page-1)*page_size : page*page_size] # ---------- 获取商家昵称 ---------- shangjia_ids = [o.shangjia_kuozhan.shangjia_id for o in orders_qs if hasattr(o, 'shangjia_kuozhan') and o.shangjia_kuozhan.shangjia_id] nickname_map = {} if shangjia_ids: users = UserMain.objects.filter(yonghuid__in=shangjia_ids).select_related('shop_profile').only( 'yonghuid', 'shop_profile__nicheng' ) for user in users: nickname_map[user.yonghuid] = user.shop_profile.nicheng or '' if hasattr(user, 'shop_profile') else '' result_list = [] for order in orders_qs: ext = getattr(order, 'shangjia_kuozhan', None) shangjia_id = ext.shangjia_id if ext else '' youxi_nicheng = order.nicheng or '' # 商家订单的玩家昵称 result_list.append({ 'dingdan_id': order.dingdan_id or '', 'jieshao': order.jieshao or '', 'zhuangtai': order.zhuangtai, 'leixing': order.leixing_id, 'fadanpingtai': order.fadan_pingtai, 'jiage': float(order.jine) if order.jine else 0.00, 'tupian_url': order.tupian or '', 'fadanshijian': order.create_time.strftime('%Y-%m-%d %H:%M:%S') if order.create_time else '', 'youxi_nicheng': youxi_nicheng }) response_data = { 'stats': { 'totalOrders': stats['total_orders'] or 0, 'completedOrders': stats['completed_orders'] or 0, 'refundOrders': stats['refund_orders'] or 0, 'pendingOrders': stats['pending_orders'] or 0 }, 'statusCounts': status_counts, 'list': result_list, 'total': total_count, 'page': page, 'pageSize': page_size } logger.info(f"商家订单接口总耗时: {time.time()-start_total:.3f}s") return Response({'code': 0, 'data': response_data}) import time import logging from django.db import models from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from yonghu.models import UserMain, UserKefu from dingdan.models import Dingdan, DingdanPingtai, DingdanShangjia, Dashoutupian, Tuikuanjilu logger = logging.getLogger(__name__) class KefuGetOrderDetailView(APIView): """ 客服获取订单详情接口 请求:POST /yonghu/kefuhqddxq 参数:{"phone": "客服手机号", "dingdan_id": "订单ID"} 认证:JWT 返回:code=0 + 订单详情数据 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): start_time = time.time() try: return self._build_order_detail_response(request, start_time) except Exception as e: logger.exception( '客服订单详情异常 dingdan_id=%s phone=%s error=%s', request.data.get('dingdan_id'), request.data.get('phone'), e, ) return Response( {'code': 500, 'msg': '获取订单详情失败', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) def _build_order_detail_response(self, request, start_time): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() if not phone or not dingdan_id: return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) # 2. 身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) # 3. 查询订单 try: dingdan_obj = Dingdan.objects.select_related( 'pingtai_kuozhan', 'shangjia_kuozhan' ).get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: logger.warning(f"订单不存在: {dingdan_id}") return Response( {'code': 404, 'msg': '订单不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND ) def _to_float(val, default=0.0): try: return float(val) if val is not None else default except (TypeError, ValueError): return default # 4. 构建基础响应数据(主表字段) response_data = { 'dingdan_id': dingdan_obj.dingdan_id or '', 'zhuangtai': dingdan_obj.zhuangtai or 0, 'fadanpingtai': dingdan_obj.fadan_pingtai or 0, 'jiage': _to_float(dingdan_obj.jine), 'dashou_fencheng': _to_float(dingdan_obj.dashou_fencheng), 'guanshi_fencheng': _to_float(getattr(dingdan_obj, 'guanshi_fencheng', 0)), 'guanshi_shangjia_fencheng': _to_float(getattr(dingdan_obj, 'guanshi_shangjia_fencheng', 0)), 'jiedan_dashou_id': dingdan_obj.jiedan_dashou_id or '', 'dashou_liuyan': dingdan_obj.dashou_liuyan or '', 'zhiding_id': dingdan_obj.zhiding_id or '', 'shangpin_id': dingdan_obj.shangpin_id or '', 'leixing': dingdan_obj.leixing_id or 0, 'tupian_url': dingdan_obj.tupian or '', 'jieshao': dingdan_obj.jieshao or '', 'beizhu': dingdan_obj.beizhu or '', 'tkly': dingdan_obj.tkly or '', 'nicheng': dingdan_obj.nicheng or '', 'clkf': dingdan_obj.clkf or '', 'chuangjianshijian': dingdan_obj.create_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan_obj.create_time else '', 'genggaishijian': dingdan_obj.update_time.strftime('%Y-%m-%d %H:%M:%S') if dingdan_obj.update_time else '', } # 5. 根据发单平台添加扩展表数据 fadan_pingtai = dingdan_obj.fadan_pingtai if fadan_pingtai == 1: # 平台订单扩展 if hasattr(dingdan_obj, 'pingtai_kuozhan') and dingdan_obj.pingtai_kuozhan: pingtai = dingdan_obj.pingtai_kuozhan response_data['pingtai_kuozhan'] = { 'laoban_id': pingtai.laoban_id or '', 'laoban_pingjia': pingtai.laoban_pingjia or '', } else: response_data['pingtai_kuozhan'] = { 'laoban_id': '', 'laoban_pingjia': '', } elif fadan_pingtai == 2: # 商家订单扩展 if hasattr(dingdan_obj, 'shangjia_kuozhan') and dingdan_obj.shangjia_kuozhan: shangjia = dingdan_obj.shangjia_kuozhan response_data['shangjia_kuozhan'] = { 'shangjia_id': shangjia.shangjia_id or '', 'sjnicheng': shangjia.sjnicheng or '', 'shangjia_pingjia': shangjia.shangjia_pingjia or '', 'cfliyou': shangjia.cfliyou or '', 'sqzhuangtai': shangjia.sqzhuangtai if shangjia.sqzhuangtai is not None else 0, 'bhliyou': shangjia.bhliyou or '', } else: response_data['shangjia_kuozhan'] = { 'shangjia_id': '', 'sjnicheng': '', 'shangjia_pingjia': '', 'cfliyou': '', 'sqzhuangtai': 0, 'bhliyou': '', } # 6. 查询打手提交的图片 try: dashou_images = Dashoutupian.objects.filter( dingdan_id=dingdan_id ).values_list('tupian', flat=True) response_data['dashou_images'] = [img for img in dashou_images if img] except Exception as e: logger.warning(f"查询打手图片失败: {str(e)}") response_data['dashou_images'] = [] # 7. 查询退款理由(从退款记录表取最新一条) try: tuikuan_record = Tuikuanjilu.objects.filter( dingdan_id=dingdan_id ).order_by('-create_time').first() if tuikuan_record and tuikuan_record.liyou: response_data['tuikuan_liyou'] = tuikuan_record.liyou else: response_data['tuikuan_liyou'] = '' except Exception as e: logger.warning(f"查询退款记录失败: {str(e)}") response_data['tuikuan_liyou'] = '' logger.info(f"订单详情接口耗时: {time.time()-start_time:.3f}s") return Response({'code': 0, 'data': response_data}) import logging from django.db import transaction, IntegrityError from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from yonghu.models import UserMain, UserKefu, UserDashou from dingdan.models import Dingdan from shangpin.models import Huiyuangoumai # 假设会员购买模型存在 logger = logging.getLogger(__name__) class KefuChangeDashouView(APIView): """ 客服更换打手接口 请求:POST /yonghu/kefughds 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID", "new_dashou_id": "新打手ID" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() new_dashou_id = request.data.get('new_dashou_id', '').strip() if not phone or not dingdan_id or not new_dashou_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单 try: order = Dingdan.objects.get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) # 4. 验证订单状态是否允许更换打手(进行中2 或 结算中8) if order.zhuangtai not in [2, 8]: return Response({'code': 400, 'msg': '订单状态不允许更换打手'}, status=status.HTTP_400_BAD_REQUEST) # 5. 查询新打手 try: new_dashou_user = UserMain.objects.get(yonghuid=new_dashou_id) except UserMain.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}) # 6. 验证打手扩展表 try: new_dashou_profile = new_dashou_user.dashou_profile except ObjectDoesNotExist: return Response({'code': 404, 'msg': '打手信息不完整'}) # 7. 验证打手状态(打手状态和账号状态必须为1) if new_dashou_profile.zhuangtai != 1 or new_dashou_profile.zhanghaozhuangtai != 1: return Response({'code': 400, 'msg': '打手接单中,状态不允许再次接单'}) # 8. 验证打手积分是否足够(至少5分) if new_dashou_profile.jifen < 5: return Response({'code': 400, 'msg': '打手积分不足,无法接单'}) # 8. 根据订单的抢单要求类型进行额外验证 yaoqiuleixing = order.yaoqiuleixing if yaoqiuleixing == 1: # 会员抢单 huiyuan_id = order.huiyuan_id if not huiyuan_id: return Response({'code': 400, 'msg': '订单会员ID为空'}) try: # 查询打手的会员购买记录 huiyuan_goumai = Huiyuangoumai.objects.get( huiyuan_id=huiyuan_id, yonghu_id=new_dashou_id # 假设 yonghu_id 字段存储用户ID ) except Huiyuangoumai.DoesNotExist: return Response({'code': 400, 'msg': '打手未购买该订单要求的会员'}) # 检查会员是否过期(调用模型方法) is_expired = huiyuan_goumai.jiance_shifou_daoqi() if is_expired or huiyuan_goumai.huiyuan_zhuangtai != 1: return Response({'code': 400, 'msg': '打手的会员已过期'}) elif yaoqiuleixing == 2: # 押金抢单 order_yajin = order.yongjin # 假设订单的 yongjin 字段存储押金要求 if order_yajin is None: return Response({'code': 400, 'msg': '订单押金为空'}) if new_dashou_profile.yajin < order_yajin: return Response({'code': 400, 'msg': '打手押金不足'}) else: return Response({'code': 400, 'msg': '订单抢单要求类型错误'}) # 9. 所有验证通过,开始更换打手(使用事务) with transaction.atomic(): # 9.1 更新新打手扩展表 new_dashou_profile.zhuangtai = 0 # 设为接单中 new_dashou_profile.jiedanzongliang += 1 new_dashou_profile.jinrijiedan += 1 new_dashou_profile.jinyuejiedan += 1 new_dashou_profile.save() # 9.2 处理原打手(如果存在) old_dashou_id = order.jiedan_dashou_id if old_dashou_id: try: old_dashou_user = UserMain.objects.get(yonghuid=old_dashou_id) old_dashou_profile = old_dashou_user.dashou_profile old_dashou_profile.zhuangtai = 1 # 恢复空闲 old_dashou_profile.tuikuanliang += 1 # 原打手退款总量+1(业务逻辑) old_dashou_profile.save() logger.info(f"原打手 {old_dashou_id} 状态已更新") except (UserMain.DoesNotExist, ObjectDoesNotExist): logger.warning(f"原打手 {old_dashou_id} 不存在或信息不完整,跳过更新") except Exception as e: logger.error(f"更新原打手时异常: {str(e)}", exc_info=True) # 9.3 更新订单 order.jiedan_dashou_id = new_dashou_id order.zhuangtai = 2 # 进行中 order.clkf = current_user.yonghuid # 记录处理客服ID order.save() return Response({'code': 0, 'msg': '更换打手成功'}) class KefuRecoverOrderView(APIView): """ 客服恢复订单接口(仅状态9可恢复) 请求:POST /yonghu/kefuhf 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单 try: order = Dingdan.objects.get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}) # 4. 验证订单状态是否为9(未付款) if order.zhuangtai != 9: return Response({'code': 400, 'msg': '订单状态不允许恢复'}) # 5. 根据是否有指定ID决定新状态 if order.zhiding_id: new_status = 7 # 指定中 else: new_status = 1 # 下单中 # 6. 更新订单 order.zhuangtai = new_status order.clkf = current_user.yonghuid order.save() return Response({'code': 0, 'msg': '恢复成功', 'data': {'new_status': new_status}}) # dingdan/views.py import logging import random import hashlib import requests import xmltodict import time from decimal import Decimal from django.db import transaction, IntegrityError from django.conf import settings from utils.xcx_sys_config import wx_cfg from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from yonghu.models import UserMain, UserKefu, UserDashou from dingdan.models import Dingdan, DingdanPingtai, Tuikuanjilu # 注意:Tuikuanjilu 模型未在本次提供,但假设存在 logger = logging.getLogger(__name__) from dingdan.utils import ( update_daily_income, sync_order_to_partners, update_daily_payout,) import hmac class KefuPlatformRefundView(APIView): """ 虚拟支付退款接口(代币订单,使用 refund_order 接口) 业务逻辑与原 KefuPlatformRefundView 完全一致 微信 API:POST /xpay/refund_order 文档:https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/virtual-payment/refund_order.html """ permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证(与原逻辑完全一致) current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单并加锁 try: with transaction.atomic(): order = Dingdan.objects.select_related('pingtai_kuozhan').select_for_update().get(dingdan_id=dingdan_id) # 4. 验证平台订单 if order.fadan_pingtai != 1: return Response({'code': 400, 'msg': '该订单不是平台订单'}, status=status.HTTP_400_BAD_REQUEST) # 5. 验证状态允许退款(沿用原有白名单) allowed_statuses = [1, 2, 4, 7, 8] if order.zhuangtai not in allowed_statuses: return Response({'code': 400, 'msg': '订单状态不允许退款'}, status=status.HTTP_400_BAD_REQUEST) # 6. 获取退款所需关键信息 jiedan_dashou_id = order.jiedan_dashou_id # 从订单扩展表获取老板 openid(退款API需要) laoban_openid = None laoban_id = order.pingtai_kuozhan.laoban_id if hasattr(order, 'pingtai_kuozhan') else None if laoban_id: laoban_user = UserMain.objects.filter(yonghuid=laoban_id).first() if laoban_user: laoban_openid = getattr(laoban_user, 'openid', None) if not laoban_openid: return Response({'code': 400, 'msg': '未找到用户openid,无法退款'}, status=status.HTTP_400_BAD_REQUEST) # 7. 调用虚拟支付退款接口(refund_order) refund_result = self.call_virtual_refund( dingdan_id=order.dingdan_id, jine=order.jine, openid=laoban_openid, wechat_transaction_id=getattr(order, 'wechat_transaction_id', None) ) if refund_result['code'] != 0: logger.error(f"虚拟支付退款失败,订单 {dingdan_id},原因:{refund_result['msg']}") return Response({'code': 400, 'msg': refund_result['msg']}, status=status.HTTP_400_BAD_REQUEST) # ========== 以下数据库操作与原逻辑完全一致(全部保留) ========== # 8. 更新订单状态为5(已退款) order.zhuangtai = 5 order.clkf = current_user.phone if tuikuan_liyou: order.tkly = tuikuan_liyou order.save() # 9. 更新打手数据 if jiedan_dashou_id: try: dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) dashou_profile = dashou_user.dashou_profile dashou_profile.tuikuanliang += 1 dashou_profile.zhuangtai = 1 dashou_profile.save() logger.info(f"平台退款:打手 {jiedan_dashou_id} 退款订单+1") except (UserMain.DoesNotExist, ObjectDoesNotExist): logger.warning(f"平台退款:打手 {jiedan_dashou_id} 不存在或扩展表缺失") # 10. 更新退款记录表 try: refund_record, created = Tuikuanjilu.objects.get_or_create( dingdan_id=dingdan_id, defaults={ 'dashouid': jiedan_dashou_id or '', 'qingqiuid': '', 'chuliid': phone, 'liyou': tuikuan_liyou or '客服退款', 'sqzhuangtai': 1, 'jine': order.jine, 'dashou_fencheng': order.dashou_fencheng or 0, 'jieshao': order.jieshao or '', 'beizhu': '客服平台退款(虚拟支付)', 'nicheng': order.nicheng or '', } ) if not created: refund_record.sqzhuangtai = 1 refund_record.chuliid = phone if tuikuan_liyou: refund_record.liyou = tuikuan_liyou refund_record.save() except Exception as e: logger.error(f"平台退款更新退款记录异常: {str(e)}", exc_info=True) # 11. 商品和店铺每日统计 try: if order.shangpin_id: pingtai_shouyi = Decimal('0.00') dianpu_shouyi = Decimal('0.00') if hasattr(order, 'pingtai_kuozhan'): pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00') dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') update_shangpin_daily_stat( shangpin_id=order.shangpin_id, caozuo_leixing=3, dingdan_jiage=order.jine, pingtai_shouyi=pingtai_shouyi, dianpu_zongshouyi=dianpu_shouyi ) logger.info(f"商品统计更新完成,商品ID: {order.shangpin_id}") dianpu_id = None if hasattr(order, 'pingtai_kuozhan'): dianpu_id = order.pingtai_kuozhan.dianpu_id if dianpu_id: dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') update_dianpu_daily_stat( dianpu_id=dianpu_id, caozuo_leixing=3, dingdan_jiage=order.jine, dianpu_shouyi=dianpu_shouyi ) logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}") except Exception as e: logger.error(f"商品/店铺统计更新失败: {e}") logger.error(traceback.format_exc()) # 12. 更新客服统计数据 kefu.jinrichuli += 1 kefu.jinyuechuli += 1 kefu.zongchuli += 1 kefu.save() # 13. 更新退款统计 update_daily_payout(order.jine) # 14. 打手每日统计(退款) try: from houtai.utils import update_dashou_daily_by_action update_dashou_daily_by_action( yonghuid=order.jiedan_dashou_id, amount=order.dashou_fencheng, action=3 ) logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功") except Exception as e: logger.error(f"打手每日统计更新失败: {e}") logger.info(f"虚拟支付退款成功,订单 {dingdan_id},退款单号 {refund_result.get('refund_id')}") return Response({'code': 0, 'msg': '退款成功'}) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f"虚拟支付退款接口异常: {str(e)}", exc_info=True) return Response({'code': 500, 'msg': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # ==================== 核心退款方法 ==================== def call_virtual_refund(self, dingdan_id, jine, openid, wechat_transaction_id=None): """ 调用虚拟支付退款接口 /xpay/refund_order :param dingdan_id: 商户订单号(order_id) :param jine: 退款金额(元) :param openid: 支付用户的 openid :param wechat_transaction_id: 微信支付交易单号(wx_order_id),若有则优先使用 :return: {'code': 0, 'msg': '成功', 'refund_id': 'xxx'} 或 {'code': 非0, 'msg': '错误信息'} """ uri = "/xpay/refund_order" base_url = "https://api.weixin.qq.com" appkey = settings.VP_APPKEY_SANDBOX if settings.VP_ENV == 1 else settings.VP_APPKEY offer_id = settings.VP_OFFER_ID # 金额:元 -> 分 refund_fee = int(float(jine) * 100) # 退款单号:R + 时间戳 + 6位随机数,保证唯一且长度合适 refund_order_id = f"R{int(time.time())}{random.randint(100000, 999999)}" # 用户 IP x_forwarded_for = self.request.META.get('HTTP_X_FORWARDED_FOR') user_ip = x_forwarded_for.split(',')[0].strip() if x_forwarded_for else self.request.META.get('REMOTE_ADDR', '127.0.0.1') # 请求体构造(优先使用 wx_order_id,若有的话) body_data = { "openid": openid, "env": settings.VP_ENV, "order_id": dingdan_id, # 商户订单号 "refund_order_id": refund_order_id, # 本次退款单号 "left_fee": refund_fee, # 订单剩余可退金额(全退时等于订单金额) "refund_fee": refund_fee, # 本次退款金额 "biz_meta": "客服退款", # 业务备注 "refund_reason": 5, # 退款原因:5 其他 "req_from": 1 # 退款来源:1 人工客服 } # 如果有微信支付交易单号,优先传入(可选) if wechat_transaction_id: body_data["wx_order_id"] = wechat_transaction_id body = json.dumps(body_data, separators=(",", ":")) # 签名:pay_sig msg = uri + "&" + body pay_sig = hmac.new(appkey.encode("utf-8"), msg.encode("utf-8"), hashlib.sha256).hexdigest() # 获取 access_token access_token = self._get_access_token() # 构建 URL url = f"{base_url}{uri}?access_token={access_token}&pay_sig={pay_sig}" # ========== 调试日志(保留,方便排查) ========== logger.info("================== 退款调试信息 ==================") logger.info(f"[退款] 订单号 (dingdan_id): {dingdan_id}") logger.info(f"[退款] 微信交易号 (wx_order_id): {wechat_transaction_id}") logger.info(f"[退款] 退款金额 (分): {refund_fee}") logger.info(f"[退款] 退款单号: {refund_order_id}") logger.info(f"[退款] 请求 URL: {url}") logger.info(f"[退款] 请求 Body: {body}") logger.info("==================================================") headers = {"Content-Type": "application/json"} try: response = requests.post(url, data=body, headers=headers, timeout=10) result = response.json() logger.info(f"[退款] 微信返回结果: {result}") if result.get("errcode") == 0: return {'code': 0, 'msg': '退款成功', 'refund_id': refund_order_id} else: errcode = result.get('errcode') errmsg = result.get('errmsg', '未知错误') # 常见错误码提示 if errcode == 268490002: errmsg = "参数错误,请检查订单号是否正确、金额是否匹配、openid是否正确" elif errcode == 268490003: errmsg = "签名错误,请检查 appkey 是否正确" elif errcode == 268490004: errmsg = "订单不存在或已退款" logger.error(f"[退款] 退款失败: errcode={errcode}, errmsg={errmsg}") return {'code': 400, 'msg': f"退款失败: {errmsg}"} except requests.exceptions.RequestException as e: logger.error(f"[退款] 网络请求异常: {e}") return {'code': 500, 'msg': '微信退款请求网络异常'} def _get_access_token(self): """获取 access_token,带缓存""" cache_key = "virtual_payment_access_token" access_token = cache.get(cache_key) if not access_token: logger.info("[退款] 缓存无 access_token,重新获取") resp = requests.get( "https://api.weixin.qq.com/cgi-bin/token", params={ "grant_type": "client_credential", "appid": wx_cfg.WEIXIN_APPID, "secret": wx_cfg.WEIXIN_SECRET, }, timeout=10, ) data = resp.json() if "access_token" in data: access_token = data["access_token"] cache.set(cache_key, access_token, data.get("expires_in", 7200) - 300) logger.info(f"[退款] access_token 已更新") else: raise Exception(f"获取 access_token 失败: {data}") return access_token '''class KefuPlatformRefundView(APIView): """ 客服平台订单退款接口(调用微信支付) 请求:POST /yonghu/kefupttk 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID", "tuikuan_liyou": "退款理由(可选)" } 认证:JWT 返回:code=0 成功,code=其他 失败 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单并加锁 try: with transaction.atomic(): order = Dingdan.objects.select_related('pingtai_kuozhan').select_for_update().get(dingdan_id=dingdan_id) # 4. 验证平台订单 if order.fadan_pingtai != 1: return Response({'code': 400, 'msg': '该订单不是平台订单'}) # 5. 验证状态允许退款(根据模型,状态值:1下单中,2进行中,3已完成,4退款审核,5已退款,6退款失败,7指定中,8结算中) allowed_statuses = [1, 2, 4, 7, 8] # 允许退款的已支付或处理中状态 if order.zhuangtai not in allowed_statuses: return Response({'code': 400, 'msg': '订单状态不允许退款'}) # 6. 获取微信交易号(如果存在) wechat_transaction_id = getattr(order, 'wechat_transaction_id', None) # 7. 调用微信退款接口 refund_result = self.call_wechat_refund( dingdan_id=order.dingdan_id, jine=order.jine, transaction_id=wechat_transaction_id ) if refund_result['code'] != 0: # 微信退款失败,直接返回错误,数据库不更新 logger.error(f"微信退款失败,订单 {dingdan_id},原因:{refund_result['msg']}") return Response({'code': 400, 'msg': refund_result['msg']}) # 8. 更新订单状态为5(已退款) order.zhuangtai = 5 order.clkf = current_user.phone # 记录处理客服手机号 if tuikuan_liyou: order.tkly = tuikuan_liyou order.save() # 9. 更新打手数据 jiedan_dashou_id = order.jiedan_dashou_id if jiedan_dashou_id: try: dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) dashou_profile = dashou_user.dashou_profile dashou_profile.tuikuanliang += 1 dashou_profile.zhuangtai = 1 # 打手状态设为空闲 dashou_profile.save() logger.info(f"平台退款:打手 {jiedan_dashou_id} 退款订单+1") except (UserMain.DoesNotExist, ObjectDoesNotExist): logger.warning(f"平台退款:打手 {jiedan_dashou_id} 不存在或扩展表缺失,跳过更新") # 10. 更新退款记录表(假设存在 Tuikuanjilu 模型,字段需根据实际调整) #更新支出记录 from dingdan.utils import update_daily_payout update_daily_payout(order.jine) try: from dingdan.models import Tuikuanjilu # 确保模型已导入 refund_record, created = Tuikuanjilu.objects.get_or_create( dingdan_id=dingdan_id, defaults={ 'dashouid': jiedan_dashou_id or '', 'qingqiuid': '', 'chuliid': phone, 'liyou': tuikuan_liyou or '客服退款', 'sqzhuangtai': 1, # 成功 'jine': order.jine, 'dashou_fencheng': order.dashou_fencheng or 0, 'jieshao': order.jieshao or '', 'beizhu': '客服平台退款', 'nicheng': order.nicheng or '', } ) if not created: refund_record.sqzhuangtai = 1 refund_record.chuliid = phone if tuikuan_liyou: refund_record.liyou = tuikuan_liyou refund_record.save() except Exception as e: logger.error(f"平台退款更新退款记录异常: {str(e)}", exc_info=True) # 11. 更新客服统计数据 kefu.jinrichuli += 1 kefu.jinyuechuli += 1 kefu.zongchuli += 1 kefu.save() logger.info(f"平台退款成功,订单 {dingdan_id},退款单号 {refund_result.get('refund_id')}") return Response({'code': 0, 'msg': '退款成功'}) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f"平台退款接口异常: {str(e)}", exc_info=True) return Response({'code': 500, 'msg': '服务器内部错误'}) def call_wechat_refund(self, dingdan_id, jine, transaction_id=None): """ 调用微信支付退款接口 :param dingdan_id: 商户订单号(我们的订单ID) :param jine: 退款金额 :param transaction_id: 微信支付订单号(优先使用) :return: {'code': 0, 'msg': '成功', 'refund_id': 'xxx'} 或 {'code': 非0, 'msg': '错误信息'} """ # 获取配置 appid = wx_cfg.WEIXIN_APPID mch_id = wx_cfg.WEIXIN_MCHID key = wx_cfg.WEIXIN_SHANGHUMIYAO cert_path = wx_cfg.WEIXIN_CERT_PATH key_path = wx_cfg.WEIXIN_KEY_PATH if not all([appid, mch_id, key, cert_path, key_path]): missing = [k for k, v in {'appid': appid, 'mch_id': mch_id, 'key': key, 'cert_path': cert_path, 'key_path': key_path}.items() if not v] logger.error(f"微信支付配置缺失: {missing}") return {'code': 500, 'msg': f'系统支付配置缺失: {missing}'} # 生成退款单号 out_refund_no = self.generate_refund_no(dingdan_id) total_fee = int(float(jine) * 100) refund_fee = total_fee refund_desc = '客服退款' # 构造请求参数 params = { 'appid': appid, 'mch_id': mch_id, 'nonce_str': self.generate_nonce_str(), 'out_refund_no': out_refund_no, 'total_fee': total_fee, 'refund_fee': refund_fee, 'refund_desc': refund_desc, } if transaction_id: params['transaction_id'] = transaction_id else: params['out_trade_no'] = dingdan_id # 使用订单ID作为商户订单号 # 生成签名 sorted_keys = sorted(params.keys()) stringA = '&'.join([f"{k}={params[k]}" for k in sorted_keys]) stringSignTemp = f"{stringA}&key={key}" sign = hashlib.md5(stringSignTemp.encode('utf-8')).hexdigest().upper() params['sign'] = sign xml_data = self.dict_to_xml(params) url = 'https://api.mch.weixin.qq.com/secapi/pay/refund' try: response = requests.post( url, data=xml_data, cert=(cert_path, key_path), timeout=10 ) result = xmltodict.parse(response.content)['xml'] logger.info(f"微信退款响应: {result}") except Exception as e: logger.error(f"微信退款请求异常: {str(e)}", exc_info=True) return {'code': 500, 'msg': '微信退款请求异常'} if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS': return {'code': 0, 'msg': 'success', 'refund_id': out_refund_no} else: err_msg = result.get('err_code_des', result.get('return_msg', '未知错误')) logger.warning(f"微信退款失败: {err_msg}") return {'code': 400, 'msg': err_msg} def generate_nonce_str(self): import string return ''.join(random.choices(string.ascii_letters + string.digits, k=32)) def generate_refund_no(self, dingdan_id): timestamp = str(int(time.time())) rand = ''.join(random.choices('0123456789', k=4)) return f"{dingdan_id}REF{timestamp}{rand}" def dict_to_xml(self, data): xml = [''] for k, v in data.items(): xml.append(f'<{k}>{v}') xml.append('') return ''.join(xml)''' # dingdan/views.py import logging from django.db import transaction, IntegrityError from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from yonghu.models import UserMain, UserKefu, UserDashou, UserShangjia from dingdan.models import Dingdan, DingdanShangjia, Tuikuanjilu logger = logging.getLogger(__name__) class KefuRejectRefundView(APIView): """ 客服拒绝退款接口 请求:POST /yonghu/kefujjtk 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID", "jujue_liyou": "拒绝理由(可选)" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() jujue_liyou = request.data.get('jujue_liyou', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单并加锁 try: with transaction.atomic(): order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id) # 4. 验证订单状态为退款审核中(4) if order.zhuangtai != 4: return Response({'code': 400, 'msg': '订单状态不是退款审核中'}) # 获取必要字段 jiedan_dashou_id = order.jiedan_dashou_id dashou_fencheng = order.dashou_fencheng fadan_pingtai = order.fadan_pingtai # 5. 验证打手分成金额是否存在 if dashou_fencheng is None: return Response({'code': 400, 'msg': '订单缺少打手分成金额'}) # 6. 更新订单状态为退款失败(6) order.zhuangtai = 6 order.clkf = current_user.phone # 记录处理客服手机号 if jujue_liyou: order.tkly = jujue_liyou order.save() # 7. 给打手结算(返还分成) if jiedan_dashou_id: try: dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) dashou_profile = dashou_user.dashou_profile # 增加打手相关统计数据 dashou_profile.chengjiaozongliang += 1 dashou_profile.yue += dashou_fencheng dashou_profile.zonge += dashou_fencheng dashou_profile.jinrishouyi += dashou_fencheng dashou_profile.jinyueshouyi += dashou_fencheng dashou_profile.save() logger.info(f"拒绝退款:打手 {jiedan_dashou_id} 获得分成 {dashou_fencheng}") except (UserMain.DoesNotExist, AttributeError): logger.warning(f"拒绝退款:打手 {jiedan_dashou_id} 不存在或扩展表缺失,跳过结算") # 8. 如果是商家发单,更新商家成交订单数量 if fadan_pingtai == 2: try: # 获取商家订单扩展表 shangjia_kuozhan = order.shangjia_kuozhan shangjia_id = shangjia_kuozhan.shangjia_id if shangjia_id: shangjia_user = UserMain.objects.get(yonghuid=shangjia_id) shangjia_profile = shangjia_user.shop_profile shangjia_profile.chengjiao += 1 shangjia_profile.save() logger.info(f"拒绝退款:商家 {shangjia_id} 成交订单+1") except (ObjectDoesNotExist, UserMain.DoesNotExist, AttributeError): logger.warning(f"拒绝退款:商家信息不存在,跳过商家更新") # 9. 更新退款记录表 try: refund_record = Tuikuanjilu.objects.get(dingdan_id=dingdan_id) refund_record.sqzhuangtai = 2 # 2表示拒绝 refund_record.chuliid = phone refund_record.bhliyou = jujue_liyou # 记录驳回理由(假设模型有此字段) refund_record.save() logger.info(f"拒绝退款:更新退款记录表,订单 {dingdan_id}") except Tuikuanjilu.DoesNotExist: # 如果退款记录不存在,可以创建一条记录(可选) Tuikuanjilu.objects.create( dingdan_id=dingdan_id, dashouid=jiedan_dashou_id or '', qingqiuid='', chuliid=phone, liyou=jujue_liyou or '客服拒绝退款', sqzhuangtai=2, jine=order.jine, dashou_fencheng=dashou_fencheng or 0, jieshao=order.jieshao or '', beizhu='客服拒绝退款', nicheng=order.nicheng or '', bhliyou=jujue_liyou or '', # 假设模型有 bhliyou 字段 ) except Exception as e: logger.error(f"拒绝退款更新退款记录异常: {str(e)}", exc_info=True) # 10. 更新客服统计数据 kefu.jinrichuli += 1 kefu.jinyuechuli += 1 kefu.zongchuli += 1 kefu.save() return Response({'code': 0, 'msg': '拒绝退款成功'}) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f"拒绝退款接口异常: {str(e)}", exc_info=True) return Response({'code': 500, 'msg': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) class KefuMerchantRefundView(APIView): """ 客服商家订单退款接口(发单平台=2) 请求:POST /yonghu/kefusjtk 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID", "tuikuan_liyou": "退款理由(可选)" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip() # 客服身份验证(同前,省略以节省篇幅,但实际必须完整) # ... 验证 if not phone or not dingdan_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 查询订单,预加载商家扩展表 try: order = Dingdan.objects.select_related('shangjia_kuozhan').get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) # 验证商家订单 if order.fadan_pingtai != 2: return Response({'code': 400, 'msg': '该订单不是商家订单'}, status=status.HTTP_400_BAD_REQUEST) # 验证状态允许退款 allowed_statuses = [1, 7, 2, 8, 4] if order.zhuangtai not in allowed_statuses: return Response({'code': 400, 'msg': '订单状态不允许退款'}, status=status.HTTP_400_BAD_REQUEST) # 获取必要数据 jiedan_dashou_id = order.jiedan_dashou_id jine = order.jine if jine is None: return Response({'code': 400, 'msg': '订单金额缺失'}, status=status.HTTP_400_BAD_REQUEST) try: shangjia_kuozhan = order.shangjia_kuozhan shangjia_id = shangjia_kuozhan.shangjia_id except ObjectDoesNotExist: return Response({'code': 400, 'msg': '订单商家信息不存在'}, status=status.HTTP_400_BAD_REQUEST) with transaction.atomic(): # 更新订单 order.zhuangtai = 5 order.clkf = current_user.phone if tuikuan_liyou: order.tkly = tuikuan_liyou order.save() # 更新打手 if jiedan_dashou_id: try: dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) dashou_profile = dashou_user.dashou_profile dashou_profile.tuikuanliang += 1 dashou_profile.zhuangtai = 1 dashou_profile.save() except (UserMain.DoesNotExist, ObjectDoesNotExist): logger.warning(f"商家退款:打手 {jiedan_dashou_id} 不存在,跳过更新") # 更新商家 try: shangjia_user = UserMain.objects.get(yonghuid=shangjia_id) shangjia_profile = shangjia_user.shop_profile shangjia_profile.tuikuan += 1 shangjia_profile.yue += jine shangjia_profile.save() except (UserMain.DoesNotExist, ObjectDoesNotExist): logger.warning(f"商家退款:商家 {shangjia_id} 不存在,跳过更新") # 更新退款记录表 try: refund_record = Tuikuanjilu.objects.get(dingdan_id=dingdan_id) refund_record.sqzhuangtai = 1 refund_record.chuliid = phone refund_record.save() except Tuikuanjilu.DoesNotExist: pass # 更新客服统计 kefu = current_user.kefu_profile kefu.jinrichuli += 1 kefu.jinyuechuli += 1 kefu.zongchuli += 1 kefu.save() return Response({'code': 0, 'msg': '退款成功'}) '''class KefuMerchantRefundView(APIView): """ 客服商家订单退款接口(发单平台=2) 请求:POST /yonghu/kefusjtk 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID", "tuikuan_liyou": "退款理由(可选)" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() tuikuan_liyou = request.data.get('tuikuan_liyou', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 客服身份验证(同前,省略以节省篇幅,但实际必须完整) current_user = request.user # ... 验证 if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 查询订单,预加载商家扩展表 try: order = Dingdan.objects.select_related('shangjia_kuozhan').get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) # 验证商家订单 if order.fadan_pingtai != 2: return Response({'code': 400, 'msg': '该订单不是商家订单'}, status=status.HTTP_400_BAD_REQUEST) # 验证状态允许退款 allowed_statuses = [1, 7, 2, 8, 4] if order.zhuangtai not in allowed_statuses: return Response({'code': 400, 'msg': '订单状态不允许退款'}, status=status.HTTP_400_BAD_REQUEST) # 获取必要数据 jiedan_dashou_id = order.jiedan_dashou_id jine = order.jine if jine is None: return Response({'code': 400, 'msg': '订单金额缺失'}, status=status.HTTP_400_BAD_REQUEST) try: shangjia_kuozhan = order.shangjia_kuozhan shangjia_id = shangjia_kuozhan.shangjia_id except ObjectDoesNotExist: return Response({'code': 400, 'msg': '订单商家信息不存在'}, status=status.HTTP_400_BAD_REQUEST) with transaction.atomic(): # 更新订单 order.zhuangtai = 5 order.clkf = current_user.phone if tuikuan_liyou: order.tkly = tuikuan_liyou order.save() # 更新打手 if jiedan_dashou_id: try: dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) dashou_profile = dashou_user.dashou_profile dashou_profile.tuikuanliang += 1 dashou_profile.zhuangtai = 1 dashou_profile.save() except (UserMain.DoesNotExist, ObjectDoesNotExist): logger.warning(f"商家退款:打手 {jiedan_dashou_id} 不存在,跳过更新") # 更新商家 try: shangjia_user = UserMain.objects.get(yonghuid=shangjia_id) shangjia_profile = shangjia_user.shop_profile shangjia_profile.tuikuan += 1 shangjia_profile.yue += jine shangjia_profile.save() except (UserMain.DoesNotExist, ObjectDoesNotExist): logger.warning(f"商家退款:商家 {shangjia_id} 不存在,跳过更新") # 更新退款记录表 try: refund_record = Tuikuanjilu.objects.get(dingdan_id=dingdan_id) refund_record.sqzhuangtai = 1 refund_record.chuliid = phone refund_record.save() except Tuikuanjilu.DoesNotExist: pass # 更新客服统计 kefu = current_user.kefu_profile kefu.jinrichuli += 1 kefu.jinyuechuli += 1 kefu.zongchuli += 1 kefu.save() return Response({'code': 0, 'msg': '退款成功'})''' logger = logging.getLogger(__name__) class KefuRejectSettlementView(APIView): """ 客服拒绝结算接口(仅平台订单,状态2/8) 请求:POST /yonghu/kefujjjs 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID", "tkly": "拒绝结算理由(必填)" } 认证:JWT 返回:code=0 成功,其他失败 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() tkly = request.data.get('tkly', '').strip() if not phone or not dingdan_id or not tkly: return Response({'code': 401, 'msg': '参数不完整'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单,预加载平台扩展表 try: order = Dingdan.objects.select_related('pingtai_kuozhan').get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) # 4. 校验订单条件:平台发单(1)且状态为2或8 if order.fadan_pingtai != 1 or order.zhuangtai not in [2, 8]: return Response({'code': 400, 'msg': '仅平台发单且状态为进行中(2)或结算中(8)的订单可拒绝结算'}, status=status.HTTP_400_BAD_REQUEST) jiedan_dashou_id = order.jiedan_dashou_id if not jiedan_dashou_id: return Response({'code': 400, 'msg': '订单暂无接单打手'}, status=status.HTTP_400_BAD_REQUEST) # 5. 使用事务更新 with transaction.atomic(): # 5.1 更新订单主表 order.zhuangtai = 5 # 已退款 order.tkly = tkly # 拒绝理由 order.clkf = phone # 记录客服手机号 order.save() # 5.2 更新平台订单扩展表:标记为拒绝结算(假设字段 jjjs_biaoshi) # 注意:需要确保 DingdanPingtai 模型中已添加 jjjs_biaoshi 字段,IntegerField,默认0 if hasattr(order, 'pingtai_kuozhan') and order.pingtai_kuozhan: pingtai_ext = order.pingtai_kuozhan pingtai_ext.jjjs_biaoshi = 1 # 1表示拒绝结算 pingtai_ext.save() else: # 理论上应该存在,但若不存在则记录日志 logger.warning(f"订单 {dingdan_id} 缺少平台扩展表,无法标记拒绝结算") # 5.3 更新打手扩展表 try: dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) dashou_profile = dashou_user.dashou_profile dashou_profile.zhuangtai = 1 # 打手状态设为空闲 dashou_profile.tuikuanliang += 1 # 退款订单总量+1 dashou_profile.save() logger.info(f"拒绝结算:打手 {jiedan_dashou_id} 退款订单+1,状态空闲") except (UserMain.DoesNotExist, ObjectDoesNotExist): logger.warning(f"拒绝结算:打手 {jiedan_dashou_id} 不存在或扩展表缺失,跳过更新") except Exception as e: logger.error(f"拒绝结算更新打手异常: {str(e)}", exc_info=True) # 5.4 更新客服扩展表统计数据 kefu.jinrichuli += 1 kefu.jinyuechuli += 1 kefu.zongchuli += 1 kefu.save() return Response({'code': 0, 'msg': '拒绝结算成功'}) class KefuTransferHallView(APIView): """ 客服恢复大厅(转移大厅)接口 请求:POST /yonghu/kefuzydt 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '参数不完整'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证(同前,略,但必须完整实现) current_user = request.user if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单 try: order = Dingdan.objects.get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) # 4. 校验订单状态(允许状态:2,8,4) if order.zhuangtai not in [2, 8, 4]: return Response({'code': 400, 'msg': '订单状态不允许转移大厅'}) if order.jiedan_dashou_id: UserDashou.objects.filter(user__yonghuid=order.jiedan_dashou_id).update(zhuangtai=1) else: pass # 5. 根据是否有指定ID决定新状态 if order.zhiding_id: new_status = 7 # 指定中 else: new_status = 1 # 下单中 # 6. 使用事务更新 with transaction.atomic(): # 清空接单打手ID,更新状态,记录客服 order.jiedan_dashou_id = None order.zhuangtai = new_status order.clkf = phone order.save() return Response({'code': 0, 'msg': '转移大厅成功'}) logger = logging.getLogger(__name__) from dingdan.utils import update_daily_dispatch_stat from dingdan.models import PreSettlement logger = logging.getLogger(__name__) logger = logging.getLogger(__name__) from shangpin.utils import update_shangpin_daily_stat from shangdian.utils import update_dianpu_daily_stat # 注意:您之前写的店铺统计方法名是 update_dianpu_daily_stat class KefuForceCompleteView(APIView): """ 客服强制结单接口(仅本地订单) 请求:POST /yonghu/kefuqiangzhijiedan 参数:{"phone": "客服手机号", "dingdan_id": "订单ID"} 认证:JWT,仅限状态正常的客服 逻辑: 1. 验证客服身份及状态 2. 查询订单并加锁(事务内) 3. 校验订单状态是否为8(结算中) 4. 校验订单必须有接单打手且打手分成金额有效 5. 给打手增加余额、成交统计、今日/本月收益 6. 如果是商家发单,更新商家成交订单数量 7. 更新订单状态为3(已完成) 8. 更新客服处理统计 9. 返回成功 """ permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '参数缺失'}) # 1. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}) if current_user.user_type != 'kefu': return Response({'code': 401, 'msg': '认证失败'}) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}) if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '客服状态异常'}) # 2. 订单处理(事务内) guanshi_settle_ctx = None try: with transaction.atomic(): order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id) # 3. 校验订单状态 if order.zhuangtai != 8: return Response({'code': 400, 'msg': f'订单状态不是结算中,当前状态: {order.zhuangtai}'}) # 4. 校验必要字段 dashou_id = order.jiedan_dashou_id dashou_fencheng = order.dashou_fencheng if not dashou_id: return Response({'code': 400, 'msg': '订单无接单打手'}) if not dashou_fencheng or dashou_fencheng <= 0: return Response({'code': 400, 'msg': '打手分成金额无效'}) # 5. 给打手结算 try: dashou_user = UserMain.objects.get(yonghuid=dashou_id) dashou_profile = dashou_user.dashou_profile # 原子更新(避免并发重复加) dashou_profile.chengjiaozongliang = F('chengjiaozongliang') + 1 dashou_profile.yue = F('yue') + dashou_fencheng dashou_profile.zonge = F('zonge') + dashou_fencheng dashou_profile.jinrishouyi = F('jinrishouyi') + dashou_fencheng dashou_profile.jinyueshouyi = F('jinyueshouyi') + dashou_fencheng dashou_profile.save(update_fields=[ 'chengjiaozongliang', 'yue', 'zonge', 'jinrishouyi', 'jinyueshouyi' ]) except (UserMain.DoesNotExist, AttributeError): return Response({'code': 400, 'msg': '打手不存在或扩展表缺失'}) # 6. 如果是商家发单,更新商家成交订单数量 if order.fadan_pingtai == 2: try: # 通过 DingdanShangjia 获取商家ID shangjia_ext = order.shangjia_kuozhan # 假设 related_name='shangjia_kuozhan' if shangjia_ext and shangjia_ext.shangjia_id: shangjia_user = UserMain.objects.get(yonghuid=shangjia_ext.shangjia_id) shangjia_profile = shangjia_user.shop_profile shangjia_profile.chengjiao = F('chengjiao') + 1 shangjia_profile.save(update_fields=['chengjiao']) # ✅ 新方法(行为驱动,更安全) from houtai.utils import update_shangjia_daily update_shangjia_daily( yonghuid=shangjia_ext.shangjia_id, amount=Decimal(str(order.jine)), action=2 # 2 = 结算 ) except ObjectDoesNotExist: # 没有商家扩展记录则忽略 pass # 7. 更新订单状态 order.zhuangtai = 3 order.clkf = phone order.save(update_fields=['zhuangtai', 'clkf']) if order.fadan_pingtai == 1: # ========== 新增:商品和店铺每日统计 ========== try: # 商品每日统计(只要有商品ID就执行) if order.shangpin_id: # 从扩展表获取已计算好的收益值(下单时已存储) pingtai_shouyi = Decimal('0.00') dianpu_shouyi = Decimal('0.00') if hasattr(order, 'pingtai_kuozhan'): pingtai_shouyi = order.pingtai_kuozhan.pingtai_shouyi or Decimal('0.00') dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') update_shangpin_daily_stat( shangpin_id=order.shangpin_id, caozuo_leixing=2, # 2 = 结单 dingdan_jiage=order.jine, pingtai_shouyi=pingtai_shouyi, dianpu_zongshouyi=dianpu_shouyi ) logger.info(f"商品统计更新完成,商品ID: {order.shangpin_id}") # 店铺每日统计(仅当存在店铺ID时执行) dianpu_id = None if hasattr(order, 'pingtai_kuozhan'): dianpu_id = order.pingtai_kuozhan.dianpu_id if dianpu_id: dianpu_shouyi = order.pingtai_kuozhan.dianpu_shouyi or Decimal('0.00') update_dianpu_daily_stat( dianpu_id=dianpu_id, caozuo_leixing=2, # 2 = 结单 dingdan_jiage=order.jine, dianpu_shouyi=dianpu_shouyi ) logger.info(f"店铺统计更新完成,店铺ID: {dianpu_id}") except Exception as e: logger.error(f"商品/店铺统计更新失败: {e}") logger.error(traceback.format_exc()) # 统计失败不影响主流程 # ============================================= # 🔥 新增:调用打手每日统计(成交) try: from houtai.utils import update_dashou_daily_by_action update_dashou_daily_by_action( yonghuid=order.jiedan_dashou_id, amount=dashou_fencheng, action=2 # 2 表示成交(结算) ) logger.info(f"打手 {order.jiedan_dashou_id} 每日统计更新成功") except Exception as e: logger.error(f"打手每日统计更新失败: {e}") # 不影响主流程 if order.fadan_pingtai == 2: guanshi_settle_ctx = (dingdan_id, dashou_id) # 8. 更新客服统计 kefu.jinrichuli = F('jinrichuli') + 1 kefu.jinyuechuli = F('jinyuechuli') + 1 kefu.zongchuli = F('zongchuli') + 1 kefu.save(update_fields=['jinrichuli', 'jinyuechuli', 'zongchuli']) if guanshi_settle_ctx: from dingdan.utils import settle_shangjia_order_guanshi_fenhong settle_order = Dingdan.objects.get(dingdan_id=guanshi_settle_ctx[0]) settle_shangjia_order_guanshi_fenhong(settle_order, guanshi_settle_ctx[1]) return Response({'code': 0, 'msg': '强制结单成功'}) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}) except Exception as e: logger.error(f"强制结单异常: {str(e)}", exc_info=True) return Response({'code': 500, 'msg': '服务器内部错误'}) '''class KefuForceCompleteView(APIView): """ 客服强制结单接口 请求:POST /yonghu/kefuqiangzhijiedan 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID" } 认证:JWT,仅限状态正常的客服 逻辑: 1. 验证客服身份及状态 2. 查询订单并加锁 3. 校验订单状态是否为8(结算中) 4. 更新订单状态为3(已完成) 5. 给打手增加分成(余额、收益统计) 6. 增加打手成交单量 7. 如果是商家订单,增加商家成交单量 8. 更新客服处理统计 9. 返回成功 """ permission_classes = [permissions.IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() if not phone or not dingdan_id: return Response( {'code': 401, 'msg': '认证失败'}, # 参数缺失视为认证失败 status=status.HTTP_401_UNAUTHORIZED ) # 2. 客服身份验证 current_user = request.user # 校验手机号是否匹配(假设 UserMain 有 phone 字段) if getattr(current_user, 'phone', '') != phone: return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) # 校验用户类型是否为客服 if current_user.user_type != 'kefu': return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) # 获取客服扩展信息 try: kefu = current_user.kefu_profile # 假设 related_name='kefu_profile' except ObjectDoesNotExist: return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) # 校验客服状态是否为正常(1 表示正常) if kefu.zhuangtai != 1: return Response( {'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED ) # 3. 查询订单并加锁(事务内) try: with transaction.atomic(): order = Dingdan.objects.select_for_update().get(dingdan_id=dingdan_id) # 4. 校验订单状态是否为结算中(8) if order.zhuangtai != 8: return Response( {'code': 400, 'msg': f'订单状态不是结算中,当前状态: {order.zhuangtai}'}, status=status.HTTP_400_BAD_REQUEST ) # 获取必要字段 jiedan_dashou_id = order.jiedan_dashou_id dashou_fencheng = order.dashou_fencheng fadan_pingtai = order.fadan_pingtai # 5. 验证打手分成金额是否存在且有效 if dashou_fencheng is None or dashou_fencheng <= 0: return Response( {'code': 400, 'msg': '订单打手分成金额无效'}, status=status.HTTP_400_BAD_REQUEST ) # 6. 更新订单状态为已完成(3) order.zhuangtai = 3 order.clkf = current_user.phone # 记录处理客服手机号 order.save() # 7. 给打手结算(增加分成) if jiedan_dashou_id: try: dashou_user = UserMain.objects.get(yonghuid=jiedan_dashou_id) dashou_profile = dashou_user.dashou_profile # 增加打手相关统计数据 dashou_profile.chengjiaozongliang += 1 # 成交订单总数 dashou_profile.yue += dashou_fencheng # 可提现余额 dashou_profile.zonge += dashou_fencheng # 总赚取金额 dashou_profile.jinrishouyi += dashou_fencheng # 今日收益 dashou_profile.jinyueshouyi += dashou_fencheng # 今月收益 dashou_profile.save() logger.info(f"强制结单:打手 {jiedan_dashou_id} 获得分成 {dashou_fencheng}") except (UserMain.DoesNotExist, AttributeError): logger.warning(f"强制结单:打手 {jiedan_dashou_id} 不存在或扩展表缺失,跳过结算") # 8. 如果是商家发单(fadan_pingtai == 2),更新商家成交订单数量 if fadan_pingtai == 2: try: # 获取商家订单扩展表(假设存在 one-to-one 关系) shangjia_kuozhan = order.shangjia_kuozhan shangjia_id = shangjia_kuozhan.shangjia_id if shangjia_id: shangjia_user = UserMain.objects.get(yonghuid=shangjia_id) shangjia_profile = shangjia_user.shop_profile shangjia_profile.chengjiao += 1 shangjia_profile.save() logger.info(f"强制结单:商家 {shangjia_id} 成交订单+1") except (ObjectDoesNotExist, UserMain.DoesNotExist, AttributeError): logger.warning(f"强制结单:商家信息不存在,跳过商家更新") # 9. 更新客服统计数据 kefu.jinrichuli += 1 kefu.jinyuechuli += 1 kefu.zongchuli += 1 kefu.save() # 事务结束,返回成功 return Response({'code': 0, 'msg': '强制结单成功'}) except Dingdan.DoesNotExist: return Response( {'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND ) except Exception as e: logger.error(f"强制结单接口异常: {str(e)}", exc_info=True) return Response( {'code': 500, 'msg': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR )''' class KefuCancelDesignationView(APIView): """ 客服取消指定接口(仅状态7) 请求:POST /yonghu/kefuqxzd 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '参数不完整'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证(同前,略,但必须完整实现) current_user = request.user if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单 try: order = Dingdan.objects.get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) # 4. 校验订单状态为7(指定中) if order.zhuangtai != 7: return Response({'code': 400, 'msg': '仅状态为指定中(7)的订单可取消指定'}, status=status.HTTP_400_BAD_REQUEST) # 5. 使用事务更新 with transaction.atomic(): # 状态改为1(下单中),清空指定ID,记录客服 order.zhuangtai = 1 order.zhiding_id = None order.clkf = phone order.save() return Response({'code': 0, 'msg': '取消指定成功'}) import logging from django.db.models import Q from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from yonghu.models import UserMain, UserKefu, UserDashou logger = logging.getLogger(__name__) class KefuGetDashouListView(APIView): """ 客服获取打手列表接口(仅打手类型) 请求:POST /yonghu/kefuhqdslb 参数:{ "phone": "客服手机号", "keyword": "搜索关键词(可选,搜索用户ID或昵称)", "page": "页码(从1开始)", "page_size": "每页数量" } 认证:JWT 返回:{ "code": 0, "data": { "list": [打手对象], "has_more": true/false } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('zhanghao', '').strip() keyword = request.data.get('keyword', '').strip() try: page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 20)) except ValueError: return Response({'code': 400, 'msg': '分页参数错误'}, status=status.HTTP_400_BAD_REQUEST) if not phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 构建查询条件(只查询打手) # 通过 UserDashou 扩展表关联 UserMain queryset = UserDashou.objects.select_related('user').all() if keyword: # 搜索用户ID或打手昵称 queryset = queryset.filter( Q(user__yonghuid__icontains=keyword) | Q(nicheng__icontains=keyword) ) # 4. 计算总数并分页 total = queryset.count() offset = (page - 1) * page_size dashou_list = queryset.order_by('-create_time')[offset:offset + page_size] # 5. 构建返回数据(只取需要的字段) result = [] for dashou in dashou_list: user = dashou.user result.append({ 'yonghuid': user.yonghuid, 'avatar': user.avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'nicheng': dashou.nicheng or '', 'zhuangtai': dashou.zhuangtai, }) # 6. 判断是否有更多数据 has_more = (offset + len(dashou_list)) < total return Response({ 'code': 0, 'data': { 'list': result, 'has_more': has_more } }) class KefuGetDashouDetailView(APIView): """ 客服获取打手详情接口 请求:POST /yonghu/kefuhqdsxq 参数:{ "phone": "客服手机号", "uid": "打手ID (yonghuid)" } 认证:JWT 返回:code=0 + 打手信息 + 会员列表 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() uid = request.data.get('uid', '').strip() if not phone or not uid: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询打手主表和扩展表 try: # 使用 select_related 预加载 dashou_profile,减少数据库查询 user_main = UserMain.objects.select_related('dashou_profile').get( yonghuid=uid ) except UserMain.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}, status=status.HTTP_404_NOT_FOUND) # 验证用户类型是否为打手(可选,但建议验证) # 如果该用户没有打手扩展表,则返回空信息(但前端会显示未注册) try: dashou = user_main.dashou_profile except ObjectDoesNotExist: # 用户存在但未注册打手身份,返回基本信息 user_info = { 'yonghuid': user_main.yonghuid, 'avatar': user_main.avatar or '', 'zaixianzhuangtai': 0, 'zhanghaozhuangtai': 0, 'zhuangtai': 0, 'nicheng': '', 'chenghao': '', 'dianhua': '', 'wechat': '', 'phone': user_main.phone or '', 'create_time': user_main.create_time, 'ip': user_main.ip or '', 'jiedanzongliang': 0, 'chengjiaozongliang': 0, 'tuikuanliang': 0, 'jinrijiedan': 0, 'yue': 0.00, 'zonge': 0.00, 'jifen': 0, 'yajin': 0.00, 'jinrishouyi': 0.00, 'jinyueshouyi': 0.00, 'yaoqingren': '', 'jieshao': '', } member_list = [] return Response({ 'code': 0, 'data': { 'user_info': user_info, 'member_list': member_list } }) # 4. 构建打手信息 user_info = { 'yonghuid': user_main.yonghuid, 'avatar': user_main.avatar or '', 'zaixianzhuangtai': dashou.zaixianzhuangtai, 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, 'zhuangtai': dashou.zhuangtai, 'nicheng': dashou.nicheng or '', 'chenghao': dashou.chenghao or '', 'dianhua': dashou.dianhua or '', 'wechat': dashou.wechat or '', 'phone': user_main.phone or '', 'create_time': user_main.create_time, 'ip': user_main.ip or '', 'jiedanzongliang': dashou.jiedanzongliang, 'chengjiaozongliang': dashou.chengjiaozongliang, 'tuikuanliang': dashou.tuikuanliang, 'jinrijiedan': dashou.jinrijiedan, 'yue': float(dashou.yue) if dashou.yue else 0.00, 'zonge': float(dashou.zonge) if dashou.zonge else 0.00, 'jifen': dashou.jifen, 'yajin': float(dashou.yajin) if dashou.yajin else 0.00, 'jinrishouyi': float(dashou.jinrishouyi) if dashou.jinrishouyi else 0.00, 'jinyueshouyi': float(dashou.jinyueshouyi) if dashou.jinyueshouyi else 0.00, 'yaoqingren': dashou.yaoqingren or '', 'jieshao': dashou.jieshao or '', } # 5. 查询打手会员信息(仅未过期) member_records = Huiyuangoumai.objects.filter( yonghu_id=uid ).order_by('-create_time') member_list = [] for record in member_records: # 调用模型方法检查是否过期 if hasattr(record, 'jiance_shifou_daoqi'): is_expired = record.jiance_shifou_daoqi() else: # 如果方法不存在,简单判断 is_expired = record.daoqi_time < timezone.now() if record.daoqi_time else True if not is_expired: # 获取会员详情 try: huiyuan = Huiyuan.objects.get(huiyuan_id=record.huiyuan_id) huiyuan_name = huiyuan.jieshao except Huiyuan.DoesNotExist: huiyuan_name = '未知会员' member_list.append({ 'id': record.id, 'huiyuan_name': huiyuan_name, 'daoqi_time': record.daoqi_time, 'is_active': record.huiyuan_zhuangtai == 1, }) return Response({ 'code': 0, 'data': { 'user_info': user_info, 'member_list': member_list } }) from yonghu.models import Xiugaijilu # 确保已导入 from houtai.utils import verify_kefu_permission logger = logging.getLogger(__name__) class KefuUpdateDashouView(APIView): """ 客服修改打手信息接口 支持操作: - jian_yue / jia_yue : 减/加打手余额 (权限001aa/001bb) - jian_yajin / jia_yajin : 减/加打手押金 (权限001aa/001bb) - jian_jifen / jia_jifen : 减/加打手积分 (权限001cc/001dd) - gai_zhanghaozhuangtai : 修改打手账号状态(封禁/解封) (权限001ee) - gai_zaixianzhuangtai : 修改打手在线状态 (权限001ee) - gai_zhuangtai : 修改打手接单状态(空闲/忙碌) (权限001ee) - jia_huiyuan / jian_huiyuan : 加/减会员 (权限001ff/001gg) """ def post(self, request): # 1. 获取前端参数 username = request.data.get('username', '').strip() dashou_id = request.data.get('dashou_id', '').strip() caozuo = request.data.get('caozuo', '').strip() value = request.data.get('value') # 金额、积分、天数等 huiyuan_id = request.data.get('huiyuan_id', '').strip() # 会员ID(加/减会员时使用) if not username or not dashou_id or not caozuo: return Response({'code': 400, 'msg': '参数不完整'}) # 2. 权限校验(统一方法) kefu, permissions = verify_kefu_permission(request, username) if kefu is None: return permissions # 直接返回错误响应 # 3. 查询打手是否存在 try: dashou_user = UserMain.objects.get(yonghuid=dashou_id, user_type='dashou') dashou_profile = dashou_user.dashou_profile except UserMain.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}) except UserDashou.DoesNotExist: return Response({'code': 404, 'msg': '打手扩展信息缺失'}) # 4. 根据操作类型执行(事务内) with transaction.atomic(): try: # 记录修改前的值(用于日志) before = { 'yue': dashou_profile.yue, 'yajin': dashou_profile.yajin, 'jifen': dashou_profile.jifen, 'zhanghaozhuangtai': dashou_profile.zhanghaozhuangtai, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'zhuangtai': dashou_profile.zhuangtai, } # ---------- 余额操作 ---------- if caozuo == 'jian_yue': if '001aa' not in permissions: return Response({'code': 403, 'msg': '无权限减打手余额'}) amount = Decimal(str(value)) if dashou_profile.yue < amount: return Response({'code': 400, 'msg': '余额不足'}) dashou_profile.yue -= amount dashou_profile.save() after_yue = dashou_profile.yue # 记录日志 Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, # 2=打手 xiugaitijiao=after_yue, xiugaitijiaoq=before['yue'], ) return Response({'code': 0, 'msg': '减余额成功', 'data': {'new_yue': str(after_yue)}}) elif caozuo == 'jia_yue': if '001bb' not in permissions: return Response({'code': 403, 'msg': '无权限加打手余额'}) amount = Decimal(str(value)) dashou_profile.yue += amount dashou_profile.save() after_yue = dashou_profile.yue Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, xiugaitijiao=after_yue, xiugaitijiaoq=before['yue'], ) return Response({'code': 0, 'msg': '加余额成功', 'data': {'new_yue': str(after_yue)}}) # ---------- 押金操作 ---------- elif caozuo == 'jian_yajin': if '001aa' not in permissions: return Response({'code': 403, 'msg': '无权限减打手押金'}) amount = Decimal(str(value)) if dashou_profile.yajin < amount: return Response({'code': 400, 'msg': '押金不足'}) dashou_profile.yajin -= amount dashou_profile.save() after_yajin = dashou_profile.yajin Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, yajin=after_yajin, yyajin=before['yajin'], ) return Response({'code': 0, 'msg': '减押金成功', 'data': {'new_yajin': str(after_yajin)}}) elif caozuo == 'jia_yajin': if '001bb' not in permissions: return Response({'code': 403, 'msg': '无权限加打手押金'}) amount = Decimal(str(value)) dashou_profile.yajin += amount dashou_profile.save() after_yajin = dashou_profile.yajin Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, yajin=after_yajin, yyajin=before['yajin'], ) return Response({'code': 0, 'msg': '加押金成功', 'data': {'new_yajin': str(after_yajin)}}) # ---------- 积分操作 ---------- elif caozuo == 'jian_jifen': if '001cc' not in permissions: return Response({'code': 403, 'msg': '无权限减打手积分'}) jifen = int(value) if dashou_profile.jifen < jifen: return Response({'code': 400, 'msg': '积分不足'}) dashou_profile.jifen -= jifen dashou_profile.save() after_jifen = dashou_profile.jifen Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, jifen=after_jifen, yjifen=before['jifen'], ) return Response({'code': 0, 'msg': '减积分成功', 'data': {'new_jifen': after_jifen}}) elif caozuo == 'jia_jifen': if '001dd' not in permissions: return Response({'code': 403, 'msg': '无权限加打手积分'}) jifen = int(value) dashou_profile.jifen += jifen dashou_profile.save() after_jifen = dashou_profile.jifen Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, jifen=after_jifen, yjifen=before['jifen'], ) return Response({'code': 0, 'msg': '加积分成功', 'data': {'new_jifen': after_jifen}}) # ---------- 状态修改 ---------- elif caozuo == 'gai_zhanghaozhuangtai': if '001ee' not in permissions: return Response({'code': 403, 'msg': '无权限修改打手账号状态'}) new_status = int(value) if new_status not in [0, 1]: return Response({'code': 400, 'msg': '状态值无效(0=封禁,1=正常)'}) dashou_profile.zhanghaozhuangtai = new_status dashou_profile.save() Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, qitashuoming=f'修改账号状态为{new_status}', ) return Response({'code': 0, 'msg': '修改账号状态成功', 'data': {'new_zhanghaozhuangtai': new_status}}) elif caozuo == 'gai_zaixianzhuangtai': if '001ee' not in permissions: return Response({'code': 403, 'msg': '无权限修改打手在线状态'}) new_status = int(value) if new_status not in [0, 1]: return Response({'code': 400, 'msg': '状态值无效(0=离线,1=在线)'}) dashou_profile.zaixianzhuangtai = new_status dashou_profile.save() Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, qitashuoming=f'修改在线状态为{new_status}', ) return Response({'code': 0, 'msg': '修改在线状态成功', 'data': {'new_zaixianzhuangtai': new_status}}) elif caozuo == 'gai_zhuangtai': if '001ee' not in permissions: return Response({'code': 403, 'msg': '无权限修改打手接单状态'}) new_status = int(value) if new_status not in [0, 1]: return Response({'code': 400, 'msg': '状态值无效(0=忙碌,1=空闲)'}) dashou_profile.zhuangtai = new_status dashou_profile.save() Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, qitashuoming=f'修改接单状态为{new_status}', ) return Response({'code': 0, 'msg': '修改接单状态成功', 'data': {'new_zhuangtai': new_status}}) # ---------- 会员操作 ---------- elif caozuo == 'jia_huiyuan': if '001ff' not in permissions: return Response({'code': 403, 'msg': '无权限给打手加会员'}) if not huiyuan_id: return Response({'code': 400, 'msg': '缺少会员ID'}) days = int(value) if value else 30 # 默认加30天 try: huiyuan = Huiyuan.objects.get(huiyuan_id=huiyuan_id) except Huiyuan.DoesNotExist: return Response({'code': 404, 'msg': '会员类型不存在'}) # 获取或创建购买记录 record, created = Huiyuangoumai.objects.select_for_update().get_or_create( yonghu_id=dashou_id, huiyuan_id=huiyuan_id, defaults={ 'huiyuan_zhuangtai': 1, 'jieshao': huiyuan.jieshao, 'daoqi_time': timezone.now() + timedelta(days=days), } ) if not created: # 延长有效期 new_daoqi = record.daoqi_time + timedelta(days=days) record.daoqi_time = new_daoqi record.huiyuan_zhuangtai = 1 record.save() # 记录修改日志 Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, huiyuants=days, huiyuan_id=huiyuan_id, qitashuoming=f'加会员 {huiyuan_id},增加{days}天', ) return Response({'code': 0, 'msg': '加会员成功', 'data': {'new_daoqi': record.daoqi_time.strftime('%Y-%m-%d %H:%M:%S')}}) elif caozuo == 'jian_huiyuan': if '001gg' not in permissions: return Response({'code': 403, 'msg': '无权限给打手减会员'}) if not huiyuan_id: return Response({'code': 400, 'msg': '缺少会员ID'}) try: record = Huiyuangoumai.objects.get(yonghu_id=dashou_id, huiyuan_id=huiyuan_id) except Huiyuangoumai.DoesNotExist: return Response({'code': 404, 'msg': '该打手未购买此会员'}) # 删除会员记录(或设置为过期,根据业务要求) record.delete() Xiugaijilu.objects.create( yonghuid=dashou_id, xiugaiid=kefu.user.phone, leixing=2, huiyuan_id=huiyuan_id, qitashuoming=f'移除会员 {huiyuan_id}', ) return Response({'code': 0, 'msg': '减会员成功'}) else: return Response({'code': 400, 'msg': '未知操作类型'}) except Exception as e: logger.error(f"修改打手信息异常: {str(e)}", exc_info=True) return Response({'code': 500, 'msg': '服务器内部错误'}) '''class KefuUpdateDashouView(APIView): """ 客服修改打手信息接口 请求:POST /yonghu/kefuxgds 参数:{ "phone": "客服手机号", "uid": "打手ID", // 以下字段可选,只传需要修改的字段 "yue": 100.00, "jifen": 200, "yajin": 50.00, "zaixianzhuangtai": 0/1, "zhanghaozhuangtai": 0/1, "zhuangtai": 1/2 } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() uid = request.data.get('uid', '').strip() if not phone or not uid: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询打手主表和扩展表 try: user_main = UserMain.objects.select_related('dashou_profile').get( yonghuid=uid ) except UserMain.DoesNotExist: return Response({'code': 404, 'msg': '打手不存在'}, status=status.HTTP_404_NOT_FOUND) try: dashou = user_main.dashou_profile except ObjectDoesNotExist: return Response({'code': 400, 'msg': '该用户未注册打手身份'}, status=status.HTTP_400_BAD_REQUEST) # 4. 收集要修改的字段 update_fields = {} editable_fields = [ 'yue', 'jifen', 'yajin', 'zaixianzhuangtai', 'zhanghaozhuangtai', 'zhuangtai' ] for field in editable_fields: if field in request.data: value = request.data.get(field) # 类型转换 if field in ['yue', 'yajin']: try: value = Decimal(str(value)) except: return Response({'code': 400, 'msg': f'{field} 格式错误'}, status=status.HTTP_400_BAD_REQUEST) elif field in ['jifen']: try: value = int(value) except: return Response({'code': 400, 'msg': f'{field} 必须为整数'}, status=status.HTTP_400_BAD_REQUEST) elif field in ['zaixianzhuangtai', 'zhanghaozhuangtai', 'zhuangtai']: try: value = int(value) if field == 'zhuangtai' and value not in [1, 2]: return Response({'code': 400, 'msg': f'{field} 值无效'}, status=status.HTTP_400_BAD_REQUEST) if field != 'zhuangtai' and value not in [0, 1]: return Response({'code': 400, 'msg': f'{field} 值无效'}, status=status.HTTP_400_BAD_REQUEST) except: return Response({'code': 400, 'msg': f'{field} 必须为数字'}, status=status.HTTP_400_BAD_REQUEST) update_fields[field] = value if not update_fields: return Response({'code': 400, 'msg': '没有要修改的字段'}, status=status.HTTP_400_BAD_REQUEST) # 5. 使用事务更新并记录修改日志 with transaction.atomic(): # 保存旧值用于记录(可选) old_values = {field: getattr(dashou, field) for field in update_fields} for field, value in update_fields.items(): setattr(dashou, field, value) dashou.save() # 记录修改日志 try: # 构建修改描述 desc_parts = [] for field in update_fields: old = old_values.get(field) new = update_fields[field] desc_parts.append(f"{field}: {old} -> {new}") desc = "; ".join(desc_parts) Xiugaijilu.objects.create( yonghuid=uid, xiugaiid=phone, # 客服手机号 leixing=2, # 打手类型 qitashuoming=desc, create_time=timezone.now() ) except Exception as e: logger.error(f"记录修改日志失败: {e}") # 不影响主流程,但记录错误 return Response({'code': 0, 'msg': '修改成功'})''' class KefuPunishmentListView(APIView): """ 客服获取处罚记录列表接口 请求:POST /yonghu/kefu_cfgl 参数:{ "phone": "客服手机号", "status": [0,3] 或 [1,2], # 状态数组 "dingdan_id": "订单ID(可选)", "dashouid": "打手ID(可选)", "page": 1, "page_size": 20 } 认证:JWT 返回:{ "code": 0, "data": { "list": [处罚记录], "total": 总数, "stats": { "zongshu": 总记录数, "daichuli": 待处理数, "yichuli": 已处理数 } } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() status_list = request.data.get('status') dingdan_id = request.data.get('dingdan_id', '').strip() dashouid = request.data.get('dashouid', '').strip() try: page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 20)) except ValueError: return Response({'code': 400, 'msg': '分页参数错误'}, status=status.HTTP_400_BAD_REQUEST) if not phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 客服身份验证 current_user = request.user # 1. 获取前端参数 username = request.data.get('phone', '').strip() if not username: return Response({'code': 400, 'msg': '缺少username'}) # 2. 公共权限校验 kefu, permissions = verify_kefu_permission(request, username) if kefu is None: return permissions # 3. 检查平台订单管理权限(002ab) if '005aa' not in permissions: return Response({'code': 403, 'msg': '您无权查看平台订单列表'}) # 3. 获取统计信息(不分页) stats = { 'zongshu': Chufajilu.objects.count(), 'daichuli': Chufajilu.objects.filter(sqzhuangtai__in=[0, 3]).count(), 'yichuli': Chufajilu.objects.filter(sqzhuangtai__in=[1, 2]).count(), } # 4. 构建查询条件 queryset = Chufajilu.objects.all() if status_list and isinstance(status_list, list): queryset = queryset.filter(sqzhuangtai__in=status_list) if dingdan_id: queryset = queryset.filter(dingdan_id=dingdan_id) if dashouid: queryset = queryset.filter(dashouid=dashouid) # 5. 分页 total = queryset.count() offset = (page - 1) * page_size records = queryset.order_by('-create_time')[offset:offset + page_size] # 6. 构建返回列表 list_data = [] for item in records: list_data.append({ 'dingdan_id': item.dingdan_id, 'dashouid': item.dashouid, 'qingqiuid': item.qingqiuid, 'cfliyou': item.cfliyou, 'sqzhuangtai': item.sqzhuangtai, 'create_time': item.create_time, 'update_time': item.update_time, # 其他字段按需添加 }) return Response({ 'code': 0, 'data': { 'list': list_data, 'total': total, 'stats': stats } }) class KefuPunishmentDetailView(APIView): """ 客服获取处罚详情接口 请求:POST /yonghu/kefu_cf_detail 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID" } 认证:JWT 返回:code=0 + 处罚记录详情(含图片) """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 查询处罚记录(按创建时间倒序取最新一条) try: record = Chufajilu.objects.filter(dingdan_id=dingdan_id).order_by('-create_time').first() if not record: return Response({'code': 404, 'msg': '处罚记录不存在'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f"查询处罚记录失败: {e}") return Response({'code': 500, 'msg': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # 查询商家证据图片 zhengju_images = [] shensu_images = [] try: # 商家证据图片:上传者为请求人 (qingqiuid) zhengju_qs = Chufatupian.objects.filter(dingdan_id=dingdan_id, yonghuid=record.qingqiuid) zhengju_images = [item.tupian for item in zhengju_qs if item.tupian] # 打手申诉图片:上传者为打手 (dashouid) shensu_qs = Chufatupian.objects.filter(dingdan_id=dingdan_id, yonghuid=record.dashouid) shensu_images = [item.tupian for item in shensu_qs if item.tupian] except Exception as e: logger.error(f"查询图片失败: {e}") # 构建返回数据 data = { 'dingdan_id': record.dingdan_id, 'dashouid': record.dashouid, 'qingqiuid': record.qingqiuid, 'chuliid': record.chuliid, 'cfliyou': record.cfliyou, 'sqzhuangtai': record.sqzhuangtai, 'bhliyou': record.bhliyou, 'jifen': record.jifen, 'ssliyou': record.ssliyou or '', 'zhengju_tupian': zhengju_images, 'shensu_tupian': shensu_images, 'create_time': record.create_time, 'update_time': record.update_time, } return Response({'code': 0, 'data': data}) from django.db import transaction, IntegrityError from decimal import Decimal class KefuPunishmentActionView(APIView): """ 客服同意/驳回处罚接口 请求:POST /yonghu/kefu_cf_action 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID", "action": 1(同意) / 2(驳回), "reject_reason": "驳回理由(驳回时必填)" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() action = request.data.get('action') reject_reason = request.data.get('reject_reason', '').strip() if not phone or not dingdan_id or action not in [1, 2]: return Response({'code': 401, 'msg': '参数错误'}, status=status.HTTP_400_BAD_REQUEST) if action == 2 and not reject_reason: return Response({'code': 400, 'msg': '驳回理由不能为空'}, status=status.HTTP_400_BAD_REQUEST) # 客服身份验证(完整验证,此处略) current_user = request.user if getattr(current_user, 'phone', '') != phone: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile if kefu.zhuangtai != 1: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) except ObjectDoesNotExist: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 查询待处理的处罚记录(状态0或3) try: record = Chufajilu.objects.filter( dingdan_id=dingdan_id, sqzhuangtai__in=[0, 3] ).order_by('-create_time').first() if not record: exists = Chufajilu.objects.filter(dingdan_id=dingdan_id).exists() if exists: return Response({'code': 400, 'msg': '该处罚记录已处理'}, status=status.HTTP_400_BAD_REQUEST) else: return Response({'code': 404, 'msg': '处罚记录不存在'}, status=status.HTTP_404_NOT_FOUND) except Exception as e: logger.error(f"查询处罚记录失败: {e}") return Response({'code': 500, 'msg': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) with transaction.atomic(): # 处理重复记录:同一订单的其他待处理/申诉中记录标记为驳回 duplicate_records = Chufajilu.objects.filter( dingdan_id=dingdan_id, sqzhuangtai__in=[0, 3] ).exclude(id=record.id) for dup in duplicate_records: dup.sqzhuangtai = 2 dup.chuliid = phone dup.bhliyou = "系统自动驳回:存在重复处罚记录" dup.save() logger.info(f"自动驳回重复处罚记录 ID={dup.id}") if action == 1: # 同意 # 可选验证打手存在 if record.dashouid: try: dashou = UserDashou.objects.get(user__yonghuid=record.dashouid) except UserDashou.DoesNotExist: return Response({'code': 400, 'msg': '被处罚打手不存在'}, status=status.HTTP_400_BAD_REQUEST) record.sqzhuangtai = 1 record.chuliid = phone record.save() # 更新商家订单扩展表 try: dingdan = Dingdan.objects.get(dingdan_id=dingdan_id) if hasattr(dingdan, 'shangjia_kuozhan'): dingdan.shangjia_kuozhan.sqzhuangtai = 1 dingdan.shangjia_kuozhan.save() except Exception as e: logger.warning(f"更新商家扩展表失败: {e}") elif action == 2: # 驳回 # 返还积分 if record.jifen > 0 and record.dashouid: try: dashou = UserDashou.objects.get(user__yonghuid=record.dashouid) if dashou.jifen <= 1000: # 安全限制 dashou.jifen += record.jifen dashou.save() logger.info(f"驳回处罚,打手{record.dashouid}返还积分{record.jifen}") else: logger.warning(f"打手积分异常({dashou.jifen}),不再返还") except UserDashou.DoesNotExist: logger.warning(f"打手{record.dashouid}不存在,无法返还积分") record.sqzhuangtai = 2 record.chuliid = phone record.bhliyou = reject_reason record.save() try: dingdan = Dingdan.objects.get(dingdan_id=dingdan_id) if hasattr(dingdan, 'shangjia_kuozhan'): dingdan.shangjia_kuozhan.sqzhuangtai = 2 dingdan.shangjia_kuozhan.bhliyou = reject_reason dingdan.shangjia_kuozhan.save() except Exception as e: logger.warning(f"更新商家扩展表失败: {e}") return Response({'code': 0, 'msg': '处理成功'}) # yonghu/views.py import logging import sys from django.db import DatabaseError from django.db.models import Q, Sum from yonghu.models import Tixianjilu, TixianShenheJilu kefu_txsh_list_logger = logging.getLogger('yonghu.kefu_txsh_list') if not kefu_txsh_list_logger.handlers: _console_handler = logging.StreamHandler(sys.stderr) _console_handler.setFormatter(logging.Formatter( '[%(asctime)s] %(levelname)s %(name)s: %(message)s' )) kefu_txsh_list_logger.addHandler(_console_handler) kefu_txsh_list_logger.setLevel(logging.DEBUG) kefu_txsh_list_logger.propagate = False _TIXIAN_LIST_BASE_FIELDS = ( 'id', 'yonghuid', 'avatar', 'phone', 'nicheng', 'leixing', 'zhifu', 'skzhanghao', 'jine', 'zhuangtai', 'fangshi', 'shenheid', 'bhliyou', 'create_time', 'update_time', ) _TIXIAN_LIST_AUDIT_FIELDS = ('shenhe_jilu_id', 'shenhe_danhao') class KefuWithdrawListView(APIView): """ 客服获取提现审核列表接口 请求:POST /yonghu/kefu_txsh_list 参数:{ "phone": "客服手机号", "page": 1, "page_size": 20, "status": 1(待审核)/2(已处理), "type": 1(打手佣金)/2(管事分红)/3(组长分红)/4(审核官分佣)/5(打手押金)/6(商家余额), 可选 "payment": 1(微信)/2(支付宝) 可选, "search_uid": "用户ID搜索" 可选, "date": "YYYY-MM-DD" 可选, "min_amount": 0, # 最小提现金额(元)可选 "max_amount": 999999, # 最大提现金额(元)可选 "dakuan_mode": 1 # 打款方式:1手动打款 2自动打款,可选 } 认证:JWT 返回:{ "code": 0, "data": { "list": [提现记录], "total": 总数, "stats": { "total": 总记录数, "awaiting": 待审核数, "processed": 已处理数, "total_amount": "总金额(元)" } } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def _safe_request_params(self, request): try: return dict(request.data) except Exception: return {'raw': str(request.data)} def _shenhe_jilu_field_available(self): """探测 shenhe_jilu_id 字段是否已在数据库中就绪""" try: Tixianjilu.objects.values_list('shenhe_jilu_id', flat=True).first() return True except DatabaseError as e: kefu_txsh_list_logger.error( 'kefu_txsh_list: shenhe_jilu_id 字段不可用,将按旧表结构查询: %s', e, exc_info=True, ) return False def _list_queryset(self, q, shenhe_field_ok): """字段未迁移时 only 旧字段,避免 SELECT 不存在的列导致 500""" fields = _TIXIAN_LIST_BASE_FIELDS + ( _TIXIAN_LIST_AUDIT_FIELDS if shenhe_field_ok else () ) return Tixianjilu.objects.filter(q).only(*fields) def _resolve_row_dakuan_mode(self, item, audit_mode_map, shenhe_field_ok): """无关联审核或审核表未标记自动 → 默认手动(1)""" if not shenhe_field_ok: return 1 sid = getattr(item, 'shenhe_jilu_id', None) if not sid: return 1 if audit_mode_map.get(sid) == 2: return 2 return 1 def post(self, request): dakuan_mode_filter = request.data.get('dakuan_mode') req_params = self._safe_request_params(request) kefu_txsh_list_logger.info('kefu_txsh_list 请求开始 params=%s', req_params) try: try: page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 20)) except (TypeError, ValueError): return Response({'code': 400, 'msg': '分页参数错误'}, status=status.HTTP_400_BAD_REQUEST) if page < 1: page = 1 if page_size < 1 or page_size > 100: page_size = min(max(page_size, 1), 100) status_filter = request.data.get('status') type_filter = request.data.get('type') payment_filter = request.data.get('payment') search_uid = (request.data.get('search_uid') or '').strip() date_filter = (request.data.get('date') or '').strip() min_amount = request.data.get('min_amount') max_amount = request.data.get('max_amount') phone = (request.data.get('phone') or '').strip() if not phone: return Response({'code': 401, 'msg': '缺少客服手机号'}, status=status.HTTP_401_UNAUTHORIZED) kefu, permissions = verify_kefu_permission(request, phone) if kefu is None: return permissions if '005bb' not in permissions: return Response({'code': 403, 'msg': '您无权查看平台订单列表'}) q = Q() try: status_val = int(status_filter) if status_filter is not None and str(status_filter).strip() != '' else None except (TypeError, ValueError): status_val = None try: dakuan_mode_val = int(dakuan_mode_filter) if dakuan_mode_filter is not None and str(dakuan_mode_filter).strip() != '' else None except (TypeError, ValueError): dakuan_mode_val = None # 未传打款方式参数 → 默认手动打款(1) effective_dakuan = dakuan_mode_val if dakuan_mode_val in (1, 2) else 1 if status_val == 1: q &= Q(zhuangtai=1) elif status_val == 2: if effective_dakuan == 2: q &= Q(zhuangtai__in=[2, 3, 4, 5, 6]) else: q &= Q(zhuangtai__in=[2, 3]) if type_filter is not None and str(type_filter).strip() != '': try: type_val = int(type_filter) if type_val in [1, 2, 3, 4, 5, 6]: q &= Q(leixing=type_val) except (TypeError, ValueError): pass if payment_filter is not None and str(payment_filter).strip() != '': try: payment_val = int(payment_filter) if payment_val in [1, 2]: q &= Q(fangshi=payment_val) except (TypeError, ValueError): pass if search_uid: q &= Q(yonghuid=search_uid) if date_filter: q &= Q(create_time__date=date_filter) if min_amount is not None and str(min_amount).strip() != '': try: q &= Q(jine__gte=float(min_amount)) except (TypeError, ValueError): pass if max_amount is not None and str(max_amount).strip() != '': try: q &= Q(jine__lte=float(max_amount)) except (TypeError, ValueError): pass shenhe_field_ok = self._shenhe_jilu_field_available() if shenhe_field_ok: if effective_dakuan == 2: q &= Q(shenhe_jilu_id__isnull=False) else: q &= Q(shenhe_jilu_id__isnull=True) elif effective_dakuan == 2: kefu_txsh_list_logger.warning('kefu_txsh_list: 自动打款筛选不可用,返回空列表') return Response({ 'code': 0, 'data': { 'list': [], 'total': 0, 'stats': {'total': 0, 'awaiting': 0, 'processed': 0, 'total_amount': '0.00'}, }, }) processed_statuses = [2, 3, 4, 5, 6] if effective_dakuan == 2 else [2, 3] base_qs = self._list_queryset(q, shenhe_field_ok) total_count = base_qs.count() awaiting_count = self._list_queryset(q & Q(zhuangtai=1), shenhe_field_ok).count() processed_count = self._list_queryset( q & Q(zhuangtai__in=processed_statuses), shenhe_field_ok, ).count() agg = base_qs.aggregate(total=Sum('jine')) total_amount = agg.get('total') or 0 stats = { 'total': total_count, 'awaiting': awaiting_count, 'processed': processed_count, 'total_amount': str(round(float(total_amount), 2)), } queryset = base_qs.order_by('create_time') total = queryset.count() records = list(queryset[(page - 1) * page_size: page * page_size]) audit_ids = [ getattr(item, 'shenhe_jilu_id', None) for item in records if getattr(item, 'shenhe_jilu_id', None) ] audit_meta_map = {} if audit_ids: try: from .tixian_shenhe_services import load_audit_meta_map audit_meta_map = load_audit_meta_map(audit_ids) except Exception as e: kefu_txsh_list_logger.error( 'kefu_txsh_list: 查询审核表信息失败: %s', e, exc_info=True, ) audit_mode_map = { aid: meta.get('dakuan_mode', 2) for aid, meta in audit_meta_map.items() } list_data = [] for item in records: shenhe_jilu_id = getattr(item, 'shenhe_jilu_id', None) if shenhe_field_ok else None shenhe_danhao = (getattr(item, 'shenhe_danhao', None) or '') if shenhe_field_ok else '' if shenhe_jilu_id and not shenhe_danhao: shenhe_danhao = audit_meta_map.get(shenhe_jilu_id, {}).get('shenhe_danhao', '') list_data.append({ 'id': item.id, 'shenhe_jilu_id': shenhe_jilu_id, 'shenhe_danhao': shenhe_danhao, 'yonghuid': item.yonghuid, 'avatar': item.avatar or '', 'phone': item.phone or '', 'nicheng': item.nicheng or '', 'leixing': item.leixing, 'zhifu': item.zhifu or '', 'skzhanghao': item.skzhanghao or '', 'jine': str(item.jine) if item.jine is not None else '0.00', 'zhuangtai': item.zhuangtai, 'fangshi': item.fangshi, 'dakuan_mode': self._resolve_row_dakuan_mode(item, audit_mode_map, shenhe_field_ok), 'shenheid': item.shenheid or '', 'bhliyou': item.bhliyou or '', 'create_time': item.create_time.strftime('%Y-%m-%d %H:%M:%S') if item.create_time else '', 'update_time': item.update_time.strftime('%Y-%m-%d %H:%M:%S') if item.update_time else '', }) return Response({ 'code': 0, 'data': { 'list': list_data, 'total': total, 'stats': stats, }, }) except DatabaseError as e: kefu_txsh_list_logger.error( 'kefu_txsh_list 数据库错误 dakuan_mode=%s params=%s err=%s', dakuan_mode_filter, req_params, e, exc_info=True, ) return Response({ 'code': 500, 'msg': '数据库查询失败,请确认已执行 python manage.py migrate yonghu', }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) except Exception as e: kefu_txsh_list_logger.error( 'kefu_txsh_list 接口异常 dakuan_mode=%s params=%s err=%s', dakuan_mode_filter, req_params, e, exc_info=True, ) return Response({ 'code': 500, 'msg': '获取提现列表失败', }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) import logging from decimal import Decimal from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from yonghu.models import UserMain, UserKefu, Tixianjilu, UserDashou, UserGuanshi logger = logging.getLogger(__name__) class KefuWithdrawDetailView(APIView): """ 客服获取提现详情接口 请求:POST /yonghu/kefu_txsh_detail 参数:{ "phone": "客服手机号", "tixian_id": "提现记录ID" } 认证:JWT 返回:code=0 + 提现记录详情 + 用户扩展信息 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): phone = request.data.get('phone', '').strip() tixian_id = request.data.get('tixian_id') if not phone or not tixian_id: return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 查询提现记录 try: record = Tixianjilu.objects.get(id=tixian_id) except Tixianjilu.DoesNotExist: return Response({'code': 404, 'msg': '提现记录不存在'}, status=status.HTTP_404_NOT_FOUND) # 查询用户主表 try: user = UserMain.objects.get(yonghuid=record.yonghuid) except UserMain.DoesNotExist: user = None # 构建基础返回数据(字段必须与前端一致) data = { 'id': record.id, 'yonghuid': record.yonghuid, 'avatar': record.avatar or '', 'phone': record.phone or '', 'nicheng': record.nicheng or '', 'leixing': record.leixing, # 1打手 2管事 'zhifu': record.zhifu or '', 'skzhanghao': record.skzhanghao or '', 'jine': str(record.jine) if record.jine else '0.00', 'zhuangtai': record.zhuangtai, 'fangshi': record.fangshi, 'shenheid': record.shenheid or '', 'bhliyou': record.bhliyou or '', 'create_time': record.create_time, 'update_time': record.update_time, } # 根据提现类型添加扩展信息 if user: if record.leixing == 1: # 打手 try: dashou = UserDashou.objects.get(user=user) # 计算成交率和退款率 total = dashou.jiedanzongliang or 0 completed = dashou.chengjiaozongliang or 0 refund = dashou.tuikuanliang or 0 completion_rate = round((completed / total * 100) if total > 0 else 0) refund_rate = round((refund / completed * 100) if completed > 0 else 0) data['dashouInfo'] = { 'zhuangtai': dashou.zhuangtai, # 工作状态 'zaixianzhuangtai': dashou.zaixianzhuangtai, # 在线状态 'zhanghaozhuangtai': dashou.zhanghaozhuangtai, # 账号状态 'yue': float(dashou.yue) if dashou.yue else 0.00, # 可提现余额 'zonge': float(dashou.zonge) if dashou.zonge else 0.00, # 接单总额 'jifen': dashou.jifen, # 积分 'jiedanzongliang': dashou.jiedanzongliang, # 接单总量 'chengjiaozongliang': dashou.chengjiaozongliang, # 成交总量 'tuikuanliang': dashou.tuikuanliang, # 退款总量 'completionRate': completion_rate, 'refundRate': refund_rate, } except ObjectDoesNotExist: data['dashouInfo'] = None elif record.leixing == 2: # 管事 try: guanshi = UserGuanshi.objects.get(user=user) data['guanshiInfo'] = { 'zhuangtai': guanshi.zhuangtai, # 账号状态 'yue': float(guanshi.yue) if guanshi.yue else 0.00, 'yaogingshuliang': guanshi.yaogingshuliang, # 邀请打手总数 'jinrichongzhi': guanshi.jinrichongzhi, # 今日充值 'jinyuechongzhi': guanshi.jinyuechongzhi, # 今月充值 'chongzhifenrun': float(guanshi.chongzhifenrun) if guanshi.chongzhifenrun else 0.00, } except ObjectDoesNotExist: data['guanshiInfo'] = None return Response({'code': 0, 'data': data}) import logging import json from decimal import Decimal from django.db import transaction, IntegrityError from django.utils import timezone from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from yonghu.models import UserMain, UserKefu, Tixianjilu, UserDashou, UserGuanshi logger = logging.getLogger(__name__) class KefuWithdrawActionView(APIView): """ 客服处理提现接口(同意/拒绝)—— 仅此接口处理后台审核打款 POST /yonghu/kefu_txsh_action 单条:{ phone, action, tixian_id, yonghuid, leixing, reason? } 批量:{ phone, action, batch_list: [{tixian_id, yonghuid, leixing}, ...], reason? } 自动打款(dakuan_mode=2):同意→6待收款;拒绝→5+驳回原因+退还申请扣款额(shenqing_jine),不更新平台收支/每日统计 手动打款(dakuan_mode=1):原逻辑不变 状态要求:Tixianjilu.zhuangtai=1 且 TixianShenheJilu.zhuangtai=1(审核中),其他状态一律拒绝 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def _verify_kefu(self, request, phone): if not phone: return None, Response({'code': 401, 'msg': '手机号不能为空'}, status=status.HTTP_400_BAD_REQUEST) current_user = request.user if getattr(current_user, 'phone', '') != phone: return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: return None, Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) return current_user, None def _parse_items(self, request): """解析单条或 batch_list,返回 [{tixian_id, yonghuid, leixing}]""" batch_list = request.data.get('batch_list') if batch_list is not None: if not isinstance(batch_list, list) or len(batch_list) == 0: return None, Response({'code': 400, 'msg': 'batch_list 不能为空'}, status=status.HTTP_400_BAD_REQUEST) items = [] for idx, row in enumerate(batch_list): if not isinstance(row, dict): return None, Response({'code': 400, 'msg': f'batch_list[{idx}] 格式错误'}, status=status.HTTP_400_BAD_REQUEST) tid = row.get('tixian_id') yid = (row.get('yonghuid') or '').strip() lx = row.get('leixing') if not tid or not yid or lx is None: return None, Response( {'code': 400, 'msg': f'batch_list[{idx}] 缺少 tixian_id / yonghuid / leixing'}, status=status.HTTP_400_BAD_REQUEST, ) try: items.append({ 'tixian_id': int(tid), 'yonghuid': yid, 'leixing': int(lx), }) except (TypeError, ValueError): return None, Response({'code': 400, 'msg': f'batch_list[{idx}] 参数格式错误'}, status=status.HTTP_400_BAD_REQUEST) return items, None tixian_id = request.data.get('tixian_id') if not tixian_id: return None, Response({'code': 400, 'msg': '提现记录ID不能为空'}, status=status.HTTP_400_BAD_REQUEST) yonghuid = (request.data.get('yonghuid') or '').strip() leixing = request.data.get('leixing') try: item = {'tixian_id': int(tixian_id), 'yonghuid': yonghuid, 'leixing': int(leixing) if leixing is not None else None} except (TypeError, ValueError): return None, Response({'code': 400, 'msg': '参数格式错误'}, status=status.HTTP_400_BAD_REQUEST) return [item], None def _process_auto_item(self, item, action, reason, kefu_user): """自动打款单条处理(须在 transaction.atomic 内调用)""" from .models import TixianShenheJilu from .tixian_shenhe_services import refund_balance, sync_audit_and_jilu_status tixian_id = item['tixian_id'] req_yonghuid = item['yonghuid'] req_leixing = item['leixing'] if req_leixing not in [1, 2, 3, 4, 5, 6]: raise ValueError(f'提现记录{tixian_id}:提现类型无效') try: tixian = Tixianjilu.objects.select_for_update().get(id=tixian_id) except Tixianjilu.DoesNotExist: raise ValueError(f'提现记录{tixian_id}不存在') if tixian.yonghuid != req_yonghuid: raise ValueError(f'提现记录{tixian_id}:用户ID不匹配') if tixian.leixing != req_leixing: raise ValueError(f'提现记录{tixian_id}:提现类型不匹配') if tixian.zhuangtai != 1: raise ValueError(f'提现记录{tixian_id}:非审核中状态,无法处理') if not tixian.shenhe_jilu_id: raise ValueError(f'提现记录{tixian_id}:非自动打款审核记录') try: audit = TixianShenheJilu.objects.select_for_update().get(id=tixian.shenhe_jilu_id) except TixianShenheJilu.DoesNotExist: raise ValueError(f'提现记录{tixian_id}:审核记录不存在') if audit.yonghuid != req_yonghuid: raise ValueError(f'提现记录{tixian_id}:审核记录用户ID不匹配') if audit.leixing != req_leixing: raise ValueError(f'提现记录{tixian_id}:审核记录提现类型不匹配') if audit.zhuangtai != 1: raise ValueError(f'提现记录{tixian_id}:审核记录非审核中状态') try: user = UserMain.objects.select_for_update().get(yonghuid=req_yonghuid) except UserMain.DoesNotExist: raise ValueError(f'用户{req_yonghuid}不存在') if action == 1: sync_audit_and_jilu_status(audit, 6, shenhe_ren_id=kefu_user.yonghuid) tixian.shenheid = kefu_user.yonghuid tixian.save(update_fields=['shenheid', 'update_time']) else: sync_audit_and_jilu_status( audit, 5, bhliyou=reason, bo_hui_ren_id=kefu_user.yonghuid, ) from .tixian_shenhe_services import get_tixianjilu_refund_amount refund_amount = get_tixianjilu_refund_amount(tixian) refund_balance(user, audit.leixing, refund_amount) tixian.shenheid = kefu_user.yonghuid tixian.bhliyou = reason tixian.save(update_fields=['shenheid', 'bhliyou', 'update_time']) def _process_manual_item(self, item, action, reason, phone): """手动打款单条处理(原逻辑,须在 transaction.atomic 内调用)""" tixian_id = item['tixian_id'] try: tixian = Tixianjilu.objects.select_for_update().get(id=tixian_id) except Tixianjilu.DoesNotExist: raise ValueError(f'提现记录{tixian_id}不存在') req_yonghuid = item.get('yonghuid') req_leixing = item.get('leixing') if req_yonghuid and tixian.yonghuid != req_yonghuid: raise ValueError(f'提现记录{tixian_id}:用户ID不匹配') if req_leixing is not None and tixian.leixing != req_leixing: raise ValueError(f'提现记录{tixian_id}:提现类型不匹配') if tixian.zhuangtai != 1: raise ValueError(f'提现记录{tixian_id}:非审核中状态,无法处理') if getattr(tixian, 'shenhe_jilu_id', None): raise ValueError(f'提现记录{tixian_id}:自动打款请走自动流程') try: user = UserMain.objects.select_for_update().get(yonghuid=tixian.yonghuid) except UserMain.DoesNotExist: raise ValueError(f'用户{tixian.yonghuid}不存在') if action == 1: tixian.zhuangtai = 2 try: from dingdan.utils import update_daily_payout from .tixian_shenhe_services import record_manual_withdraw_success_stats update_daily_payout(tixian.jine) record_manual_withdraw_success_stats(user, tixian) from peizhi.models import Szjilu szjilu, _ = Szjilu.objects.get_or_create(id=1) szjilu.zongsy -= tixian.jine szjilu.zongzc += tixian.jine szjilu.jrzc += tixian.jine szjilu.update_time = timezone.now() szjilu.save() except Exception as e: logger.error(f'更新收支记录失败: {e}') else: from .tixian_shenhe_services import refund_manual_tixianjilu refund_manual_tixianjilu(user, tixian) tixian.zhuangtai = 3 tixian.bhliyou = reason tixian.shenheid = phone tixian.update_time = timezone.now() tixian.save() def post(self, request): logger.info(f'接收到提现处理请求,数据: {request.data}') phone = request.data.get('phone', '').strip() action_raw = request.data.get('action') reason = request.data.get('reason', '').strip() try: action = int(action_raw) except (TypeError, ValueError): return Response({'code': 400, 'msg': 'action 参数必须为数字'}, status=status.HTTP_400_BAD_REQUEST) if action not in [1, 2]: return Response({'code': 400, 'msg': 'action 必须为1(同意)或2(拒绝)'}, status=status.HTTP_400_BAD_REQUEST) if action == 2 and not reason: return Response({'code': 400, 'msg': '拒绝提现时理由不能为空'}, status=status.HTTP_400_BAD_REQUEST) kefu_user, err_resp = self._verify_kefu(request, phone) if err_resp: return err_resp items, err_resp = self._parse_items(request) if err_resp: return err_resp is_batch = isinstance(request.data.get('batch_list'), list) and len(request.data.get('batch_list')) > 0 try: with transaction.atomic(): for item in items: tixian_id = item['tixian_id'] try: tixian_probe = Tixianjilu.objects.select_for_update().get(id=tixian_id) except Tixianjilu.DoesNotExist: raise ValueError(f'提现记录{tixian_id}不存在') # 有关联审核记录(shenhe_jilu_id)即为 zddksh 自动打款,不依赖 dakuan_mode 字段 is_auto = bool(getattr(tixian_probe, 'shenhe_jilu_id', None)) if is_auto: self._process_auto_item(item, action, reason, kefu_user) else: if is_batch: raise ValueError(f'提现记录{tixian_id}:批量操作仅支持自动打款记录') self._process_manual_item(item, action, reason, phone) except ValueError as e: return Response({'code': 400, 'msg': str(e)}, status=status.HTTP_400_BAD_REQUEST) except Exception as e: logger.error(f'处理提现事务失败: {e}', exc_info=True) return Response({'code': 500, 'msg': f'处理失败: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) msg = '批量处理成功' if is_batch and len(items) > 1 else '处理成功' return Response({'code': 0, 'msg': msg, 'data': {'count': len(items)}}) class KefuPunishView(APIView): """ 客服处罚打手接口 请求:POST /yonghu/kefuchufa 参数:{ "phone": "客服手机号", "dingdan_id": "订单ID", "reason": "处罚原因(可选)" } 认证:JWT 返回:code=0 成功 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 获取参数 phone = request.data.get('phone', '').strip() dingdan_id = request.data.get('dingdan_id', '').strip() reason = request.data.get('reason', '').strip() if not phone or not dingdan_id: return Response({'code': 401, 'msg': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST) # 2. 客服身份验证 current_user = request.user if getattr(current_user, 'phone', '') != phone: logger.warning(f"手机号不匹配: {phone}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'kefu': logger.warning(f"用户类型非客服: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: kefu = current_user.kefu_profile except ObjectDoesNotExist: logger.warning(f"客服扩展表不存在: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) if kefu.zhuangtai != 1: logger.warning(f"客服账号已禁用: {current_user.yonghuid}") return Response({'code': 401, 'msg': '认证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询订单 try: order = Dingdan.objects.get(dingdan_id=dingdan_id) except Dingdan.DoesNotExist: return Response({'code': 404, 'msg': '订单不存在'}, status=status.HTTP_404_NOT_FOUND) # 4. 获取打手ID dashou_id = order.jiedan_dashou_id if not dashou_id: return Response({'code': 400, 'msg': '订单未接单,无法处罚打手'}, status=status.HTTP_400_BAD_REQUEST) # 5. 查询打手 try: dashou_user = UserMain.objects.get(yonghuid=dashou_id) dashou = dashou_user.dashou_profile except UserMain.DoesNotExist: return Response({'code': 404, 'msg': '打手用户不存在'}, status=status.HTTP_404_NOT_FOUND) except ObjectDoesNotExist: return Response({'code': 400, 'msg': '该用户不是打手'}, status=status.HTTP_400_BAD_REQUEST) # 6. 使用事务 with transaction.atomic(): chufa = Chufajilu.objects.create( dashouid=dashou_id, qingqiuid=phone, # 客服手机号作为请求人 cfliyou=reason or '', sqzhuangtai=0, # 待处理 jifen=0, dingdan_id=dingdan_id, ) # 可选:更新订单的 clkf 字段记录处理客服 order.clkf = phone order.save() return Response({'code': 0, 'msg': '处罚成功'}) from django.db import transaction, IntegrityError from django.contrib.auth.hashers import make_password class AdKftjView(APIView): """ 添加客服接口 权限:管理员JWT 请求:POST /yonghu/adkftj 参数:{ "zhanghao": "管理员账号", "phone": "客服手机号(必填)", "password": "密码(必填)", "erjimima": "二级密码(必填)", "nicheng": "昵称(可选)" } 返回:code=0 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 参数 zhanghao = request.data.get('zhanghao', '').strip() phone = request.data.get('phone', '').strip() password = request.data.get('password', '').strip() erjimima = request.data.get('erjimima', '').strip() nicheng = request.data.get('nicheng', '').strip() if not all([zhanghao, phone, password, erjimima]): return Response({'code': 400, 'message': '手机号、密码、二级密码不能为空'}) # 2. 管理员验证(同前) current_user = request.user if current_user.phone != zhanghao or current_user.user_type != 'admin': return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: admin_profile = current_user.admin_profile except: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 检查手机号是否已被使用 #if UserMain.objects.filter(phone=phone).exists(): #return Response({'code': 400, 'message': '该手机号已被注册'}) # 4. 生成唯一 yonghuid def generate_yonghuid(): import random, time for _ in range(10): timestamp = str(int(time.time()))[-5:] rand = str(random.randint(0, 99)).zfill(2) uid = timestamp + rand if len(uid) == 7 and not UserMain.objects.filter(yonghuid=uid).exists(): return uid raise Exception('无法生成唯一用户ID') with transaction.atomic(): # 创建主表用户 user = UserMain.objects.create( yonghuid=generate_yonghuid(), phone=phone, password=password, user_type='kefu', openid=f'kefu_{phone}' # 临时openid ) # 创建客服扩展表 kefu = UserKefu.objects.create( user=user, nicheng=nicheng or f'客服{user.yonghuid}', erjimima=erjimima, # 建议实际项目中加密存储 zhuangtai=1, jinrichuli=0, jinyuechuli=0, zongchuli=0 ) return Response({'code': 0, 'message': '添加成功'}) from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.parsers import JSONParser from django.db.models import Q import logging from yonghu.models import UserMain, UserKefu, AdminProfile logger = logging.getLogger(__name__) class AdKfglView(APIView): """ 客服管理列表接口 权限:管理员JWT 请求:POST /yonghu/adkfgl 参数:{ "zhanghao": "管理员账号", "keyword": "搜索关键词(可选,匹配客服ID或昵称)" } 返回:{ "code": 0, "data": { "list": [客服对象...] } } """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): # 1. 参数获取 zhanghao = request.data.get('zhanghao', '').strip() keyword = request.data.get('keyword', '').strip() if not zhanghao: return Response({'code': 400, 'message': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST) # 2. 管理员身份验证 current_user = request.user if not hasattr(current_user, 'phone') or str(current_user.phone) != zhanghao: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) if current_user.user_type != 'admin': return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: admin_profile = current_user.admin_profile except AttributeError: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询所有客服(通过扩展表关联主表) queryset = UserKefu.objects.select_related('user').all() if keyword: queryset = queryset.filter( #Q(user__yonghuid__icontains=keyword) | Q(nicheng__icontains=keyword) | Q(user__phone__icontains=keyword) ) # 4. 构建返回数据 kefu_list = [] for kefu in queryset: user = kefu.user kefu_list.append({ 'yonghuid': user.phone, 'phone': user.phone or '', 'nicheng': kefu.nicheng or '', 'zhuangtai': kefu.zhuangtai, 'jinrichuli': kefu.jinrichuli, 'jinyuechuli': kefu.jinyuechuli, 'zongchuli': kefu.zongchuli, }) return Response({ 'code': 0, 'data': {'list': kefu_list} }) class AdKfxgView(APIView): """ 修改客服信息接口 权限:管理员JWT 请求:POST /yonghu/adkfxg 参数:{ "zhanghao": "管理员账号", "uid": "客服ID (yonghuid)", "nicheng": "新昵称(可选)", "phone": "新手机号(可选)", "password": "新密码(可选)", "erjimima": "新二级密码(可选)", "zhuangtai": 0/1(可选) } 返回:code=0 """ permission_classes = [IsAuthenticated] parser_classes = [JSONParser] def post(self, request): zhanghao = request.data.get('zhanghao', '').strip() uid = request.data.get('uid', '').strip() if not zhanghao or not uid: return Response({'code': 400, 'message': '参数不完整'}) # 管理员验证 current_user = request.user if current_user.phone != zhanghao or current_user.user_type != 'admin': return Response({'code': 401, 'message': '身份验证失败'}) try: admin_profile = current_user.admin_profile except: return Response({'code': 401, 'message': '身份验证失败'}) # 查询客服 try: user = UserMain.objects.get(phone=uid, user_type='kefu') kefu = user.kefu_profile except (UserMain.DoesNotExist, UserKefu.DoesNotExist): return Response({'code': 404, 'message': '客服不存在'}) # 更新数据 with transaction.atomic(): # 更新主表字段 if 'phone' in request.data: new_phone = request.data.get('phone').strip() if new_phone and UserMain.objects.filter(phone=new_phone).exclude(phone=uid).exists(): return Response({'code': 400, 'message': '手机号已被使用'}) user.phone = new_phone if 'password' in request.data: user.password = request.data['password'] user.save() # 更新扩展表 if 'nicheng' in request.data: kefu.nicheng = request.data['nicheng'] if 'erjimima' in request.data: kefu.erjimima = request.data['erjimima'] if 'zhuangtai' in request.data: kefu.zhuangtai = int(request.data['zhuangtai']) kefu.save() return Response({'code': 0, 'message': '修改成功'}) # views_tixian_v3.py import decimal import json import random import string import time from datetime import date import requests from django.db import transaction, IntegrityError from django.conf import settings from utils.xcx_sys_config import wx_cfg from rest_framework.views import APIView from rest_framework import permissions, status from rest_framework.response import Response from peizhi.models import TixianQuotaDefault from .models import UserMain, UserDashou, UserGuanshi, Tixianjilu, TixianAutoRecord from utils.wechat_v3 import build_authorization # views_tixian_v3.py import decimal import json import random import string import time from datetime import date import logging import requests from django.db import transaction, IntegrityError from django.conf import settings from utils.xcx_sys_config import wx_cfg from django.core.cache import cache from django.utils import timezone from rest_framework.views import APIView from rest_framework import permissions, status from rest_framework.response import Response from houtai.utils import TixianRiTongji from houtai.utils import update_tixian_daily_stat # 引入公共方法 from houtai.utils import update_tixian_daily_stat,decrease_tixian_daily_stat # 引入公共方法 logger = logging.getLogger(__name__) # views_tixian_v3.py import decimal import json import random import string import time from datetime import date import logging import requests from django.db import transaction, IntegrityError from django.conf import settings from utils.xcx_sys_config import wx_cfg from django.core.cache import cache from django.utils import timezone from rest_framework.views import APIView from rest_framework import permissions, status from rest_framework.response import Response from houtai.utils import TixianRiTongji from houtai.utils import update_tixian_daily_stat,decrease_tixian_daily_stat # 引入公共方法 logger = logging.getLogger(__name__) def generate_tixian_id(): """生成唯一提现订单号:TX + 时间戳(13位) + 4位随机数""" timestamp = str(int(time.time() * 1000)) rand = ''.join(random.choices(string.digits, k=4)) return f'TX{timestamp}{rand}' class TixianShenqingV3View(APIView): """ 自动提现收款接口(新流程第二步) POST /yonghu/tixiansq 必须传 shenhe_danhao;审核状态须为 6=待收款;申请时已扣款,本接口不再扣款 """ permission_classes = [permissions.IsAuthenticated] def post(self, request): shenhe_danhao = (request.data.get('shenhe_danhao') or '').strip() if shenhe_danhao: from .tixian_shenhe_views import process_audit_collect return process_audit_collect(request) return Response({ 'code': 7, 'msg': '缺少审核单号,请先提交提现申请(/yonghu/zddksh)并等待审核通过后再收款', }) '''class TixianShenqingV3View(APIView): permission_classes = [permissions.IsAuthenticated] def post(self, request): user_main = request.user yonghuid = user_main.yonghuid from utils.redis_lock import acquire_lock, release_lock lock_key = f"tixian_lock:{yonghuid}" if not acquire_lock(lock_key, timeout=10): return Response({'code': 429, 'msg': '操作频繁,请稍后重试'}) try: # ========== 2. 参数获取与校验 ========== leixing = request.data.get('leixing') jine_str = request.data.get('jine') if leixing is None or jine_str is None: return Response({'code': 1, 'msg': '缺少参数: leixing 或 jine'}) try: leixing = int(leixing) jine = decimal.Decimal(jine_str) except (ValueError, decimal.InvalidOperation): return Response({'code': 2, 'msg': '参数格式错误'}) if leixing not in [1, 2, 3, 4]: return Response({'code': 3, 'msg': '提现类型无效'}) if jine <= 0.1: return Response({'code': 4, 'msg': '提现金额必须大于0.1'}) # ========== 3. 获取费率 ========== try: if leixing == 1: lilu_obj = Lilubiao.objects.get(fadanpingtai='5') elif leixing == 2: lilu_obj = Lilubiao.objects.get(fadanpingtai='6') elif leixing == 3: lilu_obj = Lilubiao.objects.get(fadanpingtai='8') elif leixing == 4: # 🆕 考核官费率(fadanpingtai='9') lilu_obj = Lilubiao.objects.get(fadanpingtai='9') else: return Response({'code': 5, 'msg': '未知提现类型'}) feilv = decimal.Decimal(str(lilu_obj.lilu)) except Lilubiao.DoesNotExist: return Response({'code': 5, 'msg': '费率未配置'}) shouxufei = (jine * feilv).quantize(decimal.Decimal('0.01')) shijidaozhang = (jine - shouxufei).quantize(decimal.Decimal('0.01')) if shijidaozhang <= 0: return Response({'code': 6, 'msg': '提现金额过低,扣除手续费后无实际到账'}) # ========== 4. 幂等性检查:同一用户不能有处理中的提现(状态0) ========== # if TixianAutoRecord.objects.filter(yonghuid=yonghuid, zhuangtai=0).exists(): # return Response({'code': 18, 'msg': '您有正在处理中的提现,请勿重复提交'}, status=status.HTTP_400_BAD_REQUEST) # ========== 5. 平台每日总额校验 ========== today = date.today() try: daily_total_obj = TixianRiTongji.objects.get(riqi=today, leixing=leixing) daily_total = daily_total_obj.total_amount except TixianRiTongji.DoesNotExist: daily_total = decimal.Decimal('0') try: total_limit_obj = TixianQuotaDefault.objects.get(leixing=leixing + 3) # 打手->4, 管事->5, 组长->6, 考核官->7 total_limit = total_limit_obj.default_quota except TixianQuotaDefault.DoesNotExist: total_limit = decimal.Decimal('100') if daily_total + shijidaozhang > total_limit: return Response({ 'code': 33, 'msg': f'今日该角色提现总额已达上限,请明日再来' }) # ========== 6. 数据库事务:扣款 + 更新个人当日提现 + 创建记录 ========== try: with transaction.atomic(): if leixing == 1: # 打手 dashou = UserDashou.objects.select_for_update().get(user=user_main) if dashou.zhanghaozhuangtai != 1: return Response({'code': 8, 'msg': '打手账号被封禁'}) if dashou.zhuangtai != 1: return Response({'code': 9, 'msg': '有订单进行中,请完成后提现'}) if dashou.jifen != 10: return Response({'code': 10, 'msg': '积分不是10分'}) if dashou.yue < jine: return Response({'code': 11, 'msg': f'余额不足,当前余额: {dashou.yue}'}) # 个人每日限额 if dashou.last_tixian_date != today: dashou.jinritixian_jine = decimal.Decimal('0.00') dashou.last_tixian_date = today quota = dashou.ewai_xiane if (dashou.kaioi_ewai_xiane and dashou.ewai_xiane > 0) \ else TixianQuotaDefault.objects.get(leixing=1).default_quota if dashou.jinritixian_jine + jine > quota: return Response({ 'code': 12, 'msg': f'今日提现已达个人限额,今日已提{dashou.jinritixian_jine}元,限额{quota}元' }) # 扣款 & 累加个人当日提现 dashou.yue -= jine dashou.jinritixian_jine += jine dashou.save() nicheng = dashou.nicheng or '' elif leixing == 2: # 管事 guanshi = UserGuanshi.objects.select_for_update().get(user=user_main) if guanshi.zhuangtai != 1: return Response({'code': 14, 'msg': '管事账号被封禁'}) if guanshi.yue < jine: return Response({'code': 15, 'msg': f'余额不足,当前余额: {guanshi.yue}'}) if guanshi.last_tixian_date != today: guanshi.jinritixian_jine = decimal.Decimal('0.00') guanshi.last_tixian_date = today quota = guanshi.ewai_xiane if (guanshi.kaioi_ewai_xiane and guanshi.ewai_xiane > 0) \ else TixianQuotaDefault.objects.get(leixing=2).default_quota if guanshi.jinritixian_jine + jine > quota: return Response({ 'code': 16, 'msg': f'今日提现已达个人限额,今日已提{guanshi.jinritixian_jine}元,限额{quota}元' }) guanshi.yue -= jine guanshi.jinritixian_jine += jine guanshi.save() nicheng = '管事用户' elif leixing == 3: # 组长 zuzhang = UserZuzhang.objects.select_for_update().get(user=user_main) if zuzhang.zhuangtai != 1: return Response({'code': 30, 'msg': '组长账号被封禁'}) if zuzhang.ketixian_jine < jine: return Response({'code': 31, 'msg': f'组长可提现金额不足,当前余额: {zuzhang.ketixian_jine}'}) # 组长个人每日限额(字段不同) if zuzhang.last_tixian_time and zuzhang.last_tixian_time.date() != today: zuzhang.jinri_tixian = decimal.Decimal('0.00') elif not zuzhang.last_tixian_time: zuzhang.jinri_tixian = decimal.Decimal('0.00') try: default_quota = TixianQuotaDefault.objects.get(leixing=3).default_quota except TixianQuotaDefault.DoesNotExist: default_quota = decimal.Decimal('0.00') quota = zuzhang.ewai_tixian_xiane if (zuzhang.kaioi_ewai_tixian and zuzhang.ewai_tixian_xiane > 0) else default_quota if zuzhang.jinri_tixian + jine > quota: return Response({ 'code': 32, 'msg': f'今日提现已达个人限额,今日已提{zuzhang.jinri_tixian}元,限额{quota}元' }) zuzhang.ketixian_jine -= jine zuzhang.jinri_tixian += jine zuzhang.last_tixian_time = timezone.now() zuzhang.save() nicheng = '' elif leixing == 4: # 🆕 考核官 kaoheguan = UserShenheguan.objects.select_for_update().get(user=user_main) # 校验1: 账号状态(zhuangtai=1正常) if kaoheguan.zhuangtai != 1: return Response({'code': 40, 'msg': '考核官账号已被封禁,无法提现'}) # 校验2: 可提现余额是否足够 if kaoheguan.yue < jine: return Response({'code': 41, 'msg': f'考核官可提现金额不足,当前余额: {kaoheguan.yue}'}) # 考核官没有个人每日限额字段,跳过个人限额检查 # 扣款 kaoheguan.yue -= jine kaoheguan.save() nicheng = '考核官' # 生成订单号,创建记录(状态0) tixian_id = generate_tixian_id() auto_record = TixianAutoRecord.objects.create( tixian_id=tixian_id, yonghuid=yonghuid, leixing=leixing, jine=jine, shouxufei=shouxufei, shijidaozhang=shijidaozhang, feilv=feilv, zhuangtai=0, ) # 事务结束,余额已扣,个人当日提现已累加,记录已创建 except TixianQuotaDefault.DoesNotExist: return Response({'code': 17, 'msg': '默认提现限额未配置'}) except Exception as e: logger.error(f"提现申请事务异常: {str(e)}", exc_info=True) return Response({'code': 99, 'msg': '提现申请失败,请稍后重试'}) # ========== 7. 调用微信接口(事务外) ========== try: url = 'https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills' transfer_scene_id = settings.WECHAT_PAY_V3_CONFIG['TRANSFER_SCENE_ID'] transfer_scene_report_infos = [] if transfer_scene_id == '1005': if leixing == 1: role = "打手" desc = "佣金提现" elif leixing == 2: role = "管事" desc = "分红提现" elif leixing == 3: role = "组长" desc = "分红提现" elif leixing == 4: # 🆕 考核官 role = "考核官" desc = "分佣提现" else: role = "用户" desc = "提现" transfer_scene_report_infos = [ {"info_type": "岗位类型", "info_content": role}, {"info_type": "报酬说明", "info_content": desc} ] elif transfer_scene_id == '1009': transfer_scene_report_infos = [ {"info_type": "采购商品名称", "info_content": "平台服务"} ] body = { 'appid': settings.WECHAT_PAY_V3_CONFIG['APPID'], 'out_bill_no': tixian_id, 'transfer_scene_id': transfer_scene_id, 'openid': user_main.openid, 'transfer_amount': int(shijidaozhang * 100), 'transfer_remark': f'平台提现-{yonghuid}', 'transfer_scene_report_infos': transfer_scene_report_infos, } body_json = json.dumps(body, separators=(',', ':')) auth = build_authorization('POST', url, body_json) headers = { 'Authorization': auth, 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'Mozilla/5.0', } resp = requests.post(url, data=body_json, headers=headers, timeout=10) wx_result = resp.json() if resp.status_code == 200 and wx_result.get('state') == 'WAIT_USER_CONFIRM': package_info = wx_result.get('package_info') auto_record.wechat_package = json.dumps(package_info) auto_record.save(update_fields=['wechat_package']) update_tixian_daily_stat(auto_record.leixing, auto_record.shijidaozhang) return Response({ 'code': 0, 'msg': '提现申请成功,请确认收款', 'data': { 'tixian_id': tixian_id, 'package_info': package_info, 'shouxufei': str(shouxufei), 'shijidaozhang': str(shijidaozhang), 'current_rate': str(feilv), 'mch_id': settings.WECHAT_PAY_V3_CONFIG['MCHID'], 'app_id': settings.WECHAT_PAY_V3_CONFIG['APPID'], } }) else: err_msg = wx_result.get('message', wx_result.get('code', '微信接口调用失败')) raise Exception(f'微信转账接口失败: {err_msg} (HTTP {resp.status_code})') except Exception as e: # 微信调用失败,回滚操作 logger.error(f"微信调用失败: {str(e)}", exc_info=True) with transaction.atomic(): auto_record.zhuangtai = 2 auto_record.fail_reason = str(e) auto_record.save(update_fields=['zhuangtai', 'fail_reason']) if leixing == 1: dashou = UserDashou.objects.select_for_update().get(user=user_main) dashou.yue += jine dashou.jinritixian_jine -= jine dashou.save() elif leixing == 2: guanshi = UserGuanshi.objects.select_for_update().get(user=user_main) guanshi.yue += jine guanshi.jinritixian_jine -= jine guanshi.save() elif leixing == 3: zuzhang = UserZuzhang.objects.select_for_update().get(user=user_main) zuzhang.ketixian_jine += jine zuzhang.jinri_tixian -= jine zuzhang.save() elif leixing == 4: # 🆕 考核官回滚 kaoheguan = UserShenheguan.objects.select_for_update().get(user=user_main) kaoheguan.yue += jine kaoheguan.save() return Response({ 'code': 99, 'msg': '运营账户资金不足,需等管理员充值后才能提现' }) finally: release_lock(lock_key)''' from utils.wechat_v3 import verify_wechat_sign, decrypt_callback_ciphertext class TixianCallbackV3View(APIView): """ 微信转账结果回调接口(最终状态通知) 处理微信异步发送的转账成功/失败结果 """ permission_classes = [] # 无需认证,签名验证保证安全 def post(self, request): headers = request.headers body = request.body.decode('utf-8') # 1. 验证签名 if not verify_wechat_sign(headers, body): return Response({'code': 'FAIL', 'message': '签名验证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 解密数据 try: data = json.loads(body) resource = data.get('resource') if not resource: return Response({'code': 'FAIL', 'message': 'resource不存在'}, status=status.HTTP_400_BAD_REQUEST) associated_data = resource.get('associated_data', '') nonce = resource.get('nonce') ciphertext = resource.get('ciphertext') if not nonce or not ciphertext: return Response({'code': 'FAIL', 'message': '解密字段缺失'}, status=status.HTTP_400_BAD_REQUEST) plaintext = decrypt_callback_ciphertext(associated_data, nonce, ciphertext) callback_data = json.loads(plaintext) except Exception as e: logger.error(f"回调解密失败: {str(e)}", exc_info=True) return Response({'code': 'FAIL', 'message': f'解密失败: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST) # 3. 获取关键信息 out_bill_no = callback_data.get('out_bill_no') # 商户提现单号(即 tixian_id) transfer_status = ( callback_data.get('transfer_status') or callback_data.get('state') or '' ).upper() wechat_transfer_no = callback_data.get('transfer_no') or callback_data.get('transfer_bill_no') fail_reason = callback_data.get('fail_reason', '') or callback_data.get('close_reason', '') if not out_bill_no: return Response({'code': 'FAIL', 'message': 'out_bill_no缺失'}, status=status.HTTP_400_BAD_REQUEST) from .tixian_shenhe_services import ( ensure_shenhe_collect_completed, mark_transfer_success, release_collect_quota_for_record, sync_audit_and_jilu_status, ) from .models import TixianShenheJilu # 4. 幂等处理 with transaction.atomic(): try: auto_record = TixianAutoRecord.objects.select_for_update().get(tixian_id=out_bill_no) except TixianAutoRecord.DoesNotExist: return Response({'code': 'SUCCESS', 'message': 'ok'}) if transfer_status == 'SUCCESS': if auto_record.zhuangtai == 0: mark_transfer_success( auto_record, wechat_transfer_no=wechat_transfer_no, with_accounting=True, ) elif auto_record.shenhe_danhao: ensure_shenhe_collect_completed(auto_record.shenhe_danhao) return Response({'code': 'SUCCESS', 'message': 'ok'}) if auto_record.zhuangtai != 0: return Response({'code': 'SUCCESS', 'message': 'already processed'}) # 微信转账失败 auto_record.zhuangtai = 2 auto_record.fail_reason = (fail_reason or '微信转账失败')[:500] auto_record.save(update_fields=['zhuangtai', 'fail_reason']) release_collect_quota_for_record(auto_record) if auto_record.shenhe_danhao: try: audit = TixianShenheJilu.objects.select_for_update().get( shenhe_danhao=auto_record.shenhe_danhao ) audit.tixian_auto_id = None audit.fail_reason = auto_record.fail_reason sync_audit_and_jilu_status(audit, 6, fail_reason=auto_record.fail_reason) except TixianShenheJilu.DoesNotExist: logger.warning(f'回调失败但审核记录不存在: {auto_record.shenhe_danhao}') decrease_tixian_daily_stat(auto_record.leixing, auto_record.shijidaozhang) else: # 旧流程:回滚余额 yonghuid = auto_record.yonghuid jine = auto_record.jine leixing = auto_record.leixing user_main = UserMain.objects.get(yonghuid=yonghuid) if leixing == 1: dashou = UserDashou.objects.select_for_update().get(user=user_main) dashou.yue += jine dashou.jinritixian_jine -= jine dashou.save() elif leixing == 2: guanshi = UserGuanshi.objects.select_for_update().get(user=user_main) guanshi.yue += jine guanshi.jinritixian_jine -= jine guanshi.save() else: zuzhang = UserZuzhang.objects.select_for_update().get(user=user_main) zuzhang.ketixian_jine += jine zuzhang.jinri_tixian -= jine zuzhang.save() decrease_tixian_daily_stat(auto_record.leixing, auto_record.shijidaozhang) return Response({'code': 'SUCCESS', 'message': 'ok'}) class TixianQueRenAutoView(APIView): """ 用户确认提现结果 POST /yonghu/tixianqr 新审核流程:打款记录带 shenhe_danhao 时,确认成功同步审核表+提现记录表为 2;取消不退款(申请已扣),恢复待收款 6 可重试 """ permission_classes = [permissions.IsAuthenticated] def post(self, request): try: tixian_id = request.data.get('tixian_id') result = request.data.get('result') # 1成功 0失败/取消 if not tixian_id or result is None: return Response({'code': 1, 'msg': '缺少参数'}, status=status.HTTP_400_BAD_REQUEST) try: result = int(result) except ValueError: return Response({'code': 2, 'msg': '参数格式错误'}, status=status.HTTP_400_BAD_REQUEST) if result not in [0, 1]: return Response({'code': 3, 'msg': 'result值无效'}, status=status.HTTP_400_BAD_REQUEST) user_main = request.user from .tixian_shenhe_services import mark_transfer_success, release_collect_quota_for_record with transaction.atomic(): try: auto_record = TixianAutoRecord.objects.select_for_update().get( tixian_id=tixian_id, yonghuid=user_main.yonghuid, ) except TixianAutoRecord.DoesNotExist: return Response({'code': 4, 'msg': '提现记录不存在'}, status=status.HTTP_404_NOT_FOUND) if auto_record.zhuangtai != 0: if result == 1: if auto_record.shenhe_danhao: from .tixian_shenhe_services import ensure_shenhe_collect_completed ensure_shenhe_collect_completed(auto_record.shenhe_danhao) return Response({'code': 0, 'msg': '提现成功', 'data': None}) return Response({'code': 5, 'msg': '该提现已处理'}) is_audit_flow = bool(auto_record.shenhe_danhao) if result == 1: mark_transfer_success(auto_record, with_accounting=True) return Response({'code': 0, 'msg': '提现成功', 'data': None}) # 用户取消或失败 auto_record.zhuangtai = 2 auto_record.fail_reason = '用户取消收款' auto_record.save(update_fields=['zhuangtai', 'fail_reason']) release_collect_quota_for_record(auto_record) if is_audit_flow: # 新审核流程:申请时已扣款,取消不退余额,审核单恢复 6 待收款,可再次点收款 from .models import TixianShenheJilu from .tixian_shenhe_services import sync_audit_and_jilu_status try: audit = TixianShenheJilu.objects.select_for_update().get( shenhe_danhao=auto_record.shenhe_danhao, ) audit.tixian_auto_id = None sync_audit_and_jilu_status( audit, 6, fail_reason='用户取消收款,可重新发起', ) except TixianShenheJilu.DoesNotExist: logger.warning(f'确认取消但审核记录不存在: {auto_record.shenhe_danhao}') decrease_tixian_daily_stat(auto_record.leixing, auto_record.shijidaozhang) else: # 旧流程:收款时才扣款,取消需退还 jine = auto_record.jine leixing = auto_record.leixing if leixing == 1: dashou = UserDashou.objects.select_for_update().get(user=user_main) dashou.yue += jine dashou.jinritixian_jine -= jine dashou.save() elif leixing == 2: guanshi = UserGuanshi.objects.select_for_update().get(user=user_main) guanshi.yue += jine guanshi.jinritixian_jine -= jine guanshi.save() else: zuzhang = UserZuzhang.objects.select_for_update().get(user=user_main) zuzhang.ketixian_jine += jine zuzhang.jinri_tixian -= jine zuzhang.save() decrease_tixian_daily_stat(auto_record.leixing, auto_record.shijidaozhang) return Response({'code': 0, 'msg': '提现已取消', 'data': None}) except Exception as e: logger.error(f'提现确认异常: {str(e)}', exc_info=True) return Response({'code': 99, 'msg': '系统错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) from utils.wechat_v3 import verify_wechat_sign, decrypt_callback_ciphertext '''class TixianCallbackV3View(APIView): """ 微信转账结果回调接口(最终状态通知) 处理微信异步发送的转账成功/失败结果 """ permission_classes = [] # 无需认证,签名验证保证安全 def post(self, request): headers = request.headers body = request.body.decode('utf-8') # 1. 验证签名 if not verify_wechat_sign(headers, body): return Response({'code': 'FAIL', 'message': '签名验证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 2. 解密数据 try: data = json.loads(body) resource = data.get('resource') if not resource: return Response({'code': 'FAIL', 'message': 'resource不存在'}, status=status.HTTP_400_BAD_REQUEST) associated_data = resource.get('associated_data', '') nonce = resource.get('nonce') ciphertext = resource.get('ciphertext') if not nonce or not ciphertext: return Response({'code': 'FAIL', 'message': '解密字段缺失'}, status=status.HTTP_400_BAD_REQUEST) plaintext = decrypt_callback_ciphertext(associated_data, nonce, ciphertext) callback_data = json.loads(plaintext) except Exception as e: logger.error(f"回调解密失败: {str(e)}", exc_info=True) return Response({'code': 'FAIL', 'message': f'解密失败: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST) # 3. 获取关键信息 out_bill_no = callback_data.get('out_bill_no') # 商户提现单号(即 tixian_id) transfer_status = callback_data.get('transfer_status') # SUCCESS / FAIL wechat_transfer_no = callback_data.get('transfer_no') # 微信转账单号 fail_reason = callback_data.get('fail_reason', '') if not out_bill_no: return Response({'code': 'FAIL', 'message': 'out_bill_no缺失'}, status=status.HTTP_400_BAD_REQUEST) # 4. 幂等处理:只更新状态为0(处理中)的记录,已最终状态不再处理 with transaction.atomic(): try: auto_record = TixianAutoRecord.objects.select_for_update().get(tixian_id=out_bill_no) except TixianAutoRecord.DoesNotExist: # 记录不存在,视为已处理,返回成功 return Response({'code': 'SUCCESS', 'message': 'ok'}) # 如果已经是最新状态,直接返回成功(幂等) if auto_record.zhuangtai != 0: return Response({'code': 'SUCCESS', 'message': 'already processed'}) if transfer_status == 'SUCCESS': # 微信转账成功 auto_record.zhuangtai = 1 auto_record.wechat_transfer_no = wechat_transfer_no auto_record.save(update_fields=['zhuangtai', 'wechat_transfer_no']) # 更新收支记录(平台资金统计) try: from dingdan.utils import update_daily_payout update_daily_payout(auto_record.shijidaozhang) szjilu = Szjilu.objects.select_for_update().get(id=1) szjilu.zongsy -= auto_record.shijidaozhang szjilu.zongzc += auto_record.shijidaozhang szjilu.jrzc += auto_record.shijidaozhang szjilu.save() except Szjilu.DoesNotExist: Szjilu.objects.create( id=1, zongsy=-auto_record.shijidaozhang, zongzc=auto_record.shijidaozhang, jrzc=auto_record.shijidaozhang ) except Exception as e: logger.error(f"更新收支记录失败: {str(e)}", exc_info=True) raise # 抛出异常让事务回滚 else: # transfer_status == 'FAIL' # 微信转账失败 auto_record.zhuangtai = 2 auto_record.fail_reason = fail_reason or '微信转账失败' auto_record.save(update_fields=['zhuangtai', 'fail_reason']) # 回滚:恢复用户余额和个人当日提现 yonghuid = auto_record.yonghuid jine = auto_record.jine leixing = auto_record.leixing user_main = UserMain.objects.get(yonghuid=yonghuid) if leixing == 1: dashou = UserDashou.objects.select_for_update().get(user=user_main) dashou.yue += jine dashou.jinritixian_jine -= jine dashou.save() elif leixing == 2: guanshi = UserGuanshi.objects.select_for_update().get(user=user_main) guanshi.yue += jine guanshi.jinritixian_jine -= jine guanshi.save() else: # leixing == 3 zuzhang = UserZuzhang.objects.select_for_update().get(user=user_main) zuzhang.ketixian_jine += jine zuzhang.jinri_tixian -= jine zuzhang.save() # 回滚平台每日统计(减去申请时占用的实际到账金额) decrease_tixian_daily_stat(auto_record.leixing, auto_record.shijidaozhang) return Response({'code': 'SUCCESS', 'message': 'ok'}) class TixianQueRenAutoView(APIView): permission_classes = [permissions.IsAuthenticated] def post(self, request): try: tixian_id = request.data.get('tixian_id') result = request.data.get('result') # 1成功 0失败/取消 if not tixian_id or result is None: return Response({'code': 1, 'msg': '缺少参数'}, status=status.HTTP_400_BAD_REQUEST) try: result = int(result) except ValueError: return Response({'code': 2, 'msg': '参数格式错误'}, status=status.HTTP_400_BAD_REQUEST) if result not in [0, 1]: return Response({'code': 3, 'msg': 'result值无效'}, status=status.HTTP_400_BAD_REQUEST) user_main = request.user with transaction.atomic(): auto_record = TixianAutoRecord.objects.select_for_update().get( tixian_id=tixian_id, yonghuid=user_main.yonghuid ) # 幂等检查:只有状态0才能处理 if auto_record.zhuangtai != 0: return Response({'code': 5, 'msg': '该提现已处理'}) if result == 1: # 用户确认成功 → 直接处理为成功 auto_record.zhuangtai = 1 auto_record.save(update_fields=['zhuangtai']) # 更新收支记录(平台资金统计) try: from dingdan.utils import update_daily_payout update_daily_payout(auto_record.shijidaozhang) szjilu = Szjilu.objects.select_for_update().get(id=1) szjilu.zongsy -= auto_record.shijidaozhang szjilu.zongzc += auto_record.shijidaozhang szjilu.jrzc += auto_record.shijidaozhang szjilu.save() except Szjilu.DoesNotExist: Szjilu.objects.create( id=1, zongsy=-auto_record.shijidaozhang, zongzc=auto_record.shijidaozhang, jrzc=auto_record.shijidaozhang ) except Exception as e: logger.error(f"更新收支记录失败: {str(e)}", exc_info=True) raise # 注意:个人当日提现金额已经在申请接口中累加,此处不需要再操作 return Response({'code': 0, 'msg': '提现成功', 'data': None}) else: # 用户取消:回滚余额和个人当日提现 auto_record.zhuangtai = 2 auto_record.fail_reason = '用户取消' auto_record.save(update_fields=['zhuangtai', 'fail_reason']) jine = auto_record.jine leixing = auto_record.leixing if leixing == 1: dashou = UserDashou.objects.select_for_update().get(user=user_main) dashou.yue += jine dashou.jinritixian_jine -= jine dashou.save() elif leixing == 2: guanshi = UserGuanshi.objects.select_for_update().get(user=user_main) guanshi.yue += jine guanshi.jinritixian_jine -= jine guanshi.save() else: zuzhang = UserZuzhang.objects.select_for_update().get(user=user_main) zuzhang.ketixian_jine += jine zuzhang.jinri_tixian -= jine zuzhang.save() # 回滚平台每日统计(减去申请时占用的实际到账金额) decrease_tixian_daily_stat(auto_record.leixing, auto_record.shijidaozhang) return Response({'code': 0, 'msg': '提现已取消', 'data': None}) except Exception as e: logger.error(f"提现确认异常: {str(e)}", exc_info=True) return Response({'code': 99, 'msg': '系统错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)''' from .models import ( UserMain, UserBoss, UserDashou, UserGuanshi, UserZuzhang ) class WechatLoginAndGuanshiRegisterView(APIView): """ 未登录用户微信登录+管事注册一体化接口 路径: /api/yonghu/zuzhangyqmzc 方法: POST 权限: 允许所有 请求体: { "code": "微信登录code", "inviteCode": "组长邀请码" } 成功返回 code: 200,数据同 GuanshiRegisterView """ permission_classes = [AllowAny] def post(self, request): code = request.data.get('code', '').strip() yaoqingma = request.data.get('inviteCode', '').strip() if not code: return Response({'code': 400, 'msg': '微信授权码不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST) if not yaoqingma: return Response({'code': 400, 'msg': '邀请码不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST) if len(yaoqingma) > 100: return Response({'code': 400, 'msg': '邀请码长度超过限制', 'data': None}, status=status.HTTP_400_BAD_REQUEST) wechat_data = self._get_wechat_openid(code) if not wechat_data or 'openid' not in wechat_data: error_msg = wechat_data.get('errmsg', '微信授权失败') return Response({'code': 400, 'msg': f'微信登录失败: {error_msg}', 'data': None}, status=status.HTTP_400_BAD_REQUEST) openid = wechat_data['openid'] kehuduan_ip = self._huoqu_kehuduan_ip(request) with transaction.atomic(): user_main, created = get_or_create_wechat_user(openid, user_type='normal') update_wechat_user_login_info(user_main, kehuduan_ip) if created: UserBoss.objects.create(user=user_main, nickname='微信用户') if hasattr(user_main, 'guanshi_profile'): return self._fanhui_xianyou_guanshi_info(user_main) try: zuzhang = UserZuzhang.objects.select_related('user').get(yaoqingma=yaoqingma) except UserZuzhang.DoesNotExist: return Response({'code': 404, 'msg': '邀请码无效或不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND) if zuzhang.zhuangtai != 1: return Response({'code': 403, 'msg': '该邀请码对应的组长账号已被禁用', 'data': None}, status=status.HTTP_403_FORBIDDEN) zuzhang_yonghuid = zuzhang.user.yonghuid guanshi_yaoqingma = chuangjianYaoqingma(str(user_main.yonghuid)) guanshi_profile = UserGuanshi.objects.create( user=user_main, yaoqingma=guanshi_yaoqingma, yaoqingren=zuzhang_yonghuid, ) UserZuzhang.objects.filter(pk=zuzhang.pk).update( yaoqing_zongshu=models.F('yaoqing_zongshu') + 1 ) if not hasattr(user_main, 'dashou_profile'): dashou_profile = UserDashou.objects.create( user=user_main, nicheng='大手子', chenghao='普通大手', yaoqingren=zuzhang_yonghuid, ) else: dashou_profile = user_main.dashou_profile refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) response_data = self._zhuangbei_fanhui_shuju( user_main, dashou_profile, guanshi_profile, token ) return Response({'code': 0, 'msg': '登录并注册成功!', 'data': response_data}) # ---------- 辅助方法(与接口1完全相同,完整列出以保证完整性) ---------- def _get_wechat_openid(self, code): try: appid = wx_cfg.WEIXIN_APPID secret = wx_cfg.WEIXIN_SECRET if not appid or not secret: raise ValueError('微信配置未设置') url = 'https://api.weixin.qq.com/sns/jscode2session' params = { 'appid': appid, 'secret': secret, 'js_code': code, 'grant_type': 'authorization_code' } response = requests.get(url, params=params, timeout=10) result = response.json() if 'openid' in result: return result else: errcode = result.get('errcode', 'unknown') errmsg = result.get('errmsg', '未知错误') return {'errmsg': f'[{errcode}]{errmsg}'} except requests.exceptions.Timeout: logger.error('请求微信接口超时') return {'errmsg': '请求微信接口超时'} except Exception as e: logger.error(f'请求微信接口异常: {e}') return {'errmsg': f'请求微信接口异常: {str(e)}'} def _shengcheng_yonghu_id(self): for _ in range(10): timestamp_part = str(int(time.time()))[-5:].zfill(5) random_part = str(random.randint(0, 99)).zfill(2) user_id = timestamp_part + random_part if len(user_id) == 7 and user_id.isdigit(): if not UserMain.objects.filter(yonghuid=user_id).exists(): return user_id raise Exception('生成用户ID失败') def _huoqu_kehuduan_ip(self, request): x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0].strip() if ip: return ip ip = request.META.get('REMOTE_ADDR', '') return ip if ip else '0.0.0.0' def _huoqu_qun_peizhi(self): try: qun_configs = Qunpeizhi.objects.filter(id__in=[1, 2]) result = { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } for config in qun_configs: if config.id == 1: result['dashouqun'] = config.neirong or '' result['dashouqunid'] = config.qunid or '' elif config.id == 2: result['guanshiqun'] = config.neirong or '' result['guanshiqunid'] = config.qunid or '' return result except Exception as e: logger.error(f"获取群配置失败: {e}") return {'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': ''} def _zhuangbei_dashou_fanhui(self, dashou_profile): data = { 'dashounicheng': dashou_profile.nicheng, 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'yongjin': str(dashou_profile.yue), 'zonge': str(dashou_profile.zonge), 'yajin': str(dashou_profile.yajin), 'chenghao': dashou_profile.chenghao, 'jinfen': dashou_profile.jifen, 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, } huiyuan_list = [] try: records = Huiyuangoumai.objects.filter(yonghu_id=dashou_profile.user.yonghuid) for record in records: shifou_daoqi = record.jiance_shifou_daoqi() huiyuan_list.append({ 'huiyuan_id': record.huiyuan_id, 'huiyuan_zhuangtai': record.huiyuan_zhuangtai, 'daoqi_time': record.daoqi_time.isoformat() if record.daoqi_time else None, 'shifou_daoqi': shifou_daoqi, 'jieshao': record.jieshao }) except Exception as e: logger.warning(f"获取会员列表失败: {e}") data['clumber'] = huiyuan_list return data def _fanhui_xianyou_guanshi_info(self, user_main): guanshi_profile = user_main.guanshi_profile dashou_profile = getattr(user_main, 'dashou_profile', None) group_info = self._huoqu_qun_peizhi() dashou_status = 1 if dashou_profile else 0 shangjia_status = 1 if hasattr(user_main, 'shop_profile') else 0 guanshi_status = 1 try: nicheng = user_main.boss_profile.nickname or '微信用户' except: nicheng = '微信用户' refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) data = { 'token': token, 'nicheng': nicheng, 'uid': user_main.yonghuid, 'touxiang': user_main.avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, **group_info, } guanshi_data = { 'gszhstatus': guanshi_profile.zhuangtai, 'yaoqingzongshu': guanshi_profile.yaogingshuliang, 'fenyongzonge': str(guanshi_profile.chongzhifenrun), 'fenyongtixian': str(guanshi_profile.yue), 'yichongzhiDashou': guanshi_profile.jinrichongzhi, } data.update(guanshi_data) if dashou_profile: dashou_data = self._zhuangbei_dashou_fanhui(dashou_profile) data.update(dashou_data) else: data.update({ 'dashounicheng': '', 'zhanghaostatus': 0, 'yongjin': '0.00', 'zonge': '0.00', 'yajin': '0.00', 'chenghao': '', 'jinfen': 0, 'chengjiaoliang': 0, 'zaixianzhuangtai': 0, 'dashouzhuangtai': 0, 'clumber': [] }) return Response({'code': 0, 'msg': '您已是管事', 'data': data}) def _zhuangbei_fanhui_shuju(self, user_main, dashou_profile, guanshi_profile, token): try: nicheng = user_main.boss_profile.nickname or '微信用户' except: nicheng = '微信用户' dashou_status = 1 shangjia_status = 1 if hasattr(user_main, 'shop_profile') else 0 guanshi_status = 1 group_info = self._huoqu_qun_peizhi() data = { 'token': token, 'nicheng': nicheng, 'uid': user_main.yonghuid, 'touxiang': user_main.avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, **group_info, } guanshi_data = { 'gszhstatus': guanshi_profile.zhuangtai, 'yaoqingzongshu': guanshi_profile.yaogingshuliang, 'fenyongzonge': str(guanshi_profile.chongzhifenrun), 'fenyongtixian': str(guanshi_profile.yue), 'yichongzhiDashou': guanshi_profile.jinrichongzhi, } data.update(guanshi_data) dashou_data = self._zhuangbei_dashou_fanhui(dashou_profile) data.update(dashou_data) return data from django.db import transaction, IntegrityError, models class GuanshiRegisterView(APIView): """ 已登录用户注册管事接口 路径: /api/yonghu/guanshizhuce 方法: POST 权限: JWT认证 请求体: { "inviteCode": "组长邀请码" } 成功返回 code: 200 """ permission_classes = [IsAuthenticated] def post(self, request): # 1. 获取并验证邀请码 yaoqingma = request.data.get('inviteCode', '').strip() if not yaoqingma: return Response({'code': 400, 'msg': '邀请码不能为空', 'data': None}, status=status.HTTP_400_BAD_REQUEST) if len(yaoqingma) > 100: return Response({'code': 400, 'msg': '邀请码长度超过限制', 'data': None}, status=status.HTTP_400_BAD_REQUEST) current_user = request.user # 2. 检查用户是否已是管事 if hasattr(current_user, 'guanshi_profile'): # 用户已是管事,直接返回现有信息 return self._fanhui_xianyou_guanshi_info(current_user) # 3. 验证邀请码对应的组长 try: zuzhang = UserZuzhang.objects.select_related('user').get(yaoqingma=yaoqingma) except UserZuzhang.DoesNotExist: return Response({'code': 404, 'msg': '邀请码无效或不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND) if zuzhang.zhuangtai != 1: return Response({'code': 403, 'msg': '该邀请码对应的组长账号已被禁用', 'data': None}, status=status.HTTP_403_FORBIDDEN) zuzhang_yonghuid = zuzhang.user.yonghuid # 4. 事务内创建管事实体和打手(如果需要) with transaction.atomic(): # 创建管事扩展表 guanshi_yaoqingma = chuangjianYaoqingma(str(current_user.yonghuid)) guanshi_profile = UserGuanshi.objects.create( user=current_user, yaoqingma=guanshi_yaoqingma, yaoqingren=zuzhang_yonghuid, ) # 更新组长的邀请总数 UserZuzhang.objects.filter(pk=zuzhang.pk).update( yaoqing_zongshu=models.F('yaoqing_zongshu') + 1 ) # 检查用户是否已是打手,如果不是则创建 if not hasattr(current_user, 'dashou_profile'): dashou_profile = UserDashou.objects.create( user=current_user, nicheng='大手子', chenghao='普通大手', yaoqingren=zuzhang_yonghuid, ) else: dashou_profile = current_user.dashou_profile # 5. 生成新token refresh = RefreshToken.for_user(current_user) token = str(refresh.access_token) # 6. 准备返回数据(包含所有字段) response_data = self._zhuangbei_fanhui_shuju( current_user, dashou_profile, guanshi_profile, token ) return Response({'code': 200, 'msg': '注册成功!', 'data': response_data}) # ---------- 辅助方法 ---------- def _huoqu_qun_peizhi(self): """获取群配置信息""" try: qun_configs = Qunpeizhi.objects.filter(id__in=[1, 2]) result = { 'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': '' } for config in qun_configs: if config.id == 1: result['dashouqun'] = config.neirong or '' result['dashouqunid'] = config.qunid or '' elif config.id == 2: result['guanshiqun'] = config.neirong or '' result['guanshiqunid'] = config.qunid or '' return result except Exception as e: logger.error(f"获取群配置失败: {e}") return {'dashouqun': '', 'dashouqunid': '', 'guanshiqun': '', 'guanshiqunid': ''} def _zhuangbei_dashou_fanhui(self, dashou_profile): """组装打手信息(字段名与前端完全匹配)""" data = { 'dashounicheng': dashou_profile.nicheng, 'zhanghaostatus': dashou_profile.zhanghaozhuangtai, 'yongjin': str(dashou_profile.yue), 'zonge': str(dashou_profile.zonge), 'yajin': str(dashou_profile.yajin), 'chenghao': dashou_profile.chenghao, 'jinfen': dashou_profile.jifen, 'chengjiaoliang': dashou_profile.chengjiaozongliang, 'zaixianzhuangtai': dashou_profile.zaixianzhuangtai, 'dashouzhuangtai': dashou_profile.zhuangtai, } # 会员列表 huiyuan_list = [] try: records = Huiyuangoumai.objects.filter(yonghu_id=dashou_profile.user.yonghuid) for record in records: shifou_daoqi = record.jiance_shifou_daoqi() huiyuan_list.append({ 'huiyuan_id': record.huiyuan_id, 'huiyuan_zhuangtai': record.huiyuan_zhuangtai, 'daoqi_time': record.daoqi_time.isoformat() if record.daoqi_time else None, 'shifou_daoqi': shifou_daoqi, 'jieshao': record.jieshao }) except Exception as e: logger.warning(f"获取会员列表失败: {e}") data['clumber'] = huiyuan_list return data def _fanhui_xianyou_guanshi_info(self, user_main): """用户已是管事时的返回""" guanshi_profile = user_main.guanshi_profile dashou_profile = getattr(user_main, 'dashou_profile', None) group_info = self._huoqu_qun_peizhi() dashou_status = 1 if dashou_profile else 0 shangjia_status = 1 if hasattr(user_main, 'shop_profile') else 0 guanshi_status = 1 try: nicheng = user_main.boss_profile.nickname or '微信用户' except: nicheng = '微信用户' refresh = RefreshToken.for_user(user_main) token = str(refresh.access_token) data = { 'token': token, 'nicheng': nicheng, 'uid': user_main.yonghuid, 'touxiang': user_main.avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, **group_info, } # 管事信息 guanshi_data = { 'gszhstatus': guanshi_profile.zhuangtai, 'yaoqingzongshu': guanshi_profile.yaogingshuliang, 'fenyongzonge': str(guanshi_profile.chongzhifenrun), 'fenyongtixian': str(guanshi_profile.yue), 'yichongzhiDashou': guanshi_profile.jinrichongzhi, } data.update(guanshi_data) if dashou_profile: dashou_data = self._zhuangbei_dashou_fanhui(dashou_profile) data.update(dashou_data) else: data.update({ 'dashounicheng': '', 'zhanghaostatus': 0, 'yongjin': '0.00', 'zonge': '0.00', 'yajin': '0.00', 'chenghao': '', 'jinfen': 0, 'chengjiaoliang': 0, 'zaixianzhuangtai': 0, 'dashouzhuangtai': 0, 'clumber': [] }) return Response({'code': 200, 'msg': '您已是管事', 'data': data}) def _zhuangbei_fanhui_shuju(self, user_main, dashou_profile, guanshi_profile, token): """组装完整返回数据(新注册)""" try: nicheng = user_main.boss_profile.nickname or '微信用户' except: nicheng = '微信用户' dashou_status = 1 shangjia_status = 1 if hasattr(user_main, 'shop_profile') else 0 guanshi_status = 1 group_info = self._huoqu_qun_peizhi() data = { 'token': token, 'nicheng': nicheng, 'uid': user_main.yonghuid, 'touxiang': user_main.avatar or '', 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, **group_info, } # 管事信息 guanshi_data = { 'gszhstatus': guanshi_profile.zhuangtai, 'yaoqingzongshu': guanshi_profile.yaogingshuliang, 'fenyongzonge': str(guanshi_profile.chongzhifenrun), 'fenyongtixian': str(guanshi_profile.yue), 'yichongzhiDashou': guanshi_profile.jinrichongzhi, } data.update(guanshi_data) dashou_data = self._zhuangbei_dashou_fanhui(dashou_profile) data.update(dashou_data) return data from rest_framework_simplejwt.authentication import JWTAuthentication from django.db import IntegrityError class ZuzhangZhuceView(APIView): """ 组长注册/激活接口 POST /yonghu/zuzhangzhuce 请求体: {"inviteCode": "xxx"} 返回: { "code": 200, "msg": "success", "data": { "ketixian": "0.00", # 可提现金额 "guanshiCount": 0, # 邀请的管事数量 "fenhongZonge": "0.00" # 分红总额 } } 或错误码 """ permission_classes = [IsAuthenticated] # 硬编码激活码(注意转义特殊字符) ACTIVATION_CODE = "jdjdhsavjsjdb64646/'3464jdjs" def post(self, request): user = request.user invite_code = request.data.get('inviteCode', '').strip() if not invite_code: return Response({'code': 400, 'msg': '邀请码不能为空'}, status=400) # 检查是否已是组长 try: zuzhang = UserZuzhang.objects.select_related('user').get(user=user) # 已注册,直接返回数据 data = self._build_response_data(zuzhang, user) return Response({'code': 200, 'msg': '已注册', 'data': data}) except UserZuzhang.DoesNotExist: pass # 比对激活码 if invite_code != self.ACTIVATION_CODE: return Response({'code': 400, 'msg': '邀请码无效'}, status=400) # 生成唯一组长邀请码:时间戳 + 用户ID后6位 + 随机字符 yaoqingma = self._generate_unique_yaoqingma(user.yonghuid) # 创建组长扩展记录 try: with transaction.atomic(): zuzhang = UserZuzhang.objects.create( user=user, yaoqingma=yaoqingma, fenyong_zonge=0.00, ketixian_jine=0.00, yaoqing_zongshu=0, jinri_fenyong=0.00, jinyue_fenyong=0.00, zhuangtai=1, jinri_tixian=0.00, ewai_tixian_xiane=0.00, kaioi_ewai_tixian=False, kaioi_ewai_fenhong=False, ewai_fenhong_jine=0.00 ) except IntegrityError: # 极低概率重复,重试一次 yaoqingma = self._generate_unique_yaoqingma(user.yonghuid, retry=True) zuzhang = UserZuzhang.objects.create( user=user, yaoqingma=yaoqingma, # 其他字段默认同上 fenyong_zonge=0.00, ketixian_jine=0.00, yaoqing_zongshu=0, jinri_fenyong=0.00, jinyue_fenyong=0.00, zhuangtai=1, jinri_tixian=0.00, ewai_tixian_xiane=0.00, kaioi_ewai_tixian=False, kaioi_ewai_fenhong=False, ewai_fenhong_jine=0.00 ) data = self._build_response_data(zuzhang, user) return Response({'code': 200, 'msg': '注册成功', 'data': data}) def _generate_unique_yaoqingma(self, yonghuid, retry=False): """生成唯一邀请码:时间戳+用户ID后6位+随机2位字符""" import random, string timestamp = str(int(time.time()))[-6:] # 取后6位 uid_part = yonghuid[-6:] if len(yonghuid) >= 6 else yonghuid.rjust(6, '0') rand_str = ''.join(random.choices(string.ascii_uppercase + string.digits, k=2)) base = f"Z{timestamp}{uid_part}{rand_str}" # 确保唯一性(若已存在则追加随机数,最多尝试3次) for _ in range(3): if not UserZuzhang.objects.filter(yaoqingma=base).exists(): return base base = base + random.choice(string.digits) # 极端情况,再附加时间戳毫秒 millis = str(time.time()).replace('.', '')[-4:] return base + millis def _build_response_data(self, zuzhang, user): """组装前端所需数据""" # 计算邀请的管事数量(通过UserGuanshi表的yaoqingren字段关联到user的yonghuid) guanshi_count = UserGuanshi.objects.filter(yaoqingren=user.yonghuid).count() return { 'ketixian': str(zuzhang.ketixian_jine), # 可提现余额 'guanshiCount': guanshi_count, # 邀请管事数 'fenhongZonge': str(zuzhang.fenyong_zonge) # 分红总额 } class ZuzhangXinxiView(APIView): """ 组长信息查询接口 POST /yonghu/zuzhangxinxi 请求体: 无 返回: 同注册接口成功返回格式 """ permission_classes = [IsAuthenticated] def post(self, request): user = request.user try: zuzhang = UserZuzhang.objects.get(user=user) except UserZuzhang.DoesNotExist: return Response({'code': 404, 'msg': '用户不是组长'}, status=404) guanshi_count = UserGuanshi.objects.filter(yaoqingren=user.yonghuid).count() data = { 'ketixian': str(zuzhang.ketixian_jine), 'guanshiCount': guanshi_count, 'fenhongZonge': str(zuzhang.fenyong_zonge) } return Response({'code': 200, 'msg': 'success', 'data': data}) # apps/yonghu/views_dashou_jianquan.py """ 打手 IM 消息权限鉴权接口 URL: /yonghu/dshqltqx 方法: POST 认证: JWT Token (request.user) 鉴权逻辑:已注册打手且押金 > 0 即通过 """ class DashouJianquanView(APIView): """打手消息权限鉴权""" permission_classes = [IsAuthenticated] def post(self, request): user = request.user try: dashou = user.dashou_profile except UserDashou.DoesNotExist: return Response({ 'code': 0, 'allow': 0, 'msg': '您尚未注册打手身份,请先注册' }) yajin = dashou.yajin or 0 if yajin > 0: return Response({ 'code': 0, 'allow': 1, 'msg': '鉴权通过(押金满足条件)' }) return Response({ 'code': 0, 'allow': 0, 'msg': '消息权限不足,请先缴纳押金' }) # apps/yonghu/views_laoban_jianquan.py """ 老板(普通用户) IM 消息权限鉴权接口 URL: /yonghu/lbhqltqx 方法: POST 认证: JWT Token (request.user) 鉴权逻辑:已登录用户默认允许使用消息功能 """ class LaobanJianquanView(APIView): """老板消息权限鉴权""" permission_classes = [IsAuthenticated] def post(self, request): return Response({ 'code': 0, 'allow': 1, 'msg': '鉴权通过' }) class HuoQuYaoQingRenView(APIView): """ 获取当前打手的邀请人信息 POST /yonghu/hqwdyqr Headers: Authorization: Bearer 返回邀请人的用户ID、昵称(来自老板扩展表)、头像相对URL """ permission_classes = [IsAuthenticated] def post(self, request): try: current_user = request.user # 当前登录用户 yonghuid = current_user.yonghuid # 7位用户ID # 1. 校验打手身份及账号状态(只检查账号状态,不检查接单状态) try: dashou = UserDashou.objects.get(user=current_user) if dashou.zhanghaozhuangtai != 1: return Response({ 'code': 403, 'message': '您的账号已被封禁,无法查看邀请人', 'data': None }) except UserDashou.DoesNotExist: return Response({ 'code': 404, 'message': '您还不是打手,无法查看邀请人', 'data': None }) # 2. 获取邀请人ID yaoqingren_id = dashou.yaoqingren if not yaoqingren_id: return Response({ 'code': 404, 'message': '您没有邀请人', 'data': None }) # 3. 查询邀请人主表(头像) try: inviter_main = UserMain.objects.get(yonghuid=yaoqingren_id) touxiang = inviter_main.avatar or '' except UserMain.DoesNotExist: return Response({ 'code': 404, 'message': '邀请人信息不存在', 'data': None }) # 4. 查询老板扩展表(昵称),如果没有则使用空字符串 nicheng = '' try: boss = UserBoss.objects.get(user=inviter_main) nicheng = boss.nickname or '' except UserBoss.DoesNotExist: pass # 邀请人可能不是老板,昵称留空 response_data = { 'uid': yaoqingren_id, 'nicheng': nicheng, 'touxiang': touxiang # 相对路径,前端自行拼接 } return Response({ 'code': 200, 'message': '获取成功', 'data': response_data }) except Exception as e: logger.error(f'获取邀请人失败: {str(e)}', exc_info=True) return Response({ 'code': 500, 'message': '系统繁忙,请稍后重试', 'data': None }) # views.py (在处罚相关app的views中) import logging from rest_framework import status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from django.db.models import Count, Q, Case, When, IntegerField from dingdan.models import Fadan from dingdan.models import FadanShensuTupian logger = logging.getLogger('xiaochengxu') class FaKuanJiFenTongJiView(APIView): """ 获取罚款与积分处罚统计总览 路径: POST /yonghu/cffktjhq """ permission_classes = [IsAuthenticated, DashouPermission] def post(self, request): try: user_main = request.user yonghuid = user_main.yonghuid # ================= 罚款统计 ================= fadan_stats = Fadan.objects.filter(beichufa_id=yonghuid).aggregate( total=Count('id'), daijiaona=Count('id', filter=Q(zhuangtai=1)), shensuzhong=Count('id', filter=Q(zhuangtai=3)), yijiaona=Count('id', filter=Q(zhuangtai=2)), yibohui=Count('id', filter=Q(zhuangtai=4)), ) # ================= 积分处罚统计 ================= jifen_stats = Chufajilu.objects.filter(dashouid=yonghuid).aggregate( total=Count('id'), daichuli=Count('id', filter=Q(sqzhuangtai=0) | Q(sqzhuangtai=3)), yichuli=Count('id', filter=Q(sqzhuangtai=1) | Q(sqzhuangtai=2)), # 细分 daichuli_0=Count('id', filter=Q(sqzhuangtai=0)), shensuzhong_3=Count('id', filter=Q(sqzhuangtai=3)), yichufa_1=Count('id', filter=Q(sqzhuangtai=1)), yibohui_2=Count('id', filter=Q(sqzhuangtai=2)), ) data = { "fakuan": { "total": fadan_stats['total'], "daijiaona": fadan_stats['daijiaona'], "shensuzhong": fadan_stats['shensuzhong'], "yijiaona": fadan_stats['yijiaona'], "yibohui": fadan_stats['yibohui'], }, "jifen": { "total": jifen_stats['total'], "daichuli": jifen_stats['daichuli'], "yichuli": jifen_stats['yichuli'], "daichuli_0": jifen_stats['daichuli_0'], "shensuzhong_3": jifen_stats['shensuzhong_3'], "yichufa_1": jifen_stats['yichufa_1'], "yibohui_2": jifen_stats['yibohui_2'], } } return Response({'code': 0, 'msg': '获取统计成功', 'data': data}) except Exception as e: logger.error(f"获取统计异常 {yonghuid}: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙'}, status=500) class DaShouFaKuanLieBiaoView(APIView): """ 打手罚款列表接口 路径:POST /yonghu/dsfklbhq 参数: page: 页码(默认1) page_size: 每页条数(默认10,最大50) zhuangtai: 状态筛选(1待缴纳/2已缴纳/3申诉中/4申诉成功,0或不传表示全部) sousuo_dingdan_id: 订单号模糊搜索 返回:{ list: [{ id, beichufa_id, guanliandingdan_id, chufaliyou, fakuanjine, zhuangtai, yingxiang_qiangdan, shensuliyou, bohuiliyou, zhengju_tupian: [...], // 证据图片(yongtu=1) shensu_tupian: [...], // 申诉图片(yongtu=2) create_time, update_time }], has_more, page, page_size } """ permission_classes = [IsAuthenticated, DashouPermission] def post(self, request): try: user_main = request.user yonghuid = user_main.yonghuid # 获取分页参数 page = int(request.data.get('page', 1)) page_size = int(request.data.get('page_size', 10)) if page_size > 50: page_size = 50 if page_size < 1: page_size = 10 # 状态筛选 zhuangtai = request.data.get('zhuangtai') # 订单号搜索 sousuo_dingdan_id = request.data.get('sousuo_dingdan_id', '').strip() # 基础查询:当前用户的罚单 qs = Fadan.objects.filter(beichufa_id=yonghuid) # 状态筛选 if zhuangtai is not None and int(zhuangtai) in [1, 2, 3, 4]: qs = qs.filter(zhuangtai=int(zhuangtai)) # 订单号模糊搜索 if sousuo_dingdan_id: qs = qs.filter(guanliandingdan_id__icontains=sousuo_dingdan_id) # 按创建时间倒序 qs = qs.order_by('-create_time') # 分页 paginator = Paginator(qs, page_size) try: page_obj = paginator.page(page) except Exception: return Response({ 'code': 0, 'msg': '获取成功', 'data': {'list': [], 'has_more': False, 'page': page, 'page_size': page_size} }) records = list(page_obj) fadan_ids = [r.id for r in records] # 批量获取图片,按用途分组 zhengju_map = {} # key: fadan_id, value: [url, ...] shensu_map = {} if fadan_ids: tupians = FadanShensuTupian.objects.filter( fadan_id__in=fadan_ids ).values('fadan_id', 'tupian_url', 'yongtu') for t in tupians: fid = t['fadan_id'] url = t['tupian_url'] if t['yongtu'] == 1: zhengju_map.setdefault(fid, []).append(url) else: shensu_map.setdefault(fid, []).append(url) # 组装返回数据 chufa_list = [] for r in records: rid = r.id item = { 'id': rid, 'beichufa_id': r.beichufa_id, 'guanliandingdan_id': r.guanliandingdan_id or '', 'chufaliyou': r.chufaliyou or '', 'fakuanjine': str(r.fakuanjine), 'zhuangtai': r.zhuangtai, 'yingxiang_qiangdan': r.yingxiang_qiangdan, 'shensuliyou': r.shensuliyou or '', 'bohuiliyou': r.bohuiliyou or '', 'zhengju_tupian': zhengju_map.get(rid, []), # 证据图片 'shensu_tupian': shensu_map.get(rid, []), # 申诉图片 'create_time': r.create_time.strftime('%Y-%m-%d %H:%M:%S') if r.create_time else '', 'update_time': r.update_time.strftime('%Y-%m-%d %H:%M:%S') if r.update_time else '', } chufa_list.append(item) has_more = page_obj.has_next() return Response({ 'code': 0, 'msg': '获取成功', 'data': { 'list': chufa_list, 'has_more': has_more, 'page': page, 'page_size': page_size, } }) except Exception as e: logger.error(f"获取罚款列表异常 {yonghuid if 'yonghuid' in locals() else ''}: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙'}, status=500) class FaKuanShenSuView(APIView): """ 罚款申诉提交/修改接口 路径:POST /yonghu/fkss 参数: fadan_id: 罚单ID shensu_liyou: 申诉理由 shensu_tupian_urls: 图片相对URL数组(可空数组,但至少要有理由) 逻辑: - 仅待缴纳状态且无驳回理由的罚单可申诉 - 允许对已有申诉进行修改:先删除旧申诉图片(yongtu=2),再添加新图片 """ permission_classes = [IsAuthenticated, DashouPermission] def post(self, request): try: user_main = request.user yonghuid = user_main.yonghuid fadan_id = request.data.get('fadan_id') shensu_liyou = request.data.get('shensu_liyou', '').strip() tupian_urls = request.data.get('shensu_tupian_urls', []) if not fadan_id: return Response({'code': 400, 'msg': '缺少罚款ID'}, status=400) if not shensu_liyou: return Response({'code': 400, 'msg': '请输入申诉理由'}, status=400) # 查询罚单,确保属于当前打手 try: fadan = Fadan.objects.get(id=fadan_id, beichufa_id=yonghuid) except Fadan.DoesNotExist: return Response({'code': 404, 'msg': '罚单不存在或无权操作'}, status=404) # 只允许待缴纳状态(状态1)且没有驳回理由的罚单申诉 if fadan.zhuangtai != 1: return Response({'code': 400, 'msg': '当前状态不可申诉'}, status=400) if fadan.bohuiliyou: return Response({'code': 400, 'msg': '该罚单申诉已被驳回,不可再次申诉'}, status=400) # 更新罚单状态为申诉中,并记录理由 fadan.zhuangtai = 3 fadan.shensuliyou = shensu_liyou fadan.save() # 删除旧的申诉图片(yongtu=2),仅删除打手自己上传的申诉图 FadanShensuTupian.objects.filter(fadan=fadan, yongtu=2).delete() # 插入新上传的申诉图片 for url in tupian_urls: FadanShensuTupian.objects.create( fadan=fadan, beichufa_id=yonghuid, tupian_url=url, yongtu=2 ) return Response({'code': 0, 'msg': '申诉提交成功'}) except Exception as e: logger.error(f"罚款申诉异常 {yonghuid if 'yonghuid' in locals() else ''}: {e}", exc_info=True) return Response({'code': 99, 'msg': '系统繁忙'}, status=500) # 需要替换为你自己项目中的导入路径 from shangpin.models import Czjilu import string # 【致命错误修正】必须导入 string from django.views import View logger = logging.getLogger('xiaochengxu') # ==================== 1. 罚款支付下单接口 ==================== class FaKuanPayView(APIView): """ 罚款微信支付下单接口(完全照抄 YajinGoumai) 路径:POST /yonghu/fkjn """ permission_classes = [IsAuthenticated] def post(self, request): try: fadan_id = request.data.get('fadan_id') if not fadan_id: return Response({'code': 400, 'message': '参数不完整'}, status=status.HTTP_400_BAD_REQUEST) try: fadan = Fadan.objects.get(id=fadan_id, beichufa_id=request.user.yonghuid) except Fadan.DoesNotExist: return Response({'code': 404, 'message': '罚单不存在或不属于您'}, status=status.HTTP_404_NOT_FOUND) if fadan.zhuangtai not in [1, 3]: return Response({'code': 400, 'message': '罚单当前状态不可支付'}, status=status.HTTP_400_BAD_REQUEST) jine = float(fadan.fakuanjine) timestamp_ms = int(time.time() * 1000) timestamp_ns = int(time.time_ns() // 1000) % 1000000 random_num = random.randint(0, 99) dingdanid = f"FK{timestamp_ms:011d}{timestamp_ns:06d}{random_num:02d}" chongzhi_jilu = Czjilu.objects.create( dingdan_id=dingdanid, zhuangtai=9, yonghuid=request.user.yonghuid, jine=jine, leixing=4, shuoming=settings.FAKUAN_PAY_DESCRIPTION ) try: pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=jine, openid=request.user.openid, pay_type='fakuan' ) except Exception as e: chongzhi_jilu.delete() logger.error(f"生成微信支付参数失败: {str(e)}") return Response({ 'code': 500, 'message': f'支付参数生成失败: {str(e)}' }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response({ 'code': 200, 'message': '支付参数生成成功', 'payParams': pay_params, 'dingdanid': dingdanid }, status=status.HTTP_200_OK) except Exception as e: logger.error(f"罚款支付下单接口异常: {str(e)}", exc_info=True) return Response({ 'code': 500, 'message': '服务器内部错误,请稍后重试' }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) def generate_wechat_pay_params(self, dingdanid, jine, openid, pay_type): APPID = wx_cfg.WEIXIN_APPID MCHID = wx_cfg.WEIXIN_MCHID KEY = wx_cfg.WEIXIN_SHANGHUMIYAO if pay_type == 'fakuan': PAY_DESCRIPTION = settings.FAKUAN_PAY_DESCRIPTION NOTIFY_URL = settings.FAKUAN_PAY_NOTIFY_URL # 回调地址在这里 else: raise Exception('未知的支付类型') nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) total_fee = int(jine * 100) trade_type = 'JSAPI' user_ip = self.get_client_ip() # 第一次签名 sign_data = { 'appid': APPID, 'mch_id': MCHID, 'nonce_str': nonce_str, 'body': PAY_DESCRIPTION, 'out_trade_no': dingdanid, 'total_fee': total_fee, 'spbill_create_ip': user_ip, 'notify_url': NOTIFY_URL, # 关键:把回调地址传入 'trade_type': trade_type, 'openid': openid } sorted_sign_data = sorted(sign_data.items(), key=lambda x: x[0]) sign_string = '&'.join([f"{key}={value}" for key, value in sorted_sign_data if value]) sign_string += f'&key={KEY}' sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper() # 构建 XML,这里必须包含 notify_url xml_data = f""" {APPID} {PAY_DESCRIPTION} {MCHID} {nonce_str} {NOTIFY_URL} {openid} {dingdanid} {user_ip} {total_fee} {trade_type} {sign} """ response = requests.post( 'https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml_data.encode('utf-8'), headers={'Content-Type': 'application/xml'}, timeout=10 ) root = ET.fromstring(response.content) return_code = root.find('return_code').text result_code = root.find('result_code').text if return_code == 'SUCCESS' and result_code == 'SUCCESS': prepay_id = root.find('prepay_id').text timestamp = str(int(time.time())) nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) package = f'prepay_id={prepay_id}' sign_type = 'MD5' pay_sign_data = { 'appId': APPID, 'timeStamp': timestamp, 'nonceStr': nonce_str, 'package': package, 'signType': sign_type } sorted_pay_sign_data = sorted(pay_sign_data.items(), key=lambda x: x[0]) pay_sign_string = '&'.join([f"{key}={value}" for key, value in sorted_pay_sign_data]) pay_sign_string += f'&key={KEY}' pay_sign = hashlib.md5(pay_sign_string.encode('utf-8')).hexdigest().upper() return { 'appId': APPID, 'timeStamp': timestamp, 'nonceStr': nonce_str, 'package': package, 'signType': sign_type, 'paySign': pay_sign } else: err_code = root.find('err_code') err_code_des = root.find('err_code_des') error_details = f"return_code: {return_code}" if err_code is not None: error_details += f", err_code: {err_code.text}" if err_code_des is not None: error_details += f", err_code_des: {err_code_des.text}" raise Exception(f"微信支付下单失败: {error_details}") def get_client_ip(self): request = self.request x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0] else: ip = request.META.get('REMOTE_ADDR') return ip if ip else '127.0.0.1' # ==================== 2. 罚款支付回调接口 ==================== from django.views import View class FaKuanHuitiaoView(View): """ 罚款支付回调接口(严格借鉴 YajinHuitiao 接口,参数一个不少) 路径:POST /yonghu/fk_huitiao """ def post(self, request): try: # 1. 解析微信回调XML数据 request_body = request.body logger.info(f"收到微信支付回调,原始数据: {request_body.decode('utf-8')}") root = ET.fromstring(request_body) # 获取关键字段 return_code = root.find('return_code').text result_code = root.find('result_code').text out_trade_no = root.find('out_trade_no').text # 订单ID transaction_id = root.find('transaction_id').text # 微信交易号 total_fee = int(root.find('total_fee').text) # 支付金额(分) logger.info(f"回调数据解析: 订单{out_trade_no}, 状态{return_code}/{result_code}, 金额{total_fee}分") # 2. 验证基本返回状态 if return_code != 'SUCCESS' or result_code != 'SUCCESS': logger.warning(f"微信支付回调状态异常: {return_code}/{result_code}") return self.wechat_response('FAIL', '支付失败') # 3. 验证签名(确保是微信发来的) if not self.verify_signature(request_body): logger.error(f"签名验证失败: {out_trade_no}") return self.wechat_response('FAIL', '签名验证失败') # 4. 查询订单 try: order = Czjilu.objects.get(dingdan_id=out_trade_no) logger.info(f"找到订单: {order.dingdan_id}, 状态: {order.zhuangtai}, 金额: {order.jine}") except Czjilu.DoesNotExist: logger.error(f"订单不存在: {out_trade_no}") return self.wechat_response('FAIL', '订单不存在') # 5. 检查订单状态是否为未支付(9) if order.zhuangtai != 9: logger.warning(f"订单状态不是未支付: {order.dingdan_id}, 当前状态: {order.zhuangtai}") # 已处理过的订单直接返回成功,避免微信重复回调 return self.wechat_response('SUCCESS', 'OK') # 6. 验证金额(微信返回的是分,需要转换) wechat_amount_yuan = total_fee / 100 # 转换为元 order_amount_yuan = float(order.jine) # 订单金额(元) # 允许1分钱的误差(浮点数比较) if abs(wechat_amount_yuan - order_amount_yuan) > 0.01: logger.error(f"金额不一致: 订单金额{order_amount_yuan}元, 支付金额{wechat_amount_yuan}元") return self.wechat_response('FAIL', '金额不一致') # 7. 更新订单状态为已支付(3) order.zhuangtai = 3 order.save(update_fields=['zhuangtai']) logger.info(f"订单状态更新成功: {order.dingdan_id} -> 3") # 8. 更新罚单状态为已缴纳(2) try: # 根据订单中的用户ID和金额匹配待缴纳的罚单 fadan = Fadan.objects.filter( beichufa_id=order.yonghuid, fakuanjine=order.jine, zhuangtai__in=[1, 3] ).first() if fadan: fadan.zhuangtai = 2 # 已缴纳 fadan.save(update_fields=['zhuangtai']) logger.info(f"罚单 {fadan.id} 已自动标记为已缴纳") from yonghu.fadan_fenhong_utils import process_fadan_fenhong success, msg = process_fadan_fenhong(fadan.id) if not success: logger.error(f"罚单 {fadan.id} 分红处理失败: {msg}") else: logger.info(f"罚单 {fadan.id} 分红处理完成: {msg}") except Exception as e: logger.error(f"更新罚单状态失败: {str(e)}", exc_info=True) # 9. 更新收支记录表 try: from peizhi.models import Szjilu from decimal import Decimal # 获取订单金额(确保是Decimal类型) jine_decimal = Decimal(str(order.jine)) if not isinstance(order.jine, Decimal) else order.jine from dingdan.utils import update_daily_income update_daily_income(jine_decimal) # 查询或创建ID=1的收支记录 szjilu, created = Szjilu.objects.get_or_create( id=1, defaults={ 'zongsy': Decimal('0.00'), 'zongls': Decimal('0.00'), 'zongzc': Decimal('0.00'), 'jrzc': Decimal('0.00'), 'jrls': Decimal('0.00') } ) # 更新三个字段 szjilu.zongsy += jine_decimal # 总收益 szjilu.zongls += jine_decimal # 总流水 szjilu.jrls += jine_decimal # 今日流水 szjilu.save() logger.info(f"收支记录更新成功: 订单{order.dingdan_id}, 金额{jine_decimal}元") except Exception as e: # 收支记录更新失败不影响主流程,只记录日志 logger.error(f"更新收支记录失败: {str(e)}", exc_info=True) # 10. 返回成功响应给微信 logger.info(f"罚款支付回调处理完成: {out_trade_no}") return self.wechat_response('SUCCESS', 'OK') except ET.ParseError as e: logger.error(f"XML解析失败: {str(e)}") return self.wechat_response('FAIL', '数据格式错误') except Exception as e: logger.error(f"罚款支付回调处理异常: {str(e)}", exc_info=True) # 返回FAIL让微信重试 return self.wechat_response('FAIL', '处理异常') def verify_signature(self, xml_data): """验证微信支付签名(完全照搬 YajinHuitiao 的 verify_signature)""" try: root = ET.fromstring(xml_data) sign = root.find('sign').text if not sign: logger.error("签名字段不存在") return False # 获取所有参数并排除sign params = {} for child in root: if child.tag != 'sign' and child.text is not None: params[child.tag] = child.text # 按照参数名ASCII码从小到大排序 sorted_params = sorted(params.items(), key=lambda x: x[0]) # 拼接字符串 sign_string = '&'.join([f"{key}={value}" for key, value in sorted_params if value]) sign_string += f'&key={wx_cfg.WEIXIN_SHANGHUMIYAO}' # 计算签名 calculated_sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper() is_valid = sign == calculated_sign if not is_valid: logger.error(f"签名验证失败: 计算签名{calculated_sign}, 收到签名{sign}") return is_valid except Exception as e: logger.error(f"验证签名异常: {str(e)}") return False def wechat_response(self, return_code, return_msg): """返回微信支付回调响应(完全照搬 YajinHuitiao 的 wechat_response)""" xml_response = f""" """ logger.info(f"返回微信响应: {return_code} - {return_msg}") return HttpResponse(xml_response, content_type='application/xml') # ==================== 3. 支付轮询接口 ==================== class FaKuanPayPollView(APIView): """罚款支付轮询接口(借鉴 YajinLunxun)""" permission_classes = [IsAuthenticated] def post(self, request): try: dingdanid = request.data.get('dingdanid') if not dingdanid: return Response({'code': 400, 'message': '订单ID不能为空'}) order = Czjilu.objects.get(dingdan_id=dingdanid, yonghuid=request.user.yonghuid) if order.zhuangtai == 3: return Response({'code': 200, 'message': '支付成功'}) else: return Response({'code': 400, 'message': '订单尚未支付'}) except Czjilu.DoesNotExist: return Response({'code': 400, 'message': '订单不存在'}) except Exception as e: logger.error(f"轮询异常: {str(e)}") return Response({'code': 500, 'message': '服务器内部错误'}) # ==================== 4. 支付失败处理接口 ==================== class FaKuanPayFailView(APIView): """罚款支付失败处理接口(借鉴 YajinShibai)""" permission_classes = [IsAuthenticated] def post(self, request): try: dingdanid = request.data.get('dingdanid') if not dingdanid: return Response({'code': 400, 'message': '订单ID不能为空'}) order = Czjilu.objects.get(dingdan_id=dingdanid, yonghuid=request.user.yonghuid) if order.zhuangtai == 9: # 未支付 order.delete() logger.info(f"用户{request.user.yonghuid}删除未支付订单: {dingdanid}") return Response({'code': 200, 'message': '订单已删除'}) else: return Response({'code': 400, 'message': '订单状态不是未支付,无法删除'}) except Czjilu.DoesNotExist: return Response({'code': 400, 'message': '订单不存在'}) except Exception as e: logger.error(f"支付失败处理异常: {str(e)}") return Response({'code': 500, 'message': '服务器内部错误'}) from shangpin.models import Czjilu from dengji.models import Chenghao, ShenheJilu, KaoheCishuFeiyong, YonghuChenghao from dengji.utils import get_tag_fee # 您需要确保此函数已定义 from dengji.utils import create_shenhe_jilu from dengji.utils import validate_shenheguan logger = logging.getLogger('xiaochengxu') class KaohePayView(APIView): """ 考核支付下单接口 路径:POST /yonghu/khzf 参数: tag_id: 标签ID game_nick: 游戏昵称 remark: 备注 reapply: 是否再次申请 (true/false) jilu_id: 再次申请时的原记录ID(reapply=true时必传) shenheguan_id: 指定考核官ID(可选) """ permission_classes = [IsAuthenticated] def post(self, request): try: user = request.user tag_id = request.data.get('tag_id') game_nick = request.data.get('game_nick', '').strip() remark = request.data.get('remark', '').strip() reapply = request.data.get('reapply', False) jilu_id = request.data.get('jilu_id') if reapply else None shenheguan_id = request.data.get('shenheguan_id', '').strip() logger.info(f"用户 {user.yonghuid} 发起考核支付申请,标签ID: {tag_id}, 再次申请: {reapply}") if not tag_id or not game_nick: logger.warning(f"用户 {user.yonghuid} 参数不完整: tag_id={tag_id}, game_nick={game_nick}") return Response({'code': 400, 'message': '标签ID和游戏昵称不能为空'}) # 1. 检查是否有未支付的考核订单 """unpaid_order = Czjilu.objects.filter( yonghuid=user.yonghuid, leixing=5, zhuangtai=9 ).exists() if unpaid_order: logger.warning(f"用户 {user.yonghuid} 存在未支付的考核订单") return Response({'code': 400, 'message': '您有未支付的考核订单,请先完成支付或取消'})""" # 2. 验证标签 try: chenghao = Chenghao.objects.get(id=tag_id) logger.info(f"标签验证通过: {chenghao.mingcheng}") except Chenghao.DoesNotExist: logger.error(f"标签不存在: tag_id={tag_id}") return Response({'code': 404, 'message': '标签不存在'}) # 3. 检查用户是否已拥有该标签 if YonghuChenghao.objects.filter(yonghu=user, chenghao=chenghao).exists(): logger.warning(f"用户 {user.yonghuid} 已拥有标签 {chenghao.mingcheng}") return Response({'code': 400, 'message': '您已拥有该标签,无需重复考核'}) # 4. 检查是否有未完成的申请 if ShenheJilu.objects.filter( shenqingren_id=user.yonghuid, chenghao=chenghao, zhuangtai__in=[0, 3] # 审核中、待审核 ).exists(): logger.warning(f"用户 {user.yonghuid} 有未完成的考核申请") return Response({'code': 400, 'message': '您有未完成的考核申请,请等待结果'}) # 5. 计算考核次数和费用 if reapply and jilu_id: last = ShenheJilu.objects.filter( jilu_id=jilu_id, shenqingren_id=user.yonghuid, zhuangtai=2 ).first() if not last: logger.warning(f"用户 {user.yonghuid} 再次申请失败:原记录不存在或状态不是未通过,jilu_id={jilu_id}") return Response({'code': 400, 'message': '原记录不存在或状态不是未通过'}) new_cishu = last.cishu + 1 logger.info(f"再次申请,原记录ID: {jilu_id},新次数: {new_cishu}") else: max_cishu = ShenheJilu.objects.filter( shenqingren_id=user.yonghuid, chenghao=chenghao, zhuangtai__in=[1, 2] ).aggregate(max=models.Max('cishu'))['max'] or 0 new_cishu = max_cishu + 1 logger.info(f"首次申请,计算次数: {new_cishu}") # 6. 获取本次费用 fee = get_tag_fee(chenghao, new_cishu) logger.info(f"本次考核费用: {fee}元,次数: {new_cishu}") if fee <= 0: logger.warning(f"费用为0,应使用免费申请接口,tag_id={tag_id}, cishu={new_cishu}") return Response({'code': 400, 'message': '本次费用为0,请直接使用免费申请接口'}) # 7. 生成订单ID timestamp_ms = int(time.time() * 1000) timestamp_ns = int(time.time_ns() // 1000) % 1000000 random_num = random.randint(0, 99) dingdanid = f"KH{timestamp_ms:011d}{timestamp_ns:06d}{random_num:02d}" logger.info(f"生成订单ID: {dingdanid}") # 8. 创建支付订单(Czjilu) order = Czjilu.objects.create( dingdan_id=dingdanid, zhuangtai=9, # 未支付 yonghuid=user.yonghuid, jine=float(fee), leixing=5, # 5-考核支付 shuoming=settings.KAOHE_PAY_DESCRIPTION ) logger.info(f"创建支付订单成功: {dingdanid}") # 9. 保存考核支付临时上下文(用于回调时取业务参数) try: from dengji.models import KaohePayTemp KaohePayTemp.objects.create( dingdan_id=dingdanid, yonghuid=user.yonghuid, tag_id=tag_id, game_nick=game_nick, remark=remark, reapply=reapply, jilu_id=jilu_id if reapply else None, shenheguan_id=shenheguan_id, cishu=new_cishu, fee=fee, ) logger.info(f"保存临时上下文成功: {dingdanid}") except Exception as e: logger.error(f"保存临时上下文失败: {str(e)}", exc_info=True) # 临时表保存失败应该回滚订单 order.delete() return Response({'code': 500, 'message': '系统异常,请稍后重试'}) # 10. 构造 attach 参数(只传订单ID,确保不超127字节) attach_str = json.dumps({'oid': dingdanid}, ensure_ascii=False) logger.info(f"attach内容: {attach_str},长度: {len(attach_str.encode('utf-8'))}字节") # 11. 生成微信支付参数 try: pay_params = self.generate_wechat_pay_params( dingdanid=dingdanid, jine=float(fee), openid=user.openid, attach=attach_str, pay_type='kaohe' ) logger.info(f"微信支付参数生成成功: {dingdanid}") except Exception as e: order.delete() # 下单失败,删除订单记录 # 同时删除临时表记录 try: from dengji.models import KaohePayTemp KaohePayTemp.objects.filter(dingdan_id=dingdanid).delete() except Exception as del_e: logger.error(f"删除临时表记录失败: {str(del_e)}") logger.error(f"生成微信支付参数失败: {str(e)}", exc_info=True) return Response({'code': 500, 'message': f'支付参数生成失败: {str(e)}'}) return Response({ 'code': 200, 'message': '支付参数生成成功', 'payParams': pay_params, 'dingdanid': dingdanid }) except Exception as e: logger.error(f"考核支付下单异常: {str(e)}", exc_info=True) return Response({'code': 500, 'message': '服务器内部错误'}) def generate_wechat_pay_params(self, dingdanid, jine, openid, attach, pay_type): """生成微信JSAPI支付参数""" APPID = wx_cfg.WEIXIN_APPID MCHID = wx_cfg.WEIXIN_MCHID KEY = wx_cfg.WEIXIN_SHANGHUMIYAO if pay_type == 'kaohe': PAY_DESCRIPTION = settings.KAOHE_PAY_DESCRIPTION NOTIFY_URL = settings.KAOHE_PAY_NOTIFY_URL else: raise Exception('未知支付类型') nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) total_fee = int(jine * 100) trade_type = 'JSAPI' user_ip = self.get_client_ip() logger.info(f"微信支付下单参数: out_trade_no={dingdanid}, total_fee={total_fee}, openid={openid}, ip={user_ip}") # 第一次签名(统一下单) sign_data = { 'appid': APPID, 'mch_id': MCHID, 'nonce_str': nonce_str, 'body': PAY_DESCRIPTION, 'out_trade_no': dingdanid, 'total_fee': total_fee, 'spbill_create_ip': user_ip, 'notify_url': NOTIFY_URL, 'trade_type': trade_type, 'openid': openid, 'attach': attach, } sorted_sign_data = sorted(sign_data.items(), key=lambda x: x[0]) sign_string = '&'.join([f"{key}={value}" for key, value in sorted_sign_data if value]) sign_string += f'&key={KEY}' sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper() # 构建XML请求体 xml_data = f""" {APPID} {PAY_DESCRIPTION} {MCHID} {nonce_str} {NOTIFY_URL} {openid} {dingdanid} {user_ip} {total_fee} {trade_type} {sign} """ logger.info(f"微信支付请求XML: {xml_data[:500]}...") # 只打印前500字符 response = requests.post( 'https://api.mch.weixin.qq.com/pay/unifiedorder', data=xml_data.encode('utf-8'), headers={'Content-Type': 'application/xml'}, timeout=10 ) logger.info(f"微信支付响应: {response.text}") root = ET.fromstring(response.content) # 安全获取return_code return_code_node = root.find('return_code') if return_code_node is None or return_code_node.text is None: logger.error(f"微信返回缺少return_code节点,原始内容: {response.text}") raise Exception('微信支付接口异常,缺少return_code') return_code = return_code_node.text # 安全获取result_code result_code_node = root.find('result_code') if result_code_node is None or result_code_node.text is None: logger.error(f"微信返回缺少result_code节点,原始内容: {response.text}") raise Exception('微信支付接口异常,缺少result_code') result_code = result_code_node.text if return_code == 'SUCCESS' and result_code == 'SUCCESS': prepay_id_node = root.find('prepay_id') if prepay_id_node is None or prepay_id_node.text is None: logger.error(f"统一下单成功但未返回prepay_id,原始内容: {response.text}") raise Exception('微信支付接口异常,缺少prepay_id') prepay_id = prepay_id_node.text timestamp = str(int(time.time())) nonce_str = ''.join(random.choices(string.ascii_letters + string.digits, k=32)) package = f'prepay_id={prepay_id}' sign_type = 'MD5' # 第二次签名(前端调起支付用) pay_sign_data = { 'appId': APPID, 'timeStamp': timestamp, 'nonceStr': nonce_str, 'package': package, 'signType': sign_type, } sorted_pay_sign_data = sorted(pay_sign_data.items(), key=lambda x: x[0]) pay_sign_string = '&'.join([f"{key}={value}" for key, value in sorted_pay_sign_data]) pay_sign_string += f'&key={KEY}' pay_sign = hashlib.md5(pay_sign_string.encode('utf-8')).hexdigest().upper() logger.info(f"微信支付预下单成功,prepay_id={prepay_id}") return { 'appId': APPID, 'timeStamp': timestamp, 'nonceStr': nonce_str, 'package': package, 'signType': sign_type, 'paySign': pay_sign, } else: # 安全获取错误信息 err_code_node = root.find('err_code') err_code = err_code_node.text if err_code_node is not None else '未知错误码' err_code_des_node = root.find('err_code_des') err_code_des = err_code_des_node.text if err_code_des_node is not None else '未知错误描述' error_details = f"return_code: {return_code}, result_code: {result_code}, err_code: {err_code}, err_code_des: {err_code_des}" logger.error(f"微信支付下单失败: {error_details}") raise Exception(f"微信支付下单失败: {error_details}") def get_client_ip(self): """获取客户端IP""" request = self.request x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: ip = x_forwarded_for.split(',')[0] else: ip = request.META.get('REMOTE_ADDR', '127.0.0.1') return ip if ip else '127.0.0.1' class KaohePayCallbackView(View): """ 考核支付回调接口 路径:/yonghu/kh_huitiao """ def post(self, request): try: request_body = request.body logger.info(f"收到考核支付回调: {request_body.decode('utf-8')}") root = ET.fromstring(request_body) # 安全获取return_code return_code_node = root.find('return_code') if return_code_node is None or return_code_node.text is None: logger.error(f"微信回调XML缺少return_code节点,原始内容: {request_body.decode('utf-8')}") return self.wechat_response('FAIL', '数据格式错误') return_code = return_code_node.text # 安全获取result_code result_code_node = root.find('result_code') if result_code_node is None or result_code_node.text is None: logger.error(f"微信回调XML缺少result_code节点,原始内容: {request_body.decode('utf-8')}") return self.wechat_response('FAIL', '数据格式错误') result_code = result_code_node.text # 安全获取out_trade_no out_trade_no_node = root.find('out_trade_no') if out_trade_no_node is None or out_trade_no_node.text is None: logger.error(f"微信回调XML缺少out_trade_no节点,原始内容: {request_body.decode('utf-8')}") return self.wechat_response('FAIL', '数据格式错误') out_trade_no = out_trade_no_node.text # 安全获取transaction_id transaction_id_node = root.find('transaction_id') if transaction_id_node is None or transaction_id_node.text is None: logger.error(f"微信回调XML缺少transaction_id节点,原始内容: {request_body.decode('utf-8')}") return self.wechat_response('FAIL', '数据格式错误') transaction_id = transaction_id_node.text # 安全获取total_fee total_fee_node = root.find('total_fee') if total_fee_node is None or total_fee_node.text is None: logger.error(f"微信回调XML缺少total_fee节点,原始内容: {request_body.decode('utf-8')}") return self.wechat_response('FAIL', '数据格式错误') total_fee = int(total_fee_node.text) logger.info( f"回调参数解析成功: out_trade_no={out_trade_no}, transaction_id={transaction_id}, total_fee={total_fee}") if return_code != 'SUCCESS' or result_code != 'SUCCESS': logger.warning(f"支付状态异常: return_code={return_code}, result_code={result_code}") return self.wechat_response('FAIL', '支付失败') # 验证签名 if not self.verify_signature(request_body): logger.error(f"签名验证失败,订单号: {out_trade_no}") return self.wechat_response('FAIL', '签名验证失败') logger.info(f"签名验证成功,订单号: {out_trade_no}") # 查找订单 try: order = Czjilu.objects.get(dingdan_id=out_trade_no, leixing=5) logger.info(f"找到订单: {out_trade_no}, 当前状态: {order.zhuangtai}") except Czjilu.DoesNotExist: logger.error(f"考核订单不存在: {out_trade_no}") return self.wechat_response('FAIL', '订单不存在') if order.zhuangtai != 9: logger.warning(f"订单状态非未支付: {order.zhuangtai}, 订单号: {out_trade_no}") # 已支付的订单直接返回成功,避免微信重复回调 if order.zhuangtai == 3: logger.info(f"订单已支付,直接返回成功: {out_trade_no}") return self.wechat_response('SUCCESS', 'OK') return self.wechat_response('FAIL', '订单状态异常') # 验证金额 wechat_yuan = total_fee / 100 if abs(wechat_yuan - float(order.jine)) > 0.01: logger.error(f"金额不一致: 订单金额={order.jine}, 回调金额={wechat_yuan}, 订单号: {out_trade_no}") return self.wechat_response('FAIL', '金额不一致') logger.info(f"金额验证通过: {wechat_yuan}元") # 更新订单状态为已支付 order.zhuangtai = 3 order.save() logger.info(f"订单状态更新为已支付: {out_trade_no}") # 从临时表获取业务参数并创建审核记录 try: from dengji.models import KaohePayTemp from dengji.utils import create_shenhe_jilu_from_temp from yonghu.models import UserMain temp = KaohePayTemp.objects.get(dingdan_id=out_trade_no, yonghuid=order.yonghuid) logger.info(f"找到临时表记录: {out_trade_no}, 用户ID: {order.yonghuid}") user = UserMain.objects.get(yonghuid=order.yonghuid) logger.info(f"获取用户信息成功: {user.yonghuid}") success, msg, record = create_shenhe_jilu_from_temp(user, temp) if success: logger.info(f"创建审核记录成功: jilu_id={record.jilu_id}, 订单号: {out_trade_no}") else: logger.error(f"创建审核记录失败: {msg}, 订单号: {out_trade_no}") # 订单已支付但记录创建失败,需要标记订单异常,人工介入 order.zhuangtai = 10 # 10表示支付成功但业务处理失败 order.save() logger.error(f"订单状态标记为异常(10): {out_trade_no}") except KaohePayTemp.DoesNotExist: logger.error(f"临时表无记录,订单号: {out_trade_no}, 用户ID: {order.yonghuid}") # 尝试从attach解析(兼容旧数据) attach_element = root.find('attach') if attach_element is not None and attach_element.text: try: attach_data = json.loads(attach_element.text) logger.info(f"从attach解析数据成功: {attach_data}") # 这里可以保留旧逻辑,但建议只记录日志 except Exception as e: logger.error(f"解析attach失败: {str(e)}") except Exception as e: logger.error(f"处理临时表或创建审核记录异常: {str(e)}", exc_info=True) # 标记订单异常 order.zhuangtai = 10 order.save() logger.error(f"订单状态标记为异常(10): {out_trade_no}") # 更新收支记录 try: from peizhi.models import Szjilu from decimal import Decimal from dingdan.utils import update_daily_income jine_decimal = Decimal(str(order.jine)) update_daily_income(jine_decimal) logger.info(f"更新每日收入成功: {jine_decimal}元") szjilu, created = Szjilu.objects.get_or_create( id=1, defaults={ 'zongsy': Decimal('0.00'), 'zongls': Decimal('0.00'), 'zongzc': Decimal('0.00'), 'jrzc': Decimal('0.00'), 'jrls': Decimal('0.00'), } ) szjilu.zongsy += jine_decimal szjilu.zongls += jine_decimal szjilu.jrls += jine_decimal szjilu.save() logger.info(f"更新收支记录成功: zongsy={szjilu.zongsy}") except Exception as e: logger.error(f"更新收支记录失败: {str(e)}", exc_info=True) logger.info(f"考核支付回调处理完成,订单号: {out_trade_no}") return self.wechat_response('SUCCESS', 'OK') except ET.ParseError as e: logger.error(f"XML解析失败: {str(e)}, 原始数据: {request.body.decode('utf-8')}") return self.wechat_response('FAIL', '数据格式错误') except Exception as e: logger.error(f"考核支付回调异常: {str(e)}", exc_info=True) return self.wechat_response('FAIL', '处理异常') def verify_signature(self, xml_data): """验证微信支付签名""" try: root = ET.fromstring(xml_data) sign_node = root.find('sign') if sign_node is None or sign_node.text is None: logger.error("微信回调XML缺少sign节点") return False sign = sign_node.text params = {} for child in root: if child.tag != 'sign' and child.text is not None: params[child.tag] = child.text sorted_params = sorted(params.items(), key=lambda x: x[0]) sign_string = '&'.join([f"{key}={value}" for key, value in sorted_params if value]) sign_string += f'&key={wx_cfg.WEIXIN_SHANGHUMIYAO}' calculated_sign = hashlib.md5(sign_string.encode('utf-8')).hexdigest().upper() result = sign == calculated_sign if not result: logger.error(f"签名验证失败: 原签名={sign}, 计算签名={calculated_sign}") return result except Exception as e: logger.error(f"签名验证异常: {str(e)}", exc_info=True) return False def wechat_response(self, return_code, return_msg): """构造微信回调响应XML""" xml = f""" """ logger.info(f"返回微信响应: {xml}") return HttpResponse(xml, content_type='application/xml') class KaohePayPollView(APIView): """考核支付轮询""" permission_classes = [IsAuthenticated] def post(self, request): dingdanid = request.data.get('dingdanid') if not dingdanid: return Response({'code': 400, 'message': '订单ID不能为空'}) try: order = Czjilu.objects.get(dingdan_id=dingdanid, yonghuid=request.user.yonghuid, leixing=5) if order.zhuangtai == 3: return Response({'code': 200, 'message': '支付成功'}) return Response({'code': 400, 'message': '订单尚未支付'}) except Czjilu.DoesNotExist: return Response({'code': 400, 'message': '订单不存在'}) class KaohePayFailView(APIView): """考核支付失败处理""" permission_classes = [IsAuthenticated] def post(self, request): dingdanid = request.data.get('dingdanid') if not dingdanid: return Response({'code': 400, 'message': '订单ID不能为空'}) try: order = Czjilu.objects.get(dingdan_id=dingdanid, yonghuid=request.user.yonghuid, leixing=5) if order.zhuangtai == 9: order.delete() return Response({'code': 200, 'message': '订单已删除'}) return Response({'code': 400, 'message': '订单状态无法删除'}) except Czjilu.DoesNotExist: return Response({'code': 400, 'message': '订单不存在'}) class TixianAssetView(APIView): """ 获取用户所有可提现资产及费率 请求方式:POST 认证:JWT Token 返回: { "code": 200, "msg": "success", "data": { "dashou_yue": "123.45", "dashou_yajin": "500.00", "shangjia_yue": "0.00", "guanshi_fenyong": "67.89", "zuzhang_fenyong": "0.00", "kaoheguan_fenyong": "0.00", "dashou_rate": 0.08, "dashou_yajin_rate": 0.05, "shangjia_rate": 0.06, "guanshi_rate": 0.08, "zuzhang_rate": 0.05, "kaoheguan_rate": 0.07 } } """ permission_classes = [IsAuthenticated] def post(self, request): user = request.user # 初始化返回数据,默认全部为0 data = { 'dashou_yue': '0.00', 'dashou_yajin': '0.00', 'shangjia_yue': '0.00', 'guanshi_fenyong': '0.00', 'zuzhang_fenyong': '0.00', 'kaoheguan_fenyong': '0.00', 'dashou_rate': 0.00, 'dashou_yajin_rate': 0.00, 'shangjia_rate': 0.00, 'guanshi_rate': 0.00, 'zuzhang_rate': 0.00, 'kaoheguan_rate': 0.00, } # ------------------- 打手资产 ------------------- try: dashou = user.dashou_profile data['dashou_yue'] = str(dashou.yue) data['dashou_yajin'] = str(dashou.yajin) except ObjectDoesNotExist: pass # ------------------- 商家资产 ------------------- try: shangjia = user.shop_profile data['shangjia_yue'] = str(shangjia.yue) except ObjectDoesNotExist: pass # ------------------- 管事资产 ------------------- try: guanshi = user.guanshi_profile data['guanshi_fenyong'] = str(guanshi.yue) except ObjectDoesNotExist: pass # ------------------- 组长资产 ------------------- try: zuzhang = user.zuzhang_profile data['zuzhang_fenyong'] = str(zuzhang.ketixian_jine) except ObjectDoesNotExist: pass # ------------------- 审核官(考核官)资产 ------------------- try: kaoheguan = user.shenheguan_profile data['kaoheguan_fenyong'] = str(kaoheguan.yue) except ObjectDoesNotExist: pass # ------------------- 费率查询(从 Lilubiao 获取) ------------------- # 打手提现费率 (类型 '5') lilu_obj = Lilubiao.objects.filter(fadanpingtai='5').first() data['dashou_rate'] = float(lilu_obj.lilu) if lilu_obj and lilu_obj.lilu is not None else 0.00 # 打手保证金提现费率 (类型 '11') lilu_obj = Lilubiao.objects.filter(fadanpingtai='11').first() data['dashou_yajin_rate'] = float(lilu_obj.lilu) if lilu_obj and lilu_obj.lilu is not None else 0.00 # 商家提现费率 (类型 '10') lilu_obj = Lilubiao.objects.filter(fadanpingtai='10').first() data['shangjia_rate'] = float(lilu_obj.lilu) if lilu_obj and lilu_obj.lilu is not None else 0.00 # 管事提现费率 (类型 '6') lilu_obj = Lilubiao.objects.filter(fadanpingtai='6').first() data['guanshi_rate'] = float(lilu_obj.lilu) if lilu_obj and lilu_obj.lilu is not None else 0.00 # 组长提现费率 (类型 '8') lilu_obj = Lilubiao.objects.filter(fadanpingtai='8').first() data['zuzhang_rate'] = float(lilu_obj.lilu) if lilu_obj and lilu_obj.lilu is not None else 0.00 # 审核官(考核官)提现费率 (类型 '9') lilu_obj = Lilubiao.objects.filter(fadanpingtai='9').first() data['kaoheguan_rate'] = float(lilu_obj.lilu) if lilu_obj and lilu_obj.lilu is not None else 0.00 return Response({ 'code': 200, 'msg': '获取成功', 'data': data }, status=status.HTTP_200_OK)