diff --git a/jituan/services/api_compat.py b/jituan/services/api_compat.py new file mode 100644 index 0000000..f7cd05e --- /dev/null +++ b/jituan/services/api_compat.py @@ -0,0 +1,18 @@ +"""老小程序兼容:统一 JSON 错误格式(勿改前端时靠后端适配)。""" +from rest_framework import status +from rest_framework.response import Response + + +def legacy_miniapp_error(code, message, http_status=status.HTTP_200_OK): + """ + 业务错误返回 HTTP 200 + body.code。 + 老端 request.js 会把 HTTP 403 当成「请先登录」,导致只显示「注册失败」。 + 同时写 message 与 msg,兼容 dashouzhuce / wdlyhdl 两种字段名。 + """ + text = message or '操作失败' + return Response({ + 'code': code, + 'message': text, + 'msg': text, + 'data': None, + }, status=http_status) diff --git a/jituan/services/club_resolver.py b/jituan/services/club_resolver.py index 0d9a796..6404232 100644 --- a/jituan/services/club_resolver.py +++ b/jituan/services/club_resolver.py @@ -42,11 +42,38 @@ def get_club_wx_credentials(club_id): return '', '', club +def _invite_code_from_request(request): + data = getattr(request, 'data', None) or {} + if not isinstance(data, dict): + return '' + return (data.get('inviteCode') or data.get('invite_code') or '').strip() + + +def resolve_club_from_invite_code(invite_code): + """老端无 Header/Referer 时,从管事邀请码反查俱乐部。""" + invite_code = (invite_code or '').strip() + if not invite_code: + return None + try: + from users.models import UserGuanshi + from jituan.services.club_user import resolve_invite_party_club + + guanshi = UserGuanshi.query.select_related('user').get(yaoqingma=invite_code) + user = guanshi.user + club = resolve_invite_party_club(user, None) + if club: + return club + explicit = (getattr(user, 'ClubID', None) or '').strip() + return explicit or None + except Exception: + return None + + def resolve_club_id_for_wechat_request(request): """ 小程序请求解析 club_id(登录/扫码注册)。 - 优先级:X-Club-Id > body.appId > Referer 内 wx appid > 默认 xq。 - 老端 fighter.js 的 wdlyhdl 未带头时,靠 Referer 识别星之界等俱乐部。 + 优先级:X-Club-Id > body.appId > Referer > body.inviteCode 反查管事 > JWT 用户归属 > 默认 xq。 + 老端 fighter.js 的 wdlyhdl 未带头时,靠 Referer 或邀请码识别星之界。 """ from jituan.services.club_context import resolve_club_id_from_request @@ -72,6 +99,18 @@ def resolve_club_id_for_wechat_request(request): if club: return club.club_id + invite_club = resolve_club_from_invite_code(_invite_code_from_request(request)) + if invite_club: + return invite_club + + # 已登录走 dashouzhuce 时往往无 Referer,用 JWT 用户归属俱乐部 + user = getattr(request, 'user', None) + if user is not None and getattr(user, 'is_authenticated', False): + from jituan.services.club_user import get_user_club_id + user_club = get_user_club_id(user) + if user_club: + return user_club + return resolve_club_id_from_request(request) diff --git a/jituan/services/invite_guard.py b/jituan/services/invite_guard.py index 8d249b1..a1f6b2f 100644 --- a/jituan/services/invite_guard.py +++ b/jituan/services/invite_guard.py @@ -1,12 +1,20 @@ """邀请码 / 邀请链俱乐部隔离。""" import logging +from jituan.models import UserWxOpenid from jituan.services.club_resolver import resolve_club_id_for_wechat_request from jituan.services.club_user import resolve_invite_party_club logger = logging.getLogger(__name__) +def _invitee_has_club_binding(user, club_id): + uid = getattr(user, 'UserUID', None) + if not uid or not club_id: + return False + return UserWxOpenid.query.filter(yonghuid=uid, club_id=club_id).exists() + + def assert_invite_same_club(request, inviter_user, invitee_user=None): """ 校验邀请人与被邀请人属于同一俱乐部,且与请求 club 一致。 @@ -28,11 +36,13 @@ def assert_invite_same_club(request, inviter_user, invitee_user=None): if invitee_user is not None: invitee_club = resolve_invite_party_club(invitee_user, club_id) if invitee_club is not None and invitee_club != club_id: - logger.warning( - 'invite_guard 被邀请人俱乐部不符 invitee=%s invitee_club=%s req_club=%s', - getattr(invitee_user, 'UserUID', '?'), invitee_club, club_id, - ) - return False, '您的账号归属与当前小程序不一致,请使用对应俱乐部小程序' + # 老用户 ClubID 可能是 xq,但扫的是本俱乐部管事码:允许注册并回写 ClubID + if not _invitee_has_club_binding(invitee_user, club_id) and inviter_club != club_id: + logger.warning( + 'invite_guard 被邀请人俱乐部不符 invitee=%s invitee_club=%s req_club=%s', + getattr(invitee_user, 'UserUID', '?'), invitee_club, club_id, + ) + return False, '您的账号归属与当前小程序不一致,请使用对应俱乐部小程序' if invitee_club is not None and invitee_club != inviter_club: return False, '邀请码与您的俱乐部不匹配' diff --git a/users/views/auth.py b/users/views/auth.py index 954a716..112c1ec 100644 --- a/users/views/auth.py +++ b/users/views/auth.py @@ -509,22 +509,16 @@ class DashouZhuceView(APIView): permission_classes = [IsAuthenticated] def post(self, request): + from jituan.services.api_compat import legacy_miniapp_error + # 1. 获取并验证邀请码参数 yaoqingma = request.data.get('inviteCode') if not yaoqingma: - return Response({ - 'code': 400, - 'message': '邀请码不能为空', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) + return legacy_miniapp_error(400, '邀请码不能为空') yaoqingma = yaoqingma.strip() if len(yaoqingma) > 100: - return Response({ - 'code': 400, - 'message': '邀请码长度超过限制', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) + return legacy_miniapp_error(400, '邀请码长度超过限制') current_user = request.user # 注意:这里request.user是User实例 @@ -543,11 +537,7 @@ class DashouZhuceView(APIView): # 使用select_related一次性获取管事及其关联的主表用户信息,性能最高 guanshi_profile = UserGuanshi.query.select_related('user').get(yaoqingma=yaoqingma) except UserGuanshi.DoesNotExist: - return Response({ - 'code': 404, - 'message': '邀请码无效或不存在', - 'data': None - }, status=status.HTTP_404_NOT_FOUND) + return legacy_miniapp_error(404, '邀请码无效或不存在') from jituan.services.invite_guard import assert_invite_same_club from jituan.services.club_resolver import resolve_club_id_for_wechat_request @@ -559,12 +549,7 @@ class DashouZhuceView(APIView): 'dashouzhuce 俱乐部校验失败 uid=%s club=%s msg=%s', current_user.UserUID, resolve_club_id_for_wechat_request(request), msg, ) - return Response({ - 'code': 403, - 'message': msg, - 'msg': msg, - 'data': None - }, status=status.HTTP_403_FORBIDDEN) + return legacy_miniapp_error(403, msg) club_id = resolve_club_id_for_wechat_request(request) @@ -620,12 +605,8 @@ class DashouZhuceView(APIView): except Exception as e: # 事务会自动回滚 - # 这里可以记录更详细的日志 - return Response({ - 'code': 500, - 'message': f'注册过程中发生系统错误: {str(e)}', - 'data': None - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + logger.exception('dashouzhuce 注册异常 uid=%s', current_user.UserUID) + return legacy_miniapp_error(500, f'注册过程中发生系统错误: {str(e)}') ensure_user_club_id(current_user, club_id) @@ -634,6 +615,7 @@ class DashouZhuceView(APIView): return Response({ 'code': 200, 'message': '注册成功!已开通打手、管事身份。', + 'msg': '注册成功!已开通打手、管事身份。', 'data': fanhui_data }) @@ -824,30 +806,20 @@ class WechatLoginAndDashouRegisterView(APIView): 处理微信登录并注册打手请求 """ try: + from jituan.services.api_compat import legacy_miniapp_error + # 1. 获取并验证参数 code = request.data.get('code', '').strip() yaoqingma = request.data.get('inviteCode', '').strip() if not code: - return Response({ - 'code': 1, - 'msg': '微信授权码不能为空', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) + return legacy_miniapp_error(1, '微信授权码不能为空') if not yaoqingma: - return Response({ - 'code': 2, - 'msg': '邀请码不能为空', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) + return legacy_miniapp_error(2, '邀请码不能为空') if len(yaoqingma) > 100: - return Response({ - 'code': 3, - 'msg': '邀请码长度超过限制', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) + return legacy_miniapp_error(3, '邀请码长度超过限制') # 2. 获取微信openid(按俱乐部小程序 appid,勿用全局星阙配置) from jituan.services.club_resolver import ( @@ -859,11 +831,7 @@ class WechatLoginAndDashouRegisterView(APIView): if not wechat_data or 'openid' not in wechat_data: error_msg = wechat_data.get('errmsg', '微信授权失败') logger.warning('wdlyhdl 微信登录失败 club=%s err=%s', club_id, error_msg) - return Response({ - 'code': 4, - 'msg': f'微信登录失败: {error_msg}', - 'data': None - }, status=status.HTTP_400_BAD_REQUEST) + return legacy_miniapp_error(4, f'微信登录失败: {error_msg}') openid = wechat_data['openid'] unionid = wechat_data.get('unionid', '') @@ -916,9 +884,8 @@ class WechatLoginAndDashouRegisterView(APIView): # 使用select_related获取管事及其关联的主表用户信息 guanshi_profile = UserGuanshi.query.select_related('user').get(yaoqingma=yaoqingma) except UserGuanshi.DoesNotExist: - # 邀请码无效,但用户已登录,可以返回登录成功但注册失败 - # 这里我们继续执行,但只返回登录信息 - return self.fanhuiZhihouDengluXinxi(user_main, yaoqingma_wuxiao=True) + ensure_user_club_id(user_main, club_id) + return legacy_miniapp_error(404, '邀请码无效或不存在') from jituan.services.invite_guard import assert_invite_same_club from jituan.services.club_resolver import resolve_club_id_for_wechat_request @@ -931,12 +898,7 @@ class WechatLoginAndDashouRegisterView(APIView): 'wdlyhdl 俱乐部校验失败 uid=%s club=%s msg=%s invite=%s', user_main.UserUID, club_id, invite_msg, yaoqingma[:8], ) - return Response({ - 'code': 403, - 'msg': invite_msg, - 'message': invite_msg, - 'data': None - }, status=status.HTTP_403_FORBIDDEN) + return legacy_miniapp_error(403, invite_msg) ensure_user_club_id(user_main, resolve_club_id_for_wechat_request(request)) @@ -1003,12 +965,9 @@ class WechatLoginAndDashouRegisterView(APIView): exc_info=True ) - # 返回友好错误信息 - return Response({ - 'code': 99, - 'msg': '系统繁忙,请稍后重试', - 'data': None - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + # 返回友好错误信息(HTTP 200 以便老端读出 msg) + from jituan.services.api_compat import legacy_miniapp_error + return legacy_miniapp_error(99, '系统繁忙,请稍后重试') def get_wechat_openid(self, code): """