# -*- 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 ''