diff --git a/jituan/services/default_invite.py b/jituan/services/default_invite.py new file mode 100644 index 0000000..43936ac --- /dev/null +++ b/jituan/services/default_invite.py @@ -0,0 +1,47 @@ +"""俱乐部默认打手邀请码(一键登录无扫码时使用)。""" +import logging + +logger = logging.getLogger(__name__) + +CONFIG_KEY = 'default_dashou_invite' + +# 与 club_migrate.CLUB_MIGRATE_DEFAULT_GUANSHI_INVITE 保持同步(无 config_json 时回落) +_FALLBACK_DEFAULT_INVITE = { + 'lxs': '0000008VfJKPu4Spmcjt', + 'xq': '0000008RffVgKHMj7kQC', +} + + +def get_club_default_dashou_invite(club_id: str) -> str: + """ + 优先读 Club.config_json.default_dashou_invite, + 否则回落内置各店默认管事邀请码。 + """ + cid = (club_id or '').strip() + if not cid: + return '' + try: + from jituan.models import Club + club = Club.query.filter(club_id=cid).first() + if club: + cfg = club.config_json or {} + code = (cfg.get(CONFIG_KEY) or '').strip() + if code: + return code + except Exception: + logger.exception('read default_dashou_invite failed club=%s', cid) + try: + from jituan.services.club_migrate import CLUB_MIGRATE_DEFAULT_GUANSHI_INVITE + code = (CLUB_MIGRATE_DEFAULT_GUANSHI_INVITE.get(cid) or '').strip() + if code: + return code + except Exception: + pass + return (_FALLBACK_DEFAULT_INVITE.get(cid) or '').strip() + + +def set_club_default_dashou_invite(club, invite_code: str) -> None: + """写入 club.config_json,调用方负责 save。""" + cfg = dict(getattr(club, 'config_json', None) or {}) + cfg[CONFIG_KEY] = (invite_code or '').strip() + club.config_json = cfg diff --git a/jituan/urls.py b/jituan/urls.py index 9609531..7abcb4f 100644 --- a/jituan/urls.py +++ b/jituan/urls.py @@ -52,6 +52,7 @@ from jituan.views import ( ClubKefuMenuAccessView, ClubPermDebugView, ClubListView, + ClubDefaultInviteView, ClubMeContextView, ClubOrderFinanceView, ClubOrderTypesView, @@ -227,4 +228,5 @@ urlpatterns = [ path('houtai/dashou-exam-question-manage', DashouExamQuestionManageView.as_view(), name='jituan_dashou_exam_q_manage'), path('houtai/dashou-exam-image-manage', DashouExamImageManageView.as_view(), name='jituan_dashou_exam_img'), path('club/list', ClubListView.as_view(), name='jituan_club_list'), + path('club/default-invite', ClubDefaultInviteView.as_view(), name='jituan_club_default_invite'), ] diff --git a/jituan/views.py b/jituan/views.py index a71caf3..fc89b22 100644 --- a/jituan/views.py +++ b/jituan/views.py @@ -285,6 +285,37 @@ class ClubListView(APIView): return Response({'code': 0, 'msg': 'ok', 'data': data}) +class ClubDefaultInviteView(APIView): + """ + GET/POST /jituan/club/default-invite + 小程序登录页拉取当前俱乐部默认打手邀请码(AllowAny)。 + 俱乐部由 X-Club-Id / AppID 解析;码来自 config_json.default_dashou_invite 或内置回落。 + """ + permission_classes = [AllowAny] + throttle_classes = [AnonRateThrottle] + + def _payload(self, request): + from jituan.services.club_resolver import resolve_club_id_for_wechat_request + from jituan.services.default_invite import get_club_default_dashou_invite + + club_id = resolve_club_id_for_wechat_request(request) + invite = get_club_default_dashou_invite(club_id) + return Response({ + 'code': 0, + 'msg': 'ok', + 'data': { + 'club_id': club_id or '', + 'inviteCode': invite, + }, + }) + + def get(self, request): + return self._payload(request) + + def post(self, request): + return self._payload(request) + + class ClubMeContextView(APIView): """GET /jituan/auth/me-context — 已登录客服刷新俱乐部上下文。""" permission_classes = [IsAuthenticated] @@ -600,6 +631,9 @@ class ClubManageView(APIView): } def _serialize(self, club, full=False): + from jituan.services.default_invite import get_club_default_dashou_invite + + cfg_json = club.config_json or {} base = { 'club_id': club.club_id, 'name': club.name, @@ -614,6 +648,7 @@ class ClubManageView(APIView): 'goeasy_appkey': club.goeasy_appkey, 'template_id': club.template_id, 'sort_order': club.sort_order, + 'default_dashou_invite': get_club_default_dashou_invite(club.club_id), } if full: mch_key = self._effective_mch_key(club) @@ -633,6 +668,7 @@ class ClubManageView(APIView): 'encoding_aes_key': club.encoding_aes_key, 'goeasy_secret': club.goeasy_secret, 'template_max_per_minute': club.template_max_per_minute, + 'default_dashou_invite_raw': (cfg_json.get('default_dashou_invite') or '').strip(), }) return base @@ -683,6 +719,11 @@ class ClubManageView(APIView): self._sync_mch_key_to_config_json(club, request.data.get('mch_key')) if 'config_json' not in update_fields: update_fields.append('config_json') + if 'default_dashou_invite' in request.data: + from jituan.services.default_invite import set_club_default_dashou_invite + set_club_default_dashou_invite(club, request.data.get('default_dashou_invite')) + if 'config_json' not in update_fields: + update_fields.append('config_json') if update_fields: club.save(update_fields=update_fields) return Response({ diff --git a/users/views/auth.py b/users/views/auth.py index de35332..d9ae151 100644 --- a/users/views/auth.py +++ b/users/views/auth.py @@ -512,12 +512,15 @@ class DashouZhuceView(APIView): def post(self, request): from jituan.services.api_compat import legacy_miniapp_error - # 1. 获取并验证邀请码参数 - yaoqingma = request.data.get('inviteCode') + # 1. 获取并验证邀请码参数(空则用俱乐部默认码) + yaoqingma = (request.data.get('inviteCode') or '').strip() + if not yaoqingma: + from jituan.services.club_resolver import resolve_club_id_for_wechat_request + from jituan.services.default_invite import get_club_default_dashou_invite + yaoqingma = get_club_default_dashou_invite(resolve_club_id_for_wechat_request(request)) if not yaoqingma: return legacy_miniapp_error(400, '邀请码不能为空') - yaoqingma = yaoqingma.strip() if len(yaoqingma) > 100: return legacy_miniapp_error(400, '邀请码长度超过限制') @@ -813,21 +816,21 @@ class WechatLoginAndDashouRegisterView(APIView): def post(self, request): """ 处理微信登录并注册打手请求 + + 顺序:先按 openid 认身份;已是打手则直接登录(不校验邀请码); + 仅新注册才要求邀请码(请求体 / 俱乐部默认码)。 """ try: from jituan.services.api_compat import legacy_miniapp_error - # 1. 获取并验证参数 + # 1. 获取并验证参数(邀请码对新用户才强制) code = request.data.get('code', '').strip() yaoqingma = request.data.get('inviteCode', '').strip() if not code: return legacy_miniapp_error(1, '微信授权码不能为空') - if not yaoqingma: - return legacy_miniapp_error(2, '邀请码不能为空') - - if len(yaoqingma) > 100: + if yaoqingma and len(yaoqingma) > 100: return legacy_miniapp_error(3, '邀请码长度超过限制') # 2. 获取微信openid(按俱乐部小程序 appid,勿用全局星阙配置) @@ -848,11 +851,11 @@ class WechatLoginAndDashouRegisterView(APIView): # 3. 获取客户端真实IP kehuduan_ip = self.huoquKehuduanIP(request) - from jituan.constants import CLUB_ID_DEFAULT from jituan.services.club_user import ( ensure_user_club_id, resolve_or_create_wx_user, ) + from jituan.services.default_invite import get_club_default_dashou_invite # 4. 开始数据库事务(确保登录和注册的原子性) with transaction.atomic(): @@ -870,16 +873,34 @@ class WechatLoginAndDashouRegisterView(APIView): user_main.UnionID = unionid user_main.save() - # 4.2 验证邀请码对应的管事(注册逻辑) + ensure_user_club_id(user_main, club_id) + + # 4.1 已是打手:直接登录,不校验邀请码 + dashou_exists = UserDashou.query.filter(user=user_main).exists() + if dashou_exists: + dashou_profile = UserDashou.query.get(user=user_main) + if int(getattr(dashou_profile, 'zhanghaozhuangtai', 1) or 0) != 1: + reason = (getattr(dashou_profile, 'fengjin_yuanyin', '') or '').strip() + msg = '账号状态异常,您的账号已被封禁' + if reason: + msg = f'{msg}。原因:{reason}' + else: + msg = f'{msg},请联系客服' + return legacy_miniapp_error(4031, msg) + return self.fanhuiDengluZhuceshuju(user_main, dashou_profile) + + # 4.2 新用户注册:邀请码优先用请求体,否则用俱乐部默认码 + if not yaoqingma: + yaoqingma = get_club_default_dashou_invite(club_id) + if not yaoqingma: + return legacy_miniapp_error(2, '邀请码不能为空') + try: - # 使用select_related获取管事及其关联的主表用户信息 guanshi_profile = UserGuanshi.query.select_related('user').get(yaoqingma=yaoqingma) except UserGuanshi.DoesNotExist: - 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 from jituan.services.club_user import resolve_invite_party_club ok, invite_msg = assert_invite_same_club( @@ -895,27 +916,7 @@ class WechatLoginAndDashouRegisterView(APIView): ) return legacy_miniapp_error(403, invite_msg) - ensure_user_club_id(user_main, resolve_club_id_for_wechat_request(request)) - - # 验证管事状态 - - # 4.3 检查用户是否已是打手 - dashou_exists = UserDashou.query.filter(user=user_main).exists() - if dashou_exists: - # 用户已是打手,直接返回信息 - dashou_profile = UserDashou.query.get(user=user_main) - # 账号封禁:禁止登录发 token,前端进入登录页提示 - if int(getattr(dashou_profile, 'zhanghaozhuangtai', 1) or 0) != 1: - reason = (getattr(dashou_profile, 'fengjin_yuanyin', '') or '').strip() - msg = '账号状态异常,您的账号已被封禁' - if reason: - msg = f'{msg}。原因:{reason}' - else: - msg = f'{msg},请联系客服' - return legacy_miniapp_error(4031, msg) - return self.fanhuiDengluZhuceshuju(user_main, dashou_profile) - - # 4.4 核心:为用户创建所有身份(打手、商家、管事) + # 4.3 创建打手身份 guanshi_yonghuid = guanshi_profile.user.UserUID # 创建打手扩展表 @@ -1143,10 +1144,16 @@ class WechatLoginAndDashouRegisterView(APIView): except UserBoss.DoesNotExist: pass - # 检查各身份状态(已是打手 ≠ 已是商家/管事,必须按表真实查询) + # 检查各身份状态(已是打手 ≠ 已是商家/管事/组长,必须按表真实查询) dashou_status = 1 shangjia_status = 1 if UserShangjia.query.filter(user=user_main).exists() else 0 guanshi_status = 1 if UserGuanshi.query.filter(user=user_main).exists() else 0 + zuzhang_status = 0 + try: + zz = UserZuzhang.query.filter(user=user_main).first() + zuzhang_status = 1 if zz and int(getattr(zz, 'zhuangtai', 0) or 0) == 1 else 0 + except Exception: + zuzhang_status = 0 # 生成token refresh = RefreshToken.for_user(user_main) @@ -1167,6 +1174,7 @@ class WechatLoginAndDashouRegisterView(APIView): 'shangjiastatus': shangjia_status, 'dashoustatus': dashou_status, 'guanshistatus': guanshi_status, + 'zuzhangstatus': zuzhang_status, 'dashouqun': group_info.get('dashouqun', ''), 'dashouqunid': group_info.get('dashouqunid', ''), 'guanshiqun': group_info.get('guanshiqun', ''),