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

@@ -1,4 +1,4 @@
import io
import io
import os
import re
import time
@@ -394,40 +394,46 @@ class AdminUpdateConfigView(APIView):
# 🔴 修复更新QQ群配置 - 确保根据peizhiid正确更新对应的记录
qq_groups = request.data.get('qq_groups')
if qq_groups and isinstance(qq_groups, list):
# 收集所有 peizhiid批量预取已有记录避免循环内 N+1
_peizhiids = [g.get('peizhiid') for g in qq_groups if g.get('peizhiid')]
_existing_map = {}
if _peizhiids:
for q in Qunpeizhi.query.filter(GroupType__in=_peizhiids):
# 如果存在多个相同 peizhiid 的记录,取第一个(应该只有一个)
if q.GroupType not in _existing_map:
_existing_map[q.GroupType] = q
_to_create = []
_to_update = []
updated_groups = []
for group_data in qq_groups:
peizhiid = group_data.get('peizhiid')
if not peizhiid:
continue
# 🔴 修复使用filter()确保查询到正确的记录
qun_configs = Qunpeizhi.query.filter(GroupType=peizhiid)
if qun_configs.exists():
# 如果存在多个相同peizhiid的记录取第一个应该只有一个
qun_config = qun_configs.first()
# 更新字段
neirong = group_data.get('neirong')
qunid = group_data.get('qunid')
jieshao = group_data.get('jieshao')
neirong = group_data.get('neirong')
qunid = group_data.get('qunid')
jieshao = group_data.get('jieshao')
qun_config = _existing_map.get(peizhiid)
if qun_config is not None:
# 更新已有记录
if neirong is not None:
qun_config.GroupContent = neirong or ''
if qunid is not None:
qun_config.GroupID = qunid or ''
if jieshao is not None:
qun_config.Description = jieshao or ''
qun_config.save()
_to_update.append(qun_config)
else:
# 如果不存在创建新的使用提供的peizhiid
qun_config = Qunpeizhi.query.create(
# 不存在创建新的
qun_config = Qunpeizhi(
GroupType=peizhiid,
GroupContent=group_data.get('neirong', '') or '',
GroupID=group_data.get('qunid', '') or '',
Description=group_data.get('jieshao', '') or ''
GroupContent=neirong or '',
GroupID=qunid or '',
Description=jieshao or '',
)
_to_create.append(qun_config)
updated_groups.append({
'peizhiid': qun_config.GroupType,
@@ -436,6 +442,13 @@ class AdminUpdateConfigView(APIView):
'jieshao': qun_config.Description
})
if _to_create:
Qunpeizhi.objects.bulk_create(_to_create)
if _to_update:
Qunpeizhi.objects.bulk_update(
_to_update, ['GroupContent', 'GroupID', 'Description']
)
if updated_groups:
response_data['qq_groups'] = updated_groups

View File

@@ -1,4 +1,4 @@
import io
import io
import os
import re
import time
@@ -270,6 +270,13 @@ class DashouPaihangView(APIView):
# 第一名数量不足,不展示
return [], False
# 批量预取老板扩展表昵称
_boss_user_ids = [g.user_id for g in guanshi_list]
_boss_nicheng_map = {
b.user_id: b.nickname
for b in UserBoss.query.filter(user_id__in=_boss_user_ids).only('user_id', 'nickname')
}
# 构建返回数据
paihang_list = []
for index, guanshi in enumerate(guanshi_list):
@@ -277,8 +284,7 @@ class DashouPaihangView(APIView):
user_main = guanshi.user
# 管事的昵称需要查询老板扩展表
boss_profile = UserBoss.query.filter(user=user_main).first()
guanshi_nicheng = boss_profile.nickname if boss_profile else '未设置昵称'
guanshi_nicheng = _boss_nicheng_map.get(guanshi.user_id, '未设置昵称')
# 获取头像相对URL
touxiang_url = user_main.Avatar or ''
@@ -337,6 +343,13 @@ class DashouPaihangView(APIView):
# 第一名流水不足,不展示
return [], False
# 批量预取老板扩展表昵称
_boss_user_ids = [s.user_id for s in shangjia_list]
_boss_nicheng_map = {
b.user_id: b.nickname
for b in UserBoss.query.filter(user_id__in=_boss_user_ids).only('user_id', 'nickname')
}
# 构建返回数据
paihang_list = []
for index, shangjia in enumerate(shangjia_list):
@@ -344,8 +357,7 @@ class DashouPaihangView(APIView):
user_main = shangjia.user
# 商家的昵称需要查询老板扩展表
boss_profile = UserBoss.query.filter(user=user_main).first()
shangjia_nicheng = boss_profile.nickname if boss_profile else '未设置昵称'
shangjia_nicheng = _boss_nicheng_map.get(shangjia.user_id, '未设置昵称')
# 获取头像相对URL
touxiang_url = user_main.Avatar or ''
@@ -604,21 +616,21 @@ class GetXiugaiJiluView(APIView):
records = cursor.fetchall()
# 🔥 6. 处理查询结果(严格按照字段类型处理)
# 预查询修改者账号信息(循环外执行一次,避免循环内重复查询同一 SQL
xiugaizhe_zhanghao = ''
with connection.cursor() as cursor2:
cursor2.execute(
"SELECT phone, yonghuid FROM user_main WHERE phone = %s OR yonghuid = %s LIMIT 1",
[xiugaizhe_zhanghaoid, xiugaizhe_zhanghaoid]
)
xiugaizhe_info = cursor2.fetchone()
if xiugaizhe_info:
xiugaizhe_zhanghao = str(xiugaizhe_info[0]) if xiugaizhe_info[0] else str(xiugaizhe_info[1]) if \
xiugaizhe_info[1] else xiugaizhe_zhanghaoid
jilu_list = []
for record in records:
# 获取修改者账号信息(都是字符串)
xiugaizhe_zhanghao = ''
with connection.cursor() as cursor2:
# 🔥 phone和yonghuid都是CharField用字符串查询
cursor2.execute(
"SELECT phone, yonghuid FROM user_main WHERE phone = %s OR yonghuid = %s LIMIT 1",
[xiugaizhe_zhanghaoid, xiugaizhe_zhanghaoid]
)
xiugaizhe_info = cursor2.fetchone()
if xiugaizhe_info:
# 🔥 都是字符串
xiugaizhe_zhanghao = str(xiugaizhe_info[0]) if xiugaizhe_info[0] else str(xiugaizhe_info[1]) if \
xiugaizhe_info[1] else xiugaizhe_zhanghaoid
# 修改者账号信息已预查询,直接使用
# 获取会员名称(如果有)
huiyuan_mingzi = ''

View File

@@ -1,4 +1,4 @@
import io
import io
import os
import re
import time
@@ -508,7 +508,7 @@ class KehuGetDingdanLianjieView(APIView):
# 获取商家昵称
shangjia_nicheng = ''
try:
user_main = User.query.get(UserUID=lianjie_obj.UserID)
user_main = User.query.select_related('ShopProfile').get(UserUID=lianjie_obj.UserID)
shangjia_obj = user_main.ShopProfile
shangjia_nicheng = shangjia_obj.nicheng
except (User.DoesNotExist, UserShangjia.DoesNotExist):
@@ -816,7 +816,7 @@ class KehuTianxieDingdanView(APIView):
# 验证打手是否存在且有效
try:
# 先查询用户主表
dashou_user = User.query.get(UserUID=zhiding_dashou)
dashou_user = User.query.select_related('DashouProfile').get(UserUID=zhiding_dashou)
logger.info(f"找到打手用户 - 用户ID: {zhiding_dashou}")
# 通过反向关系获取打手扩展信息
@@ -922,7 +922,7 @@ class KehuTianxieDingdanView(APIView):
# 获取商家信息用于返回
try:
user_main = User.query.get(UserUID=lianjie_obj.UserID)
user_main = User.query.select_related('ShopProfile').get(UserUID=lianjie_obj.UserID)
shangjia_obj = user_main.ShopProfile
shangjia_mingcheng = shangjia_obj.nicheng
except:

View File

@@ -1,4 +1,4 @@
import io
import io
import os
import re
import time
@@ -142,17 +142,21 @@ class ShangjiaMobanListView(APIView):
return Response({'code': 400, 'msg': '页码超出范围', 'data': {}}, status=400)
current_page = paginator.page(page)
# 批量预取所有称号,避免循环内 N+1
_mobans_list = list(current_page.object_list)
_title_ids = [m.TitleID for m in _mobans_list if m.TitleID]
_ch_map = {
ch.id: ch
for ch in Chenghao.query.filter(id__in=_title_ids).only('id', 'mingcheng', 'texiao_miaoshu')
} if _title_ids else {}
formatted = []
for moban in current_page.object_list:
for moban in _mobans_list:
texiao_json = ''
label_name = ''
if moban.TitleID:
try:
ch = Chenghao.query.get(id=moban.TitleID)
label_name = ch.mingcheng
texiao_json = ch.texiao_miaoshu or ''
except Chenghao.DoesNotExist:
pass
ch = _ch_map.get(moban.TitleID)
if ch:
label_name = ch.mingcheng
texiao_json = ch.texiao_miaoshu or ''
formatted.append({
'mobanId': moban.id,
'shangpinTypeId': moban.ProductTypeID,