fix: 会员/图标换图生成新路径并删除旧OSS,避免CDN缓存导致不更新

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
XingQue
2026-07-26 02:52:52 +08:00
parent 7cb41bf4d7
commit 1234cff035
3 changed files with 56 additions and 14 deletions

View File

@@ -276,9 +276,13 @@ MEMBER_CARD_FOLDER = 'beijing/huiyuan'
def upload_member_card_image(request, huiyuan_id, field, file_obj): def upload_member_card_image(request, huiyuan_id, field, file_obj):
"""上传会员充值页卡片图,写入 club_huiyuan_price.card_image / card_bg。""" """上传会员充值页卡片图,写入 club_huiyuan_price.card_image / card_bg。
每次上传生成新相对路径并删除旧图,避免「同名覆盖但 CDN/浏览器仍显示旧图」。
"""
import os import os
from utils.oss_utils import validate_image, upload_to_oss import uuid
from utils.oss_utils import validate_image, upload_to_oss, delete_from_oss
if club_write_blocked(request): if club_write_blocked(request):
return None, CLUB_WRITE_SCOPE_MSG return None, CLUB_WRITE_SCOPE_MSG
@@ -289,18 +293,36 @@ def upload_member_card_image(request, huiyuan_id, field, file_obj):
return None, msg return None, msg
club_id = club_id_for_write(request) club_id = club_id_for_write(request)
ext = os.path.splitext(getattr(file_obj, 'name', '') or '')[1].lower()
if ext not in ('.png', '.jpg', '.jpeg', '.webp', '.gif'):
ext = '.png'
rel_path = f'{MEMBER_CARD_FOLDER}/{club_id}/{huiyuan_id}_{field}{ext}'
if not upload_to_oss(file_obj, rel_path):
return None, '上传 OSS 失败'
row = ClubHuiyuanPrice.query.filter(club_id=club_id, huiyuan_id=huiyuan_id).first() row = ClubHuiyuanPrice.query.filter(club_id=club_id, huiyuan_id=huiyuan_id).first()
if not row: if not row:
return None, '请先在当前俱乐部上架该会员' return None, '请先在当前俱乐部上架该会员'
ext = os.path.splitext(getattr(file_obj, 'name', '') or '')[1].lower()
if ext not in ('.png', '.jpg', '.jpeg', '.webp', '.gif'):
ext = '.png'
# 带时间戳+uuid保证 URL 变化,前端/CDN 不会命中旧缓存
stamp = uuid.uuid4().hex[:12]
rel_path = f'{MEMBER_CARD_FOLDER}/{club_id}/{huiyuan_id}_{field}_{stamp}{ext}'
if hasattr(file_obj, 'seek'):
try:
file_obj.seek(0)
except Exception:
pass
if not upload_to_oss(file_obj, rel_path):
return None, '上传 OSS 失败'
old_path = (getattr(row, field, None) or '').strip()
setattr(row, field, rel_path) setattr(row, field, rel_path)
row.save(update_fields=[field, 'UpdateTime']) row.save(update_fields=[field, 'UpdateTime'])
if old_path and old_path != rel_path:
try:
delete_from_oss(old_path)
except Exception as e:
logger.warning('删除旧会员卡片图失败 club=%s hy=%s field=%s path=%s err=%s',
club_id, huiyuan_id, field, old_path, e)
return {field: rel_path, 'huiyuan_id': huiyuan_id}, None return {field: rel_path, 'huiyuan_id': huiyuan_id}, None

View File

@@ -129,12 +129,20 @@ def list_tupianpeizhi_by_type(club_id, image_type):
def upsert_tupianpeizhi_single(club_id, image_type, image_url): def upsert_tupianpeizhi_single(club_id, image_type, image_url):
"""ImageType 1/2 每俱乐部仅一条。""" """ImageType 1/2 每俱乐部仅一条;换图时删除旧 OSS 对象"""
from config.models import Tupianpeizhi from config.models import Tupianpeizhi
from utils.oss_utils import delete_from_oss
row = Tupianpeizhi.query.filter(club_id=club_id, ImageType=image_type).first() row = Tupianpeizhi.query.filter(club_id=club_id, ImageType=image_type).first()
if row: if row:
old = (row.ImageURL or '').strip()
row.ImageURL = image_url row.ImageURL = image_url
row.save(update_fields=['ImageURL', 'UpdateTime']) row.save(update_fields=['ImageURL', 'UpdateTime'])
if old and old != image_url:
try:
delete_from_oss(old)
except Exception:
pass
else: else:
Tupianpeizhi.query.create( Tupianpeizhi.query.create(
club_id=club_id, ImageType=image_type, ImageURL=image_url, AccessCount=0, club_id=club_id, ImageType=image_type, ImageURL=image_url, AccessCount=0,

View File

@@ -138,15 +138,22 @@ class ClubMiniappIconManageView(APIView):
ext = os.path.splitext(file_obj.name)[1].lower() or '.png' ext = os.path.splitext(file_obj.name)[1].lower() or '.png'
if ext not in ('.png', '.jpg', '.jpeg', '.webp'): if ext not in ('.png', '.jpg', '.jpeg', '.webp'):
ext = '.png' ext = '.png'
import uuid
stamp = uuid.uuid4().hex[:12]
# 路径必须含 club_id + 唯一后缀:否则同名覆盖后 CDN 仍显示旧图,且会串俱乐部
if icon_key in POSTER_ICON_KEYS: if icon_key in POSTER_ICON_KEYS:
oss_path = f'{POSTER_FOLDER}/{club_id}_{icon_key}{ext}' oss_path = f'{POSTER_FOLDER}/{club_id}_{icon_key}_{stamp}{ext}'
else: else:
oss_path = f'{ICON_FOLDER}/{icon_key}{ext}' oss_path = f'{ICON_FOLDER}/{club_id}/{icon_key}_{stamp}{ext}'
row = ClubMiniappIcon.query.filter(club_id=club_id, icon_key=icon_key).first() row = ClubMiniappIcon.query.filter(club_id=club_id, icon_key=icon_key).first()
if row and row.image_path and row.image_path != oss_path: old_path = (row.image_path or '').strip() if row else ''
delete_from_oss(row.image_path)
if hasattr(file_obj, 'seek'):
try:
file_obj.seek(0)
except Exception:
pass
if not upload_to_oss(file_obj, oss_path): if not upload_to_oss(file_obj, oss_path):
return Response({'code': 500, 'msg': '图片上传失败'}) return Response({'code': 500, 'msg': '图片上传失败'})
@@ -165,6 +172,11 @@ class ClubMiniappIconManageView(APIView):
is_active=True, is_active=True,
is_uploaded=True, is_uploaded=True,
) )
if old_path and old_path != oss_path:
try:
delete_from_oss(old_path)
except Exception:
pass
return Response({'code': 0, 'msg': '上传成功', 'data': {'image_path': oss_path}}) return Response({'code': 0, 'msg': '上传成功', 'data': {'image_path': oss_path}})