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()