Files
Genarrative/docs/technical/WECHAT_MINIPROGRAM_WEB_VIEW_SHELL_2026-05-03.md

9.0 KiB
Raw Permalink Blame History

微信小程序 web-view 壳接入记录

日期:2026-05-03

1. 目标

本次先用微信小程序 web-view 承载现有 H5不重写 React/Vite 主前端,也不把 SpacetimeDB SDK 或业务规则搬进小程序端。

当前小程序壳只承担五件事:

  1. 提供微信开发者工具可识别的 miniprogram/ 工程根目录。
  2. 在原生小程序壳中调用 wx.login 获取小程序 code
  3. 调用服务器域名下的 /api/auth/wechat/miniprogram-login,由 Rust api-server 兑换微信身份并签发系统登录态。
  4. 若后端返回 pending_bind_phone,先在小程序原生层通过 button open-type="getPhoneNumber" 取得用户同意后的手机号动态令牌,再调用 /api/auth/wechat/bind-phone 完成绑定。
  5. 用一个全屏 web-view 打开现有 H5 入口,并把系统 auth_token 放入 H5 现有登录回调 hash。

重要边界:

  1. openid 只作为后端微信身份绑定依据,不直接暴露给 H5 当登录凭证。
  2. H5 继续消费本系统 JWT也就是 #auth_provider=wechat&auth_token=...&auth_binding_status=...
  3. 这与 WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md 中“微信只提供三方身份Axum 签发系统 JWT”的边界一致。

2. 文件入口

文件 说明
project.config.json 指定 miniprogramRoot: "miniprogram/"
miniprogram/app.json 小程序全局配置,注册 pages/web-view/index
miniprogram/config.js 业务域名入口配置,需要部署时填写。
miniprogram/pages/web-view/index.* 最小 web-view 页面。
server-rs/crates/api-server/src/wechat_auth.rs 新增小程序登录接口 /api/auth/wechat/miniprogram-login
server-rs/crates/platform-auth/src/lib.rs 新增 jscode2session 兑换能力。

3. 需要手工填写的配置

miniprogram/config.js 中填写:

const WEB_VIEW_ENTRY_URL = 'https://你的H5业务域名/';
const API_BASE_URL = 'https://你的服务器域名/';
const MINI_PROGRAM_APP_ID = '你的微信小程序 AppID';
const MINI_PROGRAM_ENV = 'develop';

约束:

  1. 必须是 https
  2. 不能是 localhost 或 IP。
  3. WEB_VIEW_ENTRY_URL 域名需要在微信小程序后台配置为业务域名。
  4. API_BASE_URL 域名需要在微信小程序后台配置为 request 合法域名。
  5. H5 页面里的 API、图片、音视频、iframe 等外链也要满足微信侧域名与证书要求。

api-server 环境变量中填写:

WECHAT_AUTH_ENABLED=true
WECHAT_AUTH_PROVIDER=real
WECHAT_MINI_PROGRAM_APP_ID="你的微信小程序 AppID"
WECHAT_MINI_PROGRAM_APP_SECRET="你的微信小程序 AppSecret"
WECHAT_JS_CODE_SESSION_ENDPOINT="https://api.weixin.qq.com/sns/jscode2session"
WECHAT_STABLE_ACCESS_TOKEN_ENDPOINT="https://api.weixin.qq.com/cgi-bin/stable_token"
WECHAT_PHONE_NUMBER_ENDPOINT="https://api.weixin.qq.com/wxa/business/getuserphonenumber"

如果开放平台网页 OAuth 与小程序使用同一个 AppID/Secret也可以继续使用已有

WECHAT_APP_ID="你的微信 AppID"
WECHAT_APP_SECRET="你的微信 AppSecret"

但正式部署建议把小程序配置写到 WECHAT_MINI_PROGRAM_APP_IDWECHAT_MINI_PROGRAM_APP_SECRET,避免和网页 OAuth 配置混淆。

WEB_VIEW_SOURCE_QUERY 默认附加:

clientType=mini_program
clientRuntime=wechat_mini_program

小程序壳调用登录接口时会补传:

x-client-type=mini_program
x-client-runtime=wechat_mini_program
x-client-platform=ios|android|unknown
x-client-instance-id=<小程序本地持久化随机值>
x-mini-program-app-id=<MINI_PROGRAM_APP_ID>
x-mini-program-env=<MINI_PROGRAM_ENV>

这些字段会进入 refresh session 的客户端身份快照URL query 只作为 H5 识别宿主来源的轻量标记,不作为鉴权依据。

4. 登录链路

当前登录链路固定为:

  1. 小程序页面启动。
  2. 调用 wx.login 获取一次性 code
  3. 小程序壳请求:
POST /api/auth/wechat/miniprogram-login
Content-Type: application/json

