From 18f415b363cef519f028f848a226d556b0ee37f7 Mon Sep 17 00:00:00 2001 From: v6ole Date: Fri, 12 Jun 2026 08:43:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=81=E5=BE=AE=20API=20=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E4=BB=A3=E7=90=86=E7=BB=95=E8=BF=87=20IP=20=E7=99=BD?= =?UTF-8?q?=E5=90=8D=E5=8D=95=E9=99=90=E5=88=B6=EF=BC=8C=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 WECHAT_API_BASE_URL 配置项,支持企微 API 代理转发 - _send_message 增加 API 错误日志(errcode + errmsg) - _get_access_token 增加失败日志 - handle_text 不再为空,返回菜单引导提示 - 防重入触发时通知用户等待时间 - menu.py API 调用统一使用配置化的 base URL Co-Authored-By: Claude --- .env.example | 3 +++ app/config.py | 1 + app/wechat/client.py | 18 +++++++++++++++--- app/wechat/handler.py | 15 +++++++++++++++ app/wechat/menu.py | 6 +++--- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 5e096bf..9c80c2b 100644 --- a/.env.example +++ b/.env.example @@ -20,6 +20,9 @@ WECHAT_TOKEN= WECHAT_ENCODING_AES_KEY= WECHAT_PORT=18001 WECHAT_HOST=0.0.0.0 +# 企微 API 代理(可选),用于绕过 IP 白名单限制 +# 留空则直连 https://qyapi.weixin.qq.com +WECHAT_API_BASE_URL=https://qyapi.weixin.qq.com # 定时任务 SCHEDULER_ENABLED=true diff --git a/app/config.py b/app/config.py index 48ac65a..77e078f 100644 --- a/app/config.py +++ b/app/config.py @@ -29,6 +29,7 @@ class Settings(BaseSettings): wechat_encoding_aes_key: str = "" wechat_port: int = 18001 wechat_host: str = "0.0.0.0" + wechat_api_base_url: str = "https://qyapi.weixin.qq.com" # 定时任务 scheduler_enabled: bool = True diff --git a/app/wechat/client.py b/app/wechat/client.py index d2ea91c..54784a9 100644 --- a/app/wechat/client.py +++ b/app/wechat/client.py @@ -1,9 +1,12 @@ +import logging import time import httpx from app.config import settings +logger = logging.getLogger(__name__) + class WeChatClient: def __init__(self): @@ -15,7 +18,7 @@ class WeChatClient: if self._access_token and now < self._token_expires_at: return self._access_token - url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" + url = f"{settings.wechat_api_base_url}/cgi-bin/gettoken" params = { "corpid": settings.wechat_corp_id, "corpsecret": settings.wechat_secret, @@ -27,6 +30,7 @@ class WeChatClient: self._access_token = data["access_token"] self._token_expires_at = now + data.get("expires_in", 7200) - 300 return self._access_token + logger.error(f"获取 access_token 失败: {data}") return None async def send_text(self, content: str, to_user: str = "@all") -> bool: @@ -59,9 +63,10 @@ class WeChatClient: ) -> bool: token = await self._get_access_token() if not token: + logger.error("无法获取 access_token,跳过消息发送") return False - url = "https://qyapi.weixin.qq.com/cgi-bin/message/send" + url = f"{settings.wechat_api_base_url}/cgi-bin/message/send" params = {"access_token": token} body = { "touser": to_user, @@ -73,4 +78,11 @@ class WeChatClient: async with httpx.AsyncClient(timeout=30) as client: response = await client.post(url, params=params, json=body) data = response.json() - return data.get("errcode") == 0 + errcode = data.get("errcode") + if errcode == 0: + return True + logger.error( + f"企业微信消息发送失败: errcode={errcode} errmsg={data.get('errmsg')} " + f"msgtype={msgtype} touser={to_user}" + ) + return False diff --git a/app/wechat/handler.py b/app/wechat/handler.py index efb4257..ea0fb78 100644 --- a/app/wechat/handler.py +++ b/app/wechat/handler.py @@ -92,6 +92,17 @@ class WeChatMessageHandler: return None async def handle_text(self, content: str, from_user: str) -> str | None: + """文本消息:返回帮助提示""" + help_text = ( + "请使用菜单操作:\n" + "---\n" + "📋 最新公告 - 获取最新公告\n" + "📊 查询 → 监控配置/系统状态/今日工作日\n" + "⚙️ 系统管理 → 立即爬取/同步节假日/AI 分析" + ) + ok = await self.client.send_text(help_text, from_user) + if not ok: + logger.warning(f"发送帮助消息失败, touser={from_user}") return None async def _handle_today_stats(self, from_user: str) -> str | None: @@ -128,7 +139,11 @@ class WeChatMessageHandler: async with _crawl_lock: now = time.monotonic() if now - _last_crawl_time < 60: + remaining = int(60 - (now - _last_crawl_time)) logger.info(f"爬取请求被忽略(防重入),距上次 {now - _last_crawl_time:.1f}s") + await self.client.send_text( + f"爬取任务进行中,请 {remaining} 秒后再试", from_user + ) return None _last_crawl_time = now diff --git a/app/wechat/menu.py b/app/wechat/menu.py index 9280637..dbeb5c1 100644 --- a/app/wechat/menu.py +++ b/app/wechat/menu.py @@ -71,7 +71,7 @@ class MenuManager: if not token: return False - url = "https://qyapi.weixin.qq.com/cgi-bin/menu/create" + url = f"{settings.wechat_api_base_url}/cgi-bin/menu/create" params = {"access_token": token, "agentid": int(settings.wechat_agent_id)} async with httpx.AsyncClient(timeout=15) as client: @@ -88,7 +88,7 @@ class MenuManager: if not token: return False - url = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete" + url = f"{settings.wechat_api_base_url}/cgi-bin/menu/delete" params = {"access_token": token, "agentid": int(settings.wechat_agent_id)} async with httpx.AsyncClient(timeout=15) as client: @@ -101,7 +101,7 @@ class MenuManager: if not token: return None - url = "https://qyapi.weixin.qq.com/cgi-bin/menu/get" + url = f"{settings.wechat_api_base_url}/cgi-bin/menu/get" params = {"access_token": token, "agentid": int(settings.wechat_agent_id)} async with httpx.AsyncClient(timeout=15) as client: