79 lines
2.4 KiB
Python
79 lines
2.4 KiB
Python
"""微信服务号:access_token、oauth code 换 openid。"""
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
from datetime import datetime, timedelta
|
||
from typing import Any, Dict, Optional
|
||
|
||
import httpx
|
||
|
||
from app.core.config import settings
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
_token: Optional[str] = None
|
||
_token_expire: Optional[datetime] = None
|
||
|
||
|
||
class WechatMpAuthError(Exception):
|
||
pass
|
||
|
||
|
||
def _require_mp_credentials() -> tuple:
|
||
appid = (settings.WX_MP_APPID or "").strip()
|
||
secret = (settings.WX_MP_SECRET or "").strip()
|
||
if not appid or not secret:
|
||
raise WechatMpAuthError("未配置服务号凭证(WX_MP_APPID / WX_MP_SECRET)")
|
||
return appid, secret
|
||
|
||
|
||
def get_mp_access_token() -> str:
|
||
global _token, _token_expire
|
||
if _token and _token_expire and datetime.now() < _token_expire:
|
||
return _token
|
||
appid, secret = _require_mp_credentials()
|
||
with httpx.Client(timeout=15.0) as client:
|
||
r = client.get(
|
||
"https://api.weixin.qq.com/cgi-bin/token",
|
||
params={
|
||
"grant_type": "client_credential",
|
||
"appid": appid,
|
||
"secret": secret,
|
||
},
|
||
)
|
||
r.raise_for_status()
|
||
data = r.json()
|
||
token = data.get("access_token")
|
||
if not token:
|
||
logger.warning("mp access_token failed: %s", data)
|
||
raise WechatMpAuthError("获取服务号 access_token 失败")
|
||
_token = token
|
||
_token_expire = datetime.now() + timedelta(seconds=int(data.get("expires_in", 7200)) - 300)
|
||
return token
|
||
|
||
|
||
def oauth_code_to_openid(code: str) -> Dict[str, Any]:
|
||
"""snsapi_base:code → openid / unionid。"""
|
||
c = (code or "").strip()
|
||
if not c:
|
||
raise WechatMpAuthError("code 不能为空")
|
||
appid, secret = _require_mp_credentials()
|
||
with httpx.Client(timeout=15.0) as client:
|
||
r = client.get(
|
||
"https://api.weixin.qq.com/sns/oauth2/access_token",
|
||
params={
|
||
"appid": appid,
|
||
"secret": secret,
|
||
"code": c,
|
||
"grant_type": "authorization_code",
|
||
},
|
||
)
|
||
r.raise_for_status()
|
||
data = r.json()
|
||
if data.get("errcode"):
|
||
raise WechatMpAuthError(data.get("errmsg") or "服务号授权失败")
|
||
openid = data.get("openid")
|
||
if not openid:
|
||
raise WechatMpAuthError("服务号未返回 openid")
|
||
return {"openid": openid, "unionid": data.get("unionid")}
|