Files
Django/rank/views/shenhe.py
TermiNexus f1c8633345 perf: P1 性能优化批量修复 + kefu 视图拆分
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
2026-07-05 23:17:38 +08:00

414 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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)