From 12d01614e97fd95aad8802323a45b6c0f94086e9 Mon Sep 17 00:00:00 2001 From: XingQue Date: Fri, 24 Jul 2026 17:47:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=BE=99=E5=85=88=E7=94=9F=E5=AE=A1?= =?UTF-8?q?=E6=A0=B8=E7=89=88=E6=B8=B8=E6=88=8F=E6=8A=A4=E8=88=AA=E5=95=86?= =?UTF-8?q?=E5=93=81=E7=A7=8D=E5=AD=90=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 仅 lxs;shenhezhuangtai=2;正向陪伴文案;默认头像图;dry-run + 双确认。 Co-authored-by: Cursor --- .../seed_lxs_shenhe_huhang_catalog.py | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 jituan/management/commands/seed_lxs_shenhe_huhang_catalog.py diff --git a/jituan/management/commands/seed_lxs_shenhe_huhang_catalog.py b/jituan/management/commands/seed_lxs_shenhe_huhang_catalog.py new file mode 100644 index 0000000..a3cea49 --- /dev/null +++ b/jituan/management/commands/seed_lxs_shenhe_huhang_catalog.py @@ -0,0 +1,311 @@ +# -*- coding: utf-8 -*- +""" +为龙先生电竞(lxs)写入审核专用(shenhezhuangtai=2)「游戏护航/陪伴」公共商品目录。 + +范围: + - 仅 club_id=lxs(名称须含「龙先生」) + - 全局审核类型:取 ShangpinLeixing 中 shenhezhuangtai=2 的第一条;若无则创建「娱乐护航」 + - 创建若干审核专区;30 个商品主要挂在第一个专区 + - 商品图统一用默认头像相对路径 a_long/morentouxiang.jpg + - 文案仅正向「护航/陪伴/开黑」,绝不出现代练、代打、上分等 + +不碰:正常商品(shenhezhuangtai=1)、其它俱乐部、店铺商品。 + +用法: + python manage.py seed_lxs_shenhe_huhang_catalog + python manage.py seed_lxs_shenhe_huhang_catalog --apply --i-confirm-club=lxs +""" +from __future__ import annotations + +from decimal import Decimal + +from django.core.management.base import BaseCommand, CommandError +from django.db import transaction + +ALLOWED_CLUB_ID = 'lxs' +NAME_HINT = '龙先生' +DEFAULT_AVATAR = 'a_long/morentouxiang.jpg' +SHENHE = 2 # 审核专用 + +# 专区(审核版) +ZONE_DEFS = [ + {'mingzi': '休闲开黑', 'paixu': 30}, + {'mingzi': '语音陪伴', 'paixu': 20}, + {'mingzi': '团队协作', 'paixu': 10}, +] + +# 30 个正向护航/陪伴向商品(标题 + 介绍 + 价格),禁止代练代打语义 +PRODUCT_DEFS = [ + ('王者荣耀·休闲开黑陪伴', '友好组队开黑,轻松娱乐,语音交流,快乐优先。', '19.90'), + ('和平精英·小队同行', '休闲模式组队畅玩,互相配合,体验团队乐趣。', '19.90'), + ('原神·探索同行', '一起探索地图与活动,聊天解谜,轻松愉快。', '29.90'), + ('星穹铁道·剧情陪伴', '陪你体验剧情与活动关卡,节奏友好,交流顺畅。', '29.90'), + ('无畏契约·娱乐五排', '娱乐局友好开黑,沟通协作,享受对局过程。', '25.90'), + ('英雄联盟·娱乐双排', '休闲娱乐局陪伴,友好沟通,重在开心。', '25.90'), + ('永劫无间·双人协作', '双人娱乐局同行,互相支援,体验动作乐趣。', '22.90'), + ('三角洲行动·小队协作', '小队任务友好配合,沟通顺畅,快乐完成目标。', '29.90'), + ('蛋仔派对·欢乐同乐', '休闲派对模式一起玩,轻松互动,气氛友好。', '15.90'), + ('逆水寒·社交同游', '一起游历风景与活动,聊天社交,轻松惬意。', '29.90'), + ('鸣潮·旅途陪伴', '结伴体验开放世界内容,节奏舒适,友好交流。', '29.90'), + ('绝区零·日常同行', '日常内容一起体验,互相提醒,轻松完成。', '22.90'), + ('崩坏3·活动陪伴', '活动关卡友好同行,沟通配合,享受剧情。', '19.90'), + ('命运冠位指定·活动同行', '活动副本休闲体验,节奏友好,互相鼓励。', '19.90'), + ('明日方舟·关卡陪伴', '关卡推图友好协助,讲解思路,轻松通关体验。', '22.90'), + ('第五人格·娱乐开黑', '娱乐模式组队,友好沟通,享受对抗乐趣。', '19.90'), + ('CS2·休闲约战', '休闲模式友好对局,语音交流,重在体验。', '25.90'), + ('Dota2·娱乐组队', '娱乐局结伴开黑,沟通协作,快乐优先。', '25.90'), + ('炉石传说·休闲对局', '休闲模式一起玩,交流构筑心得,气氛轻松。', '15.90'), + ('金铲铲之战·娱乐对局', '娱乐模式畅玩,聊天互动,轻松愉快。', '15.90'), + ('穿越火线·休闲开黑', '休闲模式组队,友好沟通,享受射击乐趣。', '19.90'), + ('CFHD·娱乐同行', '娱乐局结伴,语音交流,过程友好。', '19.90'), + ('NBA2K·友谊赛陪伴', '友谊赛模式一起打,轻松互动,享受比赛。', '22.90'), + ('FIFA·友谊赛同行', '友谊赛友好对战,交流心得,气氛轻松。', '22.90'), + ('我的世界·建造同行', '一起建造与探索,创意交流,氛围友好。', '19.90'), + ('幻兽帕鲁·合作体验', '合作模式结伴体验,互相帮助,轻松有趣。', '19.90'), + ('双人成行·合作通关', '合作解谜一起玩,沟通配合,欢乐通关。', '29.90'), + ('It Takes Two·双人协作', '双人关卡友好协作,互动解谜,享受过程。', '29.90'), + ('斯普拉遁·休闲对战', '休闲对战结伴,轻松交流,享受涂色乐趣。', '15.90'), + ('动物森友会·岛上同游', '一起逛岛、装饰与活动,聊天放松,氛围温馨。', '19.90'), +] + + +class Command(BaseCommand): + help = '龙先生(lxs)审核版护航商品种子(默认 dry-run)' + + def add_arguments(self, parser): + parser.add_argument('--apply', action='store_true', help='真正写库') + parser.add_argument( + '--i-confirm-club', + type=str, + default='', + help='必须等于 lxs 才允许 --apply', + ) + parser.add_argument( + '--club-id', + type=str, + default=ALLOWED_CLUB_ID, + help='仅允许 lxs', + ) + + def handle(self, *args, **options): + from jituan.models import Club + from products.models import Shangpin, ShangpinLeixing, ShangpinZhuanqu + + club_id = (options.get('club_id') or '').strip() + apply = bool(options.get('apply')) + confirm = (options.get('i_confirm_club') or '').strip() + + if club_id != ALLOWED_CLUB_ID: + raise CommandError( + f'安全拦截:只允许 club_id={ALLOWED_CLUB_ID!r},你传的是 {club_id!r}' + ) + + club = Club.query.filter(club_id=ALLOWED_CLUB_ID).first() + if not club: + raise CommandError(f'俱乐部 {ALLOWED_CLUB_ID} 不存在') + if NAME_HINT not in (club.name or ''): + raise CommandError( + f'安全拦截:{ALLOWED_CLUB_ID} 的 name={club.name!r} 不含「{NAME_HINT}」' + ) + + if apply and confirm != ALLOWED_CLUB_ID: + raise CommandError( + '安全拦截:--apply 必须带 --i-confirm-club=lxs\n' + ' python manage.py seed_lxs_shenhe_huhang_catalog --apply --i-confirm-club=lxs' + ) + + # 审核类型:第一条;没有则计划创建 + leixing = ( + ShangpinLeixing.query + .filter(shenhezhuangtai=SHENHE) + .order_by('id') + .first() + ) + leixing_action = 'REUSE' + if not leixing: + leixing_action = 'CREATE' + leixing = None + + # 专区计划 + zone_plans = [] + for zdef in ZONE_DEFS: + exist = ShangpinZhuanqu.query.filter( + club_id=ALLOWED_CLUB_ID, + mingzi=zdef['mingzi'], + shenhezhuangtai=SHENHE, + dianpu__isnull=True, + ).first() + if exist and leixing and exist.leixing_id == leixing.id: + zone_plans.append({'def': zdef, 'action': 'SKIP', 'row': exist}) + elif exist and not leixing: + # 类型尚未创建,名称撞车则复用该专区(apply 时再绑类型) + zone_plans.append({'def': zdef, 'action': 'SKIP', 'row': exist}) + else: + zone_plans.append({'def': zdef, 'action': 'CREATE', 'row': None}) + + # 商品计划:全部挂第一个专区 + first_zone_name = ZONE_DEFS[0]['mingzi'] + product_plans = [] + for title, intro, price in PRODUCT_DEFS: + # 预检:同 club + 同标题 + 审核态是否已有 + exist_p = Shangpin.query.filter( + club_id=ALLOWED_CLUB_ID, + biaoqian=title, + shenhezhuangtai=SHENHE, + dianpu__isnull=True, + ).first() + product_plans.append({ + 'title': title, + 'intro': intro, + 'price': price, + 'action': 'SKIP' if exist_p else 'CREATE', + 'exist_id': exist_p.id if exist_p else None, + }) + + mode = 'APPLY' if apply else 'DRY-RUN' + self.stdout.write(self.style.NOTICE( + f'\n===== {mode} 龙先生审核版护航商品 =====\n' + f'club={ALLOWED_CLUB_ID} ({club.name})\n' + f'审核类型: {leixing_action} ' + f'{("id="+str(leixing.id)+" name="+repr(leixing.jieshao)) if leixing else "将创建「娱乐护航」"}\n' + f'默认图标: {DEFAULT_AVATAR}\n' + f'专区计划: create={sum(1 for z in zone_plans if z["action"]=="CREATE")} ' + f'skip={sum(1 for z in zone_plans if z["action"]=="SKIP")}\n' + f'商品计划: create={sum(1 for p in product_plans if p["action"]=="CREATE")} ' + f'skip={sum(1 for p in product_plans if p["action"]=="SKIP")} ' + f'(共{len(product_plans)},挂专区「{first_zone_name}」)\n' + )) + + self.stdout.write('—— 专区 ——') + for z in zone_plans: + self.stdout.write(f' [{z["action"]}] {z["def"]["mingzi"]}') + + self.stdout.write('—— 商品样例(前8)——') + for p in product_plans[:8]: + self.stdout.write(f' [{p["action"]}] {p["title"]} ¥{p["price"]}') + if len(product_plans) > 8: + self.stdout.write(f' ... 其余 {len(product_plans)-8} 个省略') + + banned = ('代练', '代打', '上分', '上段', '包星', '定位赛代') + for p in product_plans: + blob = p['title'] + p['intro'] + for b in banned: + if b in blob: + raise CommandError(f'文案含禁止词「{b}」: {p["title"]}') + + if not apply: + self.stdout.write(self.style.WARNING( + '\n以上仅计划。确认后:\n' + ' python manage.py seed_lxs_shenhe_huhang_catalog --apply --i-confirm-club=lxs\n' + )) + return + + stats = { + 'leixing_created': 0, + 'zones_created': 0, + 'zones_skipped': 0, + 'products_created': 0, + 'products_skipped': 0, + } + + with transaction.atomic(): + if leixing is None: + leixing = ShangpinLeixing.query.create( + jieshao='娱乐护航', + tupian_url=DEFAULT_AVATAR, + paixu=0, + shenhezhuangtai=SHENHE, + yaoqiuleixing=None, + huiyuan_id=None, + yongjin=None, + ) + stats['leixing_created'] = 1 + elif leixing.shenhezhuangtai != SHENHE: + raise CommandError('解析到的类型不是审核专用,已中止') + + zone_id_by_name = {} + for zp in zone_plans: + zdef = zp['def'] + if zp['action'] == 'SKIP' and zp['row']: + # 若旧专区 leixing 不对,不改(只增不改);仅复用 id + zone_id_by_name[zdef['mingzi']] = zp['row'].id + stats['zones_skipped'] += 1 + continue + exist = ShangpinZhuanqu.query.filter( + club_id=ALLOWED_CLUB_ID, + mingzi=zdef['mingzi'], + leixing_id=leixing.id, + shenhezhuangtai=SHENHE, + dianpu__isnull=True, + ).first() + if exist: + zone_id_by_name[zdef['mingzi']] = exist.id + stats['zones_skipped'] += 1 + continue + row = ShangpinZhuanqu.query.create( + mingzi=zdef['mingzi'], + leixing_id=leixing.id, + club_id=ALLOWED_CLUB_ID, + tupian_url=DEFAULT_AVATAR, + shenhezhuangtai=SHENHE, + paixu=int(zdef['paixu']), + shangjia_zhuangtai=True, + fengjin_zhuangtai=True, + dianpu=None, + dianpu_leixing=None, + ) + zone_id_by_name[zdef['mingzi']] = row.id + stats['zones_created'] += 1 + + first_zq_id = zone_id_by_name.get(first_zone_name) + if not first_zq_id: + raise CommandError(f'第一个专区「{first_zone_name}」未就绪') + + for i, pp in enumerate(product_plans): + if pp['action'] == 'SKIP': + stats['products_skipped'] += 1 + continue + exist_p = Shangpin.query.filter( + club_id=ALLOWED_CLUB_ID, + biaoqian=pp['title'], + zhuanqu_id=first_zq_id, + shenhezhuangtai=SHENHE, + dianpu__isnull=True, + ).first() + if exist_p: + stats['products_skipped'] += 1 + continue + Shangpin.query.create( + biaoqian=pp['title'], + jiage=Decimal(pp['price']), + kucun=99999, + leixing_id=leixing.id, + zhuanqu_id=first_zq_id, + zhenshi_xiaoliang=0, + duiwai_xiaoliang=0, + jieshao=pp['intro'], + xiadan_xuzhi='请保持友好沟通,本服务为休闲娱乐陪伴,禁止不良言行。', + guize_tupian='', + tupian_url=DEFAULT_AVATAR, + yaoqiuleixing=leixing.yaoqiuleixing, + huiyuan_id=leixing.huiyuan_id, + yongjin=leixing.yongjin, + kaioi_ewai_dashou_fencheng=False, + ewai_dashou_fencheng=None, + paixu=1000 - i, + shenhezhuangtai=SHENHE, + shangjia_zhuangtai=True, + fengjin_zhuangtai=True, + shenhe_zhuangtai=True, + club_id=ALLOWED_CLUB_ID, + shi_zhuanpan=False, + dianpu=None, + dianpu_leixing=None, + ) + stats['products_created'] += 1 + + self.stdout.write(self.style.SUCCESS( + f'\n完成: {stats}\n' + f'审核类型 id={leixing.id}「{leixing.jieshao}」;' + f'商品均 shenhezhuangtai=2,仅审核模式可见。' + ))