Files
Django/jituan/services/club_context.py
TermiNexus f1c8633345 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
2026-07-05 23:17:38 +08:00

192 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""俱乐部上下文解析与查询辅助。"""
from jituan.constants import (
CLUB_ID_DEFAULT,
CLUB_HEADER,
CLUB_SCOPE_HEADER,
DATA_SCOPE_ALL,
DATA_SCOPE_SINGLE,
)
from jituan.models import Club
def resolve_club_id_from_request(request):
"""
从请求解析 club_id。
优先级:已注入的 request.club_id > Header X-Club-Id > 默认 xq。
旧客户端不传时恒为 xq与现网行为一致。
"""
injected = getattr(request, 'club_id', None)
if injected:
return injected
header = request.META.get(f'HTTP_{CLUB_HEADER.upper().replace("-", "_")}', '')
if not header:
header = request.headers.get(CLUB_HEADER, '') if hasattr(request, 'headers') else ''
club_id = (header or '').strip() or CLUB_ID_DEFAULT
return club_id
def resolve_club_scope(request):
"""解析数据范围ALL_CLUBS集团汇总或 SINGLE_CLUB。"""
scope = getattr(request, 'club_scope', None)
if scope:
return scope
header = request.META.get(f'HTTP_{CLUB_SCOPE_HEADER.upper().replace("-", "_")}', '')
if not header:
header = request.headers.get(CLUB_SCOPE_HEADER, '') if hasattr(request, 'headers') else ''
if (header or '').strip().lower() == 'all':
return DATA_SCOPE_ALL
return DATA_SCOPE_SINGLE
def get_active_club(club_id):
try:
club = Club.query.get(club_id=club_id, status=1)
return club
except Club.DoesNotExist:
return None
def filter_club_char_field(qs, request, field='club_id'):
"""按 club_id 字符字段过滤xq 视图兼容历史空值。"""
scope = resolve_club_scope(request)
if scope == DATA_SCOPE_ALL:
return qs
if not hasattr(qs, 'filter'):
return qs
club_id = resolve_club_id_from_request(request)
if club_id == CLUB_ID_DEFAULT:
from django.db.models import Q
return qs.filter(
Q(**{field: club_id}) | Q(**{f'{field}__isnull': True}) | Q(**{field: ''})
)
return qs.filter(**{field: club_id})
def filter_queryset_by_club(qs, request, club_field='club_id'):
"""
按请求上下文过滤 QuerySet / FluentQuery。
集团 ALL_CLUBS 视图不过滤;子公司 SINGLE_CLUB 按 club_id 过滤。
历史订单 ClubID 为空时视为 xq避免列表被滤空。
"""
scope = resolve_club_scope(request)
club_id = resolve_club_id_from_request(request)
if scope == DATA_SCOPE_ALL:
return qs
if not hasattr(qs, 'filter'):
return qs
if club_field == 'ClubID' and club_id == CLUB_ID_DEFAULT:
from django.db.models import Q
return qs.filter(
Q(ClubID=club_id) | Q(ClubID__isnull=True) | Q(ClubID='')
)
return qs.filter(**{club_field: club_id})
def resolve_effective_club_id(request, user=None):
"""
小程序/API 实际生效俱乐部(展示价、支付价、分红须一致)。
优先级Header X-Club-Id > 用户归属 ClubID > 默认 xq。
勿用 middleware 注入的 request.club_id无 Header 时恒为 xq
"""
header = request.META.get(f'HTTP_{CLUB_HEADER.upper().replace("-", "_")}', '')
if not header and hasattr(request, 'headers'):
header = request.headers.get(CLUB_HEADER, '')
header = (header or '').strip()
if header:
return header
if user is not None:
from jituan.services.club_user import get_user_club_id
user_club = get_user_club_id(user)
if user_club:
return user_club
return CLUB_ID_DEFAULT
def club_id_for_write(request, user=None):
"""写入业务流水时使用的 club_id集团汇总视图写入仍须指定目标 club"""
return resolve_effective_club_id(request, user)
def orders_for_request(request):
"""按俱乐部过滤的订单 QuerySet。"""
from orders.models import Order
return filter_queryset_by_club(Order.query.all(), request, club_field='ClubID')
def order_scope_qs(request=None):
"""
订单列表作用域 QuerySet。
request 为 None 时不按俱乐部过滤(旧 /yonghu 订单列表,与改造前一致)。
"""
from orders.models import Order
qs = Order.query.all()
if request is not None:
qs = filter_queryset_by_club(qs, request, club_field='ClubID')
return qs
def count_order_status_buckets(q_conditions, request=None):
"""
统计各状态订单数。
每次从全新 FluentQuery 起链,避免 FluentQuery.filter 原地累积导致列表被滤空。
"""
from django.db.models import Q
buckets = (
('1,7', Q(Status__in=[1, 7])),
('2', Q(Status=2)),
('8', Q(Status=8)),
('3', Q(Status=3)),
('4', Q(Status=4)),
('5', Q(Status=5)),
('6', Q(Status=6)),
)
# 合并 8 次独立 count → 1 次 aggregate with 条件 Count
from django.db.models import Count
_base = order_scope_qs(request).filter(q_conditions)
_agg_kwargs = {'all': Count('id')}
for key, status_q in buckets:
_agg_kwargs[key] = Count('id', filter=status_q)
return _base.aggregate(**_agg_kwargs)
def paginate_fluent_query(fluent_qs, page, page_size):
"""
FluentQuery 分页。直接切片底层 Django QuerySet绕过 FluentQuery 缓存。
返回 (total, items)。
"""
page = max(1, int(page))
page_size = max(1, min(int(page_size), 100))
django_qs = fluent_qs._qs
total = django_qs.count()
start = (page - 1) * page_size
return total, list(django_qs[start:start + page_size])
def platform_order_base_q():
"""平台订单:发单平台=1兼容历史 Platform 为空。"""
from django.db.models import Q
return Q(Platform=1) | Q(Platform__isnull=True)
def merchant_order_base_q():
"""商家订单:发单平台=2。"""
from django.db.models import Q
return Q(Platform=2)
def filter_user_related_by_club(qs, request, user_club_path='user__ClubID'):
"""按 User.ClubID 过滤打手/管事/商家等关联查询。"""
scope = resolve_club_scope(request)
if scope == DATA_SCOPE_ALL:
return qs
return qs.filter(**{user_club_path: resolve_club_id_from_request(request)})
def filter_user_qs_by_club(qs, request):
"""直接过滤 User 查询集(字段 ClubID"""
scope = resolve_club_scope(request)
if scope == DATA_SCOPE_ALL:
return qs
return qs.filter(ClubID=resolve_club_id_from_request(request))