Files
Django/jituan/management/commands/reset_lxs_shenhe_esports_coach.py
XingQue ff3d1e2ed3 fix: 审核商品脚本去掉须知中的代练等敏感词
自检误把「禁止代练」写进文案导致脚本自拦截;改为正向表述并不再触发敏感词。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-25 19:21:44 +08:00

343 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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
for w in BANNED_WORDS:
if w in blob:
raise CommandError(f'文案含禁止词「{w}」: {title}')
for w in BANNED_WORDS:
if w in ORDER_NOTICE:
raise CommandError(f'下单须知含禁止词「{w}」,请改成正向表述')
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'已避开代练代打与线下陪玩。'
))