fix: 不再自动创建 lsx 空俱乐部;新增按无 key 路径清理重复配置

cleanup_lsx_empty_club 只删无证书路径的龙先生相关俱乐部,保留已上传 key 的配置。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-23 21:43:12 +08:00
parent cddd231dc2
commit de88e55378
3 changed files with 221 additions and 21 deletions

View File

@@ -0,0 +1,188 @@
"""清理龙先生重复/空证书俱乐部。
规则:
- 候选club_id=lsx / 名称含「龙先生」 / wx_appid=龙先生小程序
- 「有 key」pay_key_path / pay_cert_path / private_key_path / platform_cert_dir 任一非空
- 默认 dry-run--apply 才删除「完全没有 key 路径」的俱乐部行及相关配置表
- 若只剩一条且无 key不会删避免误删唯一配置除非 --force-delete-empty-only
用法:
python manage.py cleanup_lsx_empty_club
python manage.py cleanup_lsx_empty_club --apply
python manage.py cleanup_lsx_empty_club --apply --remove-seed-users
"""
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q
from jituan.models import (
Club,
ClubDashouExamConfig,
ClubDashouExamPass,
ClubDashouExamQuestion,
ClubDashouExamQuestionImage,
ClubFadanFenhongLilv,
ClubHuiyuanBundleInclude,
ClubHuiyuanPrice,
ClubKaoheChenghaoConfig,
ClubLilubiao,
ClubPaymentChannel,
ClubShangpinLeixingConfig,
ClubTixianQuota,
ClubWithdrawConfig,
)
from users.business_models import User
from users.models import UserBoss, UserGuanshi, UserZuzhang
LSX_APPID = 'wx7ff90e9d024fcdb8'
SEED_UIDS = ('9910001', '9910002')
# 删除俱乐部时一并清掉的配置表(按 club_id
RELATED_MODELS = (
ClubPaymentChannel,
ClubHuiyuanPrice,
ClubHuiyuanBundleInclude,
ClubLilubiao,
ClubWithdrawConfig,
ClubTixianQuota,
ClubShangpinLeixingConfig,
ClubKaoheChenghaoConfig,
ClubFadanFenhongLilv,
ClubDashouExamConfig,
ClubDashouExamQuestion,
ClubDashouExamQuestionImage,
ClubDashouExamPass,
)
def _has_key_paths(club):
fields = (
getattr(club, 'pay_key_path', '') or '',
getattr(club, 'pay_cert_path', '') or '',
getattr(club, 'private_key_path', '') or '',
getattr(club, 'platform_cert_dir', '') or '',
)
return any(str(x).strip() for x in fields)
def _candidates():
return Club.query.filter(
Q(club_id__iexact='lsx')
| Q(club_id__icontains='lsx')
| Q(name__icontains='龙先生')
| Q(wx_appid=LSX_APPID)
).order_by('club_id')
def _purge_club_related(club_id, stdout, style):
for model in RELATED_MODELS:
try:
n = model.query.filter(club_id=club_id).count()
if n:
model.query.filter(club_id=club_id).delete()
stdout.write(f' - 已删 {model.__name__} x{n}')
except Exception as exc:
stdout.write(style.WARNING(f' - 跳过 {model.__name__}: {exc}'))
class Command(BaseCommand):
help = '列出并删除龙先生相关「无证书 key 路径」的重复俱乐部(默认 dry-run'
def add_arguments(self, parser):
parser.add_argument('--apply', action='store_true', help='真正写入删除')
parser.add_argument(
'--force-delete-empty-only',
action='store_true',
help='即使没有「有 key」的对照俱乐部也允许删除无 key 的候选',
)
parser.add_argument(
'--remove-seed-users',
action='store_true',
help='同时删除 seed_lsx_invite_data 种子用户 9910001/9910002',
)
parser.add_argument(
'--club-id',
type=str,
default='',
help='只处理指定 club_id精确匹配',
)
def handle(self, *args, **options):
apply = bool(options.get('apply'))
force = bool(options.get('force_delete_empty_only'))
only_id = (options.get('club_id') or '').strip()
qs = _candidates()
if only_id:
qs = Club.query.filter(club_id=only_id)
clubs = list(qs)
if not clubs:
self.stdout.write(self.style.WARNING('未找到龙先生/lsx 相关俱乐部'))
return
with_keys = []
without_keys = []
self.stdout.write(self.style.SUCCESS('=' * 60))
self.stdout.write('龙先生相关俱乐部扫描结果:')
for c in clubs:
keyed = _has_key_paths(c)
(with_keys if keyed else without_keys).append(c)
self.stdout.write('-' * 40)
self.stdout.write(f'club_id={c.club_id} name={c.name} status={c.status}')
self.stdout.write(f' wx_appid={c.wx_appid or "(空)"}')
self.stdout.write(f' pay_key_path={c.pay_key_path or "(空)"}')
self.stdout.write(f' pay_cert_path={c.pay_cert_path or "(空)"}')
self.stdout.write(f' private_key_path={c.private_key_path or "(空)"}')
self.stdout.write(f' platform_cert_dir={c.platform_cert_dir or "(空)"}')
self.stdout.write(f' >>> {"有 key 路径(保留)" if keyed else "无 key 路径(可删)"}')
self.stdout.write(self.style.SUCCESS('=' * 60))
self.stdout.write(f'有 key: {len(with_keys)} 条;无 key: {len(without_keys)}')
if not without_keys:
self.stdout.write(self.style.SUCCESS('没有「无 key 路径」的俱乐部,无需删除。'))
elif not with_keys and not force:
self.stdout.write(self.style.ERROR(
'全部候选都没有 key 路径。为安全起见不自动删。'
'若确认要删,加 --force-delete-empty-only --apply'
))
else:
for c in without_keys:
self.stdout.write(self.style.WARNING(f'将删除无 key 俱乐部: {c.club_id} ({c.name})'))
if not apply:
self.stdout.write(' (dry-run未真正删除加 --apply 执行)')
continue
with transaction.atomic():
_purge_club_related(c.club_id, self.stdout, self.style)
Club.query.filter(club_id=c.club_id).delete()
self.stdout.write(self.style.SUCCESS(f' 已删除俱乐部 {c.club_id}'))
if options.get('remove_seed_users'):
self.stdout.write('-' * 40)
self.stdout.write('处理种子用户 9910001 / 9910002 ...')
for uid in SEED_UIDS:
user = User.query.filter(UserUID=uid).first()
if not user:
self.stdout.write(f' {uid}: 不存在')
continue
self.stdout.write(
f' {uid}: ClubID={user.ClubID} Phone={user.Phone} OpenID={user.OpenID}'
)
if not apply:
self.stdout.write(' (dry-run)')
continue
with transaction.atomic():
UserZuzhang.query.filter(user=user).delete()
UserGuanshi.query.filter(user=user).delete()
UserBoss.query.filter(user=user).delete()
user.delete()
self.stdout.write(self.style.SUCCESS(f' 已删除用户 {uid}'))
if not apply:
self.stdout.write(self.style.WARNING(
'\n当前为预览。确认后执行:\n'
' python manage.py cleanup_lsx_empty_club --apply\n'
'如需顺带删种子账号:\n'
' python manage.py cleanup_lsx_empty_club --apply --remove-seed-users'
))