小程序后端接入微信登录与家长头像能力
This commit is contained in:
125
mini-program/app/service/wechat_login.py
Normal file
125
mini-program/app/service/wechat_login.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
import httpx
|
||||
|
||||
try:
|
||||
from app.settings import settings
|
||||
except ModuleNotFoundError:
|
||||
from settings import settings
|
||||
|
||||
|
||||
logger = logging.getLogger("app.wechat_login")
|
||||
|
||||
INVALID_CODE_ERRCODES = {40029, 40163}
|
||||
MISCONFIGURED_APP_ERRCODES = {40013, 40125}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WechatCodeSession:
|
||||
openid: str
|
||||
session_key: str
|
||||
unionid: str | None = None
|
||||
|
||||
|
||||
class WechatAuthError(Exception):
|
||||
def __init__(self, detail: str, *, status_code: int, errcode: int | None = None):
|
||||
super().__init__(detail)
|
||||
self.status_code = status_code
|
||||
self.errcode = errcode
|
||||
|
||||
|
||||
class WechatAuthService:
|
||||
def __init__(self, client: httpx.Client | None = None):
|
||||
self._client = client
|
||||
|
||||
def exchange_code(self, code: str) -> WechatCodeSession:
|
||||
if not settings.wechat_app_id or not settings.wechat_app_secret:
|
||||
raise WechatAuthError("wechat login is not configured", status_code=503)
|
||||
|
||||
response = self._request_code2session(code)
|
||||
data = self._parse_response_json(response)
|
||||
|
||||
errcode = self._parse_errcode(data.get("errcode"))
|
||||
if errcode not in (None, 0):
|
||||
errmsg = data.get("errmsg")
|
||||
logger.warning(
|
||||
"wechat code2session rejected login code",
|
||||
extra={
|
||||
"event": "wechat_code2session_rejected",
|
||||
"errcode": errcode,
|
||||
"errmsg": errmsg,
|
||||
},
|
||||
)
|
||||
raise self._map_exchange_error(errcode)
|
||||
|
||||
openid = data.get("openid")
|
||||
session_key = data.get("session_key")
|
||||
unionid = data.get("unionid")
|
||||
|
||||
if not isinstance(openid, str) or not openid:
|
||||
raise WechatAuthError("wechat login response missing openid", status_code=502)
|
||||
if not isinstance(session_key, str) or not session_key:
|
||||
raise WechatAuthError("wechat login response missing session_key", status_code=502)
|
||||
if not isinstance(unionid, str) or not unionid:
|
||||
unionid = None
|
||||
|
||||
return WechatCodeSession(openid=openid, session_key=session_key, unionid=unionid)
|
||||
|
||||
def _request_code2session(self, code: str) -> httpx.Response:
|
||||
params = {
|
||||
"appid": settings.wechat_app_id,
|
||||
"secret": settings.wechat_app_secret,
|
||||
"js_code": code,
|
||||
"grant_type": "authorization_code",
|
||||
}
|
||||
|
||||
client = self._client
|
||||
owns_client = client is None
|
||||
if client is None:
|
||||
client = httpx.Client(
|
||||
base_url=settings.wechat_api_base_url.rstrip("/"),
|
||||
timeout=settings.wechat_http_timeout_seconds,
|
||||
)
|
||||
|
||||
try:
|
||||
response = client.get("/sns/jscode2session", params=params)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except httpx.HTTPError as exc:
|
||||
logger.warning(
|
||||
"wechat code2session request failed",
|
||||
extra={"event": "wechat_code2session_request_failed"},
|
||||
)
|
||||
raise WechatAuthError("wechat login service unavailable", status_code=502) from exc
|
||||
finally:
|
||||
if owns_client:
|
||||
client.close()
|
||||
|
||||
def _parse_response_json(self, response: httpx.Response) -> dict:
|
||||
try:
|
||||
data = response.json()
|
||||
except ValueError as exc:
|
||||
raise WechatAuthError("invalid response from wechat login service", status_code=502) from exc
|
||||
if not isinstance(data, dict):
|
||||
raise WechatAuthError("invalid response from wechat login service", status_code=502)
|
||||
return data
|
||||
|
||||
def _parse_errcode(self, value: object) -> int | None:
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def _map_exchange_error(self, errcode: int) -> WechatAuthError:
|
||||
if errcode in INVALID_CODE_ERRCODES:
|
||||
return WechatAuthError("invalid or expired wechat login code", status_code=401, errcode=errcode)
|
||||
if errcode in MISCONFIGURED_APP_ERRCODES:
|
||||
return WechatAuthError("wechat login is not configured correctly", status_code=503, errcode=errcode)
|
||||
return WechatAuthError("wechat login service unavailable", status_code=502, errcode=errcode)
|
||||
|
||||
|
||||
def get_wechat_auth_service() -> WechatAuthService:
|
||||
return WechatAuthService()
|
||||
Reference in New Issue
Block a user