{
  "code": "wx.login 返回的 code"
}
  1. api-server 调用微信 jscode2session 兑换 openid/unionid
  2. api-server 复用现有微信身份逻辑:
    • 先按 unionid 命中已有身份
    • 再按 openid 命中已有身份
    • 都没有命中时创建 pending_bind_phone 的微信壳账号
  3. api-server 签发系统 access token并写入 refresh session。
  4. 如果返回 bindingStatus=active,小程序壳打开:
https://你的H5业务域名/#auth_provider=wechat&auth_token=<系统JWT>&auth_binding_status=active
  1. 如果返回 bindingStatus=pending_bind_phone,小程序壳暂不打开 H5而是展示原生 getPhoneNumber 按钮。用户点击并同意后,小程序把 bindgetphonenumber 事件里的 detail.code 作为 wechatPhoneCode 传给:
POST /api/auth/wechat/bind-phone
Authorization: Bearer <小程序登录返回的系统JWT>
Content-Type: application/json

{
  "wechatPhoneCode": "getPhoneNumber 返回的 code"
}
  1. api-server 通过微信 stable_token 获取小程序 access_token,再调用 getuserphonenumber 换取平台验证后的手机号,并复用现有微信待绑定账号合并逻辑。微信返回的手机号字段使用 phoneNumber / purePhoneNumber / countryCode,后端解析时必须兼容这些原始 camelCase 字段;否则会在已收到 wechatPhoneCode 的情况下误报“微信手机号授权失败:缺少手机号”。成功后重新签发 active 系统 token。
  2. H5 复用 consumeAuthCallbackResult() 消费 auth_token 并进入现有登录态恢复流程。

补充H5 里的旧短信验证码绑定页继续保留为非小程序环境兜底;小程序原生手机号授权只替代“手动输入手机号 + 短信验证码”这一步,不代表后台静默读取本机号码。

5. 微信后台配置

至少需要在小程序后台配置:

  1. 业务域名:承载 H5 的域名。
  2. request 合法域名API_BASE_URL 对应的服务器域名。
  3. socket 合法域名:若后续小程序原生层直连 WebSocket 才需要;当前不启用。

当前仓库的 H5 仍建议通过同域 /api/* 访问 Rust api-server,避免在小程序和 H5 中分别维护跨域白名单。

6. 当前不做的事

本次不做原生小程序页面迁移,原因是当前主前端依赖:

  1. React DOM 挂载、浏览器 history 和 window.location
  2. localStorage / sessionStorage
  3. 浏览器 fetchReadableStream SSE。
  4. DOM、Canvas、Three.js 等浏览器渲染能力。

这些能力不能稳定原样运行在原生小程序宿主中。后续如要原生化,应新建小程序端宿主,复用 packages/shared 契约和 api-server BFF而不是把 src/ 整体搬过去。

本次也不做 openid query 直登。原因是 openid 不是本系统签发的登录凭证,不能表达 token 版本、会话 ID、绑定状态、角色与过期时间也不能被 H5 直接信任。

7. 验收口径

可重复自动化 smoke

npm run check:wechat-miniprogram-auth

该命令固定覆盖三段链路:

  1. 静态确认 miniprogram/pages/web-view/index.js 会请求 /api/auth/wechat/miniprogram-login,携带 mini_program / wechat_mini_program 客户端来源头,并把 auth_provider/auth_token/auth_binding_status 拼入 H5 hash。
  2. 运行 api-server 定向测试 wechat_miniprogram_login_returns_system_token_and_marks_session_source,断言小程序登录返回 token/bindingStatus/user、写入 refresh cookie并且 /api/auth/sessions 能看到 clientType=mini_programclientRuntime=wechat_mini_programminiProgramAppId
  3. 静态确认小程序壳在 pending_bind_phone 时使用 getPhoneNumberwechatPhoneCode 调用 /api/auth/wechat/bind-phone,而不是打开 H5 后再要求手输手机号。
  4. 运行前端 authService 定向测试,断言 consumeAuthCallbackResult() 会消费 #auth_provider=wechat&auth_token=...&auth_binding_status=...、保存 access token并清理地址栏 hash。

手工联调仍按以下口径确认真实微信与域名配置:

  1. 微信开发者工具打开项目根目录后,识别 miniprogram/ 为小程序源码目录。
  2. 未填写 WEB_VIEW_ENTRY_URLAPI_BASE_URL 时,页面显示配置提示,不出现空白页。
  3. 填写已配置业务域名后,小程序先请求 /api/auth/wechat/miniprogram-login
  4. 后端返回 token/bindingStatus/user,并写入 refresh cookie。
  5. 若返回 pending_bind_phone,先看到小程序原生授权手机号按钮;用户同意后,小程序请求 /api/auth/wechat/bind-phone 且请求体包含 wechatPhoneCode
  6. 绑定成功后首页全屏打开 H5URL hash 中包含 auth_provider=wechatauth_tokenauth_binding_status=active
  7. H5 内 consumeAuthCallbackResult() 消费 hash 后,/api/auth/me 能返回当前用户。
  8. /api/auth/sessions 能看到来源为 mini_program / wechat_mini_program 的会话记录。