fix(wecom): 扫码绑定改用扫码授权登录端点,修复「请在企业微信客户端打开链接」+ bump 0.26.10
oauth_authorize_url 原用 open.weixin.qq.com/connect/oauth2/authorize(网页授权, 只能在企业微信客户端内打开),桌面浏览器 window.open 它 → 企业微信报「请在企业微信 客户端打开链接」,扫不了码。 改用扫码授权登录端点 login.work.weixin.qq.com/wwlogin/sso/login(login_type=CorpApp), 桌面浏览器渲染二维码,企业微信 App 扫码确认后回跳带 code,verify_state / get_user_id 逻辑不变。前置:redirect_uri 域名须配在应用「企业微信授权登录」可信域名(另一项设置)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8ab1805df4
commit
5d3cd88e2c
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
|
||||
|
||||
最后更新:2026-06-25(企业微信加「手填 userid」绑定:无 HTTPS 域名也能用企业微信推送 + bump 0.26.3)
|
||||
最后更新:2026-06-25(修复企业微信扫码绑定报「请在企业微信客户端打开链接」:换扫码授权登录端点 + bump 0.26.10)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -21,6 +21,12 @@
|
|||
|
||||
## 已完成关键能力
|
||||
|
||||
### 2026-06-25 / 修复企业微信扫码绑定报「请在企业微信客户端打开链接」(bump 0.26.10)
|
||||
|
||||
- bug:`oauth_authorize_url()` 用的是 `open.weixin.qq.com/connect/oauth2/authorize`(网页授权),这条只能在企业微信客户端内置浏览器里打开;前端 `wecomBind()` 用 `window.open` 在**桌面浏览器**新标签打开它 → 企业微信返回「请在企业微信客户端打开链接」,扫不了码。注释里「桌面浏览器=出二维码扫」是误解(那是公众号行为,企微 oauth2/authorize 不出扫码页)。
|
||||
- 修:换成**扫码授权登录**端点 `login.work.weixin.qq.com/wwlogin/sso/login?login_type=CorpApp&appid=CORPID&agentid=...&redirect_uri=...&state=...` —— 桌面浏览器会渲染二维码,用户用企业微信 App 扫码确认后回跳带 `code`,后续 `verify_state` / `get_user_id(code)` 换 userid 的逻辑完全不动。前置:redirect_uri 域名须在企业微信后台「应用 → 企业微信授权登录 → 可信域名」登记(与「网页授权可信域名」是两项不同设置)。
|
||||
- 文件:`core/wechat/wecom.py`(`OAUTH_AUTHORIZE`→`WWLOGIN_SSO`、`oauth_authorize_url`)。
|
||||
|
||||
### 2026-06-25 / 修复 wechat_push 工具漏挂企业微信(只配企微也能推,bump 0.26.9)
|
||||
|
||||
- bug:`wechat_push_available()` 只返回 `service.clawbot_enabled()`,完全没算企业微信。线上若只开了企业微信渠道(ClawBot 开关没开)→ 工具压根没注册到 agent → zcbot 照实回"我没有直接发企业微信的工具"(用户已绑企微仍推不出)。底层 `send_to_user` 其实早支持 `push_wecom`,门槛漏判而已。
|
||||
|
|
|
|||
4
RUN.md
4
RUN.md
|
|
@ -64,14 +64,14 @@
|
|||
# WECOM_CORPID=ww... # 企业 ID(管理员:我的企业→企业信息)
|
||||
# WECOM_AGENTID=1000002 # 自建应用 AgentId
|
||||
# WECOM_SECRET=... # 自建应用 Secret
|
||||
# ZCBOT_PUBLIC_BASE_URL=https://zcbot.example.com # 可选,OAuth 回调主机(须在应用「网页授权可信域名」内;缺则取请求 base)
|
||||
# ZCBOT_PUBLIC_BASE_URL=https://zcbot.example.com # 可选,OAuth 回调主机(须在应用「企业微信授权登录」可信域名内;缺则取请求 base)
|
||||
```
|
||||
> litellm 在 import 时副作用加载 .env;入口走 `main.py`,`.env` 自动生效。直跑 `python -c "from core.storage import ..."` 不经 litellm 链路时记得自己 `import litellm` 触发,或手动 `export ZCBOT_DB_URL=...`。
|
||||
- **依赖**:`pip install -r requirements.txt`(已在 `.venv` 里;含 `bcrypt`、`segno`、`cryptography`)。
|
||||
- **微信接入(ClawBot,§8.7)**:① `main.py db upgrade head` 带上 migration `0012`;② `.env` 设 `ZCBOT_WECHAT_BOT_ENABLED=1` + `ZCBOT_WECHAT_SECRET_KEY=<串>`;③ 用户登录后点**左栏 rail「微信」按钮**(`/static/wechat_bind.html` 仍保留作独立/嵌入入口)扫码绑定(需个人微信 8.0.70+ 且灰度到 ClawBot 插件)。绑定后在微信「微信 ClawBot」对话即走 zcbot;**主动推送需用户近 24h 在微信开口过一次**(冷启动/超期推不出,退邮件兜底)。
|
||||
- **企业微信(渠道 B,纯推送,§8.7)**:① 管理员建自建应用 → 填 `WECOM_CORPID/AGENTID/SECRET`(+ 可见范围含目标用户);② `main.py db upgrade head`。**绑定两条路,任选**:
|
||||
- **手填 userid(无域名时,最省)**:rail「微信」modal 企业微信段填成员 userid(管理后台→通讯录→点成员→「账号」)→ 保存。**推送是出站调用,不需要域名/HTTPS**,这条最省事。
|
||||
- **扫码授权(OAuth,要 HTTPS 域名)**:管理员另配「网页授权可信域名」指向 zcbot 域名 + `ZCBOT_PUBLIC_BASE_URL`;用户点「扫码绑定」。回调 `/v1/wecom/oauth/callback` 公开(身份从 HMAC state 验)。
|
||||
- **扫码授权登录(要 HTTPS 域名)**:管理员在应用→**「企业微信授权登录」**里把 zcbot 域名配进可信域名(注意不是「网页授权可信域名」,是另一项)+ 设 `ZCBOT_PUBLIC_BASE_URL`;用户点「扫码绑定」→ 桌面浏览器出二维码 → 企业微信 App 扫码确认。回调 `/v1/wecom/oauth/callback` 公开(身份从 HMAC state 验)。链接走 `login.work.weixin.qq.com/wwlogin/sso/login`(不是网页授权 `oauth2/authorize`,后者只能在企微客户端内打开 → 桌面浏览器会报「请在企业微信客户端打开链接」)。
|
||||
- 绑定后简报/结果**无条件主动推**(不挑活跃度、无 24h 窗口),适合必达;不做对话(要对话用 ClawBot)。
|
||||
- **PG**:`ZCBOT_DB_URL` 必填。本地 docker compose / 远端 dev / 生产任选;未设置时启动清晰报错,不引导 docker(§7.4)。
|
||||
- **Auth env**:`PLATFORM_KEY` + `JWT_SECRET` 任一缺失 web 启动 fail-fast。生成随机串:`python -c "import secrets; print(secrets.token_urlsafe(48))"`。
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
|
||||
# 改版本只动这一行。
|
||||
__version__ = "0.26.9"
|
||||
__version__ = "0.26.10"
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
只做**出站推送**(不做入站对话):
|
||||
- `access_token`:`gettoken(corpid,secret)`,进程内缓存 ~2h、线程安全、errcode 失效即重取。
|
||||
- OAuth 网页授权:`oauth_authorize_url()` 造扫码链接;`get_user_id(code)` 拿成员 userid
|
||||
(绑定用,一次性)。需管理员在应用配「网页授权可信域名」。
|
||||
- OAuth 扫码登录:`oauth_authorize_url()` 造扫码授权登录链接(桌面浏览器出二维码);
|
||||
`get_user_id(code)` 拿成员 userid(绑定用,一次性)。需管理员在应用配「企业微信授权登录」可信域名。
|
||||
- 发送:`send_text / send_markdown / send_file`(file 先 `media/upload` 换 media_id,≤20MB)。
|
||||
- `state` HMAC 签名(绑 user_id + 短 TTL,防 CSRF):回调无 JWT,用户身份从 state 来。
|
||||
|
||||
|
|
@ -24,7 +24,10 @@ from typing import Optional
|
|||
import httpx
|
||||
|
||||
QYAPI = "https://qyapi.weixin.qq.com/cgi-bin"
|
||||
OAUTH_AUTHORIZE = "https://open.weixin.qq.com/connect/oauth2/authorize"
|
||||
# 扫码授权登录(桌面浏览器渲染二维码,用企业微信 App 扫码)。
|
||||
# 不能用 open.weixin.qq.com/connect/oauth2/authorize —— 那条是「网页授权」,只能在
|
||||
# 企业微信客户端内打开,桌面浏览器会报「请在企业微信客户端打开链接」。
|
||||
WWLOGIN_SSO = "https://login.work.weixin.qq.com/wwlogin/sso/login"
|
||||
MAX_FILE_BYTES = 20 * 1024 * 1024
|
||||
|
||||
# access_token 进程内缓存
|
||||
|
|
@ -137,13 +140,17 @@ def verify_state(state: str) -> Optional[str]:
|
|||
|
||||
|
||||
def oauth_authorize_url(redirect_uri: str, state: str) -> str:
|
||||
"""造网页授权链接。桌面浏览器打开 = 出二维码扫;企业微信内 = 静默授权。"""
|
||||
"""造**扫码授权登录**链接:桌面浏览器打开会渲染二维码,用户用企业微信 App 扫码确认后
|
||||
回跳到 redirect_uri 带 code(后续 auth/getuserinfo 换 userid 不变)。
|
||||
|
||||
注意:redirect_uri 域名须在企业微信后台「应用 → 企业微信授权登录 → 可信域名」里登记,
|
||||
与「网页授权可信域名」是两项不同设置。"""
|
||||
from urllib.parse import quote
|
||||
return (
|
||||
f"{OAUTH_AUTHORIZE}?appid={_corpid()}"
|
||||
f"{WWLOGIN_SSO}?login_type=CorpApp&appid={_corpid()}"
|
||||
f"&agentid={_agentid()}"
|
||||
f"&redirect_uri={quote(redirect_uri, safe='')}"
|
||||
f"&response_type=code&scope=snsapi_base&agentid={_agentid()}"
|
||||
f"&state={quote(state, safe='')}#wechat_redirect"
|
||||
f"&state={quote(state, safe='')}"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue