import requests from django.core.cache import cache from django.core.signing import TimestampSigner, BadSignature, SignatureExpired from .models import WeChatPayConfig, WeChatUser import logging logger = logging.getLogger(__name__) def get_current_wechat_user(request): """ 根据 Authorization 头获取当前微信用户 增强逻辑:如果 Token 解析出的 OpenID 对应的用户不存在(可能已被合并删除), 但该 OpenID 是 Web 虚拟 ID (web_phone),尝试通过手机号查找现有的主账号。 """ auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return None token = auth_header.split(' ')[1] signer = TimestampSigner() try: # 签名包含 openid openid = signer.unsign(token, max_age=86400 * 30) # 30天有效 user = WeChatUser.objects.filter(openid=openid).first() if user: return user # 如果没找到用户,检查是否是 Web 虚拟 OpenID # 场景:Web 用户已被合并到小程序账号,旧 Web Token 依然有效,指向合并后的账号 if openid.startswith('web_'): try: # 格式: web_13800138000 parts = openid.split('_', 1) if len(parts) == 2: phone = parts[1] # 尝试通过手机号查找(查找合并后的主账号) user = WeChatUser.objects.filter(phone_number=phone).first() if user: return user except Exception: pass return None except (BadSignature, SignatureExpired): return None def get_access_token(config=None, force_refresh=False): """ 获取微信接口调用凭证 (client_credential) """ # 尝试从缓存获取 cache_key = 'wechat_access_token' if config: cache_key = f'wechat_access_token_{config.app_id}' if not force_refresh: token = cache.get(cache_key) if token: return token if not config: # 优先查找指定 AppID config = WeChatPayConfig.objects.filter(app_id='wxdf2ca73e6c0929f0').first() if not config: config = WeChatPayConfig.objects.filter(is_active=True).first() if not config or not config.app_id or not config.app_secret: logger.error("No active WeChatPayConfig found or missing app_id/app_secret") return None url = f"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={config.app_id}&secret={config.app_secret}" try: response = requests.get(url, timeout=10) data = response.json() if 'access_token' in data: token = data['access_token'] expires_in = data.get('expires_in', 7200) # 缓存 Token,留出 200 秒缓冲时间 cache.set(cache_key, token, expires_in - 200) return token else: logger.error(f"获取 AccessToken 失败: {data}") except Exception as e: logger.error(f"获取 AccessToken 异常: {str(e)}", exc_info=True) return None