Files
Django/rank/reward_houtai_views.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

207 lines
7.9 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.
"""排行榜奖励 — 后台 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}})