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:
2026-07-05 23:17:38 +08:00
parent 76ea5f8255
commit f1c8633345
54 changed files with 5332 additions and 4494 deletions

View File

@@ -109,7 +109,7 @@ def staff_dispatch_order(member, staff_user, data):
zhiding_dashou = None
if zhiding_uid:
try:
zhiding_user = User.query.get(UserUID=zhiding_uid)
zhiding_user = User.query.select_related('DashouProfile').get(UserUID=zhiding_uid)
zhiding_dashou = zhiding_user.DashouProfile
if zhiding_dashou.zhanghaozhuangtai != 1:
return False, {'code': 400, 'msg': '指定打手状态异常', 'data': None}

View File

@@ -119,6 +119,7 @@ def staff_generate_link(member, staff_user, data):
final_huiyuan_id = shangpin_leixing.huiyuan_id or ''
label_name = ''
ch = None
if chenghao_id:
ch = Chenghao.query.filter(id=chenghao_id).first()
if ch:
@@ -155,10 +156,9 @@ def staff_generate_link(member, staff_user, data):
MerchantNickname=shangjia.nicheng or f"商家{merchant_id}",
)
if chenghao_id:
ch = Chenghao.query.filter(id=chenghao_id).first()
if ch:
DingdanBiaoqian.query.create(dingdan=dingdan, chenghao=ch)
# 复用已查询的 ch 对象,避免重复查询
if ch:
DingdanBiaoqian.query.create(dingdan=dingdan, chenghao=ch)
UserShangjia.query.filter(id=shangjia.id).update(
fabu=F('fabu') + 1,

View File

@@ -128,8 +128,16 @@ class StaffInviteListView(APIView):
qs = MerchantStaffInvite.query.filter(merchant_id=request.user.UserUID).select_related('role')
if status_filter is not None:
qs = qs.filter(status=int(status_filter))
invites = list(qs.order_by('-CreateTime')[:200])
# 批量预取已使用邀请码对应的用户,避免循环内 N+1
from users.business_models import User
_used_uids = [inv.used_by for inv in invites if inv.used_by]
_user_map = {
u.UserUID: u
for u in User.query.filter(UserUID__in=_used_uids).only('UserUID', 'Nickname')
} if _used_uids else {}
items = []
for inv in qs.order_by('-CreateTime')[:200]:
for inv in invites:
item = {
'id': inv.id,
'code': inv.invite_code,
@@ -140,8 +148,7 @@ class StaffInviteListView(APIView):
'user': None,
}
if inv.used_by:
from users.business_models import User
u = User.query.filter(UserUID=inv.used_by).first()
u = _user_map.get(inv.used_by)
item['user'] = {'nickname': getattr(u, 'Nickname', '') or inv.used_by, 'uid': inv.used_by}
items.append(item)
return Response({'code': 0, 'msg': '成功', 'data': {'list': items, 'gudingCode': 'XQKF2026'}})
@@ -181,21 +188,51 @@ class StaffMemberListView(APIView):
merchant_id=mid,
status__in=[MEMBER_STATUS_ACTIVE, MEMBER_STATUS_DISABLED],
).select_related('role')
result = []
for m in members:
total_stats = MerchantStaffDailyStats.query.filter(member_id=m.id).aggregate(
members_list = list(members)
_member_ids = [m.id for m in members_list]
_staff_uids = [m.staff_user_id for m in members_list if m.staff_user_id]
# 批量预取聚合统计,避免循环内 N+1
_total_stats_map = {}
_today_stat_map = {}
if _member_ids:
from django.db.models import Count
for item in MerchantStaffDailyStats.query.filter(
member_id__in=_member_ids
).values('member_id').annotate(
dispatch_count=Sum('dispatch_count'),
dispatch_amount=Sum('dispatch_amount'),
settle_count=Sum('settle_count'),
settle_amount=Sum('settle_amount'),
refund_count=Sum('refund_count'),
refund_amount=Sum('refund_amount'),
)
today_stat = MerchantStaffDailyStats.query.filter(
member_id=m.id, stat_date=today,
).first()
wallet = get_or_create_wallet(m)
u = User.query.filter(UserUID=m.staff_user_id).first()
):
_total_stats_map[item['member_id']] = item
for s in MerchantStaffDailyStats.query.filter(
member_id__in=_member_ids, stat_date=today,
):
_today_stat_map[s.member_id] = s
# 批量预取用户头像
_user_map = {
u.UserUID: u
for u in User.query.filter(UserUID__in=_staff_uids).only('UserUID', 'Avatar')
} if _staff_uids else {}
# 批量预取钱包
_wallet_map = {
w.member_id: w
for w in MerchantStaffWallet.objects.filter(member_id__in=_member_ids)
} if _member_ids else {}
result = []
for m in members_list:
total_stats = _total_stats_map.get(m.id, {})
today_stat = _today_stat_map.get(m.id)
wallet = _wallet_map.get(m.id)
if wallet is None:
wallet = get_or_create_wallet(m)
u = _user_map.get(m.staff_user_id)
result.append({
'id': m.id,
'uid': m.staff_user_id,
@@ -210,13 +247,13 @@ class StaffMemberListView(APIView):
'isDisabled': m.status == MEMBER_STATUS_DISABLED,
'todayDispatchCount': today_stat.dispatch_count if today_stat else int(m.today_dispatch_count or 0),
'todayDispatchAmount': str(today_stat.dispatch_amount if today_stat else (m.today_dispatch_amount or '0.00')),
'dispatchCount': total_stats['dispatch_count'] or 0,
'dispatchAmount': str(total_stats['dispatch_amount'] or '0.00'),
'orderCount': total_stats['dispatch_count'] or 0,
'settleCount': total_stats['settle_count'] or 0,
'settleAmount': str(total_stats['settle_amount'] or '0.00'),
'refundCount': total_stats['refund_count'] or 0,
'refundAmount': str(total_stats['refund_amount'] or '0.00'),
'dispatchCount': total_stats.get('dispatch_count') or 0,
'dispatchAmount': str(total_stats.get('dispatch_amount') or '0.00'),
'orderCount': total_stats.get('dispatch_count') or 0,
'settleCount': total_stats.get('settle_count') or 0,
'settleAmount': str(total_stats.get('settle_amount') or '0.00'),
'refundCount': total_stats.get('refund_count') or 0,
'refundAmount': str(total_stats.get('refund_amount') or '0.00'),
'quota_available': str(wallet.quota_available),
'quota_total': str(wallet.quota_total),
'quota_used': str(wallet.quota_used),
@@ -618,9 +655,15 @@ class StaffRankView(APIView):
sort_field = sort_by if sort_by in ('dispatch_count', 'dispatch_amount', 'settle_count', 'settle_amount') else 'dispatch_count'
agg = sorted(agg, key=lambda x: x[sort_field] or 0, reverse=True)[:50]
# 批量预取 member 信息,避免循环内 N+1
_member_ids = [row['member_id'] for row in agg]
_members_map = {
m.id: m
for m in MerchantStaffMember.query.filter(id__in=_member_ids).only('id', 'display_name')
} if _member_ids else {}
result = []
for idx, row in enumerate(agg, 1):
m = MerchantStaffMember.query.filter(id=row['member_id']).first()
m = _members_map.get(row['member_id'])
result.append({
'rank': idx,
'member_id': row['member_id'],