huoyan-enterprise/backend/services/wechat_service.py

128 lines
4.2 KiB
Python
Raw 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.

"""
微信小程序服务模块
提供微信小程序登录功能。
"""
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