diff --git a/users/views.py b/users/views.py index 3edb4f2..e57c13b 100644 --- a/users/views.py +++ b/users/views.py @@ -610,7 +610,7 @@ class UserInfoUpdateView(APIView): # 手机号 —— 极简可靠版 code = request.data.get('shoujihao_code') if code: - phone = self._get_phone_safe(code) + phone = self._get_phone_safe(code, request) if phone is None: return Response({'code': 6, 'msg': '手机号获取失败,请重新授权', 'data': None}, status=400) user.Phone = phone @@ -630,35 +630,47 @@ class UserInfoUpdateView(APIView): return Response({'code': 99, 'msg': str(e), 'data': None}, status=500) # ---------- 安全手机号(内存去重) ---------- - def _get_phone_safe(self, code): - # 如果已经处理过这个code,直接返回结果 - if code in self._phone_cache: - cached = self._phone_cache[code] + def _get_phone_safe(self, code, request): + cache_key = f'{code}:{self._resolve_wx_club_id(request, request.user)}' + if cache_key in self._phone_cache: + cached = self._phone_cache[cache_key] return cached if cached != '__FAIL__' else None - # 标记处理中(简单防重入) - self._phone_cache[code] = '__PROCESSING__' + self._phone_cache[cache_key] = '__PROCESSING__' try: - phone = self._call_wechat(code) - self._phone_cache[code] = phone + phone = self._call_wechat(code, request) + self._phone_cache[cache_key] = phone return phone except Exception as e: logger.error(f"微信调用失败: {str(e)}") - self._phone_cache[code] = '__FAIL__' + self._phone_cache[cache_key] = '__FAIL__' return None - # ---------- 微信接口调用(每次全新token) ---------- - def _call_wechat(self, code): - token = self._get_new_token() + def _resolve_wx_club_id(self, request, user): + """手机号 code 与小程序 appid 绑定,须用对应俱乐部的 wx 配置换 token。""" + from jituan.constants import CLUB_ID_DEFAULT + from jituan.services.club_context import resolve_club_id_from_request + from jituan.services.club_user import get_user_club_id + + club_id = resolve_club_id_from_request(request) + if club_id == CLUB_ID_DEFAULT: + user_club = get_user_club_id(user) + if user_club: + return user_club + return club_id + + # ---------- 微信接口调用(按俱乐部 appid 换 token) ---------- + def _call_wechat(self, code, request): + club_id = self._resolve_wx_club_id(request, request.user) + token = self._get_new_token(club_id, force_refresh=True) url = f'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={token}' resp = requests.post(url, json={'code': code}, timeout=10) data = resp.json() errcode = data.get('errcode') if errcode == 0: return data['phone_info']['purePhoneNumber'] - # 即使刚拿的token也可能意外失效,重试一次 if errcode == 40001: - token = self._get_new_token() + token = self._get_new_token(club_id, force_refresh=True) url = f'https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={token}' resp = requests.post(url, json={'code': code}, timeout=10) data = resp.json() @@ -666,22 +678,13 @@ class UserInfoUpdateView(APIView): return data['phone_info']['purePhoneNumber'] raise Exception(f"微信错误(errcode={errcode}): {data.get('errmsg')}") - # ---------- 获取全新 access_token(不缓存,每次调微信) ---------- - def _get_new_token(self): - appid = getattr(settings, 'WEIXIN_APPID', '') - secret = getattr(settings, 'WEIXIN_SECRET', '') - if not appid or not secret: - raise Exception('缺少微信配置') - resp = requests.get('https://api.weixin.qq.com/cgi-bin/token', params={ - 'grant_type': 'client_credential', - 'appid': appid, - 'secret': secret - }) - data = resp.json() - token = data.get('access_token') + def _get_new_token(self, club_id, force_refresh=False): + from utils.weixin_token import get_club_mini_access_token + + token = get_club_mini_access_token(club_id, force_refresh=force_refresh) if token: return token - raise Exception(f"获取access_token失败: {data}") + raise Exception(f'俱乐部 {club_id} 缺少微信配置或获取 access_token 失败') # ---------- 工具 ---------- def _get_or_create_boss(self, user): diff --git a/utils/weixin_token.py b/utils/weixin_token.py index f15f12d..5b4d6e3 100644 --- a/utils/weixin_token.py +++ b/utils/weixin_token.py @@ -9,8 +9,45 @@ logger = logging.getLogger(__name__) WX_TOKEN_CACHE_KEY = 'wx_mini_access_token' -def invalidate_weixin_mini_access_token(): - cache.delete(WX_TOKEN_CACHE_KEY) +def invalidate_weixin_mini_access_token(club_id=None): + if club_id: + cache.delete(f'{WX_TOKEN_CACHE_KEY}:{club_id}') + else: + cache.delete(WX_TOKEN_CACHE_KEY) + + +def get_club_mini_access_token(club_id=None, *, force_refresh=False): + """ + 按俱乐部读取 wx_appid/wx_secret 获取 access_token(多小程序上线必需)。 + xq 缺配置时回退 settings;其它俱乐部读 club 表。 + """ + from jituan.constants import CLUB_ID_DEFAULT + from jituan.services.club_resolver import get_club_wx_credentials + + club_id = club_id or CLUB_ID_DEFAULT + cache_key = f'{WX_TOKEN_CACHE_KEY}:{club_id}' + + if not force_refresh: + token = cache.get(cache_key) + if token: + return token + + if force_refresh: + cache.delete(cache_key) + + appid, secret, _ = get_club_wx_credentials(club_id) + if not appid or not secret: + logger.error('俱乐部 %s 微信配置缺失: wx_appid/wx_secret 未设置', club_id) + return None + + token, expires_in = _fetch_stable_token(appid, secret, force_refresh) + if not token: + token, expires_in = _fetch_legacy_token(appid, secret) + + if token: + ttl = max(60, int(expires_in or 7200) - 200) + cache.set(cache_key, token, ttl) + return token def get_weixin_mini_access_token(*, force_refresh=False):