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
207 lines
7.9 KiB
Python
207 lines
7.9 KiB
Python
"""排行榜奖励 — 后台 kefu API。"""
|
||
import logging
|
||
|
||
from rest_framework.permissions import IsAuthenticated
|
||
from rest_framework.response import Response
|
||
from rest_framework.views import APIView
|
||
|
||
from backend.utils import verify_kefu_permission
|
||
from jituan.services.club_context import resolve_club_id_from_request
|
||
from jituan.services.display_config import forbid_display_write_in_all_scope
|
||
from rank.reward_models import RankRewardClaim, RankRewardScheme, RankRewardTier
|
||
from rank.reward_services import SORT_FIELD_REGISTRY, sort_field_options_for_shenfen
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
REWARD_PERMS = ('phbj666', '8080a')
|
||
|
||
|
||
def _parse_date(value):
|
||
if not value:
|
||
return None
|
||
from datetime import date
|
||
if isinstance(value, date):
|
||
return value
|
||
text = str(value).strip()[:10]
|
||
if not text:
|
||
return None
|
||
try:
|
||
return date.fromisoformat(text)
|
||
except ValueError:
|
||
return None
|
||
|
||
|
||
def _has_perm(permissions):
|
||
return any(p in permissions for p in REWARD_PERMS)
|
||
|
||
|
||
def _serialize_scheme(scheme, tiers=None):
|
||
if tiers is None:
|
||
tiers = RankRewardTier.query.filter(scheme_id=scheme.id).order_by('rank_from')
|
||
return {
|
||
'id': scheme.id,
|
||
'club_id': scheme.club_id,
|
||
'shenfen': scheme.shenfen,
|
||
'period_type': scheme.period_type,
|
||
'sort_field': scheme.sort_field,
|
||
'sort_options': sort_field_options_for_shenfen(scheme.shenfen),
|
||
'title': scheme.title,
|
||
'description': scheme.description,
|
||
'enabled': scheme.enabled,
|
||
'claim_days': scheme.claim_days,
|
||
'min_metric': float(scheme.min_metric or 0),
|
||
'activity_start': scheme.activity_start.isoformat() if scheme.activity_start else '',
|
||
'activity_end': scheme.activity_end.isoformat() if scheme.activity_end else '',
|
||
'tiers': [
|
||
{
|
||
'id': t.id,
|
||
'rank_from': t.rank_from,
|
||
'rank_to': t.rank_to,
|
||
'reward_amount': float(t.reward_amount),
|
||
'label': t.label,
|
||
}
|
||
for t in tiers
|
||
],
|
||
}
|
||
|
||
|
||
class RankRewardHoutaiListView(APIView):
|
||
"""POST /houtai/phbjhq"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username')
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return Response({'code': 403, 'msg': '身份验证失败'})
|
||
if not _has_perm(permissions):
|
||
return Response({'code': 403, 'msg': '无排行榜奖励配置权限'})
|
||
|
||
club_id = resolve_club_id_from_request(request)
|
||
schemes = RankRewardScheme.query.filter(club_id=club_id).order_by('shenfen', 'period_type')
|
||
# 批量预取所有 tiers,避免列表推导内 N+1
|
||
schemes_list = list(schemes)
|
||
_scheme_ids = [s.id for s in schemes_list]
|
||
_tiers_map = {}
|
||
if _scheme_ids:
|
||
for t in RankRewardTier.query.filter(scheme_id__in=_scheme_ids).order_by('scheme_id', 'rank_from'):
|
||
_tiers_map.setdefault(t.scheme_id, []).append(t)
|
||
return Response({
|
||
'code': 0,
|
||
'data': {
|
||
'club_id': club_id,
|
||
'schemes': [_serialize_scheme(s, _tiers_map.get(s.id, [])) for s in schemes_list],
|
||
'shenfen_options': list(SORT_FIELD_REGISTRY.keys()),
|
||
'period_options': [
|
||
{'key': RankRewardScheme.PERIOD_DAY, 'label': '日榜(昨日)'},
|
||
{'key': RankRewardScheme.PERIOD_WEEK, 'label': '周榜(上周)'},
|
||
{'key': RankRewardScheme.PERIOD_MONTH, 'label': '月榜(上月)'},
|
||
],
|
||
},
|
||
})
|
||
|
||
|
||
class RankRewardHoutaiSaveView(APIView):
|
||
"""POST /houtai/phbjbc"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username')
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return Response({'code': 403, 'msg': '身份验证失败'})
|
||
if not _has_perm(permissions):
|
||
return Response({'code': 403, 'msg': '无权限'})
|
||
|
||
deny = forbid_display_write_in_all_scope(request)
|
||
if deny:
|
||
return deny
|
||
|
||
club_id = resolve_club_id_from_request(request)
|
||
data = request.data
|
||
shenfen = (data.get('shenfen') or '').strip()
|
||
period_type = (data.get('period_type') or '').strip()
|
||
sort_field = (data.get('sort_field') or '').strip()
|
||
|
||
if shenfen not in SORT_FIELD_REGISTRY:
|
||
return Response({'code': 400, 'msg': '身份无效'})
|
||
if period_type not in (RankRewardScheme.PERIOD_DAY, RankRewardScheme.PERIOD_WEEK, RankRewardScheme.PERIOD_MONTH):
|
||
return Response({'code': 400, 'msg': '周期无效'})
|
||
if sort_field not in SORT_FIELD_REGISTRY[shenfen]:
|
||
return Response({'code': 400, 'msg': '排序指标无效'})
|
||
|
||
tiers = data.get('tiers') or []
|
||
if not tiers:
|
||
return Response({'code': 400, 'msg': '请配置至少一个奖励档位'})
|
||
|
||
activity_start = _parse_date(data.get('activity_start'))
|
||
activity_end = _parse_date(data.get('activity_end'))
|
||
if activity_start and activity_end and activity_start > activity_end:
|
||
return Response({'code': 400, 'msg': '活动开始日期不能晚于结束日期'})
|
||
|
||
scheme, _ = RankRewardScheme.objects.update_or_create(
|
||
club_id=club_id,
|
||
shenfen=shenfen,
|
||
period_type=period_type,
|
||
defaults={
|
||
'sort_field': sort_field,
|
||
'title': (data.get('title') or '').strip(),
|
||
'description': (data.get('description') or '').strip(),
|
||
'enabled': bool(data.get('enabled', False)),
|
||
'claim_days': int(data.get('claim_days') or 30),
|
||
'min_metric': data.get('min_metric') or 0,
|
||
'activity_start': activity_start,
|
||
'activity_end': activity_end,
|
||
},
|
||
)
|
||
|
||
RankRewardTier.query.filter(scheme_id=scheme.id).delete()
|
||
RankRewardTier.objects.bulk_create([
|
||
RankRewardTier(
|
||
scheme_id=scheme.id,
|
||
rank_from=int(t['rank_from']),
|
||
rank_to=int(t['rank_to']),
|
||
reward_amount=t['reward_amount'],
|
||
label=(t.get('label') or '').strip(),
|
||
)
|
||
for t in tiers
|
||
])
|
||
|
||
return Response({'code': 0, 'msg': '保存成功', 'data': _serialize_scheme(scheme)})
|
||
|
||
|
||
class RankRewardHoutaiClaimsView(APIView):
|
||
"""POST /houtai/phbjjl 领取明细"""
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def post(self, request):
|
||
username = request.data.get('username')
|
||
kefu, permissions = verify_kefu_permission(request, username)
|
||
if kefu is None:
|
||
return Response({'code': 403, 'msg': '身份验证失败'})
|
||
if not _has_perm(permissions):
|
||
return Response({'code': 403, 'msg': '无权限'})
|
||
|
||
club_id = resolve_club_id_from_request(request)
|
||
status = request.data.get('status')
|
||
qs = RankRewardClaim.query.filter(club_id=club_id).order_by('-CreateTime')[:100]
|
||
if status is not None:
|
||
qs = qs.filter(status=int(status))
|
||
|
||
status_map = {0: '待领取', 1: '已领取', 2: '已过期', 3: '已取消'}
|
||
items = [
|
||
{
|
||
'id': c.id,
|
||
'yonghuid': c.yonghuid,
|
||
'shenfen': c.shenfen,
|
||
'mingci': c.mingci,
|
||
'amount': float(c.reward_amount),
|
||
'status': c.status,
|
||
'status_text': status_map.get(c.status, ''),
|
||
'claimed_at': c.claimed_at.isoformat() if c.claimed_at else '',
|
||
'CreateTime': c.CreateTime.isoformat() if c.CreateTime else '',
|
||
}
|
||
for c in qs
|
||
]
|
||
return Response({'code': 0, 'data': {'list': items}})
|