From 435422e71628afd9a0fd5baa754988d059405417 Mon Sep 17 00:00:00 2001 From: XingQue Date: Tue, 21 Jul 2026 02:48:16 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=82=B9=E5=8D=95=E7=AB=AF=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E5=AE=98=E6=8C=89UID=E7=BB=91=E5=AE=9A=EF=BC=8C?= =?UTF-8?q?=E4=BA=8C=E7=BB=B4=E7=A0=81=E8=BF=9B=E9=A6=96=E9=A1=B5=E4=B8=94?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E9=82=80=E8=AF=B7=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 扫码参数改为 yaoqingren=UserUID 落地点单首页;海报默认不再复用管事背景;不影响打手端与支付抢单分红。 Co-authored-by: Cursor --- config/views/qrcode_poster.py | 23 ++++++---- jituan/services/miniapp_assets.py | 10 +++-- users/services/c_invite.py | 73 +++++++++++-------------------- users/views/c_invite.py | 46 ++++++++++++------- 4 files changed, 76 insertions(+), 76 deletions(-) diff --git a/config/views/qrcode_poster.py b/config/views/qrcode_poster.py index d134cf3..3d32cdd 100644 --- a/config/views/qrcode_poster.py +++ b/config/views/qrcode_poster.py @@ -353,7 +353,7 @@ class GuanshiQRCodeView(APIView): class CYaoqingQRCodeView(APIView): """ - 点单端用户邀请小程序码。 + 点单端老板推荐官小程序码(落地点单首页,按 UserUID 绑定,无邀请码)。 POST /peizhi/cyaoqingewm """ permission_classes = [permissions.IsAuthenticated] @@ -363,11 +363,18 @@ class CYaoqingQRCodeView(APIView): try: from jituan.services.club_context import resolve_effective_club_id from utils.weixin_token import get_club_mini_access_token, is_weixin_token_invalid - from users.services.c_invite import ensure_c_invite_code + from users.services.c_invite import get_or_create_c_yaoqing from users.models import YonghuCYaoqing + if not getattr(current_user, 'UserUID', None): + return Response( + {'code': 500, 'message': '用户身份异常', 'data': None}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + club_id = resolve_effective_club_id(request, current_user) - profile, invite_code, _ = ensure_c_invite_code(current_user) + profile = get_or_create_c_yaoqing(current_user) + inviter_uid = str(current_user.UserUID) access_token = get_club_mini_access_token(club_id) if not access_token: @@ -376,8 +383,8 @@ class CYaoqingQRCodeView(APIView): status=status.HTTP_500_INTERNAL_SERVER_ERROR ) - encoded_invite = urllib.parse.quote(invite_code, safe='') - page_path = f'pages/index/index?inviteCode={encoded_invite}' + # 必须进点单首页,禁止进打手/管事页 + page_path = f'pages/index/index?yaoqingren={urllib.parse.quote(inviter_uid, safe="")}' post_data = { 'path': page_path, 'width': 430, @@ -457,7 +464,7 @@ class CYaoqingQRCodeView(APIView): try: delete_from_oss(old_url) except Exception as e: - logger.warning('删除旧 C 端二维码失败: %s', e) + logger.warning('删除旧点单端推荐二维码失败: %s', e) with transaction.atomic(): locked = YonghuCYaoqing.objects.select_for_update().get(pk=profile.pk) @@ -467,10 +474,10 @@ class CYaoqingQRCodeView(APIView): return Response({ 'code': 0, 'message': 'success', - 'data': {'url': relative_path, 'yaoqingma': invite_code}, + 'data': {'url': relative_path, 'uid': inviter_uid}, }) except Exception as e: - logger.error('生成点单端邀请二维码失败: %s', e, exc_info=True) + logger.error('生成点单端推荐二维码失败: %s', e, exc_info=True) return Response( {'code': 500, 'message': '服务器内部错误', 'data': None}, status=status.HTTP_500_INTERNAL_SERVER_ERROR diff --git a/jituan/services/miniapp_assets.py b/jituan/services/miniapp_assets.py index 8448374..a66a370 100644 --- a/jituan/services/miniapp_assets.py +++ b/jituan/services/miniapp_assets.py @@ -76,7 +76,7 @@ MINIAPP_ICON_META = [ ('icon_refresh', '通用刷新'), ('guanshi_poster_bg', '管事推广海报背景'), ('zuzhang_poster_bg', '组长推广海报背景'), - ('cuser_poster_bg', '点单端推广海报背景'), + ('cuser_poster_bg', '点单端推荐官海报背景'), ] MINIAPP_ICON_KEYS = [k for k, _ in MINIAPP_ICON_META] @@ -137,7 +137,8 @@ MINIAPP_ICON_DEFAULTS['fighter_mine_switch'] = f'{ICON_FOLDER}/fighter_mine_swit MINIAPP_ICON_DEFAULTS['mine_pindao'] = 'beijing/tubiao/grzx_pindao.jpg' MINIAPP_ICON_DEFAULTS['guanshi_poster_bg'] = 'beijing/haibaobeijing.jpg' MINIAPP_ICON_DEFAULTS['zuzhang_poster_bg'] = 'beijing/zuzhangbeijing.jpg' -MINIAPP_ICON_DEFAULTS['cuser_poster_bg'] = 'beijing/haibaobeijing.jpg' +# 点单端推荐官海报:独立路径,禁止复用管事/打手背景 +MINIAPP_ICON_DEFAULTS['cuser_poster_bg'] = 'beijing/haibao/cuser_poster_bg.jpg' POSTER_BG_FIELDS = { 'guanshi_bg': 'guanshi_poster_bg', @@ -147,7 +148,7 @@ POSTER_BG_FIELDS = { POSTER_BG_DEFAULTS = { 'guanshi_bg': 'beijing/haibaobeijing.jpg', 'zuzhang_bg': 'beijing/zuzhangbeijing.jpg', - 'cuser_bg': 'beijing/haibaobeijing.jpg', + 'cuser_bg': 'beijing/haibao/cuser_poster_bg.jpg', } POSTER_FOLDER = 'beijing/haibao' POSTER_ICON_KEYS = frozenset(POSTER_BG_FIELDS.values()) @@ -218,6 +219,9 @@ def get_poster_payload(club_id: str) -> dict: row = rows.get(icon_key) if row and row.is_uploaded and (row.image_path or '').strip(): result[field] = row.image_path.strip() + elif field == 'cuser_bg': + # 点单端未上传时不回落到管事背景,避免错图 + result[field] = (row.image_path.strip() if row and (row.image_path or '').strip() else '') or default else: result[field] = default return result diff --git a/users/services/c_invite.py b/users/services/c_invite.py index 795446a..f804e1d 100644 --- a/users/services/c_invite.py +++ b/users/services/c_invite.py @@ -1,11 +1,10 @@ -"""点单端 C 用户邀请:邀请码、换绑、店铺跟随。""" +"""点单端老板推荐官:按推荐官 UserUID 绑定,店铺跟随。无邀请码。""" from django.db import transaction from django.db.models import F from shop.models import Dianpu, YonghuDianpuBangding from users.models import YonghuCYaoqing from users.business_models import User -from utils.invitationcode_utils import CreateInvitationCode, VerifyInvitationCode import logging @@ -13,33 +12,11 @@ logger = logging.getLogger(__name__) def get_or_create_c_yaoqing(user): - """获取或创建用户的点单端邀请扩展记录。""" + """获取或创建用户的点单端推荐关系记录。""" profile, _ = YonghuCYaoqing.objects.get_or_create(user=user) return profile -def ensure_c_invite_code(user): - """确保用户有邀请码,返回 (profile, yaoqingma, is_new)。""" - profile = get_or_create_c_yaoqing(user) - existing = (profile.yaoqingma or '').strip() - if existing: - return profile, existing, False - - new_code = CreateInvitationCode(str(user.UserUID)) - if not VerifyInvitationCode(new_code): - raise ValueError('邀请码生成失败') - - # 极低概率冲突时再生成一次 - if YonghuCYaoqing.query.filter(yaoqingma=new_code).exclude(pk=profile.pk).exists(): - new_code = CreateInvitationCode(str(user.UserUID) + 'R') - if not VerifyInvitationCode(new_code): - raise ValueError('邀请码生成失败') - - profile.yaoqingma = new_code - profile.save(update_fields=['yaoqingma', 'UpdateTime']) - return profile, new_code, True - - def _unbind_shop(user_locked): """解除用户店铺绑定并扣减旧店绑定人数。""" binding = ( @@ -60,7 +37,7 @@ def _unbind_shop(user_locked): def _bind_shop(user_locked, dianpu_locked): """将用户绑定到指定店铺(已存在则换绑)。返回 (changed, msg)。""" if dianpu_locked.zhuangtai != 1: - return False, '邀请人店铺已被封禁' + return False, '推荐官店铺已被封禁' try: existing = YonghuDianpuBangding.objects.select_for_update().get(yonghu=user_locked) @@ -76,14 +53,14 @@ def _bind_shop(user_locked, dianpu_locked): dianpu_locked.bangding_yonghushu = F('bangding_yonghushu') + 1 dianpu_locked.save(update_fields=['bangding_yonghushu']) - return True, '店铺已跟随邀请人换绑' + return True, '店铺已跟随推荐官换绑' def sync_shop_from_inviter(invitee_user, inviter_user): """ - 按邀请人店铺同步被邀请人店铺: - - 邀请人有正常店铺 → 被邀请人绑到同一店 - - 邀请人无店铺 → 被邀请人解绑店铺(回落平台公共商品逻辑) + 按推荐官店铺同步被推荐人店铺: + - 推荐官有正常店铺 → 被推荐人绑到同一店 + - 推荐官无店铺 → 被推荐人解绑店铺(回落平台公共商品逻辑) 须在 transaction.atomic 内调用。 """ invitee_locked = User.objects.select_for_update().get(UserUUID=invitee_user.UserUUID) @@ -106,31 +83,32 @@ def sync_shop_from_inviter(invitee_user, inviter_user): 'shop_synced': False, 'dianpu_id': None, 'changed': bool(old_id), - 'msg': '邀请人未绑定店铺,已解除店铺绑定' if old_id else '邀请人未绑定店铺', + 'msg': '推荐官未绑定店铺,已解除店铺绑定' if old_id else '推荐官未绑定店铺', } -def bind_c_inviter_by_code(invitee_user, invite_code): +def bind_c_inviter_by_uid(invitee_user, inviter_uid): """ - 用邀请码绑定/换绑邀请人,并同步店铺。 - 返回 dict: code/msg/data + 用推荐官 UserUID 绑定/换绑,并同步店铺。 + 扫码参数:yaoqingren / inviterUid(不是邀请码)。 """ - code = (invite_code or '').strip() - if not code: - return {'code': 400, 'msg': '邀请码不能为空', 'data': None} - if len(code) > 100: - return {'code': 400, 'msg': '邀请码格式错误', 'data': None} + uid = (inviter_uid or '').strip() + if not uid: + return {'code': 400, 'msg': '推荐官ID不能为空', 'data': None} + if len(uid) > 20: + return {'code': 400, 'msg': '推荐官ID格式错误', 'data': None} try: - inviter_profile = YonghuCYaoqing.query.select_related('user').get(yaoqingma=code) - except YonghuCYaoqing.DoesNotExist: - return {'code': 404, 'msg': '邀请码无效', 'data': None} + inviter = User.query.get(UserUID=uid) + except User.DoesNotExist: + return {'code': 404, 'msg': '推荐官不存在', 'data': None} - inviter = inviter_profile.user if inviter.UserUID == invitee_user.UserUID: - return {'code': 400, 'msg': '不能绑定自己为邀请人', 'data': None} + return {'code': 400, 'msg': '不能绑定自己为推荐官', 'data': None} with transaction.atomic(): + # 推荐官侧也要有档案,便于计数「我推荐的人」 + inviter_profile = get_or_create_c_yaoqing(inviter) invitee_profile = get_or_create_c_yaoqing(invitee_user) invitee_profile = YonghuCYaoqing.objects.select_for_update().get(pk=invitee_profile.pk) inviter_profile = YonghuCYaoqing.objects.select_for_update().get(pk=inviter_profile.pk) @@ -142,7 +120,7 @@ def bind_c_inviter_by_code(invitee_user, invite_code): shop_info = sync_shop_from_inviter(invitee_user, inviter) return { 'code': 200, - 'msg': '已绑定该邀请人', + 'msg': '已绑定该推荐官', 'data': { 'yaoqingren': new_inviter_uid, 'rebinding': False, @@ -150,7 +128,6 @@ def bind_c_inviter_by_code(invitee_user, invite_code): }, } - # 换绑:旧邀请人计数 -1,新邀请人 +1 if old_inviter_uid: old_p = ( YonghuCYaoqing.objects.select_for_update() @@ -170,12 +147,12 @@ def bind_c_inviter_by_code(invitee_user, invite_code): shop_info = sync_shop_from_inviter(invitee_user, inviter) logger.info( - 'C端邀请绑定: invitee=%s inviter=%s old=%s shop=%s', + '点单端推荐官绑定: invitee=%s inviter=%s old=%s shop=%s', invitee_user.UserUID, new_inviter_uid, old_inviter_uid, shop_info, ) return { 'code': 200, - 'msg': '绑定成功' if not old_inviter_uid else '已换绑邀请人', + 'msg': '绑定成功' if not old_inviter_uid else '已换绑推荐官', 'data': { 'yaoqingren': new_inviter_uid, 'rebinding': bool(old_inviter_uid), diff --git a/users/views/c_invite.py b/users/views/c_invite.py index 923c500..9d5ce7e 100644 --- a/users/views/c_invite.py +++ b/users/views/c_invite.py @@ -1,4 +1,4 @@ -"""点单端 C 用户邀请相关接口。""" +"""点单端老板推荐官相关接口(无邀请码,按 UserUID)。""" import logging from django.core.paginator import Paginator, EmptyPage @@ -11,8 +11,7 @@ from rest_framework.views import APIView from users.models import YonghuCYaoqing, UserBoss from users.business_models import User from users.services.c_invite import ( - ensure_c_invite_code, - bind_c_inviter_by_code, + bind_c_inviter_by_uid, get_or_create_c_yaoqing, ) @@ -20,7 +19,11 @@ logger = logging.getLogger(__name__) class CYaoqingmaView(APIView): - """获取或生成点单端邀请码。POST /yonghu/cyaoqingma""" + """ + 点单端推荐官摘要(兼容旧路由名 cyaoqingma)。 + 不再生成邀请码,只返回本人 UID / 推荐人数 / 当前推荐官。 + POST /yonghu/cyaoqingma + """ permission_classes = [IsAuthenticated] def post(self, request): @@ -28,29 +31,39 @@ class CYaoqingmaView(APIView): if not getattr(user, 'UserUID', None): return Response({'code': 500, 'msg': '用户身份异常', 'data': None}) try: - profile, code, is_new = ensure_c_invite_code(user) + profile = get_or_create_c_yaoqing(user) return Response({ 'code': 200, - 'msg': '成功生成新邀请码' if is_new else '成功获取现有邀请码', + 'msg': 'ok', 'data': { - 'yaoqingma': code, - 'is_new': is_new, + 'uid': user.UserUID, 'yaogingshuliang': profile.yaogingshuliang or 0, 'yaoqingren': profile.yaoqingren or '', + # 兼容前端旧字段:以前误用邀请码,现改为本人 UID + 'yaoqingma': user.UserUID, }, }) except Exception as e: - logger.exception('生成点单端邀请码失败: %s', e) - return Response({'code': 500, 'msg': '邀请码生成失败', 'data': None}) + logger.exception('获取推荐官摘要失败: %s', e) + return Response({'code': 500, 'msg': '获取失败', 'data': None}) class CYaoqingBangdingView(APIView): - """绑定/换绑邀请人(并同步店铺)。POST /yonghu/cyaoqingbd""" + """ + 绑定/换绑推荐官(并同步店铺)。 + POST /yonghu/cyaoqingbd + 参数:yaoqingren / inviterUid / inviteCode(后两者兼容扫码旧参,均按 UserUID 解析) + """ permission_classes = [IsAuthenticated] def post(self, request): - invite_code = request.data.get('inviteCode') or request.data.get('yaoqingma') or '' - result = bind_c_inviter_by_code(request.user, invite_code) + inviter_uid = ( + request.data.get('yaoqingren') + or request.data.get('inviterUid') + or request.data.get('inviteCode') + or '' + ) + result = bind_c_inviter_by_uid(request.user, inviter_uid) return Response({ 'code': result['code'], 'msg': result['msg'], @@ -59,7 +72,7 @@ class CYaoqingBangdingView(APIView): class CYaoqingListView(APIView): - """我邀请的人列表。POST /yonghu/cwyqr""" + """我推荐的人列表。POST /yonghu/cwyqr""" permission_classes = [IsAuthenticated] def post(self, request): @@ -102,7 +115,6 @@ class CYaoqingListView(APIView): has_more = False filtered_total = paginator.count - # 批量取昵称 user_uuids = [p.user_id for p in page_obj] nick_map = {} if user_uuids: @@ -134,7 +146,7 @@ class CYaoqingListView(APIView): class CYaoqingWodeView(APIView): - """当前用户邀请关系摘要。POST /yonghu/cyqwd""" + """当前用户推荐关系摘要。POST /yonghu/cyqwd""" permission_classes = [IsAuthenticated] def post(self, request): @@ -160,7 +172,7 @@ class CYaoqingWodeView(APIView): 'code': 200, 'msg': 'ok', 'data': { - 'yaoqingma': profile.yaoqingma or '', + 'uid': request.user.UserUID, 'yaogingshuliang': profile.yaogingshuliang or 0, 'yaoqingren': profile.yaoqingren or '', 'inviter': inviter_info,