239 lines
9.2 KiB
Python
239 lines
9.2 KiB
Python
"""
|
||
灌入「今日订单」展示假单(TodayOrderFake),与抢单池 FakeGrabOrder 无关。
|
||
|
||
用法(服务器项目根目录):
|
||
python manage.py seed_today_order_fakes --club-id xq
|
||
python manage.py seed_today_order_fakes --club-id xq --count 500
|
||
python manage.py seed_today_order_fakes --club-id xq --clear # 先清该俱乐部旧假单再灌
|
||
python manage.py seed_today_order_fakes --club-id xq --dry-run
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import random
|
||
from decimal import Decimal
|
||
|
||
from django.core.management.base import BaseCommand, CommandError
|
||
from django.db import transaction
|
||
from django.utils import timezone
|
||
|
||
from orders.models import TodayOrderFake
|
||
|
||
|
||
# 用户提供的介绍模板 + 价格(原样,不改写)
|
||
TEMPLATES = [
|
||
(
|
||
'绝密清图撤离1把,一直无法清图则保底2000万,所有的变卖和装备都归老板保持良好的服务态度',
|
||
'35.00',
|
||
),
|
||
(
|
||
'保底305W-800W【不卡保底老板自选图】机密',
|
||
'20.00',
|
||
),
|
||
(
|
||
'机密200W,打手打完之后一定要提供相关明确的老板主页,战绩,收益,证据截图',
|
||
'13.00',
|
||
),
|
||
(
|
||
'绝密清图保底310W-777W【不卡保底老板自选图】别试图蒙混,让老板挑刺',
|
||
'23.00',
|
||
),
|
||
(
|
||
'不卡保底444W~999W,打手打完之后一定要提供相关明确的老板主页,战绩,收益,证据截图',
|
||
'33.00',
|
||
),
|
||
(
|
||
'金牌护航,绝密清图,不卡保底2026W~2888W,所有的变卖和装备都归老板保持良好的服务态度',
|
||
'200.00',
|
||
),
|
||
(
|
||
'绝密清图单局800W,打手别让老板来挑刺,打手打完之后一定要提供相关明确的老板主页,战绩,收益,证据截图',
|
||
'80.00',
|
||
),
|
||
(
|
||
'护航600万(上不封顶),打手打完之后一定要提供相关明确的老板主页,战绩,收益,证据截图',
|
||
'50.00',
|
||
),
|
||
(
|
||
'绝密老板选图749W-1769W,炸弹一把加60w',
|
||
'65.00',
|
||
),
|
||
(
|
||
'绝密清图单局555W,别让老板来挑刺,打手打完之后一定要提供相关明确的老板主页,战绩,收益,证据截图',
|
||
'50.00',
|
||
),
|
||
(
|
||
'保底105W-555W【不卡保底】机密,打手打完之后一定要提供相关明确的老板主页,战绩,收益,证据截图',
|
||
'7.50',
|
||
),
|
||
(
|
||
'绝密连续清图撤离两把断一把重新打,所有的变卖和装备都归老板保持良好的服务态度',
|
||
'75.00',
|
||
),
|
||
(
|
||
'绝密赌老板自选图单局一个随机红,打手打完之后一定要提供相关明确的老板主页,战绩,收益,证据截图',
|
||
'25.00',
|
||
),
|
||
]
|
||
|
||
|
||
def _rel_media(path: str) -> str:
|
||
p = (path or '').strip()
|
||
if not p:
|
||
return ''
|
||
for prefix in ('https://', 'http://'):
|
||
if p.startswith(prefix):
|
||
# 取域名后路径
|
||
rest = p.split('://', 1)[-1]
|
||
slash = rest.find('/')
|
||
return rest[slash + 1 :] if slash >= 0 else ''
|
||
return p.lstrip('/')
|
||
|
||
|
||
def _resolve_sanjiaozhou(club_id: str, leixing_id: int | None):
|
||
"""解析三角洲类型 id + 图标相对 URL。"""
|
||
from products.models import ShangpinLeixing
|
||
from jituan.models import ClubShangpinLeixingConfig
|
||
|
||
lx = None
|
||
if leixing_id:
|
||
lx = ShangpinLeixing.query.filter(id=int(leixing_id)).first()
|
||
if not lx:
|
||
raise CommandError(f'找不到商品类型 id={leixing_id}')
|
||
else:
|
||
qs = ShangpinLeixing.query.filter(jieshao__icontains='三角洲').order_by('id')
|
||
rows = list(qs)
|
||
if not rows:
|
||
raise CommandError('库里没有名称含「三角洲」的商品类型,请用 --leixing-id 指定')
|
||
# 优先俱乐部已上架的
|
||
enabled_ids = set(
|
||
ClubShangpinLeixingConfig.query.filter(
|
||
club_id=club_id, is_enabled=True,
|
||
).values_list('leixing_id', flat=True)
|
||
)
|
||
preferred = [r for r in rows if r.id in enabled_ids] or rows
|
||
lx = preferred[0]
|
||
|
||
image = ''
|
||
cfg = ClubShangpinLeixingConfig.query.filter(
|
||
club_id=club_id, leixing_id=lx.id,
|
||
).first()
|
||
if cfg and (cfg.tupian_url or '').strip():
|
||
image = _rel_media(cfg.tupian_url)
|
||
if not image:
|
||
image = _rel_media(lx.tupian_url or '')
|
||
if not image:
|
||
raise CommandError(
|
||
f'三角洲类型 id={lx.id}({lx.jieshao})没有图标 URL,'
|
||
f'请先在后台配置类型图或俱乐部专属图标'
|
||
)
|
||
return int(lx.id), image, (lx.jieshao or '')
|
||
|
||
|
||
class Command(BaseCommand):
|
||
help = '灌入今日订单展示假单 TodayOrderFake(默认 500 条,模板循环随机)'
|
||
|
||
def add_arguments(self, parser):
|
||
parser.add_argument('--club-id', default='xq', help='俱乐部 ID,默认 xq')
|
||
parser.add_argument('--count', type=int, default=500, help='灌入条数,默认 500')
|
||
parser.add_argument(
|
||
'--leixing-id', type=int, default=None,
|
||
help='商品类型 ID;默认自动找名称含「三角洲」的类型',
|
||
)
|
||
parser.add_argument(
|
||
'--merchant-ratio', type=float, default=0.75,
|
||
help='商家单占比,默认 0.75(其余为平台单)',
|
||
)
|
||
parser.add_argument(
|
||
'--done-ratio', type=float, default=0.70,
|
||
help='已完成占比,默认 0.70(其余为进行中)',
|
||
)
|
||
parser.add_argument(
|
||
'--clear', action='store_true',
|
||
help='灌入前删除该俱乐部全部 TodayOrderFake',
|
||
)
|
||
parser.add_argument('--dry-run', action='store_true', help='只打印不写库')
|
||
parser.add_argument('--seed', type=int, default=None, help='随机种子(可复现)')
|
||
|
||
def handle(self, *args, **options):
|
||
club_id = (options['club_id'] or 'xq').strip()
|
||
count = max(1, int(options['count'] or 500))
|
||
merchant_ratio = float(options['merchant_ratio'])
|
||
done_ratio = float(options['done_ratio'])
|
||
dry = bool(options['dry_run'])
|
||
if options['seed'] is not None:
|
||
random.seed(int(options['seed']))
|
||
|
||
leixing_id, image_url, lx_name = _resolve_sanjiaozhou(club_id, options['leixing_id'])
|
||
|
||
self.stdout.write(
|
||
f'club={club_id} leixing={leixing_id}({lx_name}) '
|
||
f'image={image_url} count={count} '
|
||
f'merchant={merchant_ratio:.0%} done={done_ratio:.0%}'
|
||
)
|
||
self.stdout.write(f'模板数={len(TEMPLATES)} dry_run={dry} clear={options["clear"]}')
|
||
|
||
if dry:
|
||
# 抽样预览
|
||
for i in range(min(5, count)):
|
||
jieshao, price = random.choice(TEMPLATES)
|
||
plat = 2 if random.random() < merchant_ratio else 1
|
||
st = TodayOrderFake.STATUS_DONE if random.random() < done_ratio else TodayOrderFake.STATUS_DOING
|
||
self.stdout.write(
|
||
f' sample[{i}] platform={plat} status={st} price={price} | {jieshao[:40]}...'
|
||
)
|
||
self.stdout.write(self.style.WARNING('dry-run:未写入数据库'))
|
||
return
|
||
|
||
with transaction.atomic():
|
||
if options['clear']:
|
||
deleted, _ = TodayOrderFake.query.filter(club_id=club_id).delete()
|
||
self.stdout.write(f'已清理 club={club_id} 旧假单 {deleted} 条')
|
||
|
||
now = timezone.now()
|
||
if timezone.is_aware(now):
|
||
try:
|
||
now = timezone.make_naive(now)
|
||
except Exception:
|
||
pass
|
||
rows = []
|
||
for i in range(count):
|
||
jieshao, price = random.choice(TEMPLATES)
|
||
platform = (
|
||
TodayOrderFake.PLATFORM_SHANGJIA
|
||
if random.random() < merchant_ratio
|
||
else TodayOrderFake.PLATFORM_PINGTAI
|
||
)
|
||
status = (
|
||
TodayOrderFake.STATUS_DONE
|
||
if random.random() < done_ratio
|
||
else TodayOrderFake.STATUS_DOING
|
||
)
|
||
rows.append(
|
||
TodayOrderFake(
|
||
club_id=club_id,
|
||
platform=platform,
|
||
leixing_id=leixing_id,
|
||
jieshao=jieshao,
|
||
price=Decimal(price),
|
||
image_url=image_url,
|
||
status=status,
|
||
is_active=True,
|
||
sort_order=count - i,
|
||
CreateTime=now,
|
||
UpdateTime=now,
|
||
)
|
||
)
|
||
TodayOrderFake.objects.bulk_create(rows, batch_size=200)
|
||
|
||
total = TodayOrderFake.query.filter(club_id=club_id, is_active=True).count()
|
||
done_n = TodayOrderFake.query.filter(
|
||
club_id=club_id, is_active=True, status=TodayOrderFake.STATUS_DONE,
|
||
).count()
|
||
sj_n = TodayOrderFake.query.filter(
|
||
club_id=club_id, is_active=True, platform=TodayOrderFake.PLATFORM_SHANGJIA,
|
||
).count()
|
||
self.stdout.write(self.style.SUCCESS(
|
||
f'完成:新写入 {count} 条;当前启用假单共 {total} 条 '
|
||
f'(已完成 {done_n},商家单 {sj_n})'
|
||
))
|