From 4c7f273cf77050d430033efc454e090526b06102 Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 11 Aug 2026 21:17:16 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E5=A2=9E=E5=8A=A0=E4=BB=8A=E6=97=A5?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=B1=95=E7=A4=BA=E5=81=87=E5=8D=95=E7=81=8C?= =?UTF-8?q?=E5=85=A5=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../commands/seed_today_order_fakes.py | 238 ++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 orders/management/commands/seed_today_order_fakes.py diff --git a/orders/management/commands/seed_today_order_fakes.py b/orders/management/commands/seed_today_order_fakes.py new file mode 100644 index 0000000..f018910 --- /dev/null +++ b/orders/management/commands/seed_today_order_fakes.py @@ -0,0 +1,238 @@ +""" +灌入「今日订单」展示假单(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})' + ))