From 1d2dacc3abd6467d545ec82c4b52c401f84cba52 Mon Sep 17 00:00:00 2001 From: v6ole Date: Fri, 26 Jun 2026 11:58:12 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=81=E5=BE=AE=20API=20=E4=BB=A3?= =?UTF-8?q?=E7=90=86=20+=20=E8=8F=9C=E5=8D=95=E9=83=A8=E7=BD=B2=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config.py: - 新增 WECOM_API_BASE 环境变量 (默认 https://qyapi.weixin.qq.com) wecom.py: - 所有 API 调用改用 settings.WECOM_API_BASE (支持代理) - 修复 _post_with_retry URL 拼接 bug (? vs &) - 菜单名去掉 emoji (企微限制 16 字符) api/wecom.py: - setup-menu 端点菜单名修正 .env.example: - 新增 WECOM_API_BASE 说明 当前 .env 配置: WECOM_API_BASE=https://api.v6ole.top 已验证: 通过代理获取 token ✅, 菜单创建 ✅ Co-Authored-By: Claude --- backend/.env.example | 2 ++ backend/app/api/wecom.py | 12 ++---------- backend/app/config.py | 1 + backend/app/services/wecom.py | 19 ++++++++++--------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 36713ee..f67f3ac 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -36,6 +36,8 @@ WECOM_AGENT_ID=your-agent-id WECOM_SECRET=your-app-secret WECOM_TOKEN=your-token WECOM_ENCODING_AES_KEY=your-encoding-aes-key +# 企微 API 代理(解决 IP 白名单问题),默认直连 https://qyapi.weixin.qq.com +WECOM_API_BASE=https://qyapi.weixin.qq.com # ── AI / LLM (OpenAI 兼容接口,用于周报摘要) ── # 支持 OpenAI / DeepSeek / 通义千问 / 本地 Ollama 等 diff --git a/backend/app/api/wecom.py b/backend/app/api/wecom.py index 661c2df..5a75f70 100644 --- a/backend/app/api/wecom.py +++ b/backend/app/api/wecom.py @@ -141,16 +141,8 @@ async def test_message( async def setup_menu(current_user: dict = Depends(require_director)): """Create the standard app menu (开始填报 + 绑定账号).""" buttons = [ - { - "type": "view", - "name": "📝 开始填报", - "url": "https://qj.dhdx.fun/m", - }, - { - "type": "view", - "name": "🔗 绑定账号", - "url": "https://qj.dhdx.fun/bind-wechat", - }, + {"type": "view", "name": "开始填报", "url": "https://qj.dhdx.fun/m"}, + {"type": "view", "name": "绑定账号", "url": "https://qj.dhdx.fun/bind-wechat"}, ] success = await wecom_client.create_menu(buttons) return {"success": success, "message": "菜单已部署" if success else "菜单创建失败"} diff --git a/backend/app/config.py b/backend/app/config.py index d9e8298..14a9d28 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -36,6 +36,7 @@ class Settings(BaseSettings): WECOM_SECRET: str = "" WECOM_TOKEN: str = "" WECOM_ENCODING_AES_KEY: str = "" + WECOM_API_BASE: str = "https://qyapi.weixin.qq.com" # AI / LLM (OpenAI-compatible) AI_API_URL: str = "" diff --git a/backend/app/services/wecom.py b/backend/app/services/wecom.py index c82f60d..41d9fd5 100644 --- a/backend/app/services/wecom.py +++ b/backend/app/services/wecom.py @@ -19,7 +19,7 @@ class WecomClient: if self._access_token and time.time() < self._token_expires_at - 60: return self._access_token - url = f"https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={self.corp_id}&corpsecret={self.secret}" + url = f"{settings.WECOM_API_BASE}/cgi-bin/gettoken?corpid={self.corp_id}&corpsecret={self.secret}" async with httpx.AsyncClient() as client: resp = await client.get(url, timeout=10) data = resp.json() @@ -35,7 +35,8 @@ class WecomClient: for attempt in range(max_retries): try: token = await self._get_token() - full_url = f"{url}?access_token={token}" + sep = "&" if "?" in url else "?" + full_url = f"{url}{sep}access_token={token}" async with httpx.AsyncClient() as client: resp = await client.post(full_url, json=body, timeout=10) data = resp.json() @@ -70,7 +71,7 @@ class WecomClient: """Exchange OAuth2 code for userid (used in silent login).""" try: token = await self._get_token() - url = f"https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token={token}&code={code}" + url = f"{settings.WECOM_API_BASE}/cgi-bin/auth/getuserinfo?access_token={token}&code={code}" async with httpx.AsyncClient() as client: resp = await client.get(url, timeout=10) data = resp.json() @@ -92,7 +93,7 @@ class WecomClient: "text": {"content": content}, } await self._post_with_retry( - "https://qyapi.weixin.qq.com/cgi-bin/message/send", body + f"{settings.WECOM_API_BASE}/cgi-bin/message/send", body ) return True except Exception: @@ -110,7 +111,7 @@ class WecomClient: "markdown": {"content": content}, } await self._post_with_retry( - "https://qyapi.weixin.qq.com/cgi-bin/message/send", body + f"{settings.WECOM_API_BASE}/cgi-bin/message/send", body ) return True except Exception: @@ -149,7 +150,7 @@ class WecomClient: }, } await self._post_with_retry( - "https://qyapi.weixin.qq.com/cgi-bin/message/send", body + f"{settings.WECOM_API_BASE}/cgi-bin/message/send", body ) return True except Exception: @@ -165,7 +166,7 @@ class WecomClient: try: body = {"button": buttons} await self._post_with_retry( - f"https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid={settings.WECOM_AGENT_ID}", + f"{settings.WECOM_API_BASE}/cgi-bin/menu/create?agentid={settings.WECOM_AGENT_ID}", body, ) return True @@ -178,7 +179,7 @@ class WecomClient: return None try: token = await self._get_token() - url = f"https://qyapi.weixin.qq.com/cgi-bin/menu/get?access_token={token}&agentid={settings.WECOM_AGENT_ID}" + url = f"{settings.WECOM_API_BASE}/cgi-bin/menu/get?access_token={token}&agentid={settings.WECOM_AGENT_ID}" async with httpx.AsyncClient() as client: resp = await client.get(url, timeout=10) data = resp.json() @@ -194,7 +195,7 @@ class WecomClient: return False try: token = await self._get_token() - url = f"https://qyapi.weixin.qq.com/cgi-bin/menu/delete?access_token={token}&agentid={settings.WECOM_AGENT_ID}" + url = f"{settings.WECOM_API_BASE}/cgi-bin/menu/delete?access_token={token}&agentid={settings.WECOM_AGENT_ID}" async with httpx.AsyncClient() as client: resp = await client.get(url, timeout=10) data = resp.json()