Files
WechatExplorer/docs/deployment/experimental-wechat-share-card.md
Wxw-Gu 4436d7c8ce feat: 新增实验性微信卡片分享与自动部署能力
补充 Cloudflare Worker、R2 存储、微信 JS-SDK 签名与上传鉴权
增加自动部署 Skill 和配置引导文档
优化报告工具栏、微信卡片弹窗及窄屏响应式布局
补充 Worker 鉴权、卡片生成、过期清理与安全转义测试
2026-08-13 10:48:07 +08:00

16 KiB
Raw Permalink Blame History

实验性功能:自托管微信分享卡片

微信卡片分享效果示例

实验性功能
该能力需要用户自行准备 Cloudflare、域名和微信公众平台测试号,目前不属于开箱即用的稳定功能。Cloudflare、微信 JS-SDK、测试号权限或微信客户端行为变化,都可能导致分享卡片失效。

新手推荐:直接交给 Agent

如果你不熟悉 Cloudflare、Wrangler 或命令行,不需要手动照着整篇文档操作。把下面这个 Skill 文件夹交给 Codex、Claude Code 或其他能够操作项目终端的编程 Agent:

docs/skill/setup-wechat-share-card/

然后对 Agent 说:

请使用 setup-wechat-share-card Skill,帮我部署 TraceMemo 的实验性微信分享卡片服务。尽量自动完成,只在缺少必要信息时一次性问我。

Agent 会自动:

  • 检查 Node.js、pnpm 和 Wrangler
  • 必要时临时下载 Wrangler
  • 打开 Cloudflare 登录并执行 whoami
  • 自动生成 UPLOAD_TOKEN
  • 创建或复用私有 R2 Bucket
  • 写入 Worker Secret
  • 根据你的域名生成本机 Worker 配置;
  • 部署 Worker并执行健康检查和微信签名检查;
  • 把上传密钥复制到剪贴板,供你粘贴到 TraceMemo。

Agent 无法替你创建微信测试号或决定使用哪个域名,因此通常只需要你提供:

  1. 你准备使用的分享域名,例如 share.example.com
  2. 微信测试号页面中的 AppID
  3. 微信测试号页面中的 AppSecret;
  4. 浏览器弹出 Cloudflare OAuth 页面时完成一次登录授权。

真实配置保存在被 Git 忽略的本机 .env 中,不会写入 .env.example。不要把 .env 发给别人或提交到仓库。

TraceMemo 可以把已经生成的群聊日报长图发布为一个临时网页,并在微信中分享成带有标题、描述和缩略图的卡片。

TraceMemo 不提供公共卡片服务器。使用该功能前,需要按照本文部署一套属于你自己的卡片服务。日报图片将上传到你自己的 Cloudflare R2,而不是上传到 TraceMemo 作者的服务器。

这个功能解决什么问题

直接把日报 PNG 发到微信,只会显示为一张普通图片。微信卡片还需要:

  • 一个域名 (未能备案的话 在微信里点击多次 可能会被微信内置窗口提示需要备案);
  • 卡片标题和描述;
  • 一张微信可以读取的缩略图;
  • 微信 JS-SDK 签名;
  • 一个临时保存日报图片的位置。

本项目提供的 Cloudflare Worker 负责这些工作。桌面端上传日报后,会得到一个分享链接和二维码。用微信扫码打开链接,再点击右上角菜单分享,即可生成微信卡片。

数据会经过哪里

flowchart LR
    A[TraceMemo 本机日报 PNG] -->|带 UPLOAD_TOKEN 上传| B[你的 Cloudflare Worker]
    B --> C[你的私有 R2 Bucket]
    B -->|AppID + AppSecret| D[微信公众平台接口]
    D -->|access_token 与 jsapi_ticket| B
    B --> E[临时分享网页]
    E --> F[微信 JS-SDK]
    F --> G[微信好友或群聊卡片]

与 TraceMemo 的本地浏览能力不同,启用分享卡片后,当前日报长图、缩略图、卡片标题和描述会离开本机,上传到你控制的 Cloudflare 账号。

你需要准备什么

项目 用途 从哪里获得
Cloudflare 账号 运行 Worker 和保存 R2 图片 自行注册 Cloudflare
托管在 Cloudflare 的域名 提供 HTTPS 分享地址 使用自己的域名,例如 share.example.com
R2 Bucket 临时保存日报和缩略图 使用 Wrangler 创建
UPLOAD_TOKEN 阻止陌生人调用你的上传接口 由你自己随机生成
微信测试号 AppID 标识调用 JS-SDK 的微信应用 微信公众平台接口测试号页面
微信测试号 AppSecret Worker 获取微信接口凭据 微信公众平台接口测试号页面
JS 接口安全域名 告诉微信哪些网页可以使用该 AppID 调用 JS-SDK 在微信测试号页面填写你的分享域名

微信公众平台接口测试号入口:

https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index

微信 JS-SDK 官方文档:

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html

理解三个重要配置

UPLOAD_TOKEN 从哪里来

UPLOAD_TOKEN 不是从 Cloudflare 或微信后台领取的,它是卡片服务部署者自己生成的一段随机密码。

它用于保护 Worker 的上传接口:TraceMemo 上传日报时,会发送:

Authorization: Bearer <UPLOAD_TOKEN>

Worker 只有在密钥完全一致时才接受上传。没有它,任何知道接口地址的人都可能向你的 R2 上传文件并消耗资源。

在 macOS 或 Linux 中生成一枚 64 位十六进制随机密钥:

openssl rand -hex 32

示例输出只用于说明格式,不要直接使用:

8a4d...一共 64 个十六进制字符...72ef

生成后,同一个值需要配置到两个地方:

  1. Cloudflare Worker Secret UPLOAD_TOKEN
  2. TraceMemo“生成微信卡片”弹窗中的“上传密钥”。

如果两边不一致,卡片服务会返回 HTTP 401 或“未授权”。

TraceMemo 会使用 Electron safeStorage 将服务地址和上传密钥加密保存在本机。不要把密钥提交到 Git,也不要写入 wrangler.jsonc

AppID 和 AppSecret 从哪里来

打开微信公众平台接口测试号,使用微信扫码登录。

页面上方会显示:

  • appID
  • appsecret

将它们分别保存为 Worker Secret

WECHAT_APP_ID
WECHAT_APP_SECRET

它们的作用不同:

  • AppID 用于标识这个微信测试应用;
  • AppSecret 是高敏感凭据,Worker 用它向微信服务器获取 access_token
  • Worker 再使用 access_token 获取 jsapi_ticket
  • 最后使用 jsapi_ticket、当前网页 URL、时间戳和随机串生成 JS-SDK 签名。

AppSecret 只能保存在 Worker Secret 中。不要把它填写到 TraceMemo 的“上传密钥”输入框,不要发送给前端,也不要提交到仓库。怀疑泄露时,应立即在微信后台重置并更新 Worker Secret。

JS 接口安全域名是干什么的

JS 接口安全域名是微信对网页来源的白名单。

假设你的分享服务地址是:

https://share.example.com

那么测试号页面中的“JS 接口安全域名”应填写:

share.example.com

填写时:

  • 不带 https://
  • 不带 /s/xxx 等路径;
  • 不要填写 Cloudflare Worker 名称;
  • 必须与用户实际打开分享页时的域名一致。

它不是用来解析 DNS 的。域名仍然需要先在 Cloudflare 中正确绑定到 Worker。安全域名的作用是告诉微信:允许这个域名下的网页使用当前 AppID 请求 JS-SDK 能力。

如果没有配置、填错域名,或签名 URL 与实际页面 URL 不一致,通常会出现 invalid signatureconfig:fail 或分享信息没有生效。

微信可能要求下载一个 TXT 验证文件,并确保它可以通过下面的地址访问:

https://share.example.com/微信提供的文件名.txt

项目 Worker 已包含根路径验证文件的实现方式。你需要把自己的文件名和内容加入 services/share-card-worker/src/index.js 中的 WECHAT_DOMAIN_VERIFICATION,然后重新部署。

自托管部署步骤

以下命令均在项目根目录执行。

1. 登录 Cloudflare

项目建议使用本地 Wrangler

pnpm exec wrangler login
pnpm exec wrangler whoami

如果本地版本的 OAuth 登录出现 invalid_scope 等问题,可临时使用更新版本:

pnpm dlx wrangler@latest login
pnpm dlx wrangler@latest whoami

登录注意事项:

  • 让 Wrangler 自动打开浏览器最稳妥;
  • 不要复用以前生成的 OAuth 链接;
  • 不要修改链接中的 statecode_challenge 或回调地址;
  • 不建议使用无痕窗口或跨浏览器复制链接;
  • 默认回调使用 localhost:8976,端口被占用时先结束旧的 Wrangler 登录进程;
  • 浏览器提示授权成功后,仍应通过 whoami 核对账号。

2. 修改 Worker 配置

打开:

services/share-card-worker/wrangler.jsonc

至少修改下面两个位置:

{
  "routes": [
    {
      "pattern": "share.example.com",
      "custom_domain": true
    }
  ],
  "vars": {
    "PUBLIC_ORIGIN": "https://share.example.com",
    "DEFAULT_EXPIRY_DAYS": "7"
  }
}

routes[].pattern 是 Worker 自定义域名,PUBLIC_ORIGIN 是生成分享链接和校验签名来源时使用的完整 HTTPS 地址,两者必须一致。

不要直接照抄仓库维护者的域名。请替换为你自己 Cloudflare 账号中的域名或子域名。

3. 创建私有 R2 Bucket

默认配置使用 Bucket 名称:

wechatexplorer-share-reports

创建:

pnpm exec wrangler r2 bucket create wechatexplorer-share-reports \
  --config services/share-card-worker/wrangler.jsonc

Worker 中的绑定名称是 REPORTS。R2 会保存:

cards/<card-id>/card.json
cards/<card-id>/report.png
cards/<card-id>/thumbnail.jpg
  • card.json:标题、描述、创建时间和过期时间;
  • report.png:完整日报长图;
  • thumbnail.jpg:微信卡片缩略图。

请保持 R2 Bucket 私有,不要启用公开 r2.dev 开发 URL。图片应统一通过 Worker 的随机卡片 URL 读取。

4. 生成并配置 UPLOAD_TOKEN

openssl rand -hex 32

复制生成结果,然后执行:

pnpm exec wrangler secret put UPLOAD_TOKEN \
  --config services/share-card-worker/wrangler.jsonc

Wrangler 提示输入时粘贴密钥。终端不会正常显示 Secret 内容。

5. 配置微信 AppID 和 AppSecret

微信公众平台接口测试号复制 AppID

pnpm exec wrangler secret put WECHAT_APP_ID \
  --config services/share-card-worker/wrangler.jsonc

再复制 AppSecret

pnpm exec wrangler secret put WECHAT_APP_SECRET \
  --config services/share-card-worker/wrangler.jsonc

Secret 不会出现在 wrangler.jsonc 中。如果你更换 Cloudflare 账号或重新创建 Worker,需要重新配置全部三个 Secret。

6. 配置微信测试号

在测试号页面完成:

  1. 使用测试微信关注该测试号;
  2. share.example.com 填入“JS 接口安全域名”;
  3. 按页面提示完成 TXT 文件域名验证;
  4. 确认 AppID/AppSecret 与刚才写入 Worker 的值来自同一个测试号。

测试号只适合开发和验证。正式公众号的接口权限、认证要求和后台菜单可能不同,请以微信公众平台实际规则为准。

7. 部署 Worker

pnpm exec wrangler deploy \
  --config services/share-card-worker/wrangler.jsonc

Cloudflare Custom Domain 要求域名已经位于同一 Cloudflare 账号中。如果该子域名已经存在 A、AAAA 或 CNAME 记录,绑定可能失败。删除冲突记录,或者换一个未使用的子域名,例如 share2.example.com

更新 Secret 后,如果线上仍提示旧配置,可再执行一次完整部署。

在 TraceMemo 中配置

