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
This commit is contained in:
@@ -3,7 +3,7 @@ import json
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from django.db import models, transaction
|
||||
from django.db.models import F
|
||||
from django.db.models import F, Prefetch
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
@@ -118,15 +118,26 @@ class KaoheJinpaiTagsView(APIView):
|
||||
.values_list('chenghao__mingcheng', flat=True)
|
||||
)
|
||||
|
||||
# 板块列表
|
||||
# 板块列表(prefetch 一次性加载标签和费用,避免三层嵌套 N+1)
|
||||
plate_list = []
|
||||
for bk in Bankuai.objects.all():
|
||||
tags = Chenghao.objects.filter(bankuai=bk, leixing='dashou')
|
||||
all_bankuai = Bankuai.objects.prefetch_related(
|
||||
Prefetch(
|
||||
'chenghao_set',
|
||||
queryset=Chenghao.objects.filter(leixing='dashou').prefetch_related(
|
||||
Prefetch(
|
||||
'kaohecishufeiyong_set',
|
||||
queryset=KaoheCishuFeiyong.objects.order_by('cishu'),
|
||||
)
|
||||
),
|
||||
to_attr='dashou_tags'
|
||||
)
|
||||
).all()
|
||||
for bk in all_bankuai:
|
||||
tag_list = []
|
||||
for tag in tags:
|
||||
# 费用列表
|
||||
fee_objs = KaoheCishuFeiyong.objects.filter(chenghao=tag).order_by('cishu')
|
||||
fee_list = [{'cishu': f.cishu, 'feiyong': float(f.feiyong)} for f in fee_objs]
|
||||
for tag in getattr(bk, 'dashou_tags', []):
|
||||
# 费用列表(已预加载,保持 cishu 升序)
|
||||
fee_list = [{'cishu': f.cishu, 'feiyong': float(f.feiyong)}
|
||||
for f in tag.kaohecishufeiyong_set.all()]
|
||||
|
||||
# 若没有记录,补充默认费用
|
||||
if not fee_list:
|
||||
@@ -151,12 +162,13 @@ class KaoheJinpaiTagsView(APIView):
|
||||
|
||||
# 新增:返回所有在职考核官信息(用于指定考核官)
|
||||
kaoheguan_list = []
|
||||
for kg in UserShenheguan.objects.filter(zhuangtai=1).select_related('user'):
|
||||
# select_related 预加载 OneToOne 反向关系 DashouProfile,避免循环内 N+1
|
||||
for kg in UserShenheguan.objects.filter(zhuangtai=1).select_related('user', 'user__DashouProfile'):
|
||||
uid = kg.user.UserUID
|
||||
avatar = kg.user.Avatar or ''
|
||||
# 获取考核官的打手昵称(从打手扩展表)
|
||||
# 获取考核官的打手昵称(从打手扩展表,已预加载)
|
||||
dashou_nick = ''
|
||||
dashou_prof = UserDashou.objects.filter(user=kg.user).first()
|
||||
dashou_prof = getattr(kg.user, 'DashouProfile', None)
|
||||
if dashou_prof:
|
||||
dashou_nick = dashou_prof.nicheng or ''
|
||||
kaoheguan_list.append({
|
||||
|
||||
@@ -48,20 +48,42 @@ class KaoheJiluView(APIView):
|
||||
total = query.count()
|
||||
records = query.order_by('-CreateTime')[(page-1)*page_size : page*page_size]
|
||||
|
||||
# 批量预取关联数据,避免循环内 N+1
|
||||
records = records.select_related('chenghao', 'bankuai')
|
||||
records_list = list(records)
|
||||
jilu_ids = [jl.jilu_id for jl in records_list]
|
||||
|
||||
# 批量汇总缴纳总额(按 jilu_id 分组聚合)
|
||||
_fee_map = {}
|
||||
if jilu_ids:
|
||||
_fee_agg = KaoheJiluFeiyong.objects.filter(
|
||||
jilu_id__in=jilu_ids
|
||||
).values('jilu_id').annotate(total=models.Sum('jine'))
|
||||
for item in _fee_agg:
|
||||
_fee_map[item['jilu_id']] = float(item['total'] or 0.00)
|
||||
|
||||
# 批量获取上次拒绝原因(每个 jilu_id 取最近一条)
|
||||
_fail_map = {}
|
||||
if jilu_ids:
|
||||
_reject_qs = KaoheJujueJilu.objects.filter(
|
||||
jilu_id__in=jilu_ids
|
||||
).order_by('jilu_id', '-CreateTime')
|
||||
for rej in _reject_qs:
|
||||
if rej.jilu_id not in _fail_map: # 只取首条(最近)
|
||||
_fail_map[rej.jilu_id] = rej.yuanyin or ''
|
||||
|
||||
# 批量获取考核官头像
|
||||
_avatar_map = {}
|
||||
_kg_ids = {jl.shenheguan_id for jl in records_list if jl.shenheguan_id}
|
||||
if _kg_ids:
|
||||
for u in User.objects.filter(UserUID__in=_kg_ids):
|
||||
_avatar_map[u.UserUID] = u.Avatar or ''
|
||||
|
||||
data = []
|
||||
for jl in records:
|
||||
# 汇总缴纳总额
|
||||
total_fee = KaoheJiluFeiyong.objects.filter(jilu=jl).aggregate(
|
||||
total=models.Sum('jine')
|
||||
)['total'] or 0.00
|
||||
|
||||
# 上次拒绝原因(最近一条)
|
||||
last_fail = KaoheJujueJilu.objects.filter(jilu=jl).order_by('-CreateTime').first()
|
||||
fail_reason = last_fail.yuanyin if last_fail else ''
|
||||
|
||||
# 考核官头像
|
||||
kg_user = User.objects.filter(UserUID=jl.shenheguan_id).first()
|
||||
kg_avatar = kg_user.Avatar if kg_user else ''
|
||||
for jl in records_list:
|
||||
total_fee = _fee_map.get(jl.jilu_id, 0.00)
|
||||
fail_reason = _fail_map.get(jl.jilu_id, '')
|
||||
kg_avatar = _avatar_map.get(jl.shenheguan_id, '')
|
||||
|
||||
data.append({
|
||||
'jilu_id': jl.jilu_id,
|
||||
|
||||
@@ -3,7 +3,7 @@ import json
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from django.db import models, transaction
|
||||
from django.db.models import F
|
||||
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
|
||||
@@ -33,12 +33,17 @@ class ShenheDatingModuleView(APIView):
|
||||
.values_list('bankuai_id', flat=True))
|
||||
|
||||
bankuai_list = []
|
||||
for bk in Bankuai.objects.all():
|
||||
count = ShenheJilu.objects.filter(bankuai=bk, zhuangtai=3, shenheguan_id__isnull=True).count()
|
||||
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': count,
|
||||
'count': bk.pending_count,
|
||||
'is_related': bk.bankuai_id in related_ids
|
||||
})
|
||||
|
||||
@@ -67,9 +72,13 @@ class ShenheDatingDataView(APIView):
|
||||
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.objects.filter(UserUID=jl.shenqingren_id).first()
|
||||
user = user_map.get(jl.shenqingren_id)
|
||||
avatar = user.Avatar if user else ''
|
||||
texiao_json = {}
|
||||
if jl.chenghao and jl.chenghao.texiao_miaoshu:
|
||||
@@ -202,9 +211,27 @@ class ShenheXiangqingView(APIView):
|
||||
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.objects.filter(UserUID=jl.shenqingren_id).first()
|
||||
user = user_map.get(jl.shenqingren_id)
|
||||
avatar = user.Avatar if user else ''
|
||||
texiao_json = {}
|
||||
if jl.chenghao and jl.chenghao.texiao_miaoshu:
|
||||
@@ -216,25 +243,10 @@ class ShenheXiangqingView(APIView):
|
||||
# 是否开启金牌
|
||||
is_jinpai = jl.chenghao.kaioi_jinpai if jl.chenghao else False
|
||||
|
||||
# 本次待收考核费(基于记录中的当前次数,从费用明细表查询)
|
||||
daishou_fei = 0.00
|
||||
fee_detail = KaoheJiluFeiyong.objects.filter(
|
||||
jilu=jl,
|
||||
cishu=jl.cishu
|
||||
).first()
|
||||
if fee_detail:
|
||||
daishou_fei = float(fee_detail.jine)
|
||||
else:
|
||||
# 兜底:若明细表无记录,尝试用通用费用表计算
|
||||
daishou_fei = 0.00
|
||||
# 上次失败原因
|
||||
last_fail_reason = ''
|
||||
last_fail = KaoheJujueJilu.objects.filter(
|
||||
jilu=jl,
|
||||
cishu=jl.cishu - 1
|
||||
).order_by('-CreateTime').first()
|
||||
if last_fail:
|
||||
last_fail_reason = last_fail.yuanyin
|
||||
# 本次待收考核费(从预取 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,
|
||||
|
||||
Reference in New Issue
Block a user