Files
Django/jituan/services/club_resolver.py
T
XingQueandCursor 97ddccb0c2 fix: 星之界/星阙支付按俱乐部分别取 openid 与 AppID,启动静默刷新绑定
- resolve_club_payment_openid: xzj 回落 UserName 并 lazy 写绑定;xq 回落 OpenID;禁止跨俱乐部混用
- 统一 get_club_miniapp_appid;非 xq 禁止回落全局 AppID
- 新增 /jituan/auth/refresh-wx-bind;小程序有 token 时静默刷新 openid

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 20:57:30 +08:00

178 lines
5.8 KiB
Python

"""俱乐部微信配置与 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
try:
club = Club.query.get(club_id=club_id)
except Club.DoesNotExist:
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,
)
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 > 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)}'}