feat: 假单列表全局2分钟打乱一次,全用户顺序一致

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-06-28 18:22:57 +08:00
parent 48bb43d9a8
commit 5c7bb82a7b
3 changed files with 89 additions and 3 deletions

View File

@@ -0,0 +1,23 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('orders', '0011_fakegraborder_merchantnickname'),
]
operations = [
migrations.CreateModel(
name='FakeGrabOrderShuffleState',
fields=[
('id', models.PositiveSmallIntegerField(default=1, primary_key=True, serialize=False, verbose_name='固定主键=1')),
('LastShuffleTime', models.DateTimeField(verbose_name='上次打乱时刻')),
('IntervalSeconds', models.PositiveIntegerField(default=120, verbose_name='打乱间隔秒数默认2分钟')),
('UpdateTime', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
],
options={
'db_table': 'fake_grab_order_shuffle',
},
),
]

View File

@@ -1094,6 +1094,24 @@ class FakeGrabOrder(QModel):
] ]
class FakeGrabOrderShuffleState(QModel):
"""假单列表全局打乱时刻(仅 id=1 一条记录,全用户共用)。"""
id = models.PositiveSmallIntegerField(
primary_key=True, default=1, verbose_name='固定主键=1',
)
LastShuffleTime = models.DateTimeField(
verbose_name='上次打乱时刻',
)
IntervalSeconds = models.PositiveIntegerField(
default=120, verbose_name='打乱间隔秒数默认2分钟',
)
UpdateTime = models.DateTimeField(auto_now=True, verbose_name='更新时间')
class Meta:
db_table = 'fake_grab_order_shuffle'
# =========================================================================== # ===========================================================================
# 兼容别名(渐进迁移,全部迁移完成后可删除) # 兼容别名(渐进迁移,全部迁移完成后可删除)
# =========================================================================== # ===========================================================================

View File

@@ -4,15 +4,17 @@ from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.utils import timezone from django.utils import timezone
from rest_framework.response import Response from rest_framework.response import Response
from backend.utils import fmt_datetime, pick_leixing_id from backend.utils import fmt_datetime, pick_leixing_id
from jituan.services.club_context import resolve_effective_club_id from jituan.services.club_context import resolve_effective_club_id
from jituan.services.dashou_exam import user_has_active_member from jituan.services.dashou_exam import user_has_active_member
from orders.models import FakeGrabOrder from orders.models import FakeGrabOrder, FakeGrabOrderShuffleState
DEFAULT_DISPATCHER_AVATAR = 'beijing/morentouxiang.jpg' DEFAULT_DISPATCHER_AVATAR = 'beijing/morentouxiang.jpg'
SHUFFLE_STATE_PK = 1
def dashou_should_use_fake_pool(user): def dashou_should_use_fake_pool(user):
@@ -46,6 +48,49 @@ def _random_display_time_str():
return fmt_datetime(dt) return fmt_datetime(dt)
def _resolve_shuffle_epoch():
"""
全用户共用一条打乱状态id=1
- 距上次打乱 >= IntervalSeconds更新 LastShuffleTime进入新打乱窗口
- 否则:沿用上次打乱时刻,本窗口内顺序不变
返回用于固定顺序的 epoch 时间戳;若应使用库表顺序则返回 None。
"""
now = timezone.now()
with transaction.atomic():
state, created = FakeGrabOrderShuffleState.objects.select_for_update().get_or_create(
pk=SHUFFLE_STATE_PK,
defaults={
'LastShuffleTime': now - timedelta(days=1),
'IntervalSeconds': 120,
},
)
interval = max(1, int(state.IntervalSeconds or 120))
elapsed = (now - state.LastShuffleTime).total_seconds()
if created or elapsed >= interval:
state.LastShuffleTime = now
state.save(update_fields=['LastShuffleTime', 'UpdateTime'])
return int(state.LastShuffleTime.timestamp())
return None
def _order_fake_rows(rows, shuffle_epoch):
"""shuffle_epoch 有值:按全局 epoch 固定打乱None按库表 SortOrder/CreateTime。"""
items = list(rows)
if shuffle_epoch is None:
items.sort(
key=lambda r: (
-(r.SortOrder or 0),
r.CreateTime or timezone.datetime.min.replace(tzinfo=timezone.utc),
r.id or 0,
),
)
return items
base = sorted(items, key=lambda r: (r.id or 0, r.OrderID or ''))
rng = random.Random(shuffle_epoch)
rng.shuffle(base)
return base
def _format_fake_row(row, display_time=None): def _format_fake_row(row, display_time=None):
"""字段与 DashouDingdanHuoquView 真实单列表保持一致。""" """字段与 DashouDingdanHuoquView 真实单列表保持一致。"""
# 平台单用 tupian、商家单用 tupian + sj_avatar统一默认头像 # 平台单用 tupian、商家单用 tupian + sj_avatar统一默认头像
@@ -97,8 +142,8 @@ def build_fake_order_pool_response(request):
if leixing_id is not None: if leixing_id is not None:
qs = qs.filter(ProductTypeID=leixing_id) qs = qs.filter(ProductTypeID=leixing_id)
rows = list(qs) shuffle_epoch = _resolve_shuffle_epoch()
random.shuffle(rows) rows = _order_fake_rows(qs, shuffle_epoch)
total = len(rows) total = len(rows)
offset = (page - 1) * page_size offset = (page - 1) * page_size