Files
Neo-ZQYY/apps/backend/app/services/wechat.py
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

93 lines
2.7 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.
# -*- coding: utf-8 -*-
"""
微信认证服务 —— 封装 code2Session API 调用。
通过 httpx.AsyncClient 异步调用微信 jscode2session 接口,
将小程序端的临时登录凭证 (code) 换取 openid / session_key。
"""
from __future__ import annotations
import logging
import httpx
from app.config import WX_APPID, WX_SECRET
from app.trace.decorators import trace_service
logger = logging.getLogger(__name__)
CODE2SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"
# 微信 errcode → (HTTP 状态码, 用户可见提示)
_WX_ERROR_MAP: dict[int, tuple[int, str]] = {
40029: (401, "登录凭证无效,请重新登录"),
45011: (429, "请求过于频繁"),
40226: (403, "账号存在风险"),
}
class WeChatAuthError(Exception):
"""微信认证错误,包含 errcode 和 errmsg。"""
def __init__(self, errcode: int, errmsg: str) -> None:
self.errcode = errcode
self.errmsg = errmsg
super().__init__(f"WeChatAuthError({errcode}): {errmsg}")
@property
def http_status(self) -> int:
"""根据 errcode 映射到建议的 HTTP 状态码。"""
return _WX_ERROR_MAP.get(self.errcode, (401, ""))[0]
@property
def detail(self) -> str:
"""根据 errcode 映射到用户可见的错误提示。"""
return _WX_ERROR_MAP.get(self.errcode, (401, "微信登录失败"))[1]
@trace_service(description_zh="微信登录code换session", description_en="WeChat code to session")
async def code2session(code: str) -> dict:
"""
调用微信 code2Session 接口。
参数:
code: 小程序端 wx.login() 获取的临时登录凭证
返回:
{"openid": str, "session_key": str, "unionid": str | None}
异常:
WeChatAuthError: 微信接口返回非零 errcode 时抛出
RuntimeError: WX_APPID / WX_SECRET 环境变量缺失时抛出
"""
appid = WX_APPID
secret = WX_SECRET
if not appid or not secret:
raise RuntimeError("微信配置缺失WX_APPID 或 WX_SECRET 未设置")
params = {
"appid": appid,
"secret": secret,
"js_code": code,
"grant_type": "authorization_code",
}
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.get(CODE2SESSION_URL, params=params)
resp.raise_for_status()
data = resp.json()
errcode = data.get("errcode", 0)
if errcode != 0:
errmsg = data.get("errmsg", "unknown error")
logger.warning("微信 code2Session 失败: errcode=%s, errmsg=%s", errcode, errmsg)
raise WeChatAuthError(errcode, errmsg)
return {
"openid": data["openid"],
"session_key": data["session_key"],
"unionid": data.get("unionid"),
}