diff --git a/config/migrations/0013_tupianpeizhi_drop_type_unique.py b/config/migrations/0013_tupianpeizhi_drop_type_unique.py new file mode 100644 index 0000000..91e6d0c --- /dev/null +++ b/config/migrations/0013_tupianpeizhi_drop_type_unique.py @@ -0,0 +1,26 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + """ + 打手规则图(ImageType=1)、关注快手(3) 需支持多图; + 原先 unique(club_id, ImageType) 会挡掉第二条。默认头像(2)仍由业务层 upsert 保单条。 + """ + + dependencies = [ + ('config', '0012_miniapp_script'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='tupianpeizhi', + unique_together=set(), + ), + migrations.AddIndex( + model_name='tupianpeizhi', + index=models.Index( + fields=['club_id', 'ImageType'], + name='tupian_club_type_idx', + ), + ), + ] diff --git a/config/models.py b/config/models.py index 5179fa1..b5792b6 100644 --- a/config/models.py +++ b/config/models.py @@ -60,7 +60,10 @@ class Tupianpeizhi(QModel): db_table = 'tupianpeizhi' verbose_name = '配置图片' verbose_name_plural = '配置图片' - # ImageType 1/2 每俱乐部唯一;3(关注快手) 允许多张 + # ImageType 2(默认头像) 业务层保单条;1(打手规则)/3(关注快手) 允许多张 + indexes = [ + models.Index(fields=['club_id', 'ImageType'], name='tupian_club_type_idx'), + ] class Szjilu(QModel): diff --git a/config/views/miniapp_config.py b/config/views/miniapp_config.py index 69aa335..6e39538 100644 --- a/config/views/miniapp_config.py +++ b/config/views/miniapp_config.py @@ -66,7 +66,7 @@ class GetDynamicConfigView(APIView): def post(self, request): try: - from jituan.services.display_config import get_tupianpeizhi_url + from jituan.services.display_config import get_tupianpeizhi_url, get_tupianpeizhi_urls from jituan.services.miniapp_assets import build_miniapp_assets_payload @@ -77,10 +77,12 @@ class GetDynamicConfigView(APIView): cos_region = getattr(settings, 'COS_REGION', 'ap-shanghai') goeasy_host = 'hangzhou.goeasy.io' goeasy_appkey = getattr(settings, 'GOEASY_APPKEY', '') - rule_img = get_tupianpeizhi_url(request, 1) + rule_imgs = get_tupianpeizhi_urls(request, 1) avatar_img = get_tupianpeizhi_url(request, 2) morentouxiang = avatar_img or 'beijing/morentouxiang.jpg' - dashouguize = rule_img or 'a_long/dashouguize.jpg' + # 兼容旧字段:首图;无配置时不返回已 404 的 a_long 默认路径 + dashouguize = rule_imgs[0] if rule_imgs else '' + dashouguize_list = rule_imgs from jituan.services.club_context import resolve_club_id_from_request from jituan.services.wecom_kefu import miniapp_kefu_payload @@ -103,7 +105,8 @@ class GetDynamicConfigView(APIView): }, "otherConfig": { "morentouxiang": morentouxiang, - "dashouguize": dashouguize + "dashouguize": dashouguize, + "dashouguize_list": dashouguize_list, }, "kefu": kefu_payload, "miniappAssets": build_miniapp_assets_payload(request), diff --git a/jituan/services/display_config.py b/jituan/services/display_config.py index db58aa3..b58a731 100644 --- a/jituan/services/display_config.py +++ b/jituan/services/display_config.py @@ -104,12 +104,23 @@ def list_response_meta(request): def get_tupianpeizhi_url(request, image_type): - """按俱乐部读图片配置(各俱乐部独立,不回退 xq,避免串台)。""" + """按俱乐部读图片配置(各俱乐部独立,不回退 xq,避免串台)。取该类型第一条。""" + urls = get_tupianpeizhi_urls(request, image_type) + return urls[0] if urls else '' + + +def get_tupianpeizhi_urls(request, image_type): + """按俱乐部读某类型全部图片路径(相对 Key 或完整 URL),按 id 升序。""" from config.models import Tupianpeizhi club_id = _display_club_id(request) - row = Tupianpeizhi.query.filter(club_id=club_id, ImageType=image_type).first() - return (row.ImageURL or '').strip() if row else '' + rows = Tupianpeizhi.query.filter(club_id=club_id, ImageType=image_type).order_by('id') + out = [] + for r in rows: + u = (r.ImageURL or '').strip() + if u: + out.append(u) + return out TUPIAN_TYPE_LABELS = { @@ -118,6 +129,9 @@ TUPIAN_TYPE_LABELS = { 3: '关注快手', } +# 允许多张的 ImageType;其余走 upsert 单条 +TUPIAN_MULTI_TYPES = frozenset({1, 3}) + def list_tupianpeizhi_by_type(club_id, image_type): from config.models import Tupianpeizhi @@ -129,7 +143,7 @@ def list_tupianpeizhi_by_type(club_id, image_type): def upsert_tupianpeizhi_single(club_id, image_type, image_url): - """ImageType 1/2 每俱乐部仅一条;换图时删除旧 OSS 对象。""" + """默认头像等单条类型:每俱乐部仅一条;换图时删除旧 OSS 对象。""" from config.models import Tupianpeizhi from utils.oss_utils import delete_from_oss @@ -169,8 +183,11 @@ def copy_display_config(template_club_id, new_club_id): Content=row.Content, ) - for row in Tupianpeizhi.query.filter(club_id=template_club_id): - if Tupianpeizhi.query.filter(club_id=new_club_id, ImageType=row.ImageType).exists(): + existing_tupian_types = { + r.ImageType for r in Tupianpeizhi.query.filter(club_id=new_club_id) + } + for row in Tupianpeizhi.query.filter(club_id=template_club_id).order_by('id'): + if row.ImageType in existing_tupian_types: continue Tupianpeizhi.query.create( club_id=new_club_id, diff --git a/jituan/views_display.py b/jituan/views_display.py index c923ce4..ecd64d9 100644 --- a/jituan/views_display.py +++ b/jituan/views_display.py @@ -19,6 +19,7 @@ from jituan.services.display_config import ( LUNBO_PAGE_OPTIONS, lunbo_image_type_for_page, normalize_page_key, + TUPIAN_MULTI_TYPES, TUPIAN_TYPE_LABELS, upsert_tupianpeizhi_single, ) @@ -183,7 +184,7 @@ class ClubTupianListView(APIView): for t, label in TUPIAN_TYPE_LABELS.items(): data[str(t)] = { 'label': label, - 'multi': t == 3, + 'multi': t in TUPIAN_MULTI_TYPES, 'list': list_tupianpeizhi_by_type(club_id, t), } return Response({ @@ -247,19 +248,19 @@ class ClubTupianManageView(APIView): return Response({'code': 400, 'msg': msg}) ext = os.path.splitext(file_obj.name)[1].lower() or '.jpg' random_str = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6)) - folder = 'guanzhu' if image_type == 3 else 'misc' + folder = 'guanzhu' if image_type == 3 else ('dashou_rule' if image_type == 1 else 'misc') oss_file_path = f"beijing/{folder}/{club_id}_{random_str}{ext}" if not upload_to_oss(file_obj, oss_file_path): return Response({'code': 500, 'msg': '图片上传失败'}) - if image_type in (1, 2): - upsert_tupianpeizhi_single(club_id, image_type, oss_file_path) - else: + if image_type in TUPIAN_MULTI_TYPES: Tupianpeizhi.query.create( club_id=club_id, ImageType=image_type, ImageURL=oss_file_path, AccessCount=0, ) + else: + upsert_tupianpeizhi_single(club_id, image_type, oss_file_path) return Response({'code': 0, 'msg': '上传成功', 'data': {'image_url': oss_file_path}}) return Response({'code': 400, 'msg': 'action 必须为 add 或 delete'})