feat: 增加 import_ufo_banners,仅向 ufo 灌入轮播与我的背景

写入 Lunbo:accept_order / accept_order_banner2 / boss_center;不动其它俱乐部。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQueandCursor committed 2026-07-22 23:41:13 +08:00
1 parent bc29118a6e
commit 4826a3ca23
2 files changed
+189 -2

No files matched your search

+12 -2
View File
@@ -20,7 +20,8 @@
"user_center": 0,
"zones": 12
},
"images_total": 132
"images_total": 132,
"boss_center": 1
},
"banners": [
{
@@ -4023,7 +4024,8 @@
"thumb_local": "images/misc/home_cat_1.jpg"
}
],
"raffle": null
"raffle": null,
"bg_img": "https://ufo.qn.17v.tech/uploads/20251001/Fls0cQjtfsV7IUXl4cwqO6BFvPyh.jpg"
},
"platform_rule_images": [
{
@@ -4032,5 +4034,13 @@
"url": "https://ufo.qn.17v.tech/uploads/20250929/FvOszrybjo3aAGdOBfyZ2VNIofi5.jpg",
"local": "images/rules/platform_look_game_1_1.jpg"
}
],
"boss_center": [
{
"url": "https://ufo.qn.17v.tech/uploads/20251001/Fls0cQjtfsV7IUXl4cwqO6BFvPyh.jpg",
"kind": "boss_center",
"page_key_target": "boss_center",
"source": "getUserCenterInfo.bg_img"
}
]
}
@@ -0,0 +1,177 @@
# -*- 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 ''