P1 性能优化(避免循环内 N+1 / N 次 DB 查询): - shop_order_views._batch_images: 移除 ThreadPoolExecutor 掩盖的 N 次查询,改单次批量 + Python 侧分组 - product_query.DashouHuiyuanList: 5N 查询 -> 3 次批量预取 + 本地闭包判断 - roles.GetRolePermissionView / 用户列表: 循环内 RolePermission/Permission/UserRole/Role -> 批量 __in 预取 - guanli / zuzhang 杜次分红 4.2/4.3: 循环内 update_or_create + 单条 delete -> bulk_create + bulk_update + 批量 delete - admin_config QQ 群配置: 循环内 exists/first/save/create -> 批量预取 + bulk_create + bulk_update - jituan 服务层多处合并统计查询、批量预取 map - rank 多处循环 N+1 改批量预取 - backend 多处循环内 count/create 改批量 - config/orders/merchant_ops 多处循环 N+1 改批量预取 其他改动: - users/views/kefu.py 拆分为 kefu_base/kefu_dashou/kefu_orders/kefu_punishment/kefu_withdraw 5 个文件 - 删除遗留脚本 check_prod_uid.py / create_rbac_tables.sql
414 lines
19 KiB
Python
414 lines
19 KiB
Python
"""rank.views.shenhe - auto-generated by split script."""
|
||
import json
|
||
import logging
|
||
from decimal import Decimal
|
||
from django.db import models, transaction
|
||
from django.db.models import F, Count, Q
|
||
from rest_framework.views import APIView
|
||
from rest_framework.response import Response
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from ..utils import get_tag_fee, create_shenhe_jilu, validate_shenheguan
|
||
from ..models import (
|
||
YonghuChenghao, Chenghao, KaoheCishuFeiyong, KaoheJujueJilu,
|
||
KaoheguanBankuai, Bankuai, ShenheJilu, KaoheJiluFeiyong
|
||
)
|
||
from users.models import UserShenheguan, UserDashou
|
||
from users.business_models import User
|
||
from products.models import ShangpinLeixing
|
||
|
||
|
||
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class ShenheDatingModuleView(APIView):
|
||
"""审核大厅获取模块列表(板块 + 待审核数量 + 是否关联)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
yonghu = request.user
|
||
related_ids = set(KaoheguanBankuai.objects.filter(kaoheguan_id=yonghu.UserUID)
|
||
.values_list('bankuai_id', flat=True))
|
||
|
||
bankuai_list = []
|
||
all_bankuai = Bankuai.objects.annotate(
|
||
pending_count=Count(
|
||
'shenhejilu',
|
||
filter=Q(shenhejilu__zhuangtai=3, shenhejilu__shenheguan_id__isnull=True)
|
||
)
|
||
)
|
||
for bk in all_bankuai:
|
||
bankuai_list.append({
|
||
'bankuai_id': bk.bankuai_id,
|
||
'mingcheng': bk.mingcheng,
|
||
'count': bk.pending_count,
|
||
'is_related': bk.bankuai_id in related_ids
|
||
})
|
||
|
||
return Response({'code': 0, 'data': {'modules': bankuai_list}})
|
||
except Exception as e:
|
||
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||
|
||
|
||
class ShenheDatingDataView(APIView):
|
||
"""获取审核大厅待审核记录列表"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
data = request.data
|
||
bankuai_id = data.get('bankuai_id')
|
||
page = int(data.get('page', 1))
|
||
page_size = int(data.get('page_size', 5))
|
||
|
||
query = ShenheJilu.objects.filter(zhuangtai=3, shenheguan_id__isnull=True)
|
||
if bankuai_id:
|
||
query = query.filter(bankuai_id=bankuai_id)
|
||
|
||
total = query.count()
|
||
offset = (page - 1) * page_size
|
||
jilu_list = query.select_related('bankuai', 'chenghao').order_by('-CreateTime')[
|
||
offset:offset + page_size]
|
||
|
||
# 批量加载申请人用户,避免循环内 N+1
|
||
shenqingren_ids = [jl.shenqingren_id for jl in jilu_list if jl.shenqingren_id]
|
||
user_map = {u.UserUID: u for u in User.objects.filter(UserUID__in=shenqingren_ids)}
|
||
|
||
records = []
|
||
for jl in jilu_list:
|
||
user = user_map.get(jl.shenqingren_id)
|
||
avatar = user.Avatar if user else ''
|
||
texiao_json = {}
|
||
if jl.chenghao and jl.chenghao.texiao_miaoshu:
|
||
try:
|
||
texiao_json = json.loads(jl.chenghao.texiao_miaoshu)
|
||
except:
|
||
pass
|
||
|
||
# 新增字段:本次考核费用
|
||
kaohe_feiyong = float(jl.jiaofei_jine) if jl.jiaofei_jine is not None else 0.0
|
||
|
||
records.append({
|
||
'jilu_id': jl.jilu_id,
|
||
'shenqingren_id': jl.shenqingren_id,
|
||
'shenqingren_avatar': avatar,
|
||
'dashou_youxi_id': jl.dashou_youxi_id or '',
|
||
'dashou_beizhu': jl.dashou_beizhu or '',
|
||
'bankuai_id': jl.bankuai.bankuai_id if jl.bankuai else None,
|
||
'bankuai_mingcheng': jl.bankuai.mingcheng if jl.bankuai else '',
|
||
'chenghao_id': jl.chenghao_id,
|
||
'chenghao_mingcheng': jl.chenghao.mingcheng if jl.chenghao else '',
|
||
'chenghao_texiao_json': texiao_json,
|
||
'guize_neirong': jl.guize_neirong or '',
|
||
'kaohe_feiyong': kaohe_feiyong, # 新增
|
||
'CreateTime': jl.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if jl.CreateTime else '',
|
||
})
|
||
|
||
has_more = (page * page_size) < total
|
||
return Response({'code': 0, 'data': {'list': records, 'has_more': has_more, 'total': total}})
|
||
except Exception as e:
|
||
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||
|
||
|
||
class ShenheQiangdanView(APIView):
|
||
"""
|
||
审核官抢单(接审核任务)
|
||
校验:身份、账号封禁、已有进行中审核、记录状态、不能审核自己、板块权限
|
||
成功后更新考核官扩展表:审核总数+1,审核状态设为“审核中”
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
yonghu = request.user
|
||
jilu_id = request.data.get('jilu_id')
|
||
if not jilu_id:
|
||
return Response({'code': 400, 'msg': '记录ID不能为空'})
|
||
|
||
# 1. 检查考核官身份及账号是否被禁用
|
||
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
|
||
if not kg:
|
||
return Response({'code': 403, 'msg': '您不是考核官或账号已被禁用'})
|
||
|
||
# 2. 检查是否有正在审核中的记录(zhuangtai=0),防止同时审核多个
|
||
ongoing = ShenheJilu.objects.filter(
|
||
shenheguan_id=yonghu.UserUID, zhuangtai=0
|
||
).exists()
|
||
if ongoing:
|
||
return Response({'code': 400, 'msg': '您有正在审核中的任务,请完成后再接新任务'})
|
||
|
||
with transaction.atomic():
|
||
# 3. 行锁抢单:仅当状态为待审核(3)且无审核官时才能抢
|
||
jilu = ShenheJilu.objects.select_for_update().filter(
|
||
jilu_id=jilu_id,
|
||
zhuangtai=3,
|
||
shenheguan_id__isnull=True
|
||
).first()
|
||
if not jilu:
|
||
return Response({'code': 400, 'msg': '该审核任务已被接走、状态变更或不存在'})
|
||
|
||
# 4. 不能审核自己
|
||
if jilu.shenqingren_id == yonghu.UserUID:
|
||
return Response({'code': 400, 'msg': '不能审核自己提交的申请'})
|
||
|
||
# 5. 板块权限:考核官必须属于该板块
|
||
is_related = KaoheguanBankuai.objects.filter(
|
||
kaoheguan_id=yonghu.UserUID,
|
||
bankuai=jilu.bankuai
|
||
).exists()
|
||
if not is_related:
|
||
return Response({'code': 400, 'msg': '您不属于该板块,无法审核'})
|
||
|
||
# 新增:6. 费用>0时,要求考核官已认证
|
||
if jilu.jiaofei_jine and float(jilu.jiaofei_jine) > 0:
|
||
if not kg.is_renzheng:
|
||
return Response({'code': 400, 'msg': '本考核需要认证考核官接单,您尚未认证'})
|
||
|
||
# 7. 更新审核记录:分配给当前考核官,状态变为审核中
|
||
jilu.zhuangtai = 0 # 审核中
|
||
jilu.shenheguan_id = yonghu.UserUID
|
||
jilu.save()
|
||
|
||
# 8. 更新考核官扩展表:审核总数 +1,审核状态置为审核中(1)
|
||
UserShenheguan.objects.filter(user=yonghu).update(
|
||
shenhe_zongshu=F('shenhe_zongshu') + 1,
|
||
shenhe_zhuangtai=1
|
||
)
|
||
|
||
return Response({
|
||
'code': 0,
|
||
'msg': '抢单成功,请前往游戏内审核,审核完成后请到考核打分页面进行评定。',
|
||
'data': {'bankuai_id': jilu.bankuai.bankuai_id}
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"审核官抢单异常: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
|
||
|
||
|
||
class ShenheXiangqingView(APIView):
|
||
"""获取当前考核官审核中的记录详情(新增待收考核费字段)"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
yonghu = request.user
|
||
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
|
||
if not kg:
|
||
return Response({'code': 403, 'msg': '您不是考核官或账号异常'})
|
||
|
||
page = int(request.data.get('page', 1))
|
||
page_size = int(request.data.get('page_size', 10))
|
||
|
||
# 查询当前考核官名下状态为“审核中”的记录
|
||
base_query = ShenheJilu.objects.filter(
|
||
shenheguan_id=yonghu.UserUID,
|
||
zhuangtai=0
|
||
).select_related('bankuai', 'chenghao')
|
||
total = base_query.count()
|
||
offset = (page - 1) * page_size
|
||
jilu_list = base_query[offset:offset + page_size]
|
||
|
||
# 批量加载申请人用户,避免循环内 N+1
|
||
shenqingren_ids = [jl.shenqingren_id for jl in jilu_list if jl.shenqingren_id]
|
||
user_map = {u.UserUID: u for u in User.objects.filter(UserUID__in=shenqingren_ids)}
|
||
|
||
# 批量预取费用明细和拒绝记录,避免循环内 N+1
|
||
_jilu_ids = [jl.jilu_id for jl in jilu_list]
|
||
_fee_detail_map = {} # (jilu_id, cishu) -> jine
|
||
_fail_map = {} # (jilu_id, cishu) -> yuanyin
|
||
if _jilu_ids:
|
||
for _fee in KaoheJiluFeiyong.objects.filter(jilu_id__in=_jilu_ids):
|
||
_fee_detail_map[(_fee.jilu_id, _fee.cishu)] = float(_fee.jine)
|
||
for _rej in KaoheJujueJilu.objects.filter(
|
||
jilu_id__in=_jilu_ids
|
||
).order_by('jilu_id', 'cishu', '-CreateTime'):
|
||
_key = (_rej.jilu_id, _rej.cishu)
|
||
if _key not in _fail_map: # 只取首条(最近)
|
||
_fail_map[_key] = _rej.yuanyin or ''
|
||
|
||
records = []
|
||
for jl in jilu_list:
|
||
user = user_map.get(jl.shenqingren_id)
|
||
avatar = user.Avatar if user else ''
|
||
texiao_json = {}
|
||
if jl.chenghao and jl.chenghao.texiao_miaoshu:
|
||
try:
|
||
texiao_json = json.loads(jl.chenghao.texiao_miaoshu)
|
||
except:
|
||
pass
|
||
|
||
# 是否开启金牌
|
||
is_jinpai = jl.chenghao.kaioi_jinpai if jl.chenghao else False
|
||
|
||
# 本次待收考核费(从预取 dict 查询)
|
||
daishou_fei = _fee_detail_map.get((jl.jilu_id, jl.cishu), 0.00)
|
||
# 上次失败原因(从预取 dict 查询)
|
||
last_fail_reason = _fail_map.get((jl.jilu_id, jl.cishu - 1), '')
|
||
|
||
records.append({
|
||
'jilu_id': jl.jilu_id,
|
||
'shenqingren_id': jl.shenqingren_id,
|
||
'shenqingren_avatar': avatar,
|
||
'bankuai_mingcheng': jl.bankuai.mingcheng if jl.bankuai else '',
|
||
'chenghao_mingcheng': jl.chenghao.mingcheng if jl.chenghao else '',
|
||
'chenghao_texiao_json': texiao_json,
|
||
'guize_neirong': jl.guize_neirong or '',
|
||
'dashou_beizhu': jl.dashou_beizhu or '',
|
||
'dashou_youxi_id': jl.dashou_youxi_id or '',
|
||
'shenheguan_youxi_id': jl.shenheguan_youxi_id or '',
|
||
'cishu': jl.cishu,
|
||
'daishou_fei': daishou_fei, # 新增:待收考核费
|
||
'last_fail_reason': last_fail_reason,
|
||
'is_jinpai': is_jinpai,
|
||
'CreateTime': jl.CreateTime.strftime('%Y-%m-%d %H:%M:%S') if jl.CreateTime else '',
|
||
})
|
||
|
||
has_more = (page * page_size) < total
|
||
return Response({'code': 0, 'data': {'list': records, 'has_more': has_more, 'total': total}})
|
||
except Exception as e:
|
||
return Response({'code': 500, 'msg': str(e)}, status=500)
|
||
|
||
|
||
class ShenheCaozuoView(APIView):
|
||
"""
|
||
考核官执行操作(同意/拒绝/转移/修改昵称),并更新考核官收益
|
||
- 只有“同意”或“拒绝”才增加余额和总额,金额严格从 KaoheJiluFeiyong 表获取
|
||
- 查不到费用则加 0 元,绝不影响流程
|
||
- 转移、修改昵称等操作不涉及收益
|
||
"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
try:
|
||
yonghu = request.user
|
||
# 1. 验证考核官身份及账号状态
|
||
kg = UserShenheguan.objects.filter(user=yonghu, zhuangtai=1).first()
|
||
if not kg:
|
||
return Response({'code': 403, 'msg': '账号异常,无法操作'})
|
||
|
||
jilu_id = request.data.get('jilu_id')
|
||
caozuo = request.data.get('caozuo') # agree / reject / transfer / modify_nick
|
||
if not jilu_id or not caozuo:
|
||
return Response({'code': 400, 'msg': '缺少必要参数'})
|
||
|
||
with transaction.atomic():
|
||
# 2. 锁定审核记录,确保属于当前考核官且状态为审核中(0)
|
||
jilu = ShenheJilu.objects.select_for_update().filter(
|
||
jilu_id=jilu_id,
|
||
shenheguan_id=yonghu.UserUID,
|
||
zhuangtai=0
|
||
).first()
|
||
if not jilu:
|
||
return Response({'code': 400, 'msg': '该记录不存在或不属于您'})
|
||
|
||
# 3. 本次操作产生的收入(仅通过/拒绝时有意义)
|
||
daishou_fei = 0.00
|
||
|
||
# 4. 根据操作类型执行对应业务逻辑
|
||
if caozuo == 'agree':
|
||
# --- 通过考核 ---
|
||
# 查询本次考核应得的费用(基于记录ID和当前次数)
|
||
fee_detail = KaoheJiluFeiyong.objects.filter(
|
||
jilu=jilu,
|
||
cishu=jilu.cishu
|
||
).first()
|
||
if fee_detail:
|
||
daishou_fei = float(fee_detail.jine)
|
||
# 找不到就保持 0,绝不使用任何默认值
|
||
|
||
jilu.zhuangtai = 1
|
||
jilu.save()
|
||
|
||
# 授予标签
|
||
if jilu.chenghao:
|
||
yh = User.objects.get(UserUID=jilu.shenqingren_id)
|
||
if not YonghuChenghao.objects.filter(yonghu=yh, chenghao=jilu.chenghao).exists():
|
||
YonghuChenghao.objects.create(yonghu=yh, chenghao=jilu.chenghao)
|
||
# 如果标签开启了金牌,同步更新打手称号
|
||
if jilu.chenghao.kaioi_jinpai:
|
||
dashou_prof = UserDashou.objects.filter(user=yh).first()
|
||
if dashou_prof:
|
||
dashou_prof.chenghao = '金牌选手'
|
||
dashou_prof.save()
|
||
|
||
elif caozuo == 'reject':
|
||
# --- 拒绝考核 ---
|
||
yuanyin = request.data.get('yuanyin', '')
|
||
if not yuanyin.strip():
|
||
return Response({'code': 400, 'msg': '请填写拒绝原因'})
|
||
|
||
# 查询本次考核应得的费用(拒绝时同样获得收入)
|
||
fee_detail = KaoheJiluFeiyong.objects.filter(
|
||
jilu=jilu,
|
||
cishu=jilu.cishu
|
||
).first()
|
||
if fee_detail:
|
||
daishou_fei = float(fee_detail.jine)
|
||
# 找不到就保持 0
|
||
|
||
jilu.zhuangtai = 2
|
||
jilu.save()
|
||
# 记录拒绝原因
|
||
KaoheJujueJilu.objects.create(
|
||
jilu=jilu,
|
||
cishu=jilu.cishu,
|
||
shenheguan_id=yonghu.UserUID,
|
||
yuanyin=yuanyin
|
||
)
|
||
|
||
# 新增:不合格时,如果考核官未认证,则清空考核官ID和游戏昵称
|
||
if not kg.is_renzheng:
|
||
jilu.shenheguan_id = None
|
||
jilu.shenheguan_youxi_id = ''
|
||
jilu.save(update_fields=['shenheguan_id', 'shenheguan_youxi_id'])
|
||
|
||
elif caozuo == 'transfer':
|
||
# 转移考核官:清空考核官信息,状态回退为待审核
|
||
jilu.shenheguan_id = None
|
||
jilu.shenheguan_youxi_id = None
|
||
jilu.zhuangtai = 3
|
||
jilu.save()
|
||
# 转移不更新收益,直接返回
|
||
|
||
|
||
elif caozuo == 'modify_nick':
|
||
# 修改考核官游戏昵称
|
||
youxi_id = request.data.get('youxi_id', '').strip()
|
||
if not youxi_id:
|
||
return Response({'code': 400, 'msg': '请输入游戏昵称'})
|
||
jilu.shenheguan_youxi_id = youxi_id
|
||
jilu.save()
|
||
return Response({'code': 0, 'msg': '修改成功'})
|
||
|
||
else:
|
||
return Response({'code': 400, 'msg': '无效的操作类型'})
|
||
|
||
# 5. 更新考核官收益(仅“通过”或“拒绝”时执行)
|
||
if caozuo in ('transfer'):
|
||
update_fields = {
|
||
|
||
'shenhe_zhuangtai': 0, # 设为空闲
|
||
}
|
||
UserShenheguan.objects.filter(user=yonghu).update(**update_fields)
|
||
if caozuo in ('agree', 'reject'):
|
||
update_fields = {
|
||
'yue': F('yue') + Decimal(str(daishou_fei)),
|
||
'zonge': F('zonge') + Decimal(str(daishou_fei)),
|
||
'shenhe_zhuangtai': 0, # 设为空闲
|
||
}
|
||
# 只有同意时才增加“通过总数”
|
||
if caozuo == 'agree':
|
||
update_fields['tongguo_zongshu'] = F('tongguo_zongshu') + 1
|
||
|
||
# 审核总数不再增加
|
||
UserShenheguan.objects.filter(user=yonghu).update(**update_fields)
|
||
|
||
return Response({'code': 0, 'msg': '操作成功'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"考核操作异常: {str(e)}", exc_info=True)
|
||
return Response({'code': 500, 'msg': '服务器内部错误'}, status=500)
|