fix: bug
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
import httpx
|
||||
@@ -8,21 +9,60 @@ import markdown2
|
||||
from app.config import settings
|
||||
from app.schemas import PublishResponse, WechatPublishRequest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _detail_for_token_error(data: dict | None) -> str:
|
||||
"""把微信返回的 errcode 转成可操作的说明。"""
|
||||
if not data:
|
||||
return "获取微信 access_token 失败(无返回内容)"
|
||||
code = data.get("errcode")
|
||||
msg = (data.get("errmsg") or "").strip()
|
||||
if code == 40164:
|
||||
return (
|
||||
"微信 errcode=40164:当前请求使用的出口 IP 未在公众号「IP 白名单」中。"
|
||||
"请到 微信公众平台 → 设置与开发 → 基本配置 → IP 白名单,添加本服务对外的公网 IP"
|
||||
"(日志里 invalid ip 后面的地址)。若在本地/Docker 调试,出口 IP 常会变,需填当前出口或改用固定出口的服务器。"
|
||||
f" 微信原文:{msg}"
|
||||
)
|
||||
if code == 40013:
|
||||
return f"微信 errcode=40013:AppSecret 无效或已重置,请检查 WECHAT_SECRET。{msg}"
|
||||
if code == 40125:
|
||||
return f"微信 errcode=40125:AppSecret 配置错误。{msg}"
|
||||
return f"获取微信 access_token 失败:errcode={code} errmsg={msg}"
|
||||
|
||||
|
||||
class WechatPublisher:
|
||||
def __init__(self) -> None:
|
||||
self._access_token = None
|
||||
self._expires_at = 0
|
||||
|
||||
async def publish_draft(self, req: WechatPublishRequest) -> PublishResponse:
|
||||
async def publish_draft(self, req: WechatPublishRequest, request_id: str = "") -> PublishResponse:
|
||||
rid = request_id or "-"
|
||||
if not settings.wechat_appid or not settings.wechat_secret:
|
||||
logger.warning("wechat skipped rid=%s reason=missing_appid_or_secret", rid)
|
||||
return PublishResponse(ok=False, detail="缺少 WECHAT_APPID / WECHAT_SECRET 配置")
|
||||
|
||||
token = await self._get_access_token()
|
||||
token, token_from_cache, token_err_body = await self._get_access_token()
|
||||
if not token:
|
||||
return PublishResponse(ok=False, detail="获取微信 access_token 失败")
|
||||
detail = _detail_for_token_error(token_err_body)
|
||||
logger.error("wechat access_token_unavailable rid=%s detail=%s", rid, detail[:200])
|
||||
return PublishResponse(ok=False, detail=detail, data=token_err_body)
|
||||
|
||||
logger.info(
|
||||
"wechat_token rid=%s cache_hit=%s",
|
||||
rid,
|
||||
token_from_cache,
|
||||
)
|
||||
|
||||
html = markdown2.markdown(req.body_markdown)
|
||||
logger.info(
|
||||
"wechat_draft_build rid=%s title_chars=%d digest_chars=%d html_chars=%d",
|
||||
rid,
|
||||
len(req.title or ""),
|
||||
len(req.summary or ""),
|
||||
len(html or ""),
|
||||
)
|
||||
payload = {
|
||||
"articles": [
|
||||
{
|
||||
@@ -39,19 +79,37 @@ class WechatPublisher:
|
||||
|
||||
async with httpx.AsyncClient(timeout=25) as client:
|
||||
url = f"https://api.weixin.qq.com/cgi-bin/draft/add?access_token={token}"
|
||||
logger.info(
|
||||
"wechat_http_post rid=%s endpoint=cgi-bin/draft/add http_timeout_s=25",
|
||||
rid,
|
||||
)
|
||||
r = await client.post(url, json=payload)
|
||||
data = r.json()
|
||||
|
||||
if data.get("errcode", 0) != 0:
|
||||
logger.warning(
|
||||
"wechat_draft_failed rid=%s errcode=%s errmsg=%s raw=%s",
|
||||
rid,
|
||||
data.get("errcode"),
|
||||
data.get("errmsg"),
|
||||
data,
|
||||
)
|
||||
return PublishResponse(ok=False, detail=f"微信发布失败: {data}", data=data)
|
||||
|
||||
logger.info(
|
||||
"wechat_draft_ok rid=%s media_id=%s",
|
||||
rid,
|
||||
data.get("media_id", data),
|
||||
)
|
||||
return PublishResponse(ok=True, detail="已发布到公众号草稿箱", data=data)
|
||||
|
||||
async def _get_access_token(self) -> str | None:
|
||||
async def _get_access_token(self) -> tuple[str | None, bool, dict | None]:
|
||||
"""成功时第三项为 None;失败时为微信返回的 JSON(含 errcode/errmsg)。"""
|
||||
now = int(time.time())
|
||||
if self._access_token and now < self._expires_at - 60:
|
||||
return self._access_token
|
||||
return self._access_token, True, None
|
||||
|
||||
logger.info("wechat_http_get endpoint=cgi-bin/token reason=refresh_access_token")
|
||||
async with httpx.AsyncClient(timeout=20) as client:
|
||||
r = await client.get(
|
||||
"https://api.weixin.qq.com/cgi-bin/token",
|
||||
@@ -61,12 +119,17 @@ class WechatPublisher:
|
||||
"secret": settings.wechat_secret,
|
||||
},
|
||||
)
|
||||
data = r.json()
|
||||
data = r.json() if r.content else {}
|
||||
|
||||
token = data.get("access_token")
|
||||
if not token:
|
||||
return None
|
||||
logger.warning(
|
||||
"wechat_token_refresh_failed http_status=%s body=%s",
|
||||
r.status_code,
|
||||
data,
|
||||
)
|
||||
return None, False, data if isinstance(data, dict) else None
|
||||
|
||||
self._access_token = token
|
||||
self._expires_at = now + int(data.get("expires_in", 7200))
|
||||
return token
|
||||
return token, False, None
|
||||
|
||||
Reference in New Issue
Block a user