fix: 企微 API 通过代理绕过 IP 白名单限制,补充错误日志
- 新增 WECHAT_API_BASE_URL 配置项,支持企微 API 代理转发 - _send_message 增加 API 错误日志(errcode + errmsg) - _get_access_token 增加失败日志 - handle_text 不再为空,返回菜单引导提示 - 防重入触发时通知用户等待时间 - menu.py API 调用统一使用配置化的 base URL Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,9 @@ WECHAT_TOKEN=
|
|||||||
WECHAT_ENCODING_AES_KEY=
|
WECHAT_ENCODING_AES_KEY=
|
||||||
WECHAT_PORT=18001
|
WECHAT_PORT=18001
|
||||||
WECHAT_HOST=0.0.0.0
|
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
|
SCHEDULER_ENABLED=true
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class Settings(BaseSettings):
|
|||||||
wechat_encoding_aes_key: str = ""
|
wechat_encoding_aes_key: str = ""
|
||||||
wechat_port: int = 18001
|
wechat_port: int = 18001
|
||||||
wechat_host: str = "0.0.0.0"
|
wechat_host: str = "0.0.0.0"
|
||||||
|
wechat_api_base_url: str = "https://qyapi.weixin.qq.com"
|
||||||
|
|
||||||
# 定时任务
|
# 定时任务
|
||||||
scheduler_enabled: bool = True
|
scheduler_enabled: bool = True
|
||||||
|
|||||||
+15
-3
@@ -1,9 +1,12 @@
|
|||||||
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class WeChatClient:
|
class WeChatClient:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -15,7 +18,7 @@ class WeChatClient:
|
|||||||
if self._access_token and now < self._token_expires_at:
|
if self._access_token and now < self._token_expires_at:
|
||||||
return self._access_token
|
return self._access_token
|
||||||
|
|
||||||
url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
|
url = f"{settings.wechat_api_base_url}/cgi-bin/gettoken"
|
||||||
params = {
|
params = {
|
||||||
"corpid": settings.wechat_corp_id,
|
"corpid": settings.wechat_corp_id,
|
||||||
"corpsecret": settings.wechat_secret,
|
"corpsecret": settings.wechat_secret,
|
||||||
@@ -27,6 +30,7 @@ class WeChatClient:
|
|||||||
self._access_token = data["access_token"]
|
self._access_token = data["access_token"]
|
||||||
self._token_expires_at = now + data.get("expires_in", 7200) - 300
|
self._token_expires_at = now + data.get("expires_in", 7200) - 300
|
||||||
return self._access_token
|
return self._access_token
|
||||||
|
logger.error(f"获取 access_token 失败: {data}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def send_text(self, content: str, to_user: str = "@all") -> bool:
|
async def send_text(self, content: str, to_user: str = "@all") -> bool:
|
||||||
@@ -59,9 +63,10 @@ class WeChatClient:
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
token = await self._get_access_token()
|
token = await self._get_access_token()
|
||||||
if not token:
|
if not token:
|
||||||
|
logger.error("无法获取 access_token,跳过消息发送")
|
||||||
return False
|
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}
|
params = {"access_token": token}
|
||||||
body = {
|
body = {
|
||||||
"touser": to_user,
|
"touser": to_user,
|
||||||
@@ -73,4 +78,11 @@ class WeChatClient:
|
|||||||
async with httpx.AsyncClient(timeout=30) as client:
|
async with httpx.AsyncClient(timeout=30) as client:
|
||||||
response = await client.post(url, params=params, json=body)
|
response = await client.post(url, params=params, json=body)
|
||||||
data = response.json()
|
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
|
||||||
|
|||||||
@@ -92,6 +92,17 @@ class WeChatMessageHandler:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
async def handle_text(self, content: str, from_user: str) -> str | 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
|
return None
|
||||||
|
|
||||||
async def _handle_today_stats(self, from_user: str) -> str | None:
|
async def _handle_today_stats(self, from_user: str) -> str | None:
|
||||||
@@ -128,7 +139,11 @@ class WeChatMessageHandler:
|
|||||||
async with _crawl_lock:
|
async with _crawl_lock:
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
if now - _last_crawl_time < 60:
|
if now - _last_crawl_time < 60:
|
||||||
|
remaining = int(60 - (now - _last_crawl_time))
|
||||||
logger.info(f"爬取请求被忽略(防重入),距上次 {now - _last_crawl_time:.1f}s")
|
logger.info(f"爬取请求被忽略(防重入),距上次 {now - _last_crawl_time:.1f}s")
|
||||||
|
await self.client.send_text(
|
||||||
|
f"爬取任务进行中,请 {remaining} 秒后再试", from_user
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
_last_crawl_time = now
|
_last_crawl_time = now
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -71,7 +71,7 @@ class MenuManager:
|
|||||||
if not token:
|
if not token:
|
||||||
return False
|
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)}
|
params = {"access_token": token, "agentid": int(settings.wechat_agent_id)}
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=15) as client:
|
async with httpx.AsyncClient(timeout=15) as client:
|
||||||
@@ -88,7 +88,7 @@ class MenuManager:
|
|||||||
if not token:
|
if not token:
|
||||||
return False
|
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)}
|
params = {"access_token": token, "agentid": int(settings.wechat_agent_id)}
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=15) as client:
|
async with httpx.AsyncClient(timeout=15) as client:
|
||||||
@@ -101,7 +101,7 @@ class MenuManager:
|
|||||||
if not token:
|
if not token:
|
||||||
return None
|
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)}
|
params = {"access_token": token, "agentid": int(settings.wechat_agent_id)}
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=15) as client:
|
async with httpx.AsyncClient(timeout=15) as client:
|
||||||
|
|||||||
Reference in New Issue
Block a user