"""users.views.admin - auto-generated by split script.""" import os import sys import json import re import time import uuid import random import string import requests import hashlib import traceback import decimal import jwt import ipaddress import xmltodict from datetime import datetime, timedelta from decimal import Decimal import defusedxml.ElementTree as ET from django.conf import settings from django.db import models, transaction, IntegrityError, DatabaseError, connection from django.db.models import Q, F, Sum, Count, Case, When, IntegerField, Prefetch from django.core.cache import cache from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.exceptions import ObjectDoesNotExist from django.shortcuts import get_object_or_404 from django.utils import timezone from django.http import HttpResponse from django.views import View from django.views.decorators.csrf import csrf_exempt from django.contrib.auth.hashers import make_password from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import permissions, status from rest_framework.permissions import AllowAny, IsAuthenticated from rest_framework.parsers import MultiPartParser, FormParser, JSONParser from rest_framework.throttling import AnonRateThrottle from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.authentication import JWTAuthentication from gvsdsdk.fluent import db, func, FQ from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss from utils.money import yuan_to_fen from utils.fadan_utils import check_fadan_qiangdan_eligible from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode from users.fadan_fenhong_utils import process_fadan_fenhong from orders.utils import ( update_daily_payout, settle_shangjia_order_guanshi_fenhong ) from backend.utils import ( update_dashou_daily_by_action, update_guanshi_daily_by_action, update_shangjia_daily, update_zuzhang_daily_by_action, verify_kefu_permission ) from rank.utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan from ..models import ( UserBoss, UserDashou, UserShangjia, UserGuanshi, UserZuzhang, UserKefu, AdminProfile, OfficialAccountUser, TixianAutoRecord, TixianShenheJilu, Tixianjilu, Xiugaijilu, RankingRecord ) from users.business_models import User from ..tixian_shenhe_services import ( load_audit_meta_map, refund_balance, sync_audit_and_jilu_status, mark_transfer_success, release_collect_quota_for_record, close_audit_wechat_failed, query_wechat_transfer_bill, check_shijidaozhang_within_limit, check_dashou_trial_blocks_commission_withdraw, WX_STATE_FAIL, WX_STATE_SUCCESS, WX_STATE_WAIT, WX_STATE_CANCELING, ) from ..tixian_shenhe_views import process_audit_collect from orders.models import ( Order, PlatformOrderExt, MerchantOrderExt, CommissionRate, PenaltyRecord, PenaltyEvidenceImage, RefundRecord, PlayerDeliveryImage, Penalty, PenaltyAppealImage ) from products.models import ( ShangpinLeixing, Huiyuan, Huiyuangoumai, Gsfenhong, Czjilu ) from config.models import Qunpeizhi from backend.models import MerchantDailyStats from rank.models import ( KaohePayTemp, Chenghao, ShenheJilu, KaoheCishuFeiyong, YonghuChenghao ) from users.models import UserShenheguan from products.utils import update_shangpin_daily_stat from shop.utils import update_dianpu_daily_stat from rank.utils import create_shenhe_jilu_from_temp import logging logger = logging.getLogger(__name__) 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一次性获取管理员扩展表 # 只查询管理员类型的用户(密码用 CheckPassword 验证,不能在 ORM 中直接匹配哈希) user = User.query.filter( Phone=zhanghao, # 手机号作为账号 AdminProfile__isnull=False # 必须是管理员类型 ).select_related('AdminProfile').first() # 如果用户不存在或不是管理员,返回401 if not user: return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 验证密码(bcrypt 哈希验证) if not user.CheckPassword(mima): return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 【优化点2】检查管理员扩展表是否存在 if not hasattr(user, 'AdminProfile'): return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 【优化点3】验证二次密码(管理员扩展表中的password字段) admin_profile = user.AdminProfile 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个字符(保持兼容性) cunchu_ip = kehuduan_ip #print(cunchu_ip) # 更新用户信息 user.IP = cunchu_ip # 存储客户端IP user.UserLastLoginDate = 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.UserUID, 'phone': user.Phone or '', 'user_type': user.UserType, 'last_login_time': now_time.strftime('%Y-%m-%d %H:%M:%S') if now_time else '', } # 可以添加管理员特定信息 if hasattr(user, 'AdminProfile'): response_data['wechat'] = user.AdminProfile.wechat or '' return Response({ 'code': 0, 'msg': '登录成功', 'data': response_data }) except Exception as e: # 【安全点】任何异常都返回401,不暴露具体错误信息 # 记录日志用于排查问题,但前端只收到通用错误信息 # 这里可以添加日志记录,比如: logging.error(f'管理员登录异常: {str(e)}', exc_info=True) return Response( {'code': 401, 'msg': '登录失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) 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就是User实例) dangqianyonghu = request.user # 3. 验证用户类型是否为admin if not hasattr(dangqianyonghu, 'UserType') or dangqianyonghu.UserType != '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一次性获取管理员扩展表 # UserType 是 @property 非数据库字段,用 AdminProfile__isnull=False 替代 yonghu_xiangxi = User.query.select_related('AdminProfile').get( pk=dangqianyonghu.pk, AdminProfile__isnull=False ) # 检查管理员扩展表是否存在 if not hasattr(yonghu_xiangxi, 'AdminProfile'): return Response( {'code': 401, 'msg': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) except User.DoesNotExist: return Response( {'code': 401, 'msg': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 查询收支记录表(按俱乐部 xq,旧管理员端默认) from jituan.constants import CLUB_ID_DEFAULT from jituan.services.szjilu_accounting import get_szjilu_snapshot huizongshuju = get_szjilu_snapshot(CLUB_ID_DEFAULT) # 7. 返回成功响应 return Response({ 'code': 0, 'msg': '获取成功', 'data': huizongshuju }) except Exception as e: # 【安全策略】任何异常都返回401,不暴露具体错误信息 # 这里可以记录日志,但不返回给前端 # logging.error(f'管理员财务数据接口异常: {str(e)}', exc_info=True) return Response( {'code': 401, 'msg': '获取失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) 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.UserType != 'admin': return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 3. 验证管理员扩展表 try: guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 4. 查询订单类型(商品类型)表 # 注意:这里需要根据你实际的模型名调整 # 如果模型名不是ShangpinType,请告诉我正确的模型名 # 假设模型名是ShangpinLeixing(因为我之前看到你有商品类型表) # 你需要告诉我确切的模型名 try: # 请替换为你的实际模型名 # 只查询前端需要的三个字段 shangpin_types = ShangpinLeixing.query.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 ) class AdGetOrderList(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.UserType != 'admin': return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 3. 验证管理员扩展表 try: guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 400, 'message': '参数不完整', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 5. 构建查询条件(严格按前端字段名映射到数据库字段) query_conditions = Q() # 判断是否为搜索模式 is_search_mode = bool(dingdan_id) if is_search_mode: # 搜索模式:只按订单ID查询 query_conditions &= Q(OrderID=dingdan_id) else: # 筛选模式 # 发单平台筛选(前端: fadanpingtai -> 数据库: Platform) if fadanpingtai is not None: try: fadanpingtai_int = int(fadanpingtai) if fadanpingtai_int in [1, 2]: query_conditions &= Q(Platform=fadanpingtai_int) except (ValueError, TypeError): pass # 订单类型筛选(前端: leixing -> 数据库: ProductTypeID) if leixing is not None: try: leixing_int = int(leixing) query_conditions &= Q(ProductTypeID=leixing_int) except (ValueError, TypeError): pass # 订单状态筛选(前端: zhuangtai -> 数据库: Status) 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(Status__in=status_list) except (ValueError, TypeError): pass # 6. 获取统计数据(平台订单和商家订单) # 注意:这里统计所有订单,不考虑筛选条件 try: # 平台订单统计 (Platform=1) platform_total = Order.query.filter(Platform=1).count() platform_completed = Order.query.filter(Platform=1, Status=3).count() # 商家订单统计 (Platform=2) merchant_total = Order.query.filter(Platform=2).count() merchant_completed = Order.query.filter(Platform=2, Status=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 = Order.query.filter(query_conditions).count() # 查询当前页的数据(只查询主表,只查询前端需要的字段) dingdan_list = Order.query.filter(query_conditions).only( 'OrderID', 'Description', 'Status', 'ProductTypeID', 'Platform', 'Amount', 'ImageURL', 'CreateTime' ).order_by('-CreateTime')[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.OrderID or '', 'jieshao': dingdan.Description or '', 'zhuangtai': dingdan.Status or 1, 'leixing': dingdan.ProductTypeID or 0, # 前端需要 leixing 'fadanpingtai': dingdan.Platform or 1, # 前端需要 fadanpingtai 'jiage': float(dingdan.Amount) if dingdan.Amount else 0.00, # 价格对应 Amount 字段 'tupian_url': dingdan.ImageURL or '', # 图片对应 ImageURL 字段 'fadanshijian': dingdan.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if dingdan.CreateTime 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 ) 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.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.AdminProfile 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.GuanshiProfile except AttributeError: # 管事扩展表不存在,创建一个 try: # 创建默认的管事扩展表 guanshi_profile = UserGuanshi.query.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.UserUID}创建了管事扩展表") 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: # 生成邀请码 new_yaoqingma = CreateInvitationCode(str(yonghuid_str)) # 验证邀请码格式 if not VerifyInvitationCode(new_yaoqingma): raise Exception('邀请码格式验证失败') except ImportError: # 如果导入失败,使用简单方法生成邀请码 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.UserUID}生成邀请码: {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 ) 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.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.AdminProfile 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: stats = { '1': UserBoss.query.count(), '2': UserDashou.query.count(), '3': UserGuanshi.query.count(), '4': UserShangjia.query.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: queryset = UserBoss.query.select_related('user') if keyword: queryset = queryset.filter( Q(user__UserUID__icontains=keyword) | Q(nickname__icontains=keyword) ) # 获取总数 total_count = queryset.count() # 获取当前页的数据 users = list(queryset.order_by('-CreateTime')[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.UserUID, '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: queryset = UserDashou.query.select_related('user') if keyword: queryset = queryset.filter( Q(user__UserUID__icontains=keyword) | Q(nicheng__icontains=keyword) | Q(chenghao__icontains=keyword) ) # 获取总数 total_count = queryset.count() # 获取当前页的数据 dashou_list = list(queryset.order_by('-CreateTime')[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.UserUID, '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: queryset = UserGuanshi.query.select_related('user') if keyword: queryset = queryset.filter( Q(user__UserUID__icontains=keyword) ) # 获取总数 total_count = queryset.count() # 获取当前页的数据 guanshi_list = list(queryset.order_by('-CreateTime')[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.UserUID, 'avatar': user_main.Avatar or '', 'zhanghaozhuangtai': guanshi.zhuangtai, 'nicheng': f'管事{user_main.UserUID}', } 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: queryset = UserShangjia.query.select_related('user') if keyword: queryset = queryset.filter( Q(user__UserUID__icontains=keyword) | Q(nicheng__icontains=keyword) ) # 获取总数 total_count = queryset.count() # 获取当前页的数据 shangjia_list = list(queryset.order_by('-CreateTime')[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.UserUID, '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 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.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 7. 获取统计信息(总是需要统计) zongshu = PenaltyRecord.query.count() daichuli = PenaltyRecord.query.filter(ApplyStatus__in=[0, 3]).count() yichuli = PenaltyRecord.query.filter(ApplyStatus__in=[1, 2]).count() # 8. 如果只请求统计信息,直接返回 if qingqiu_tongji: return Response({ 'code': 0, 'message': '成功', 'data': { 'zongshu': zongshu, 'daichuli': daichuli, 'yichuli': yichuli } }) # 9. 构建查询集 queryset = PenaltyRecord.query.all() # 10. 根据状态筛选 if sqzhuangtai is not None: if isinstance(sqzhuangtai, list): # 如果是数组,表示多个状态 queryset = queryset.filter(ApplyStatus__in=sqzhuangtai) else: # 单个状态 queryset = queryset.filter(ApplyStatus=sqzhuangtai) # 11. 获取总数(用于分页计算) total_count = queryset.count() # 12. 计算分页偏移量 offset = (page - 1) * page_size # 13. 分页查询 chufa_list = list(queryset.order_by('-CreateTime')[offset:offset + page_size]) # 🔴【新增】高效获取图片信息 if chufa_list: # 收集所有需要查询的组合 dingdan_ids = [] yonghu_combinations = [] # 保存 (dingdan_id, yonghuid) 组合 for chufa in chufa_list: if chufa.OrderID: dingdan_ids.append(chufa.OrderID) # 商家图片组合 if chufa.ApplicantID: yonghu_combinations.append((chufa.OrderID, chufa.ApplicantID)) # 打手图片组合 if chufa.PlayerID: yonghu_combinations.append((chufa.OrderID, chufa.PlayerID)) # 去重 dingdan_ids = list(set(dingdan_ids)) yonghu_combinations = list(set(yonghu_combinations)) # 🔴【新增】批量查询图片表(高效查询,避免循环) tupian_mapping = {} if yonghu_combinations: # 构建查询条件 query_conditions = Q() for dingdan_id, yonghuid in yonghu_combinations: query_conditions |= Q(OrderID=dingdan_id, UserID=yonghuid) # 批量查询 tupian_queryset = PenaltyEvidenceImage.query.filter(query_conditions).values( 'OrderID', 'UserID', 'ImageURL' ) # 构建映射:key为"OrderID_UserID",value为图片URL列表 for item in tupian_queryset: key = f"{item['OrderID']}_{item['UserID']}" if key not in tupian_mapping: tupian_mapping[key] = [] if item['ImageURL']: tupian_mapping[key].append(item['ImageURL']) else: tupian_mapping = {} # 🔴【修改】14. 构建返回数据 - 添加图片和申诉理由字段 chufa_data = [] for chufa in chufa_list: # 🔴【新增】获取商家证据图片 zhengju_tupian = [] if chufa.OrderID and chufa.ApplicantID: key = f"{chufa.OrderID}_{chufa.ApplicantID}" zhengju_tupian = tupian_mapping.get(key, []) # 🔴【新增】获取打手申诉图片 shensu_tupian = [] if chufa.OrderID and chufa.PlayerID: key = f"{chufa.OrderID}_{chufa.PlayerID}" shensu_tupian = tupian_mapping.get(key, []) chufa_data.append({ 'dashouid': chufa.PlayerID, 'qingqiuid': chufa.ApplicantID, 'chuliid': chufa.ProcessorID, 'cfliyou': chufa.PenaltyReason, 'sqzhuangtai': chufa.ApplyStatus, 'bhliyou': chufa.RejectReason, 'jifen': chufa.DeductedPoints, 'dingdan_id': chufa.OrderID, 'CreateTime': chufa.CreateTime, 'UpdateTime': chufa.UpdateTime, # 🔴【新增】申诉理由字段 'ssliyou': chufa.AppealReason 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 ) 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.UserType != 'admin': return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 5. 验证管理员扩展表是否存在 try: guanliyuan = dangqianyonghu.AdminProfile except AttributeError: return Response( {'code': 401, 'message': '身份验证失败', 'data': None}, status=status.HTTP_401_UNAUTHORIZED ) # 6. 使用事务保证数据一致性 with transaction.atomic(): # 8. 🔴 修复:查询处罚记录(处理重复记录问题) # 使用 filter().first() 而不是 get(),以防出现多条记录 chufajilu = PenaltyRecord.query.filter( OrderID=dingdan_id, ApplyStatus__in=[0, 3] # 🔴【修改】包括待处理和申诉中状态 ).order_by('-CreateTime').first() # 取最新的一条 if not chufajilu: # 如果没有待处理的处罚记录,检查是否有已处理的 exists_processed = PenaltyRecord.query.filter(OrderID=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.ApplyStatus not in [0, 3]: # 🔴【修改】包括待处理(0)和申诉中(3) return Response( {'code': 400, 'message': '该处罚记录已处理', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 10. 🔴 修复:检查是否有重复的待处理/申诉中记录,并记录警告 duplicate_count = PenaltyRecord.query.filter( OrderID=dingdan_id, ApplyStatus__in=[0, 3] # 🔴【修改】包括待处理和申诉中 ).count() if duplicate_count > 1: # 记录警告日志 logger.warning(f"订单ID {dingdan_id} 存在 {duplicate_count} 条待处理/申诉中处罚记录,处理最新的一条") # 🔴 可选:将其他重复的待处理/申诉中记录标记为已拒绝 # 防止用户重复提交相同订单的处罚申请 duplicate_records = PenaltyRecord.query.filter( OrderID=dingdan_id, ApplyStatus__in=[0, 3] # 🔴【修改】包括待处理和申诉中 ).exclude(id=chufajilu.id) for dup in duplicate_records: dup.ApplyStatus = 2 # 已拒绝 dup.ProcessorID = qianzhanghao dup.RejectReason = "系统自动拒绝:存在重复处罚记录" dup.save() # 11. 根据操作类型处理 if caozuo == 1: # 同意处罚 - 不扣分,只改状态 # 11.1 检查打手是否存在 if not chufajilu.PlayerID: return Response( {'code': 400, 'message': '被处罚打手ID不存在', 'data': None}, status=status.HTTP_400_BAD_REQUEST ) # 11.2 查询打手扩展表(只是验证存在性) try: dashou = UserDashou.query.get(user__UserUID=chufajilu.PlayerID) except UserDashou.DoesNotExist: return Response( {'code': 404, 'message': '被处罚打手不存在', 'data': None}, status=status.HTTP_404_NOT_FOUND ) # 🔴【修改】11.3 同意处罚时不扣分(因为申请时已经扣了),只更新状态 chufajilu.ApplyStatus = 1 # 已处罚 chufajilu.ProcessorID = qianzhanghao # 处理人ID为管理员账号 chufajilu.save() # 11.4 更新商家订单扩展表 try: dingdan = Order.query.get(OrderID=dingdan_id) if hasattr(dingdan, 'shangjia_kuozhan'): shangjia_kuozhan = dingdan.shangjia_kuozhan shangjia_kuozhan.PenaltyApplyStatus = 1 # 已处罚 shangjia_kuozhan.save() except (Order.DoesNotExist, AttributeError): # 如果订单或扩展表不存在,继续执行,不中断 pass logger.info(f"同意处罚成功:订单ID={dingdan_id},打手ID={chufajilu.PlayerID}") elif caozuo == 2: # 拒绝处罚 - 需要返还积分(加分) # 🔴【新增】11.5 返还打手积分(加5分) if chufajilu.DeductedPoints > 0: try: dashou = UserDashou.query.get(user__UserUID=chufajilu.PlayerID) # 🔴【新增】安全验证:必须积分小于等于5分才允许加分 if dashou.jifen <= 5: # 判断积分是否小于等于5分 dashou.jifen += chufajilu.DeductedPoints # 加5分 dashou.save() logger.info( f"拒绝处罚,给打手{dashou.user.UserUID}返还积分{chufajilu.DeductedPoints}分,当前积分{dashou.jifen}") else: # 积分已超过5分,可能是异常情况,记录警告但不中断处理 logger.warning( f"拒绝处罚时打手积分异常:打手ID={chufajilu.PlayerID},当前积分{dashou.jifen}已超过5分,不再加分") except UserDashou.DoesNotExist: # 打手不存在,记录错误,但继续处理状态 logger.error(f"拒绝处罚时打手不存在:打手ID={chufajilu.PlayerID}") # 11.6 更新处罚记录 chufajilu.ApplyStatus = 2 # 已拒绝 chufajilu.ProcessorID = qianzhanghao # 处理人ID为管理员账号 chufajilu.RejectReason = bohui_liyou # 拒绝理由 chufajilu.save() # 11.7 更新商家订单扩展表 try: dingdan = Order.query.get(OrderID=dingdan_id) if hasattr(dingdan, 'shangjia_kuozhan'): shangjia_kuozhan = dingdan.shangjia_kuozhan shangjia_kuozhan.PenaltyApplyStatus = 2 # 已拒绝 shangjia_kuozhan.RejectReason = bohui_liyou # 拒绝理由 shangjia_kuozhan.save() except (Order.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 ) 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.UserType != 'admin': return Response({ 'code': 403, 'message': '权限不足', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 3. 验证管理员扩展表存在 admin_profile = getattr(request.user, 'AdminProfile', 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 = User.query.filter(UserUID=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.UserUID, 'avatar': user_main.Avatar, 'phone': user_main.Phone, 'ip': user_main.IP, 'CreateTime': user_main.UserCreateTime, 'UpdateTime': user_main.UserCreateTime, 'user_type': user_type } # 根据用户类型查询扩展信息 if user_type == 1: # 老板 # 使用select_related预取boss_profile user_main = User.query.filter(UserUID=uid).select_related('BossProfile').first() if getattr(user_main, 'BossProfile', None): boss_profile = user_main.BossProfile user_info.update({ 'nickname': boss_profile.nickname, 'zonge': boss_profile.zonge, 'alldingdan': boss_profile.alldingdan, 'alltui': boss_profile.alltui, 'CreateTime_boss': boss_profile.CreateTime }) elif user_type == 2: # 打手 # 使用select_related预取dashou_profile user_main = User.query.filter(UserUID=uid).select_related('DashouProfile').first() if getattr(user_main, 'DashouProfile', None): dashou_profile = user_main.DashouProfile # 构建打手信息 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, 'CreateTime_dashou': dashou_profile.CreateTime }) # 查询打手的会员信息(只返回未过期的) # 使用select_related预取huiyuan信息(如果有关联的话) member_records = Huiyuangoumai.query.filter( yonghu_id=uid ).order_by('-CreateTime') member_list = [] for record in member_records: # 调用方法检查是否过期,并更新状态 is_expired = record.jiance_shifou_daoqi() if not is_expired: # 只返回未过期的 # 查询会员详情 huiyuan = Huiyuan.query.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, 'CreateTime': record.CreateTime }) response_data['member_list'] = member_list elif user_type == 3: # 管事 # 使用select_related预取guanshi_profile user_main = User.query.filter(UserUID=uid).select_related('GuanshiProfile').first() if getattr(user_main, 'GuanshiProfile', None): guanshi_profile = user_main.GuanshiProfile 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, # 管事可提现余额 'CreateTime_guanshi': guanshi_profile.CreateTime }) elif user_type == 4: # 商家 # 使用select_related预取shop_profile user_main = User.query.filter(UserUID=uid).select_related('ShopProfile').first() if getattr(user_main, 'ShopProfile', None): shop_profile = user_main.ShopProfile 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, 'CreateTime_shop': shop_profile.CreateTime }) # 获取所有会员列表(用于前端下拉选择) all_huiyuan = Huiyuan.query.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.UserType != 'admin': return Response({ 'code': 403, 'message': '权限不足', 'data': None }, status=status.HTTP_403_FORBIDDEN) admin_profile = getattr(request.user, 'AdminProfile', 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 = User.query.filter(UserUID=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': '', 'CreateTime': 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.query.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.query.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.query.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.query.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.query.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) from jituan.services.club_user import get_user_club_id new_record = Huiyuangoumai.query.create( huiyuan_id=huiyuan_id, yonghu_id=uid, jieshao=huiyuan.jieshao, daoqi_time=daoqi_time, club_id=get_user_club_id(User.query.filter(UserUID=uid).first()), ) # 记录会员购买 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.query.create(**modify_record_data) elif modify_record_data.get('qitashuoming', '').strip(): # 只有非敏感字段修改,也记录但qitashuoming不同 Xiugaijilu.query.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) 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.UserType != 'admin': return Response({ 'code': 403, 'message': '权限不足', 'data': None }, status=status.HTTP_403_FORBIDDEN) # 3. 验证管理员扩展表存在 admin_profile = getattr(request.user, 'AdminProfile', 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 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.query.filter(query).order_by('CreateTime') # 🔴【修改】分页逻辑:如果是搜索模式,不分页,返回所有匹配记录 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.query.filter( zhuangtai=1, leixing=leixing ).count(), 'processed': Tixianjilu.query.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, 'CreateTime': record.CreateTime, 'UpdateTime': record.UpdateTime }) 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 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.query.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) # 使用事务确保数据一致性 with transaction.atomic(): # 更新提现记录 withdrawal.zhuangtai = result withdrawal.shenheid = request.user.UserUID if result == 3: # 拒绝 withdrawal.bhliyou = reason withdrawal.UpdateTime = timezone.now() withdrawal.save() # 🟢 新增:如果同意提现(result=2),更新收支记录表 if result == 2: try: from jituan.services.szjilu_accounting import apply_szjilu_expense tixian_jine = withdrawal.jine from jituan.services.club_user import get_user_club_id payout_club = get_user_club_id( User.query.filter(UserUID=withdrawal.yonghuid).first() ) update_daily_payout(tixian_jine, payout_club) apply_szjilu_expense(tixian_jine, payout_club) except Exception as e: # 如果更新收支记录失败,抛出异常,事务会回滚 raise Exception(f"更新收支记录失败: {str(e)}") return Response({ 'code': 0, 'message': '处理成功', 'data': { 'id': withdrawal.id, 'zhuangtai': withdrawal.zhuangtai, 'shenheid': withdrawal.shenheid, 'UpdateTime': withdrawal.UpdateTime } }, 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 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.query.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, '') 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.query.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.query.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 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.UserType != 'admin': return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: admin_profile = current_user.AdminProfile except: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 检查手机号是否已被使用 if User.query.filter(Phone=phone).exists(): return Response({'code': 400, 'message': '该手机号已被注册'}) # 4. 生成唯一 yonghuid def generate_yonghuid(): 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 User.query.filter(UserUID=uid).exists(): return uid raise Exception('无法生成唯一用户ID') with transaction.atomic(): # 创建主表用户 user = User.query.create( UserUID=generate_yonghuid(), UserName=f'kefu_{phone}', Phone=phone, OpenID=f'kefu_{phone}' # 临时openid ) user.SetPassword(password) user.save(update_fields=['UserPassword']) # 创建客服扩展表 import bcrypt as _bcrypt erjimima_hashed = _bcrypt.hashpw(erjimima.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8') kefu = UserKefu.query.create( user=user, nicheng=nicheng or f'客服{user.UserUID}', erjimima=erjimima_hashed, zhuangtai=1, jinrichuli=0, jinyuechuli=0, zongchuli=0 ) return Response({'code': 0, 'message': '添加成功'}) 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.UserType != 'admin': return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) try: admin_profile = current_user.AdminProfile except AttributeError: return Response({'code': 401, 'message': '身份验证失败'}, status=status.HTTP_401_UNAUTHORIZED) # 3. 查询所有客服(通过扩展表关联主表) queryset = UserKefu.query.select_related('user').all() if keyword: queryset = queryset.filter( #Q(user__UserUID__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.UserType != 'admin': return Response({'code': 401, 'message': '身份验证失败'}) try: admin_profile = current_user.AdminProfile except: return Response({'code': 401, 'message': '身份验证失败'}) # 查询客服 try: user = User.query.get(Phone=uid) kefu = user.KefuProfile except (User.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 User.query.filter(Phone=new_phone).exclude(UserUID=uid).exists(): return Response({'code': 400, 'message': '手机号已被使用'}) user.Phone = new_phone if 'password' in request.data: user.UserPassword = request.data['password'] user.save() # 更新扩展表 if 'nicheng' in request.data: kefu.nicheng = request.data['nicheng'] if 'erjimima' in request.data: import bcrypt as _bcrypt new_erji = request.data['erjimima'] kefu.erjimima = _bcrypt.hashpw(new_erji.encode('utf-8'), _bcrypt.gensalt(rounds=12)).decode('utf-8') if 'zhuangtai' in request.data: kefu.zhuangtai = int(request.data['zhuangtai']) kefu.save() return Response({'code': 0, 'message': '修改成功'})