From 1234cff035bc9de55a69cc260be10a5e12af1d13 Mon Sep 17 00:00:00 2001 From: XingQue Date: Sun, 26 Jul 2026 02:52:52 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=9A=E5=91=98/=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E6=8D=A2=E5=9B=BE=E7=94=9F=E6=88=90=E6=96=B0=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=B9=B6=E5=88=A0=E9=99=A4=E6=97=A7OSS=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8DCDN=E7=BC=93=E5=AD=98=E5=AF=BC=E8=87=B4=E4=B8=8D?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- jituan/services/club_member_admin.py | 40 +++++++++++++++++++++------- jituan/services/display_config.py | 10 ++++++- jituan/views_miniapp_assets.py | 20 +++++++++++--- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/jituan/services/club_member_admin.py b/jituan/services/club_member_admin.py index 3c11310..9372cf6 100644 --- a/jituan/services/club_member_admin.py +++ b/jituan/services/club_member_admin.py @@ -276,9 +276,13 @@ MEMBER_CARD_FOLDER = 'beijing/huiyuan' 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 - 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): return None, CLUB_WRITE_SCOPE_MSG @@ -289,18 +293,36 @@ def upload_member_card_image(request, huiyuan_id, field, file_obj): return None, msg 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() if not row: 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) 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 diff --git a/jituan/services/display_config.py b/jituan/services/display_config.py index 711e995..db58aa3 100644 --- a/jituan/services/display_config.py +++ b/jituan/services/display_config.py @@ -129,12 +129,20 @@ def list_tupianpeizhi_by_type(club_id, image_type): def upsert_tupianpeizhi_single(club_id, image_type, image_url): - """ImageType 1/2 每俱乐部仅一条。""" + """ImageType 1/2 每俱乐部仅一条;换图时删除旧 OSS 对象。""" 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() if row: + old = (row.ImageURL or '').strip() row.ImageURL = image_url row.save(update_fields=['ImageURL', 'UpdateTime']) + if old and old != image_url: + try: + delete_from_oss(old) + except Exception: + pass else: Tupianpeizhi.query.create( club_id=club_id, ImageType=image_type, ImageURL=image_url, AccessCount=0, diff --git a/jituan/views_miniapp_assets.py b/jituan/views_miniapp_assets.py index c14b71b..d187a58 100644 --- a/jituan/views_miniapp_assets.py +++ b/jituan/views_miniapp_assets.py @@ -138,15 +138,22 @@ class ClubMiniappIconManageView(APIView): ext = os.path.splitext(file_obj.name)[1].lower() or '.png' if ext not in ('.png', '.jpg', '.jpeg', '.webp'): ext = '.png' + import uuid + stamp = uuid.uuid4().hex[:12] + # 路径必须含 club_id + 唯一后缀:否则同名覆盖后 CDN 仍显示旧图,且会串俱乐部 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: - 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() - if row and row.image_path and row.image_path != oss_path: - delete_from_oss(row.image_path) + old_path = (row.image_path or '').strip() if row else '' + if hasattr(file_obj, 'seek'): + try: + file_obj.seek(0) + except Exception: + pass if not upload_to_oss(file_obj, oss_path): return Response({'code': 500, 'msg': '图片上传失败'}) @@ -165,6 +172,11 @@ class ClubMiniappIconManageView(APIView): is_active=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}})