写入 Lunbo:accept_order / accept_order_banner2 / boss_center;不动其它俱乐部。 Co-authored-by: Cursor <cursoragent@cursor.com>
178 lines
6.3 KiB
Python
178 lines
6.3 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
仅导入 UFO 展示图到 Lunbo(不碰商品)。只写 club_id=ufo。
|
||
|
||
覆盖:
|
||
- accept_order 首页主轮播
|
||
- accept_order_banner2 首页第二横幅
|
||
- boss_center 「我的」顶区背景(原站 bg_img)
|
||
|
||
默认 dry-run;--apply 才写库。只删/写 ufo 对应 page_key,不影响 xq/xzj。
|
||
|
||
cd /opt/1panel/apps/openresty/nginx/www/sites/43.142.166.152.443/django
|
||
git pull origin main
|
||
./venv/bin/python manage.py import_ufo_banners
|
||
./venv/bin/python manage.py import_ufo_banners --apply
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import mimetypes
|
||
import os
|
||
import re
|
||
import ssl
|
||
import uuid
|
||
from datetime import datetime
|
||
from io import BytesIO
|
||
from pathlib import Path
|
||
from urllib.request import Request, urlopen
|
||
|
||
from django.core.management.base import BaseCommand, CommandError
|
||
from django.db import transaction
|
||
|
||
TARGET_CLUB = 'ufo'
|
||
DEFAULT_CATALOG = Path(__file__).resolve().parents[2] / 'data' / 'ufo_catalog.json'
|
||
|
||
# 原站「我的」顶区背景(getUserCenterInfo.bg_img);catalog 里没有时用这个兜底
|
||
BOSS_CENTER_FALLBACK = (
|
||
'https://ufo.qn.17v.tech/uploads/20251001/Fls0cQjtfsV7IUXl4cwqO6BFvPyh.jpg'
|
||
)
|
||
|
||
|
||
class Command(BaseCommand):
|
||
help = '导入 UFO 轮播 + 我的背景到 Lunbo(仅 club=ufo)'
|
||
|
||
def add_arguments(self, parser):
|
||
parser.add_argument('--apply', action='store_true')
|
||
parser.add_argument('--catalog', type=str, default='')
|
||
parser.add_argument('--skip-oss', action='store_true')
|
||
parser.add_argument('--club-id', type=str, default=TARGET_CLUB)
|
||
|
||
def handle(self, *args, **options):
|
||
club_id = (options.get('club_id') or '').strip()
|
||
if club_id != TARGET_CLUB:
|
||
raise CommandError(f'只允许 club_id={TARGET_CLUB}')
|
||
|
||
catalog_path = Path(options['catalog']).expanduser() if options['catalog'] else DEFAULT_CATALOG
|
||
if not catalog_path.is_file():
|
||
raise CommandError(f'找不到 {catalog_path}')
|
||
|
||
data = json.loads(catalog_path.read_text(encoding='utf-8'))
|
||
banners = list(data.get('banners') or [])
|
||
banners2 = list(data.get('banners2') or [])
|
||
boss = list(data.get('boss_center') or [])
|
||
if not boss:
|
||
# catalog 内嵌或 misc
|
||
misc = data.get('misc') or {}
|
||
bg = misc.get('bg_img') or BOSS_CENTER_FALLBACK
|
||
boss = [{'url': bg, 'kind': 'boss_center'}]
|
||
|
||
apply = bool(options['apply'])
|
||
skip_oss = bool(options['skip_oss'])
|
||
|
||
self.stdout.write(self.style.NOTICE(
|
||
f'club={TARGET_CLUB} accept_order={len(banners)} '
|
||
f'banner2={len(banners2)} boss_center={len(boss)} '
|
||
f'mode={"APPLY" if apply else "DRY-RUN"}'
|
||
))
|
||
for b in banners:
|
||
self.stdout.write(f' [主轮播] {b.get("url")}')
|
||
for b in banners2:
|
||
self.stdout.write(f' [第二横幅] {b.get("url")}')
|
||
for b in boss:
|
||
self.stdout.write(f' [我的背景] {b.get("url")}')
|
||
|
||
if not apply:
|
||
self.stdout.write(self.style.WARNING(
|
||
'dry-run。确认后:\n ./venv/bin/python manage.py import_ufo_banners --apply'
|
||
))
|
||
return
|
||
|
||
from config.models import Lunbo
|
||
from jituan.models import Club
|
||
from jituan.services.display_config import (
|
||
LUNBO_PAGE_ACCEPT_BANNER2,
|
||
LUNBO_PAGE_ACCEPT_ORDER,
|
||
LUNBO_PAGE_BOSS_CENTER,
|
||
lunbo_image_type_for_page,
|
||
)
|
||
|
||
if not Club.query.filter(club_id=TARGET_CLUB).exists():
|
||
raise CommandError('俱乐部 ufo 不存在')
|
||
|
||
stats = {'ok': 0, 'fail': 0}
|
||
|
||
def to_oss(url: str) -> str:
|
||
if not url:
|
||
return ''
|
||
if skip_oss:
|
||
return url
|
||
if not str(url).startswith('http'):
|
||
return url
|
||
path = self._upload_remote(url)
|
||
if not path:
|
||
stats['fail'] += 1
|
||
raise CommandError(f'OSS 上传失败: {url}')
|
||
stats['ok'] += 1
|
||
return path
|
||
|
||
def replace_page(page_key, items):
|
||
image_type = lunbo_image_type_for_page(page_key)
|
||
# 只删本俱乐部本 page_key
|
||
deleted, _ = Lunbo.query.filter(
|
||
club_id=TARGET_CLUB,
|
||
page_key=page_key,
|
||
ImageType=image_type,
|
||
).delete()
|
||
self.stdout.write(f' clear {page_key} deleted={deleted}')
|
||
n = 0
|
||
for b in items:
|
||
url = to_oss(b.get('url') or b.get('path') or '')
|
||
if not url:
|
||
continue
|
||
Lunbo.query.create(
|
||
club_id=TARGET_CLUB,
|
||
page_key=page_key,
|
||
ImageURL=url,
|
||
ImageType=image_type,
|
||
)
|
||
n += 1
|
||
return n
|
||
|
||
with transaction.atomic():
|
||
n1 = replace_page(LUNBO_PAGE_ACCEPT_ORDER, banners)
|
||
n2 = replace_page(LUNBO_PAGE_ACCEPT_BANNER2, banners2)
|
||
n3 = replace_page(LUNBO_PAGE_BOSS_CENTER, boss)
|
||
|
||
self.stdout.write(self.style.SUCCESS(
|
||
f'完成仅 ufo:主轮播={n1} 第二横幅={n2} 我的背景={n3} '
|
||
f'OSS成功={stats["ok"]} 失败={stats["fail"]}'
|
||
))
|
||
|
||
@staticmethod
|
||
def _upload_remote(url: str) -> str:
|
||
from utils.oss_utils import upload_to_oss
|
||
|
||
try:
|
||
ctx = ssl.create_default_context()
|
||
req = Request(url, headers={'User-Agent': 'UFO-Import/1.0'})
|
||
with urlopen(req, timeout=60, context=ctx) as resp:
|
||
raw = resp.read()
|
||
ctype = resp.headers.get('Content-Type') or ''
|
||
except Exception:
|
||
return ''
|
||
|
||
ext = mimetypes.guess_extension((ctype or '').split(';')[0].strip()) or ''
|
||
if not ext or ext == '.jpe':
|
||
m = re.search(r'\.(jpg|jpeg|png|gif|webp|bmp)(?:\?|$)', url, re.I)
|
||
ext = ('.' + m.group(1).lower()) if m else '.jpg'
|
||
if ext == '.jpeg':
|
||
ext = '.jpg'
|
||
|
||
ts = datetime.now().strftime('%Y%m%d%H%M%S')
|
||
rel = f"a_long/lunbo/{ts}_{uuid.uuid4().hex[:8]}{ext}"
|
||
bio = BytesIO(raw)
|
||
bio.name = os.path.basename(rel)
|
||
ok = upload_to_oss(bio, rel)
|
||
return rel if ok else ''
|