| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- """微信服务号: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")}
|