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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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 '',
|
||||
|
||||
@@ -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 '',
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user