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

@@ -580,12 +580,19 @@ class KhjlglView(APIView):
pending=Count('jilu_id', filter=Q(zhuangtai=3)),
).order_by('-total')
_stats_agg = stats_base.aggregate(
total=Count('id'),
passed=Count('id', filter=Q(zhuangtai=1)),
failed=Count('id', filter=Q(zhuangtai=2)),
in_progress=Count('id', filter=Q(zhuangtai=0)),
pending=Count('id', filter=Q(zhuangtai=3)),
)
stats = {
'total': stats_base.count(),
'passed': stats_base.filter(zhuangtai=1).count(),
'failed': stats_base.filter(zhuangtai=2).count(),
'in_progress': stats_base.filter(zhuangtai=0).count(),
'pending': stats_base.filter(zhuangtai=3).count(),
'total': _stats_agg['total'],
'passed': _stats_agg['passed'],
'failed': _stats_agg['failed'],
'in_progress': _stats_agg['in_progress'],
'pending': _stats_agg['pending'],
'total_fee': float(agg['total_fee'] or 0),
'tag_stats': [
{
@@ -702,8 +709,11 @@ class KhjlczView(APIView):
for f in KaoheJiluFeiyong.query.filter(jilu_id=jilu_id).order_by('cishu')
]
reject_list = []
for r in KaoheJujueJilu.query.filter(jilu_id=jilu_id).order_by('-CreateTime'):
kg_prof = _batch_user_profile_map([r.shenheguan_id]).get(r.shenheguan_id, {})
_reject_records = list(KaoheJujueJilu.query.filter(jilu_id=jilu_id).order_by('-CreateTime'))
_kg_ids = [r.shenheguan_id for r in _reject_records if r.shenheguan_id]
_kg_prof_map = _batch_user_profile_map(_kg_ids) if _kg_ids else {}
for r in _reject_records:
kg_prof = _kg_prof_map.get(r.shenheguan_id, {})
reject_list.append({
'cishu': r.cishu,
'shenheguan_id': r.shenheguan_id,

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -12,6 +12,7 @@ import secrets
import random
import string
from calendar import monthrange
from collections import defaultdict
from decimal import Decimal
from datetime import date, datetime, timedelta
from django.utils import timezone
@@ -155,13 +156,18 @@ class CwhybkhqView(APIView):
if 'huiyuancaiwu' not in permissions:
return Response({'code': 403, 'msg': '无会员财务查看权限'}, status=403)
all_bankuai = list(Bankuai.query.all())
bankuai_ids = [bk.bankuai_id for bk in all_bankuai]
all_members = Huiyuan.query.filter(bankuai_id__in=bankuai_ids).values('huiyuan_id', 'jieshao', 'bankuai_id')
members_by_bankuai = defaultdict(list)
for m in all_members:
members_by_bankuai[m['bankuai_id']].append(dict(m))
bankuai_list = []
for bk in Bankuai.query.all():
members = Huiyuan.query.filter(bankuai=bk).values('huiyuan_id', 'jieshao', 'bankuai_id')
for bk in all_bankuai:
bankuai_list.append({
'bankuai_id': bk.bankuai_id,
'mingcheng': bk.mingcheng,
'members': list(members)
'members': members_by_bankuai.get(bk.bankuai_id, [])
})
return Response({'code': 0, 'msg': 'ok', 'data': bankuai_list})
@@ -597,21 +603,30 @@ class CwhqjtddsjView(APIView):
summary_qs = filter_queryset_by_club(
Order.query.filter(**filters), request, club_field='ClubID',
)
total_order_count = summary_qs.count()
total_order_amount = summary_qs.aggregate(s=Sum('Amount'))['s'] or 0
total_success_count = summary_qs.filter(Status=3).count()
total_success_amount = summary_qs.filter(Status=3).aggregate(s=Sum('Amount'))['s'] or 0
total_refund_count = summary_qs.filter(Status=5).count()
total_dashou = summary_qs.filter(Status=3).aggregate(s=Sum('PlayerCommission'))['s'] or 0
total_dianpu_fenhong = summary_qs.filter(Status=3, Platform=1).aggregate(
s=Sum('pingtai_kuozhan__ShopIncome'))['s'] or 0
platform_amount = summary_qs.filter(Status=3, Platform=1).aggregate(s=Sum('Amount'))['s'] or 0
merchant_amount = summary_qs.filter(Status=3, Platform=2).aggregate(s=Sum('Amount'))['s'] or 0
platform_dashou_sum = \
summary_qs.filter(Status=3, Platform=1).aggregate(s=Sum('PlayerCommission'))['s'] or 0
merchant_dashou_sum = \
summary_qs.filter(Status=3, Platform=2).aggregate(s=Sum('PlayerCommission'))['s'] or 0
agg = summary_qs.aggregate(
total_order_count=Count('id'),
total_order_amount=Sum('Amount'),
total_success_count=Count('id', filter=Q(Status=3)),
total_success_amount=Sum('Amount', filter=Q(Status=3)),
total_refund_count=Count('id', filter=Q(Status=5)),
total_dashou=Sum('PlayerCommission', filter=Q(Status=3)),
total_dianpu_fenhong=Sum('pingtai_kuozhan__ShopIncome', filter=Q(Status=3, Platform=1)),
platform_amount=Sum('Amount', filter=Q(Status=3, Platform=1)),
merchant_amount=Sum('Amount', filter=Q(Status=3, Platform=2)),
platform_dashou_sum=Sum('PlayerCommission', filter=Q(Status=3, Platform=1)),
merchant_dashou_sum=Sum('PlayerCommission', filter=Q(Status=3, Platform=2)),
)
total_order_count = agg['total_order_count'] or 0
total_order_amount = agg['total_order_amount'] or 0
total_success_count = agg['total_success_count'] or 0
total_success_amount = agg['total_success_amount'] or 0
total_refund_count = agg['total_refund_count'] or 0
total_dashou = agg['total_dashou'] or 0
total_dianpu_fenhong = agg['total_dianpu_fenhong'] or 0
platform_amount = agg['platform_amount'] or 0
merchant_amount = agg['merchant_amount'] or 0
platform_dashou_sum = agg['platform_dashou_sum'] or 0
merchant_dashou_sum = agg['merchant_dashou_sum'] or 0
profit_platform = platform_amount - platform_dashou_sum - total_dianpu_fenhong
profit_merchant = merchant_amount - merchant_dashou_sum

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -357,9 +357,16 @@ class GetGuanliDetailView(APIView):
custom_first = {} # 首次分红定制cishu=1
extra_map = {} # 其他次数 { cishu: { huiyuan_id: { guanshi_fenhong, jieshao, jiage } } }
# 批量预取会员信息
_huiyuan_ids = [r.huiyuan for r in duoci_records]
_huiyuan_map = {
h.huiyuan_id: h
for h in Huiyuan.query.filter(huiyuan_id__in=_huiyuan_ids).only('huiyuan_id', 'jieshao', 'jiage')
}
for record in duoci_records:
# 获取会员信息
member = Huiyuan.query.filter(huiyuan_id=record.huiyuan).first()
member = _huiyuan_map.get(record.huiyuan)
if not member:
continue
member_info = {
@@ -711,10 +718,16 @@ class UpdateGuanliView(APIView):
if custom_first is not None and isinstance(custom_first, dict):
from jituan.services.club_penalty import resolve_gsfenhong_club_id
guanshi_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
# 取当前所有首次定制记录
existing_first = DuociFenhong.query.filter(
club_id=guanshi_club, yonghuid=yonghuid, cishu=1,
# 批量预取当前所有首次定制记录(避免循环内 N+1
existing_first_list = list(
DuociFenhong.query.filter(
club_id=guanshi_club, yonghuid=yonghuid, cishu=1,
)
)
_existing_map = {r.huiyuan: r for r in existing_first_list}
_to_create = []
_to_update = []
_to_delete_ids = []
# 前端传来的定制字典 {会员ID: 金额}
for huiyuan_id, amount in custom_first.items():
try:
@@ -722,24 +735,34 @@ class UpdateGuanliView(APIView):
except:
continue
if amount <= 0:
DuociFenhong.query.filter(
club_id=guanshi_club, yonghuid=yonghuid, huiyuan=huiyuan_id, cishu=1,
).delete()
rec = _existing_map.get(huiyuan_id)
if rec is not None:
_to_delete_ids.append(rec.id)
else:
DuociFenhong.query.update_or_create(
club_id=guanshi_club,
yonghuid=yonghuid,
huiyuan=huiyuan_id,
cishu=1,
defaults={
'guanshi_fenhong': amount,
'zuzhang_fenhong': Decimal('0'),
},
)
rec = _existing_map.get(huiyuan_id)
if rec is not None:
rec.guanshi_fenhong = amount
rec.zuzhang_fenhong = Decimal('0')
_to_update.append(rec)
else:
_to_create.append(DuociFenhong(
club_id=guanshi_club,
yonghuid=yonghuid,
huiyuan=huiyuan_id,
cishu=1,
guanshi_fenhong=amount,
zuzhang_fenhong=Decimal('0'),
))
# 删除那些在前端字典中不存在的首次定制记录(即已取消定制)
for record in existing_first:
if record.huiyuan not in custom_first:
record.delete()
for rec in existing_first_list:
if rec.huiyuan not in custom_first and rec.id not in _to_delete_ids:
_to_delete_ids.append(rec.id)
if _to_create:
DuociFenhong.objects.bulk_create(_to_create)
if _to_update:
DuociFenhong.objects.bulk_update(_to_update, ['guanshi_fenhong', 'zuzhang_fenhong'])
if _to_delete_ids:
DuociFenhong.objects.filter(id__in=_to_delete_ids).delete()
# 4.3 额外次数分红cishu >= 2
if extra_map is not None and isinstance(extra_map, dict):
@@ -747,6 +770,8 @@ class UpdateGuanliView(APIView):
guanshi_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
# 收集需要保留的记录
keep_set = set()
# 解析前端数据,先汇总 keep_set 与目标金额
_extra_targets = [] # [(cishu, huiyuan_id, amount)]
for times_str, members in extra_map.items():
try:
cishu = int(times_str)
@@ -767,24 +792,42 @@ class UpdateGuanliView(APIView):
except:
continue
keep_set.add((cishu, huiyuan_id))
# 更新或创建
DuociFenhong.query.update_or_create(
_extra_targets.append((cishu, huiyuan_id, amount))
# 批量预取已有额外次数记录
existing_extra_list = list(
DuociFenhong.query.filter(
club_id=guanshi_club, yonghuid=yonghuid, cishu__gte=2,
)
)
_extra_map = {(r.cishu, r.huiyuan): r for r in existing_extra_list}
_to_create = []
_to_update = []
_to_delete_ids = []
for cishu, huiyuan_id, amount in _extra_targets:
rec = _extra_map.get((cishu, huiyuan_id))
if rec is not None:
rec.guanshi_fenhong = amount
rec.zuzhang_fenhong = Decimal('0')
_to_update.append(rec)
else:
_to_create.append(DuociFenhong(
club_id=guanshi_club,
yonghuid=yonghuid,
huiyuan=huiyuan_id,
cishu=cishu,
defaults={
'guanshi_fenhong': amount,
'zuzhang_fenhong': Decimal('0')
}
)
guanshi_fenhong=amount,
zuzhang_fenhong=Decimal('0'),
))
# 删除不在 keep_set 中的额外次数记录
existing_extra = DuociFenhong.query.filter(
club_id=guanshi_club, yonghuid=yonghuid, cishu__gte=2,
)
for record in existing_extra:
if (record.cishu, record.huiyuan) not in keep_set:
record.delete()
for rec in existing_extra_list:
if (rec.cishu, rec.huiyuan) not in keep_set:
_to_delete_ids.append(rec.id)
if _to_create:
DuociFenhong.objects.bulk_create(_to_create)
if _to_update:
DuociFenhong.objects.bulk_update(_to_update, ['guanshi_fenhong', 'zuzhang_fenhong'])
if _to_delete_ids:
DuociFenhong.objects.filter(id__in=_to_delete_ids).delete()
dividend_parts = []
if perm_enabled is not None:

View File

@@ -342,6 +342,12 @@ class KefuGetDashouDetailView(APIView):
member_records = Huiyuangoumai.query.filter(
yonghu_id=uid
).order_by('-CreateTime')
# 批量预取会员信息
_huiyuan_ids = [r.huiyuan_id for r in member_records]
_huiyuan_map = {
h.huiyuan_id: h.jieshao
for h in Huiyuan.query.filter(huiyuan_id__in=_huiyuan_ids).only('huiyuan_id', 'jieshao')
}
member_list = []
for record in member_records:
# 检查是否过期
@@ -351,11 +357,7 @@ class KefuGetDashouDetailView(APIView):
is_expired = record.daoqi_time < timezone.now() if record.daoqi_time else True
if not is_expired:
try:
huiyuan = Huiyuan.query.get(huiyuan_id=record.huiyuan_id)
huiyuan_name = huiyuan.jieshao
except Huiyuan.DoesNotExist:
huiyuan_name = '未知会员'
huiyuan_name = _huiyuan_map.get(record.huiyuan_id, '未知会员')
member_list.append({
'id': record.id,
'huiyuan_name': huiyuan_name,
@@ -828,8 +830,12 @@ class KefuGetShangjiaListView(APIView):
return Response({'code': 400, 'msg': '商家状态格式错误'})
# 5. 统计总商家数(应用筛选后)
total = shangjia_qs.count()
valid_merchant_count = shangjia_qs.filter(zhuangtai=1).count()
_agg = shangjia_qs.aggregate(
total=Count('id'),
valid=Count('id', filter=Q(zhuangtai=1)),
)
total = _agg['total']
valid_merchant_count = _agg['valid']
# 6. 分页
offset = (page - 1) * page_size

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -518,13 +518,15 @@ class FaKuanChuangJianView(APIView):
user=request.user,
),
)
for relative_url in uploaded_urls:
PenaltyAppealImage.query.create(
PenaltyAppealImage.objects.bulk_create([
PenaltyAppealImage(
Penalty=penalty,
PenalizedUserID=PenalizedUserID,
ImageURL=relative_url,
Purpose=1
Purpose=1,
)
for relative_url in uploaded_urls
])
from users.fadan_fenhong_utils import lock_penalty_bonus
lock_penalty_bonus(penalty)

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -147,14 +147,16 @@ class GetProductBaseDataView(APIView):
# 获取所有商品类型(按 paixu 升序)
type_qs = ShangpinLeixing.query.all().order_by('paixu')
# 批量聚合商品数量,避免循环内 N+1 count
_type_counts = {
item['leixing_id']: item['cnt']
for item in Shangpin.query.filter(
dianpu__isnull=True, dianpu_leixing__isnull=True, leixing_id__isnull=False
).values('leixing_id').annotate(cnt=Count('id'))
}
type_list = []
for t in type_qs:
# 🔥 修改:统计该类型下的公共商品数量(店铺为空且店铺商品类型为空)
product_count = Shangpin.query.filter(
leixing_id=t.id,
dianpu__isnull=True, # 所属店铺为空
dianpu_leixing__isnull=True # 店铺商品类型为空
).count()
product_count = _type_counts.get(t.id, 0)
type_list.append({
'id': t.id,
'jieshao': t.jieshao or '',
@@ -166,14 +168,15 @@ class GetProductBaseDataView(APIView):
# 获取所有专区(按 paixu 升序)
zone_qs = ShangpinZhuanqu.query.all().order_by('paixu')
_zone_counts = {
item['zhuanqu_id']: item['cnt']
for item in Shangpin.query.filter(
dianpu__isnull=True, dianpu_leixing__isnull=True, zhuanqu_id__isnull=False
).values('zhuanqu_id').annotate(cnt=Count('id'))
}
zone_list = []
for z in zone_qs:
# 🔥 修改:统计该专区下的公共商品数量(店铺为空且店铺商品类型为空)
product_count = Shangpin.query.filter(
zhuanqu_id=z.id,
dianpu__isnull=True, # 所属店铺为空
dianpu_leixing__isnull=True # 店铺商品类型为空
).count()
product_count = _zone_counts.get(z.id, 0)
zone_list.append({
'id': z.id,
'mingzi': z.mingzi or '',
@@ -368,10 +371,14 @@ class GetProductListView(APIView):
# 获取所有商品类型(按 paixu 升序)
type_qs = ShangpinLeixing.query.all().order_by('paixu')
# 批量聚合商品数量,避免循环内 N+1 count
_type_counts = {
item['leixing_id']: item['cnt']
for item in Shangpin.query.filter(leixing_id__isnull=False).values('leixing_id').annotate(cnt=Count('id'))
}
type_list = []
for t in type_qs:
# 统计该类型下的商品数量(忽略审核状态?通常统计所有商品,可根据需求调整)
product_count = Shangpin.query.filter(leixing_id=t.id).count()
product_count = _type_counts.get(t.id, 0)
type_list.append({
'id': t.id,
'jieshao': t.jieshao or '',
@@ -383,9 +390,13 @@ class GetProductListView(APIView):
# 获取所有专区(按 paixu 升序)
zone_qs = ShangpinZhuanqu.query.all().order_by('paixu')
_zone_counts = {
item['zhuanqu_id']: item['cnt']
for item in Shangpin.query.filter(zhuanqu_id__isnull=False).values('zhuanqu_id').annotate(cnt=Count('id'))
}
zone_list = []
for z in zone_qs:
product_count = Shangpin.query.filter(zhuanqu_id=z.id).count()
product_count = _zone_counts.get(z.id, 0)
zone_list.append({
'id': z.id,
'mingzi': z.mingzi or '',

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -157,19 +157,29 @@ class GetRolePermissionView(APIView):
kefu_user_uuids = list(
User.objects.filter(KefuProfile__isnull=False).values_list('UserUUID', flat=True)
)
roles = filter_roles_for_request(request)
roles = list(filter_roles_for_request(request))
# 批量预取 RolePermission + Permission避免循环内 N+1
_role_uuids = [r.RoleUUID for r in roles]
_rp_pairs = list(
RolePermission.objects.filter(RoleUUID__in=_role_uuids)
.values_list('RoleUUID', 'PermUUID')
) if _role_uuids else []
_perm_uuids = list({p for _, p in _rp_pairs})
_perm_rows_by_uuid = {
p['PermUUID']: p
for p in Permission.objects.filter(PermUUID__in=_perm_uuids)
.values('PermUUID', 'PermCode', 'PermName', 'PermDesc')
} if _perm_uuids else {}
_perms_by_role = {}
for role_uuid, perm_uuid in _rp_pairs:
perm_row = _perm_rows_by_uuid.get(perm_uuid)
if perm_row is not None:
_perms_by_role.setdefault(role_uuid, []).append(_serialize_permission_row(perm_row))
roles_data = []
for role in roles:
user_count = count_role_users_in_scope(role, request, kefu_user_uuids)
role_perm_uuids = RolePermission.objects.filter(
RoleUUID=role.RoleUUID
).values_list('PermUUID', flat=True)
perms_of_role = [
_serialize_permission_row(p)
for p in Permission.objects.filter(
PermUUID__in=list(role_perm_uuids)
).values('PermCode', 'PermName', 'PermDesc')
]
perms_of_role = _perms_by_role.get(role.RoleUUID, [])
roles_data.append({
'role_code': role.RoleName,
'role_name': role.RoleName,
@@ -434,18 +444,29 @@ class GetAdminUserListView(APIView):
paginator = Paginator(users, page_size)
page_obj = paginator.get_page(page)
# 批量预取本页所有用户的 UserRole + Role避免循环内 N+1
_page_users = list(page_obj)
_page_user_uuids = [u.UserUUID for u in _page_users]
_ur_pairs = list(
UserRole.objects.filter(UserUUID__in=_page_user_uuids)
.values_list('UserUUID', 'RoleUUID')
) if _page_user_uuids else []
_role_uuids_in_page = list({r for _, r in _ur_pairs})
_role_name_by_uuid = {
r.RoleUUID: r.RoleName
for r in Role.objects.filter(RoleUUID__in=_role_uuids_in_page)
} if _role_uuids_in_page else {}
_roles_by_user = {}
for user_uuid, role_uuid in _ur_pairs:
role_name = _role_name_by_uuid.get(role_uuid)
if role_name is not None:
_roles_by_user.setdefault(user_uuid, []).append(
{'role_code': role_name, 'role_name': role_name}
)
data_list = []
for user in page_obj:
# 通过 gvsdsdk UserRole 获取用户角色
user_role_uuids = list(
UserRole.objects.filter(UserUUID=user.UserUUID)
.values_list('RoleUUID', flat=True)
)
user_roles = Role.objects.filter(RoleUUID__in=user_role_uuids)
roles_data = [
{'role_code': r.RoleName, 'role_name': r.RoleName}
for r in user_roles
]
for user in _page_users:
roles_data = _roles_by_user.get(user.UserUUID, [])
data_list.append({
'phone': user.Phone or '',
'yonghuid': user.UserUID or '',

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -173,19 +173,27 @@ class HqbkxxView(APIView):
price_map[row.huiyuan_id] = row
huiyuan_list = []
for hy in Huiyuan.query.all():
row = price_map.get(hy.huiyuan_id)
# 在受限 scope 下预过滤,避免 Huiyuan 全表遍历
# 注:当 scope != DATA_SCOPE_ALL 且 club_id != CLUB_ID_DEFAULT 时,
# 只有 price_map 中存在记录的会员才可能被保留(无记录的会被跳过)
huiyuan_qs = Huiyuan.query.all()
if scope != DATA_SCOPE_ALL and club_id != CLUB_ID_DEFAULT:
huiyuan_qs = huiyuan_qs.filter(huiyuan_id__in=list(price_map.keys()))
# 仅查询所需字段减少内存占用dict 比 model 实例更轻)
huiyuan_qs = huiyuan_qs.values('huiyuan_id', 'jieshao', 'bankuai_id', 'jiage', 'guanshifc', 'zuzhangfc')
for hy in huiyuan_qs:
row = price_map.get(hy['huiyuan_id'])
if scope != DATA_SCOPE_ALL and club_id != CLUB_ID_DEFAULT and row is None:
continue
if row is not None and not row.is_enabled:
continue
item = {
'huiyuan_id': hy.huiyuan_id,
'jieshao': hy.jieshao,
'bankuai_id': hy.bankuai_id,
'jiage': str(row.jiage) if row else str(hy.jiage),
'guanshifc': str(row.guanshifc) if row else str(hy.guanshifc),
'zuzhangfc': str(row.zuzhangfc) if row else str(hy.zuzhangfc),
'huiyuan_id': hy['huiyuan_id'],
'jieshao': hy['jieshao'],
'bankuai_id': hy['bankuai_id'],
'jiage': str(row.jiage) if row else str(hy['jiage']),
'guanshifc': str(row.guanshifc) if row else str(hy['guanshifc']),
'zuzhangfc': str(row.zuzhangfc) if row else str(hy['zuzhangfc']),
}
huiyuan_list.append(item)

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -241,11 +241,16 @@ class GetProductTypeZoneView(APIView):
return Response({'code': 400, 'msg': scope_err})
# 查询所有商品类型(含专区数量)
# 一次性分组聚合所有类型的专区数量,避免循环内 N+1 count
# 注ShangpinZhuanqu.leixing_id 是 PositiveIntegerField非 FK无法用反向 annotate
zone_count_rows = ShangpinZhuanqu.query.values('leixing_id').annotate(cnt=Count('id'))
zone_count_map = {row['leixing_id']: row['cnt'] for row in zone_count_rows}
types = ShangpinLeixing.query.all().order_by('-paixu') # paixu 越大越靠前
type_list = []
for t in types:
# 统计该类型下的专区数量
zone_count = ShangpinZhuanqu.query.filter(leixing_id=t.id).count()
# 从预聚合的 map 中取专区数量
zone_count = zone_count_map.get(t.id, 0)
type_list.append({
'id': t.id,
'jieshao': t.jieshao or '',
@@ -531,10 +536,11 @@ class ModifyProductTypeZoneView(APIView):
# 删除该类型下的所有专区
zones = ShangpinZhuanqu.query.filter(leixing_id=type_id)
if delete_products:
# 删除专区下的所有商品
for zone in zones:
Shangpin.query.filter(zhuanqu_id=zone.id).delete()
zones.delete()
# 收集 zone_ids 后批量删除,避免循环内 N+1 delete
zone_ids = list(zones.values_list('id', flat=True))
if zone_ids:
Shangpin.query.filter(zhuanqu_id__in=zone_ids).delete()
ShangpinZhuanqu.query.filter(id__in=zone_ids).delete()
# 删除直接关联该类型的商品
Shangpin.query.filter(leixing_id=type_id).delete()
else:
@@ -928,7 +934,8 @@ class PopupNoticeModifyAPIView(APIView):
if cascade:
# 级联删除:使用事务保证原子性
with transaction.atomic():
popups = page.popups.all()
# 预加载 images 关联,避免循环内 N+1 访问 related manager
popups = page.popups.prefetch_related('images')
for popup in popups:
# 删除弹窗下的所有图片文件
for img in popup.images.all():

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -19,7 +19,7 @@ from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Paginator
from django.db import models, transaction, IntegrityError
from django.db.models import Q, Count, Sum, F, Exists, OuterRef
from django.db.models import Q, Count, Sum, F, Exists, OuterRef, Prefetch
from gvsdsdk.fluent import db, func, FQ
from django.db.models.functions import TruncDate, TruncMonth, TruncYear
from django.contrib.auth.hashers import make_password
@@ -132,13 +132,16 @@ class KhpzhqView(APIView):
bankuai_list = list(Bankuai.query.values('bankuai_id', 'mingcheng'))
chenghao_qs = Chenghao.query.prefetch_related('kaohecishufeiyong_set').all()
chenghao_qs = Chenghao.query.prefetch_related(
Prefetch('kaohecishufeiyong_set', queryset=KaoheCishuFeiyong.objects.order_by('cishu'))
).all()
chenghao_list = []
for ch in chenghao_qs:
# 获取费用:按次数排序,转为 {cishu: feiyong} 字典
# prefetch_related 已用 Prefetch 排序,直接遍历不会绕过缓存
feiyong_dict = {
item.cishu: float(item.feiyong)
for item in ch.kaohecishufeiyong_set.order_by('cishu')
for item in ch.kaohecishufeiyong_set.all()
}
max_cishu = max(feiyong_dict.keys()) if feiyong_dict else 0

View File

@@ -1,4 +1,4 @@
import hmac
import hmac
import threading
import traceback
import uuid
@@ -351,9 +351,16 @@ class GetZuzhangDetailView(APIView):
custom_first = {} # 首次分红定制cishu=1
extra_map = {} # 其他次数 { cishu: { huiyuan_id: { zuzhang_fenhong, jieshao, jiage } } }
# 批量预取会员信息
_huiyuan_ids = [r.huiyuan for r in duoci_records]
_huiyuan_map = {
h.huiyuan_id: h
for h in Huiyuan.query.filter(huiyuan_id__in=_huiyuan_ids).only('huiyuan_id', 'jieshao', 'jiage')
}
for record in duoci_records:
# 获取会员信息
member = Huiyuan.query.filter(huiyuan_id=record.huiyuan).first()
member = _huiyuan_map.get(record.huiyuan)
if not member:
continue
member_info = {
@@ -580,9 +587,16 @@ class UpdateZuzhangView(APIView):
if custom_first is not None and isinstance(custom_first, dict):
from jituan.services.club_penalty import resolve_gsfenhong_club_id
zuzhang_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
existing_first = DuociFenhong.query.filter(
club_id=zuzhang_club, yonghuid=yonghuid, cishu=1,
# 批量预取当前所有首次定制记录(避免循环内 N+1
existing_first_list = list(
DuociFenhong.query.filter(
club_id=zuzhang_club, yonghuid=yonghuid, cishu=1,
)
)
_existing_map = {r.huiyuan: r for r in existing_first_list}
_to_create = []
_to_update = []
_to_delete_ids = []
for huiyuan_id, amount in custom_first.items():
try:
amount = Decimal(str(amount))
@@ -590,30 +604,41 @@ class UpdateZuzhangView(APIView):
continue
if amount <= 0:
# 删除定制(恢复默认)
DuociFenhong.query.filter(
club_id=zuzhang_club, yonghuid=yonghuid, huiyuan=huiyuan_id, cishu=1,
).delete()
rec = _existing_map.get(huiyuan_id)
if rec is not None:
_to_delete_ids.append(rec.id)
else:
DuociFenhong.query.update_or_create(
club_id=zuzhang_club,
yonghuid=yonghuid,
huiyuan=huiyuan_id,
cishu=1,
defaults={
'zuzhang_fenhong': amount,
'guanshi_fenhong': Decimal('0') # 组长表只关心组长分红
}
)
rec = _existing_map.get(huiyuan_id)
if rec is not None:
rec.zuzhang_fenhong = amount
rec.guanshi_fenhong = Decimal('0') # 组长表只关心组长分红
_to_update.append(rec)
else:
_to_create.append(DuociFenhong(
club_id=zuzhang_club,
yonghuid=yonghuid,
huiyuan=huiyuan_id,
cishu=1,
zuzhang_fenhong=amount,
guanshi_fenhong=Decimal('0'),
))
# 删除那些在前端字典中不存在的首次定制记录(即已取消定制)
for record in existing_first:
if record.huiyuan not in custom_first:
record.delete()
for rec in existing_first_list:
if rec.huiyuan not in custom_first and rec.id not in _to_delete_ids:
_to_delete_ids.append(rec.id)
if _to_create:
DuociFenhong.objects.bulk_create(_to_create)
if _to_update:
DuociFenhong.objects.bulk_update(_to_update, ['zuzhang_fenhong', 'guanshi_fenhong'])
if _to_delete_ids:
DuociFenhong.objects.filter(id__in=_to_delete_ids).delete()
# 4.3 额外次数分红cishu >= 2
if extra_map is not None and isinstance(extra_map, dict):
from jituan.services.club_penalty import resolve_gsfenhong_club_id
zuzhang_club = resolve_gsfenhong_club_id(dashouid=yonghuid)
keep_set = set()
_extra_targets = [] # [(cishu, huiyuan_id, amount)]
for times_str, members in extra_map.items():
try:
cishu = int(times_str)
@@ -634,23 +659,42 @@ class UpdateZuzhangView(APIView):
except:
continue
keep_set.add((cishu, huiyuan_id))
DuociFenhong.query.update_or_create(
_extra_targets.append((cishu, huiyuan_id, amount))
# 批量预取已有额外次数记录
existing_extra_list = list(
DuociFenhong.query.filter(
club_id=zuzhang_club, yonghuid=yonghuid, cishu__gte=2,
)
)
_extra_map = {(r.cishu, r.huiyuan): r for r in existing_extra_list}
_to_create = []
_to_update = []
_to_delete_ids = []
for cishu, huiyuan_id, amount in _extra_targets:
rec = _extra_map.get((cishu, huiyuan_id))
if rec is not None:
rec.zuzhang_fenhong = amount
rec.guanshi_fenhong = Decimal('0')
_to_update.append(rec)
else:
_to_create.append(DuociFenhong(
club_id=zuzhang_club,
yonghuid=yonghuid,
huiyuan=huiyuan_id,
cishu=cishu,
defaults={
'zuzhang_fenhong': amount,
'guanshi_fenhong': Decimal('0')
}
)
zuzhang_fenhong=amount,
guanshi_fenhong=Decimal('0'),
))
# 删除不在 keep_set 中的额外次数记录
existing_extra = DuociFenhong.query.filter(
club_id=zuzhang_club, yonghuid=yonghuid, cishu__gte=2,
)
for record in existing_extra:
if (record.cishu, record.huiyuan) not in keep_set:
record.delete()
for rec in existing_extra_list:
if (rec.cishu, rec.huiyuan) not in keep_set:
_to_delete_ids.append(rec.id)
if _to_create:
DuociFenhong.objects.bulk_create(_to_create)
if _to_update:
DuociFenhong.objects.bulk_update(_to_update, ['zuzhang_fenhong', 'guanshi_fenhong'])
if _to_delete_ids:
DuociFenhong.objects.filter(id__in=_to_delete_ids).delete()
dividend_parts = []
if perm_enabled is not None:

View File

@@ -175,7 +175,7 @@ class ZxkfghdsView(APIView):
shangjia_ext = old_order.shangjia_kuozhan
shangjia_id = shangjia_ext.MerchantID
# 获取商家昵称用于新订单扩展表
shangjia_user = User.query.get(UserUID=shangjia_id)
shangjia_user = User.query.select_related('ShopProfile').get(UserUID=shangjia_id)
shangjia_nicheng = shangjia_user.ShopProfile.nicheng or f"商家{shangjia_id}"
except (ObjectDoesNotExist, UserShangjia.DoesNotExist):
logger.error(f"商家信息缺失,订单: {OrderID}")