2916 lines
119 KiB
Python
2916 lines
119 KiB
Python
"""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)
|
||
_agg = Order.query.aggregate(
|
||
platform_total=Count('id', filter=Q(Platform=1)),
|
||
platform_completed=Count('id', filter=Q(Platform=1, Status=3)),
|
||
merchant_total=Count('id', filter=Q(Platform=2)),
|
||
merchant_completed=Count('id', filter=Q(Platform=2, Status=3)),
|
||
)
|
||
platform_total = _agg['platform_total']
|
||
platform_completed = _agg['platform_completed']
|
||
|
||
# 商家订单统计 (Platform=2)
|
||
merchant_total = _agg['merchant_total']
|
||
merchant_completed = _agg['merchant_completed']
|
||
|
||
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. 获取统计信息(总是需要统计)
|
||
_agg = PenaltyRecord.query.aggregate(
|
||
zongshu=Count('id'),
|
||
daichuli=Count('id', filter=Q(ApplyStatus__in=[0, 3])),
|
||
yichuli=Count('id', filter=Q(ApplyStatus__in=[1, 2])),
|
||
)
|
||
zongshu = _agg['zongshu']
|
||
daichuli = _agg['daichuli']
|
||
yichuli = _agg['yichuli']
|
||
|
||
# 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')
|
||
|
||
# 批量预取会员详情
|
||
_huiyuan_ids = [r.huiyuan_id for r in member_records]
|
||
_huiyuan_map = {
|
||
h.huiyuan_id: h
|
||
for h in Huiyuan.query.filter(huiyuan_id__in=_huiyuan_ids).only('huiyuan_id', 'jieshao')
|
||
}
|
||
|
||
member_list = []
|
||
for record in member_records:
|
||
# 调用方法检查是否过期,并更新状态
|
||
is_expired = record.jiance_shifou_daoqi()
|
||
|
||
if not is_expired: # 只返回未过期的
|
||
# 查询会员详情
|
||
huiyuan = _huiyuan_map.get(record.huiyuan_id)
|
||
|
||
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')
|
||
from jituan.services.tixian_club import filter_tixianjilu_by_request
|
||
queryset = filter_tixianjilu_by_request(queryset, request)
|
||
|
||
# 🔴【修改】分页逻辑:如果是搜索模式,不分页,返回所有匹配记录
|
||
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_qs = filter_tixianjilu_by_request(
|
||
Tixianjilu.query.filter(leixing=leixing), request,
|
||
)
|
||
_agg = _status_qs.aggregate(
|
||
awaiting=Count('id', filter=Q(zhuangtai=1)),
|
||
processed=Count('id', filter=Q(zhuangtai__in=[2, 3])),
|
||
)
|
||
status_counts = {
|
||
'awaiting': _agg['awaiting'],
|
||
'processed': _agg['processed'],
|
||
}
|
||
|
||
# 构建响应数据
|
||
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)
|
||
|
||
from jituan.services.tixian_club import forbid_if_tixian_out_of_scope
|
||
deny = forbid_if_tixian_out_of_scope(request, withdrawal)
|
||
if deny:
|
||
return deny
|
||
|
||
# 检查状态,只有待审核的可以处理
|
||
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.tixian_club import resolve_payout_club_id
|
||
payout_club = resolve_payout_club_id(
|
||
jilu=withdrawal,
|
||
user=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': '修改成功'})
|