From aeaa54583a84045fd989e2f23a70dba106f01b70 Mon Sep 17 00:00:00 2001 From: XingQue Date: Wed, 8 Jul 2026 18:00:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=89=AB=E7=A0=81=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E7=94=A8=E6=88=B7=E6=9F=A5=E6=89=BE=E4=B8=8E?= =?UTF-8?q?=E4=BF=B1=E4=B9=90=E9=83=A8=E6=A0=A1=E9=AA=8C=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dwdlyhdl=E4=B8=8E=E8=87=AA=E5=8A=A8=E6=B3=A8=E5=86=8C?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- jituan/services/club_user.py | 76 +++++++++++++++++++++++++++++++++ jituan/services/invite_guard.py | 15 ++++--- jituan/services/wechat_login.py | 50 ++++++---------------- users/views/auth.py | 53 +++++++++++++++++------ 4 files changed, 137 insertions(+), 57 deletions(-) diff --git a/jituan/services/club_user.py b/jituan/services/club_user.py index ea91401..9668b2b 100644 --- a/jituan/services/club_user.py +++ b/jituan/services/club_user.py @@ -1,8 +1,84 @@ """用户所属俱乐部解析。""" +from django.db import IntegrityError + from jituan.constants import CLUB_ID_DEFAULT from jituan.models import UserWxOpenid +def resolve_invite_party_club(user, request_club_id=None): + """ + 邀请链校验用:只返回已落库的俱乐部归属。 + 未归属任何俱乐部时返回 None(勿默认 xq,避免星之界扫码误拦)。 + """ + if not user: + return None + + explicit = (getattr(user, 'ClubID', None) or '').strip() + if explicit: + return explicit + + uid = getattr(user, 'UserUID', None) or getattr(user, 'yonghuid', None) + if not uid: + return None + + req = (request_club_id or '').strip() + if req: + hit = UserWxOpenid.query.filter(yonghuid=uid, club_id=req).first() + if hit: + return req + + binding = UserWxOpenid.query.filter(yonghuid=uid).order_by('id').first() + if binding: + return binding.club_id + + return None + + +def find_user_by_wx_openid(club_id, openid): + """按俱乐部 openid 绑定或 legacy OpenID/UserName 定位用户。""" + from users.business_models import User + + if club_id and openid: + binding = UserWxOpenid.query.filter(club_id=club_id, openid=openid).first() + if binding: + user = User.query.filter(UserUID=binding.yonghuid).first() + if not user: + user = User.objects.filter(UserUID=binding.yonghuid).first() + if user: + return user + + user = User.query.filter(OpenID=openid).first() + if not user: + user = User.objects.filter(OpenID=openid).first() + if not user: + user = User.query.filter(UserName=openid).first() + if not user: + user = User.objects.filter(UserName=openid).first() + return user + + +def ensure_wx_openid_binding(club_id, openid, user, unionid=''): + if not club_id or not openid or not user: + return None + uid = getattr(user, 'UserUID', None) + if not uid: + return None + + binding = UserWxOpenid.query.filter(club_id=club_id, openid=openid).first() + if binding: + return binding + + try: + return UserWxOpenid.query.create( + club_id=club_id, + openid=openid, + yonghuid=uid, + unionid=unionid or getattr(user, 'UnionID', None) or '', + ) + except IntegrityError: + return UserWxOpenid.query.filter(club_id=club_id, openid=openid).first() + + def get_user_club_id(user): """ 解析用户归属 club(注册/邀请链隔离用)。 diff --git a/jituan/services/invite_guard.py b/jituan/services/invite_guard.py index ec43e9a..ffb2e8e 100644 --- a/jituan/services/invite_guard.py +++ b/jituan/services/invite_guard.py @@ -1,24 +1,27 @@ """邀请码 / 邀请链俱乐部隔离。""" +from jituan.constants import CLUB_ID_DEFAULT from jituan.services.club_resolver import resolve_club_id_for_wechat_request -from jituan.services.club_user import get_user_club_id +from jituan.services.club_user import resolve_invite_party_club def assert_invite_same_club(request, inviter_user, invitee_user=None): """ 校验邀请人与被邀请人属于同一俱乐部,且与请求 club 一致。 - 现网全部 xq 时行为与改造前一致。 + 未归属俱乐部的被邀请人允许在本小程序完成首次注册。 返回 (ok, message) """ club_id = resolve_club_id_for_wechat_request(request) - inviter_club = get_user_club_id(inviter_user) + inviter_club = resolve_invite_party_club(inviter_user, club_id) + if inviter_club is None and club_id == CLUB_ID_DEFAULT: + inviter_club = CLUB_ID_DEFAULT if inviter_club != club_id: return False, '邀请码不属于当前小程序俱乐部' if invitee_user is not None: - invitee_club = get_user_club_id(invitee_user) - if invitee_club != club_id: + invitee_club = resolve_invite_party_club(invitee_user, club_id) + if invitee_club is not None and invitee_club != club_id: return False, '您的账号归属与当前小程序不一致,请使用对应俱乐部小程序' - if invitee_club != inviter_club: + if invitee_club is not None and invitee_club != inviter_club: return False, '邀请码与您的俱乐部不匹配' return True, '' diff --git a/jituan/services/wechat_login.py b/jituan/services/wechat_login.py index 0731f2d..81d4713 100644 --- a/jituan/services/wechat_login.py +++ b/jituan/services/wechat_login.py @@ -4,13 +4,18 @@ import random import time import uuid -from django.db import IntegrityError, transaction +from django.db import transaction from django.utils import timezone from rest_framework_simplejwt.tokens import RefreshToken from jituan.constants import CLUB_ID_DEFAULT from jituan.models import UserWxOpenid from jituan.services.club_resolver import jscode2session, resolve_club_by_appid +from jituan.services.club_user import ( + ensure_user_club_id, + ensure_wx_openid_binding, + find_user_by_wx_openid, +) from users.business_models import User from users.models import UserBoss, UserDashou, UserGuanshi, UserShangjia, UserZuzhang, UserShenheguan from gvsdsdk.models import Role @@ -105,37 +110,6 @@ def _collect_role_status(user): } -def _find_user_by_openid_legacy(openid): - """兼容旧登录/扫码注册:User.OpenID 或 UserName 存的是 openid。""" - user = User.query.filter(OpenID=openid).first() - if not user: - user = User.objects.filter(OpenID=openid).first() - if not user: - user = User.query.filter(UserName=openid).first() - if not user: - user = User.objects.filter(UserName=openid).first() - return user - - -def _ensure_wx_binding(resolved_club_id, openid, user, unionid=''): - binding = UserWxOpenid.query.filter( - club_id=resolved_club_id, openid=openid, - ).first() - if binding: - return binding - try: - return UserWxOpenid.query.create( - club_id=resolved_club_id, - openid=openid, - yonghuid=user.UserUID, - unionid=unionid or user.UnionID or '', - ) - except IntegrityError: - return UserWxOpenid.query.filter( - club_id=resolved_club_id, openid=openid, - ).first() - - def login_or_register_by_club(code, club_id=None, app_id=None, client_ip=''): """ 俱乐部微信登录核心逻辑。 @@ -164,9 +138,9 @@ def login_or_register_by_club(code, club_id=None, app_id=None, client_ip=''): user = User.objects.filter(UserUID=binding.yonghuid).first() if not user: - user = _find_user_by_openid_legacy(openid) + user = find_user_by_wx_openid(resolved_club_id, openid) if user: - _ensure_wx_binding(resolved_club_id, openid, user, unionid) + ensure_wx_openid_binding(resolved_club_id, openid, user, unionid) if not user: user_uuid = uuid.uuid4().bytes @@ -179,17 +153,18 @@ def login_or_register_by_club(code, club_id=None, app_id=None, client_ip=''): UnionID=unionid or None, ) try: + from django.db import IntegrityError user.save() except IntegrityError: - user = _find_user_by_openid_legacy(openid) + user = find_user_by_wx_openid(resolved_club_id, openid) if not user: raise - _ensure_wx_binding(resolved_club_id, openid, user, unionid) + ensure_wx_openid_binding(resolved_club_id, openid, user, unionid) else: created = True _ensure_boss_profile(user) _ensure_consumer_role(user) - _ensure_wx_binding(resolved_club_id, openid, user, unionid) + ensure_wx_openid_binding(resolved_club_id, openid, user, unionid) if unionid and unionid != (user.UnionID or ''): user.UnionID = unionid @@ -198,7 +173,6 @@ def login_or_register_by_club(code, club_id=None, app_id=None, client_ip=''): user.UserLastLoginDate = timezone.now() user.save() - from jituan.services.club_user import ensure_user_club_id ensure_user_club_id(user, resolved_club_id) roles = _collect_role_status(user) diff --git a/users/views/auth.py b/users/views/auth.py index 6bffdd6..838d2b6 100644 --- a/users/views/auth.py +++ b/users/views/auth.py @@ -550,6 +550,9 @@ class DashouZhuceView(APIView): }, status=status.HTTP_404_NOT_FOUND) from jituan.services.invite_guard import assert_invite_same_club + from jituan.services.club_resolver import resolve_club_id_for_wechat_request + from jituan.services.club_user import ensure_user_club_id + ok, msg = assert_invite_same_club(request, guanshi_profile.user, current_user) if not ok: return Response({ @@ -558,6 +561,8 @@ class DashouZhuceView(APIView): 'data': None }, status=status.HTTP_403_FORBIDDEN) + club_id = resolve_club_id_for_wechat_request(request) + # 4. 验证管事状态 # 5. 核心:在数据库事务中创建用户的所有身份 @@ -617,6 +622,8 @@ class DashouZhuceView(APIView): 'data': None }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + ensure_user_club_id(current_user, club_id) + # 6. 注册成功,返回信息 (新注册用户没有会员记录,clumber返回空列表) fanhui_data = self.zhuangbeiFanhuiShuju(dashou_profile, current_user, is_new=True) return Response({ @@ -851,31 +858,51 @@ class WechatLoginAndDashouRegisterView(APIView): }, status=status.HTTP_400_BAD_REQUEST) openid = wechat_data['openid'] + unionid = wechat_data.get('unionid', '') # 3. 获取客户端真实IP kehuduan_ip = self.huoquKehuduanIP(request) + from jituan.constants import CLUB_ID_DEFAULT + from jituan.services.club_user import ( + ensure_user_club_id, + ensure_wx_openid_binding, + find_user_by_wx_openid, + ) + # 4. 开始数据库事务(确保登录和注册的原子性) with transaction.atomic(): - # 4.1 创建或获取用户(登录逻辑) - user_main, created = User.objects.select_for_update().get_or_create( - OpenID=openid, - defaults={ - 'UserUID': self.shengchengYonghuID(), - 'UserName': openid, - } - ) + # 4.1 定位或创建用户(兼容俱乐部登录建立的 user_wx_openid) + user_main = find_user_by_wx_openid(club_id, openid) + created = False + + if not user_main: + try: + user_main = User( + UserUUID=uuid.uuid4().bytes, + UserUID=self.shengchengYonghuID(), + UserName=openid, + OpenID=openid if club_id == CLUB_ID_DEFAULT else None, + UnionID=unionid or None, + ) + user_main.save() + created = True + UserBoss.query.create(user=user_main, nickname='微信用户') + except IntegrityError: + user_main = find_user_by_wx_openid(club_id, openid) + if not user_main: + raise + + ensure_wx_openid_binding(club_id, openid, user_main, unionid) # 更新用户IP和最后登录时间 cunchu_ip = kehuduan_ip user_main.IP = cunchu_ip user_main.UserLastLoginDate = timezone.now() + if unionid and unionid != (user_main.UnionID or ''): + user_main.UnionID = unionid user_main.save() - if created: - # 新用户:创建老板扩展表 - UserBoss.query.create(user=user_main, nickname='微信用户') - # 4.2 验证邀请码对应的管事(注册逻辑) try: # 使用select_related获取管事及其关联的主表用户信息 @@ -886,7 +913,6 @@ class WechatLoginAndDashouRegisterView(APIView): return self.fanhuiZhihouDengluXinxi(user_main, yaoqingma_wuxiao=True) from jituan.services.invite_guard import assert_invite_same_club - from jituan.services.club_user import ensure_user_club_id from jituan.services.club_resolver import resolve_club_id_for_wechat_request ok, invite_msg = assert_invite_same_club( @@ -1204,6 +1230,7 @@ class WechatLoginAndDashouRegisterView(APIView): 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, + 'club_id': getattr(user_main, 'ClubID', None) or '', 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''),