Files
Django/jituan/services/club_resolver.py
XingQue ce86b488d9 fix: 微信登录失败时返回明确错误,附属信息异常不阻断登录
避免 staff/订单统计异常导致整次 wechat-login 500;缺俱乐部配置时提示更清晰。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 21:00:09 +08:00

185 lines
6.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""俱乐部微信配置与 openid 解析。"""
import logging
import re
import requests
from django.conf import settings
from jituan.constants import CLUB_ID_DEFAULT
from jituan.models import Club
logger = logging.getLogger(__name__)
_REFERER_APPID_RE = re.compile(r'servicewechat\.com/(wx[0-9a-fA-F]+)/', re.I)
def get_club_miniapp_appid(club):
"""小程序 AppID登录 jscode2session 与 JSAPI 支付须一致。"""
if not club:
return ''
return (club.wx_appid or club.pay_app_id or '').strip()
def resolve_club_by_appid(app_id):
if not app_id:
return None
app_id = app_id.strip()
try:
club = Club.query.filter(wx_appid=app_id, status=1).first()
if club:
return club
return Club.query.filter(pay_app_id=app_id, status=1).first()
except Exception:
club = Club.objects.filter(wx_appid=app_id, status=1).first()
if club:
return club
return Club.objects.filter(pay_app_id=app_id, status=1).first()
def get_club_wx_credentials(club_id):
"""优先读 club 表xq 缺省时回退 settings 全局配置(兼容现网)。"""
club_id = club_id or CLUB_ID_DEFAULT
club = None
try:
club = Club.query.get(club_id=club_id)
except Club.DoesNotExist:
club = None
except Exception:
logger.exception('读取俱乐部失败 club_id=%s', club_id)
club = None
appid = get_club_miniapp_appid(club)
secret = (club.wx_secret or '').strip() if club else ''
if appid and secret:
return appid, secret, club
if club_id == CLUB_ID_DEFAULT:
return (
getattr(settings, 'WEIXIN_APPID', ''),
getattr(settings, 'WEIXIN_SECRET', ''),
club,
)
# 非默认俱乐部未配置时给出明确原因,避免后续空指针 500
if not club:
return '', '', None
return appid or '', secret or '', 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 > body.inviteCode 反查管事 > JWT 用户归属 > 默认 xq。
老端 fighter.js 的 wdlyhdl 未带头时,靠 Referer 或邀请码识别星之界。
"""
from jituan.services.club_context import resolve_club_id_from_request
header = (request.META.get('HTTP_X_CLUB_ID') or '').strip()
if not header and hasattr(request, 'headers'):
header = (request.headers.get('X-Club-Id') or '').strip()
if header:
return header
app_id = ''
data = getattr(request, 'data', None) or {}
if isinstance(data, dict):
app_id = (data.get('appId') or data.get('app_id') or '').strip()
if not app_id:
referer = request.META.get('HTTP_REFERER', '') or ''
matched = _REFERER_APPID_RE.search(referer)
if matched:
app_id = matched.group(1)
if app_id:
club = resolve_club_by_appid(app_id)
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)
def wechat_jscode2session_for_request(request, code):
"""按请求上下文换取 openid多俱乐部小程序"""
club_id = resolve_club_id_for_wechat_request(request)
return jscode2session(code, club_id=club_id)
def jscode2session(code, club_id=None, app_id=None):
"""
按俱乐部微信配置换取 openid。
club_id 与 app_id 二选一app_id 用于从小程序 appId 反查 club。
"""
club = None
if app_id:
club = resolve_club_by_appid(app_id)
club_id = club.club_id if club else club_id
appid, secret, club = get_club_wx_credentials(club_id)
if not appid or not secret:
return {'errmsg': '俱乐部微信配置未设置'}
url = 'https://api.weixin.qq.com/sns/jscode2session'
params = {
'appid': appid,
'secret': secret,
'js_code': code,
'grant_type': 'authorization_code',
}
try:
response = requests.get(url, params=params, timeout=10)
result = response.json()
if 'openid' in result:
return {
'openid': result['openid'],
'session_key': result.get('session_key', ''),
'unionid': result.get('unionid', ''),
'club_id': club_id or CLUB_ID_DEFAULT,
'wx_appid': appid,
}
errcode = result.get('errcode', 'unknown')
errmsg = result.get('errmsg', '未知错误')
return {'errmsg': f'[{errcode}]{errmsg}'}
except requests.exceptions.Timeout:
return {'errmsg': '请求微信接口超时'}
except Exception as e:
logger.exception('jscode2session 异常 club_id=%s', club_id)
return {'errmsg': f'请求微信接口异常: {str(e)}'}