fix: 今日订单假单按间隔累加,刷新不再整批重抽

This commit is contained in:
XingQue committed 2026-08-11 21:27:38 +08:00
1 parent 4c7f273cf7
commit 9f6618969b
2 files changed
+122 -38

No files matched your search

+10 -6
View File
@@ -1291,13 +1291,13 @@ class TodayOrderFake(QModel):
class TodayOrderShowConfig(QModel): class TodayOrderShowConfig(QModel):
"""俱乐部「今日订单」真假混合与随机批规则。""" """俱乐部「今日订单」真假混合:按间隔累加假单数(不整批重抽)。"""
id = models.BigAutoField(primary_key=True) id = models.BigAutoField(primary_key=True)
club_id = models.CharField(max_length=16, unique=True, db_index=True) club_id = models.CharField(max_length=16, unique=True, db_index=True)
mix_enabled = models.BooleanField(default=False, verbose_name='真假混合') mix_enabled = models.BooleanField(default=False, verbose_name='真假混合')
batch_size = models.PositiveIntegerField(default=5, verbose_name='每批随机假单数') batch_size = models.PositiveIntegerField(default=5, verbose_name='每间隔累加假单数')
interval_seconds = models.PositiveIntegerField(default=120, verbose_name='换批间隔秒') interval_seconds = models.PositiveIntegerField(default=120, verbose_name='累加间隔秒')
CreateTime = models.DateTimeField(auto_now_add=True) CreateTime = models.DateTimeField(auto_now_add=True)
UpdateTime = models.DateTimeField(auto_now=True) UpdateTime = models.DateTimeField(auto_now=True)
@@ -1306,12 +1306,16 @@ class TodayOrderShowConfig(QModel):
class TodayOrderFakeBatchState(QModel): class TodayOrderFakeBatchState(QModel):
"""当前展示中的假单批次(按俱乐部)。""" """
当日假单累加状态(按俱乐部一行,不记历史日):
- fake_ids:已展示假单主键(只增不换)
- last_shuffle_at:上次累加时间(跨日则重置)
"""
id = models.BigAutoField(primary_key=True) id = models.BigAutoField(primary_key=True)
club_id = models.CharField(max_length=16, unique=True, db_index=True) club_id = models.CharField(max_length=16, unique=True, db_index=True)
last_shuffle_at = models.DateTimeField(null=True, blank=True) last_shuffle_at = models.DateTimeField(null=True, blank=True, verbose_name='上次累加时间')
fake_ids = models.JSONField(default=list, blank=True, verbose_name='当前批次假单主键列表') fake_ids = models.JSONField(default=list, blank=True, verbose_name='已展示假单主键列表')
UpdateTime = models.DateTimeField(auto_now=True) UpdateTime = models.DateTimeField(auto_now=True)
class Meta: class Meta:
+112 -32
View File
@@ -7,7 +7,7 @@ from datetime import datetime, time, timedelta
from decimal import Decimal from decimal import Decimal
from django.conf import settings from django.conf import settings
from django.db import IntegrityError from django.db import IntegrityError, transaction
from django.db.models import Count, Q from django.db.models import Count, Q
from django.utils import timezone from django.utils import timezone
@@ -120,7 +120,43 @@ def _real_qs(club_id: str):
) )
def _parse_fake_ids(raw) -> list[int]:
if not isinstance(raw, list):
return []
out = []
for x in raw:
try:
if isinstance(x, bool):
continue
out.append(int(x))
except (TypeError, ValueError):
continue
return out
def _ensure_batch_state(club_id: str) -> TodayOrderFakeBatchState:
state = TodayOrderFakeBatchState.query.filter(club_id=club_id).first()
if state:
return state
state = TodayOrderFakeBatchState(club_id=club_id, fake_ids=[], last_shuffle_at=None)
try:
state.save()
except IntegrityError:
state = TodayOrderFakeBatchState.query.filter(club_id=club_id).first()
if not state:
raise
return state
def _pick_fake_batch(club_id: str, cfg: TodayOrderShowConfig) -> list[TodayOrderFake]: def _pick_fake_batch(club_id: str, cfg: TodayOrderShowConfig) -> list[TodayOrderFake]:
"""
假单累加展示(惰性,仅有用户请求时推进):
- 当天首次:抽出 batch_size 条,写入状态
- 之后每隔 interval_seconds,在原列表上再追加 batch_size(已展示的不变)
- 间隔未到:原样返回,刷新不重抽
- 无用户刷新:时间到了也不自动变
- 跨自然日:清空累加,重新从首批开始
"""
if not cfg.mix_enabled or int(cfg.batch_size or 0) <= 0: if not cfg.mix_enabled or int(cfg.batch_size or 0) <= 0:
return [] return []
pool = list( pool = list(
@@ -128,39 +164,83 @@ def _pick_fake_batch(club_id: str, cfg: TodayOrderShowConfig) -> list[TodayOrder
) )
if not pool: if not pool:
return [] return []
batch_n = min(int(cfg.batch_size), len(pool))
batch_n = max(1, int(cfg.batch_size))
interval = max(10, int(cfg.interval_seconds or 120)) interval = max(10, int(cfg.interval_seconds or 120))
now = _now_naive() now = _now_naive()
state = TodayOrderFakeBatchState.query.filter(club_id=club_id).first() today = now.date()
need_reshuffle = True pool_by_id = {int(x.id): x for x in pool}
ids = []
if state and state.last_shuffle_at and isinstance(state.fake_ids, list): with transaction.atomic():
last = state.last_shuffle_at # 行锁,避免并发刷新重复累加
if timezone.is_aware(last): state = (
last = timezone.make_naive(last) TodayOrderFakeBatchState.objects
elapsed = (now - last).total_seconds() .select_for_update()
if elapsed < interval and state.fake_ids: .filter(club_id=club_id)
ids = [int(x) for x in state.fake_ids if str(x).isdigit() or isinstance(x, int)] .first()
need_reshuffle = False )
if need_reshuffle:
chosen = random.sample(pool, batch_n)
ids = [int(x.id) for x in chosen]
if not state: if not state:
state = TodayOrderFakeBatchState(club_id=club_id) state = _ensure_batch_state(club_id)
state.last_shuffle_at = now state = (
state.fake_ids = ids TodayOrderFakeBatchState.objects
try: .select_for_update()
state.save() .filter(club_id=club_id)
except IntegrityError: .first()
# 并发首次创建同一 club 批次行:改更新已有行 )
state = TodayOrderFakeBatchState.query.filter(club_id=club_id).first() if not state:
if state: return []
state.last_shuffle_at = now
state.fake_ids = ids last = state.last_shuffle_at
state.save() if last and timezone.is_aware(last):
return chosen last = timezone.make_naive(last)
id_set = set(ids)
return [x for x in pool if int(x.id) in id_set] ids = _parse_fake_ids(state.fake_ids)
# 跨日重置(不记历史,只保留「当前时段」)
if last is None or last.date() != today:
ids = []
last = None
# 池子里已删的 id 丢掉,保持展示稳定且可继续追加
ids = [i for i in ids if i in pool_by_id]
id_set = set(ids)
add_batches = 0
if not ids:
add_batches = 1
elif last is not None:
elapsed = (now - last).total_seconds()
if elapsed >= interval:
# 离开期间跨过多档则一次补上(仍只在有人刷新时发生)
add_batches = int(elapsed // interval)
if add_batches > 0:
available = [x for x in pool if int(x.id) not in id_set]
for _ in range(add_batches):
if not available:
break
n = min(batch_n, len(available))
picked = random.sample(available, n)
for row in picked:
rid = int(row.id)
ids.append(rid)
id_set.add(rid)
available = [x for x in available if int(x.id) not in id_set]
if last is None:
new_last = now
else:
new_last = last + timedelta(seconds=add_batches * interval)
if new_last > now:
new_last = now
state.fake_ids = ids
state.last_shuffle_at = new_last
state.save(update_fields=['fake_ids', 'last_shuffle_at', 'UpdateTime'])
elif ids != _parse_fake_ids(state.fake_ids):
# 仅清理失效 id
state.fake_ids = ids
state.save(update_fields=['fake_ids', 'UpdateTime'])
return [pool_by_id[i] for i in ids if i in pool_by_id]
def _serialize_real(o: Order, icon_map: dict[int, str]) -> dict: def _serialize_real(o: Order, icon_map: dict[int, str]) -> dict:
@@ -253,7 +333,7 @@ def build_summary(items: list[dict], source: str = 'all') -> dict:
def today_order_count(club_id: str) -> dict: def today_order_count(club_id: str) -> dict:
"""抢单池角标:当日总量(真单 + 当前假批次),不分平台。""" """抢单池角标:当日总量(真单 + 当前已累加假单),不分平台。"""
cid = (club_id or '').strip() cid = (club_id or '').strip()
if not cid: if not cid:
return {'total': 0} return {'total': 0}