feat: 龙先生审核目录重置为电竞技巧/策略指导商品
对齐小程序简介口径,避开代练代打与线下陪玩语义,仅写入 lxs 审核专用专区与商品。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
338
jituan/management/commands/reset_lxs_shenhe_esports_coach.py
Normal file
338
jituan/management/commands/reset_lxs_shenhe_esports_coach.py
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
龙先生(lxs)审核版目录重置:对齐小程序简介的「专业电竞护航 / 技巧支持 / 策略指导」。
|
||||||
|
|
||||||
|
目标(审核口径):
|
||||||
|
- 强调:线上电竞技巧讲解、对局策略交流、团队协作指导、正向技能提升
|
||||||
|
- 绝不出现:代练、代打、上分、线下陪玩、陪玩约玩等
|
||||||
|
- 仅改 club_id=lxs + 全局审核类型(shenhezhuangtai=2) 下的专区/商品
|
||||||
|
- 不碰正常商品(shenhezhuangtai=1)、其它俱乐部、店铺商品、订单
|
||||||
|
|
||||||
|
动作:
|
||||||
|
1) 将 3 个审核类型名改为:电竞技巧指导 / 对局策略交流 / 线上技能提升
|
||||||
|
2) 清空 lxs 下这些类型的审核专区+商品(只删 DB,不删 OSS)
|
||||||
|
3) 每个类型 2 个专区;每个专区 10 个商品(共 60)
|
||||||
|
4) 图标统一 a_long/morentouxiang.jpg
|
||||||
|
5) 下单须知写明:仅线上指导交流,禁止代练代打与线下服务
|
||||||
|
|
||||||
|
用法:
|
||||||
|
python manage.py reset_lxs_shenhe_esports_coach
|
||||||
|
python manage.py reset_lxs_shenhe_esports_coach --list
|
||||||
|
python manage.py reset_lxs_shenhe_esports_coach --apply --i-confirm=lxs-esports-coach
|
||||||
|
"""
|
||||||
|
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 = '龙先生'
|
||||||
|
SHENHE = 2
|
||||||
|
DEFAULT_AVATAR = 'a_long/morentouxiang.jpg'
|
||||||
|
CONFIRM_TOKEN = 'lxs-esports-coach'
|
||||||
|
EXPECTED_TYPE_COUNT = 3
|
||||||
|
|
||||||
|
TYPE_NAMES = ['电竞技巧指导', '对局策略交流', '线上技能提升']
|
||||||
|
|
||||||
|
ZONE_NAMES_BY_TYPE = [
|
||||||
|
['技巧讲解', '操作指导'],
|
||||||
|
['策略复盘', '思路交流'],
|
||||||
|
['协作训练', '正向成长'],
|
||||||
|
]
|
||||||
|
|
||||||
|
PRODUCTS_PER_ZONE = [
|
||||||
|
[
|
||||||
|
('基础操作技巧讲解', '线上讲解基础操作与键位思路,帮助建立正确习惯,重在理解与练习。', '19.90'),
|
||||||
|
('走位思路实时指导', '对局中讲解走位与观察习惯,提升意识,由学员本人操作完成。', '25.90'),
|
||||||
|
('技能连招要点拆解', '拆解技能释放节奏与连招要点,循序渐进,强调自学自练。', '22.90'),
|
||||||
|
('地图资源意识教学', '讲解地图信息与资源节奏,培养全局观,纯技巧交流。', '22.90'),
|
||||||
|
('瞄准与预判要点分享', '分享瞄准预判的练习方法,给出训练建议,学员自行练习。', '25.90'),
|
||||||
|
('开局节奏规划指导', '讲解开局规划与信息收集,帮助形成稳定开局习惯。', '19.90'),
|
||||||
|
('道具使用思路讲解', '讲解常用道具使用时机与配合思路,强调理解而非包办。', '19.90'),
|
||||||
|
('视野与信息读取教学', '教学如何读图、读信息,提升判断力,正向技能成长。', '22.90'),
|
||||||
|
('残局处理要点交流', '交流残局决策思路与优先级,鼓励独立思考与复盘。', '25.90'),
|
||||||
|
('个性化技巧答疑', '针对个人薄弱点线上答疑,给出可执行的练习清单。', '29.90'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
('对局策略思路交流', '线上交流阵容与战术思路,重在理解策略,学员自主决策执行。', '25.90'),
|
||||||
|
('团战站位策略讲解', '讲解团战站位与目标优先级,提升团队配合意识。', '25.90'),
|
||||||
|
('资源分配策略讨论', '讨论资源分配与节奏取舍,培养理性决策能力。', '22.90'),
|
||||||
|
('对手习惯观察复盘', '复盘如何观察对手习惯,给出分析框架,鼓励自我复盘。', '22.90'),
|
||||||
|
('中期转线策略交流', '交流中期转线与节奏切换思路,帮助建立阶段目标。', '25.90'),
|
||||||
|
('防守反击策略拆解', '拆解防守反击的触发条件与执行步骤,逻辑清晰可练。', '22.90'),
|
||||||
|
('进攻时机判断指导', '讲解进攻时机判断要素,强调信息与纪律,杜绝侥幸。', '25.90'),
|
||||||
|
('阵容克制关系科普', '科普常见克制关系与应对思路,开阔视野,正向学习。', '19.90'),
|
||||||
|
('赛后复盘方法教学', '教学赛后复盘步骤:录像、标注、总结,养成成长闭环。', '19.90'),
|
||||||
|
('策略问答一对一', '一对一策略问答,解答困惑,给出清晰可落地的改进方向。', '29.90'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
('团队沟通协作指导', '指导语音沟通话术与分工习惯,打造文明友好的协作环境。', '19.90'),
|
||||||
|
('良性竞争心态辅导', '交流输赢心态与压力管理,传递体育精神与正能量。', '15.90'),
|
||||||
|
('训练计划制定协助', '协助制定可坚持的训练计划,目标清晰、循序渐进。', '19.90'),
|
||||||
|
('团队默契练习指导', '指导信号、分工与节奏同步练习方法,提升协作效率。', '22.90'),
|
||||||
|
('文明竞技礼仪分享', '分享线上竞技礼仪与友好互动规范,拒绝不良言行。', '12.90'),
|
||||||
|
('专注力与节奏管理', '交流专注力与作息节奏管理小方法,支持长期进步。', '15.90'),
|
||||||
|
('新手友好入门指导', '面向新手的入门讲解与练习指导,学员全程自主操作。', '19.90'),
|
||||||
|
('进阶目标拆解辅导', '把长期目标拆成周计划,每周复盘一次,稳步提升。', '22.90'),
|
||||||
|
('录像点评成长课', '基于学员录像做要点点评,指出亮点与改进点,鼓励成长。', '29.90'),
|
||||||
|
('电竞护航技巧总复习', '汇总技巧与策略要点,形成个人笔记,巩固线上学习成果。', '25.90'),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
BANNED_WORDS = (
|
||||||
|
'代练', '代打', '上分', '上段', '包星', '定位赛', '代肝', '代刷',
|
||||||
|
'代打手', '代练平台', '代练服务', '护航上分', '包赢',
|
||||||
|
'陪玩', '线下陪玩', '上门', '约玩', '陪睡觉', '色情',
|
||||||
|
'赌博', '出租号', '买号', '卖号',
|
||||||
|
)
|
||||||
|
|
||||||
|
ORDER_NOTICE = (
|
||||||
|
'本服务为龙先生电竞提供的线上电竞技巧支持、对局策略指导与正向技能交流。'
|
||||||
|
'全程由学员本人操作与决策;禁止任何代练、代打、上分行为;服务仅限线上指导,不提供面见式服务。'
|
||||||
|
'请文明沟通,共建专业、健康的电竞学习环境。'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = '龙先生审核版改为电竞技巧/策略指导正能量目录(默认 dry-run)'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('--apply', action='store_true', help='真正写库')
|
||||||
|
parser.add_argument(
|
||||||
|
'--i-confirm',
|
||||||
|
type=str,
|
||||||
|
default='',
|
||||||
|
help=f'必须等于 {CONFIRM_TOKEN} 才允许 --apply',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--list',
|
||||||
|
action='store_true',
|
||||||
|
help='只列出当前审核类型及 lxs 存量',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--skip-rename-types',
|
||||||
|
action='store_true',
|
||||||
|
help='不修改全局审核类型名称,仅重建 lxs 专区/商品',
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
from jituan.models import Club
|
||||||
|
from products.models import Shangpin, ShangpinLeixing, ShangpinZhuanqu
|
||||||
|
|
||||||
|
apply = bool(options.get('apply'))
|
||||||
|
confirm = (options.get('i_confirm') or '').strip()
|
||||||
|
skip_rename = bool(options.get('skip_rename_types'))
|
||||||
|
|
||||||
|
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}」'
|
||||||
|
)
|
||||||
|
|
||||||
|
audit_types = list(
|
||||||
|
ShangpinLeixing.query.filter(shenhezhuangtai=SHENHE).order_by('id')
|
||||||
|
)
|
||||||
|
if len(audit_types) != EXPECTED_TYPE_COUNT:
|
||||||
|
raise CommandError(
|
||||||
|
f'安全拦截:审核类型数量应为 {EXPECTED_TYPE_COUNT},'
|
||||||
|
f'当前为 {len(audit_types)}:'
|
||||||
|
+ ', '.join(f'id={t.id}「{t.jieshao}」' for t in audit_types)
|
||||||
|
)
|
||||||
|
|
||||||
|
leixing_ids = [t.id for t in audit_types]
|
||||||
|
|
||||||
|
def counts():
|
||||||
|
z = ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=ALLOWED_CLUB_ID,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).count()
|
||||||
|
p = Shangpin.query.filter(
|
||||||
|
club_id=ALLOWED_CLUB_ID,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).count()
|
||||||
|
return z, p
|
||||||
|
|
||||||
|
if options.get('list'):
|
||||||
|
self.stdout.write(self.style.NOTICE('审核类型 → 计划改名;lxs 当前专区/商品:'))
|
||||||
|
for i, t in enumerate(audit_types):
|
||||||
|
z = ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=ALLOWED_CLUB_ID, leixing_id=t.id,
|
||||||
|
shenhezhuangtai=SHENHE, dianpu__isnull=True,
|
||||||
|
).count()
|
||||||
|
p = Shangpin.query.filter(
|
||||||
|
club_id=ALLOWED_CLUB_ID, leixing_id=t.id,
|
||||||
|
shenhezhuangtai=SHENHE, dianpu__isnull=True,
|
||||||
|
).count()
|
||||||
|
self.stdout.write(
|
||||||
|
f' id={t.id}「{t.jieshao}」→「{TYPE_NAMES[i]}」 '
|
||||||
|
f'lxs zones={z} products={p}'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
for zone_list in PRODUCTS_PER_ZONE:
|
||||||
|
if len(zone_list) != 10:
|
||||||
|
raise CommandError('内部错误:每个类型必须恰好 10 个商品模板')
|
||||||
|
for title, intro, _price in zone_list:
|
||||||
|
blob = title + intro + ORDER_NOTICE
|
||||||
|
for w in BANNED_WORDS:
|
||||||
|
if w in blob:
|
||||||
|
raise CommandError(f'文案含禁止词「{w}」: {title}')
|
||||||
|
|
||||||
|
for zones in ZONE_NAMES_BY_TYPE:
|
||||||
|
if len(zones) != 2:
|
||||||
|
raise CommandError('内部错误:每个类型必须恰好 2 个专区')
|
||||||
|
for zn in zones:
|
||||||
|
for w in BANNED_WORDS:
|
||||||
|
if w in zn:
|
||||||
|
raise CommandError(f'专区名含禁止词「{w}」: {zn}')
|
||||||
|
|
||||||
|
for tn in TYPE_NAMES:
|
||||||
|
for w in BANNED_WORDS:
|
||||||
|
if w in tn:
|
||||||
|
raise CommandError(f'类型名含禁止词「{w}」: {tn}')
|
||||||
|
|
||||||
|
if apply and confirm != CONFIRM_TOKEN:
|
||||||
|
raise CommandError(
|
||||||
|
f'安全拦截:--apply 必须带 --i-confirm={CONFIRM_TOKEN}\n'
|
||||||
|
f' python manage.py reset_lxs_shenhe_esports_coach '
|
||||||
|
f'--apply --i-confirm={CONFIRM_TOKEN}'
|
||||||
|
)
|
||||||
|
|
||||||
|
old_z, old_p = counts()
|
||||||
|
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'将清空 lxs 审核专区={old_z} 商品={old_p},再写入 '
|
||||||
|
f'{EXPECTED_TYPE_COUNT}类型×2专区×10商品=60\n'
|
||||||
|
f'图标: {DEFAULT_AVATAR}\n'
|
||||||
|
f'改名全局类型: {"否" if skip_rename else "是(影响所有俱乐部审核展示名)"}\n'
|
||||||
|
f'定位: 线上技巧支持 / 策略指导 / 正向技能提升;无代练代打、无线下陪玩\n'
|
||||||
|
))
|
||||||
|
|
||||||
|
self.stdout.write('—— 类型改名计划 ——')
|
||||||
|
for i, t in enumerate(audit_types):
|
||||||
|
self.stdout.write(f' id={t.id}「{t.jieshao}」→「{TYPE_NAMES[i]}」')
|
||||||
|
|
||||||
|
self.stdout.write('—— 专区/商品结构 ——')
|
||||||
|
for i, tname in enumerate(TYPE_NAMES):
|
||||||
|
for zn in ZONE_NAMES_BY_TYPE[i]:
|
||||||
|
self.stdout.write(f' 「{tname}」/「{zn}」→ 10 个指导类商品')
|
||||||
|
|
||||||
|
self.stdout.write('—— 商品样例 ——')
|
||||||
|
for title, intro, price in PRODUCTS_PER_ZONE[0][:3]:
|
||||||
|
self.stdout.write(f' {title} ¥{price} | {intro}')
|
||||||
|
|
||||||
|
if not apply:
|
||||||
|
self.stdout.write(self.style.WARNING(
|
||||||
|
'\n以上仅计划。确认后执行:\n'
|
||||||
|
f' python manage.py reset_lxs_shenhe_esports_coach '
|
||||||
|
f'--apply --i-confirm={CONFIRM_TOKEN}\n'
|
||||||
|
))
|
||||||
|
return
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
'types_renamed': 0,
|
||||||
|
'deleted_products': 0,
|
||||||
|
'deleted_zones': 0,
|
||||||
|
'zones_created': 0,
|
||||||
|
'products_created': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
if not skip_rename:
|
||||||
|
for i, t in enumerate(audit_types):
|
||||||
|
new_name = TYPE_NAMES[i]
|
||||||
|
if (t.jieshao or '') != new_name:
|
||||||
|
t.jieshao = new_name
|
||||||
|
t.tupian_url = t.tupian_url or DEFAULT_AVATAR
|
||||||
|
t.save()
|
||||||
|
stats['types_renamed'] += 1
|
||||||
|
|
||||||
|
n_prod, _ = Shangpin.query.filter(
|
||||||
|
club_id=ALLOWED_CLUB_ID,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).delete()
|
||||||
|
n_zone, _ = ShangpinZhuanqu.query.filter(
|
||||||
|
club_id=ALLOWED_CLUB_ID,
|
||||||
|
leixing_id__in=leixing_ids,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
dianpu__isnull=True,
|
||||||
|
).delete()
|
||||||
|
stats['deleted_products'] = n_prod
|
||||||
|
stats['deleted_zones'] = n_zone
|
||||||
|
|
||||||
|
for i, lx in enumerate(audit_types):
|
||||||
|
lx = ShangpinLeixing.query.filter(id=lx.id).first()
|
||||||
|
zone_names = ZONE_NAMES_BY_TYPE[i]
|
||||||
|
base_products = PRODUCTS_PER_ZONE[i]
|
||||||
|
for zi, zname in enumerate(zone_names):
|
||||||
|
zrow = ShangpinZhuanqu.query.create(
|
||||||
|
mingzi=zname,
|
||||||
|
leixing_id=lx.id,
|
||||||
|
club_id=ALLOWED_CLUB_ID,
|
||||||
|
tupian_url=DEFAULT_AVATAR,
|
||||||
|
shenhezhuangtai=SHENHE,
|
||||||
|
paixu=20 - zi * 10,
|
||||||
|
shangjia_zhuangtai=True,
|
||||||
|
fengjin_zhuangtai=True,
|
||||||
|
dianpu=None,
|
||||||
|
dianpu_leixing=None,
|
||||||
|
)
|
||||||
|
stats['zones_created'] += 1
|
||||||
|
|
||||||
|
for pi, (title, intro, price) in enumerate(base_products):
|
||||||
|
full_title = f'{title}·{zname}'
|
||||||
|
for w in BANNED_WORDS:
|
||||||
|
if w in full_title or w in intro:
|
||||||
|
raise CommandError(f'禁止词命中: {full_title}')
|
||||||
|
Shangpin.query.create(
|
||||||
|
biaoqian=full_title[:45],
|
||||||
|
jiage=Decimal(price),
|
||||||
|
kucun=99999,
|
||||||
|
leixing_id=lx.id,
|
||||||
|
zhuanqu_id=zrow.id,
|
||||||
|
zhenshi_xiaoliang=0,
|
||||||
|
duiwai_xiaoliang=0,
|
||||||
|
jieshao=intro,
|
||||||
|
xiadan_xuzhi=ORDER_NOTICE,
|
||||||
|
guize_tupian='',
|
||||||
|
tupian_url=DEFAULT_AVATAR,
|
||||||
|
yaoqiuleixing=lx.yaoqiuleixing,
|
||||||
|
huiyuan_id=lx.huiyuan_id,
|
||||||
|
yongjin=lx.yongjin,
|
||||||
|
kaioi_ewai_dashou_fencheng=False,
|
||||||
|
ewai_dashou_fencheng=None,
|
||||||
|
paixu=100 - pi,
|
||||||
|
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
|
||||||
|
|
||||||
|
if stats['zones_created'] != 6 or stats['products_created'] != 60:
|
||||||
|
raise CommandError(
|
||||||
|
f'数量异常: zones={stats["zones_created"]} '
|
||||||
|
f'products={stats["products_created"]},期望 6/60,已回滚'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(
|
||||||
|
f'\n完成: {stats}\n'
|
||||||
|
f'均为审核专用(shenhezhuangtai=2);文案对齐「技巧支持/策略指导」,'
|
||||||
|
f'已避开代练代打与线下陪玩。'
|
||||||
|
))
|
||||||
Reference in New Issue
Block a user