生成一份日报后,点击“生成微信卡片(实验性)”。首次使用需要填写:

服务地址:https://share.example.com
上传密钥:你自己通过 openssl rand -hex 32 生成的 UPLOAD_TOKEN

这里的“上传密钥”绝对不是微信 AppSecret。

配置保存后,TraceMemo 会上传当前日报和缩略图,返回二维码。使用已经关注测试号的微信扫码,打开页面后再通过右上角菜单分享。

验证部署

健康检查

curl -fsS https://share.example.com/health

正常结果类似:

{ "ok": true, "service": "wechatexplorer-share-card", "storage": "ready" }

JS-SDK 签名检查

curl -fsS \
  'https://share.example.com/api/wx-signature?url=https%3A%2F%2Fshare.example.com%2Fhealth'

正常结果应包含:

appId
timestamp
nonceStr
signature

响应中不应包含 AppSecret、access_tokenjsapi_ticket

常见问题

HTTP 401 / 未授权

TraceMemo 中保存的上传密钥与 Worker 的 UPLOAD_TOKEN 不一致。重新生成或重新配置时,必须同步更新两边。

“微信 JS-SDK 尚未配置”

Worker 缺少 WECHAT_APP_IDWECHAT_APP_SECRET。执行两个 secret put,再重新部署。

invalid signature 或分享信息不生效

依次检查:

  • PUBLIC_ORIGIN 是否与浏览器实际访问的 origin 完全一致;
  • JS 接口安全域名是否只填写了域名;
  • AppID/AppSecret 是否属于同一个测试号;
  • AppSecret 是否已被重置但 Worker 仍保存旧值;
  • 分享页面是否经过了改变 URL 的代理或重定向;
  • 测试微信是否已关注测试号。

自定义域名绑定失败

检查同名 A、AAAA、CNAME 记录是否已经存在,域名是否位于当前 Wrangler 登录的 Cloudflare 账号中。

R2 未配置

确认 Bucket 存在,并且 wrangler.jsonc 中的绑定名称为 REPORTSbucket_name 与实际 Bucket 一致。

卡片过期或图片消失

默认有效期为 7 天。Worker 的定时任务会删除过期卡片的元数据、日报和缩略图,这是设计行为。

安全和隐私注意事项

  • 日报可能包含敏感群聊内容。只分享你有权分享的内容。
  • 获得分享 URL 的人,在过期前可能查看对应日报;当前实现不是按访问者身份授权。
  • UPLOAD_TOKEN 是整个 Worker 的服务级密钥,不是每个用户独立的账号凭据。
  • 不要把 UPLOAD_TOKEN、AppSecret 或 Wrangler 凭据提交到 Git。
  • R2 保持私有,不要把 Bucket 直接公开。
  • 建议定期轮换 UPLOAD_TOKEN,怀疑泄露时立即轮换。
  • 微信 AppSecret 泄露时,应在微信后台重置,并立即更新 Worker Secret。
  • 自托管者自行承担 Cloudflare 用量、域名、数据合规和微信平台规则相关责任。

当前实验性限制

  • 需要用户自己部署,普通用户无法直接开箱使用;
  • 使用一个共享的 UPLOAD_TOKEN,没有多用户账号系统;
  • 分享链接在有效期内属于“知道链接即可访问”;
  • 依赖微信 JS-SDK 和测试号能力,微信侧规则变化可能造成失效;
  • 当前仅上传 PNG 日报和 JPEG 缩略图;
  • 没有管理后台用于列出、提前删除或审计所有卡片;
  • 过期清理由定时任务完成,不保证到期瞬间立即删除。

代码入口

  • Workerservices/share-card-worker/src/index.js
  • Worker 配置:services/share-card-worker/wrangler.jsonc
  • Worker 测试:services/share-card-worker/test/index.test.js
  • 桌面端上传:src/main/wechat-share-card-service.ts
  • 本地加密配置:src/main/wechat-share-config-store.ts
  • 分享弹窗:src/renderer/src/components/reports/WechatShareCardDialog.tsx
  • 共享类型:src/shared/wechat-share-card.ts

参考资料