""" 微信小程序服务模块 提供微信小程序登录功能。 """ import httpx from typing import Optional from core.config import settings from logger.logging import get_logger logger = get_logger(__name__) class WechatService: """微信小程序服务类""" @staticmethod async def code2session(code: str) -> Optional[dict]: """ 通过微信登录凭证获取 session 信息 Args: code: 微信登录凭证 Returns: dict: {"openid": str, "session_key": str, "unionid": str (可选)} """ if not settings.wechat_app_id or not settings.wechat_app_secret: logger.error("微信小程序配置缺失") return None url = "https://api.weixin.qq.com/sns/jscode2session" params = { "appid": settings.wechat_app_id, "secret": settings.wechat_app_secret, "js_code": code, "grant_type": "authorization_code" } try: async with httpx.AsyncClient() as client: response = await client.get(url, params=params) data = response.json() if "errcode" in data and data["errcode"] != 0: logger.error(f"微信登录失败: {data.get('errmsg')}") return None return { "openid": data.get("openid"), "session_key": data.get("session_key"), "unionid": data.get("unionid") } except Exception as e: logger.exception(f"微信登录异常: {e}") return None @staticmethod async def get_phone_number(phone_code: str) -> Optional[str]: """ 通过手机号授权码获取用户手机号 微信新版 API,使用 getPhoneNumber 返回的 code 获取手机号 Args: phone_code: 手机号授权码 (getPhoneNumber 返回的 code) Returns: str: 用户手机号,失败返回 None """ if not settings.wechat_app_id or not settings.wechat_app_secret: logger.error("微信小程序配置缺失") return None access_token = await WechatService._get_access_token() if not access_token: return None url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber" try: async with httpx.AsyncClient() as client: response = await client.post( url, params={"access_token": access_token}, json={"code": phone_code} ) data = response.json() if data.get("errcode", 0) != 0: logger.error(f"获取手机号失败: {data.get('errmsg')}") return None phone_info = data.get("phone_info", {}) return phone_info.get("purePhoneNumber") or phone_info.get("phoneNumber") except Exception as e: logger.exception(f"获取手机号异常: {e}") return None @staticmethod async def _get_access_token() -> Optional[str]: """ 获取微信小程序 access_token 注意:生产环境应缓存 access_token,有效期 2 小时 """ url = "https://api.weixin.qq.com/cgi-bin/token" params = { "grant_type": "client_credential", "appid": settings.wechat_app_id, "secret": settings.wechat_app_secret } try: async with httpx.AsyncClient() as client: response = await client.get(url, params=params) data = response.json() if "access_token" in data: return data["access_token"] logger.error(f"获取 access_token 失败: {data.get('errmsg')}") return None except Exception as e: logger.exception(f"获取 access_token 异常: {e}") return None