9.0 KiB
微信小程序 web-view 壳接入记录
日期:2026-05-03
1. 目标
本次先用微信小程序 web-view 承载现有 H5,不重写 React/Vite 主前端,也不把 SpacetimeDB SDK 或业务规则搬进小程序端。
当前小程序壳只承担五件事:
- 提供微信开发者工具可识别的
miniprogram/工程根目录。 - 在原生小程序壳中调用
wx.login获取小程序code。 - 调用服务器域名下的
/api/auth/wechat/miniprogram-login,由 Rustapi-server兑换微信身份并签发系统登录态。 - 若后端返回
pending_bind_phone,先在小程序原生层通过button open-type="getPhoneNumber"取得用户同意后的手机号动态令牌,再调用/api/auth/wechat/bind-phone完成绑定。 - 用一个全屏
web-view打开现有 H5 入口,并把系统auth_token放入 H5 现有登录回调 hash。
重要边界:
openid只作为后端微信身份绑定依据,不直接暴露给 H5 当登录凭证。- H5 继续消费本系统 JWT,也就是
#auth_provider=wechat&auth_token=...&auth_binding_status=...。 - 这与
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';
约束:
- 必须是
https。 - 不能是
localhost或 IP。 WEB_VIEW_ENTRY_URL域名需要在微信小程序后台配置为业务域名。API_BASE_URL域名需要在微信小程序后台配置为 request 合法域名。- 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_ID 与 WECHAT_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. 登录链路
当前登录链路固定为:
- 小程序页面启动。
- 调用
wx.login获取一次性code。 - 小程序壳请求:
POST /api/auth/wechat/miniprogram-login
Content-Type: application/json
{
"code": "wx.login 返回的 code"
}
api-server调用微信jscode2session兑换openid/unionid。api-server复用现有微信身份逻辑:- 先按
unionid命中已有身份 - 再按
openid命中已有身份 - 都没有命中时创建
pending_bind_phone的微信壳账号
- 先按
api-server签发系统 access token,并写入 refresh session。- 如果返回
bindingStatus=active,小程序壳打开:
https://你的H5业务域名/#auth_provider=wechat&auth_token=<系统JWT>&auth_binding_status=active
- 如果返回
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"
}
api-server通过微信stable_token获取小程序access_token,再调用getuserphonenumber换取平台验证后的手机号,并复用现有微信待绑定账号合并逻辑。微信返回的手机号字段使用phoneNumber/purePhoneNumber/countryCode,后端解析时必须兼容这些原始 camelCase 字段;否则会在已收到wechatPhoneCode的情况下误报“微信手机号授权失败:缺少手机号”。成功后重新签发active系统 token。- H5 复用
consumeAuthCallbackResult()消费auth_token并进入现有登录态恢复流程。
补充:H5 里的旧短信验证码绑定页继续保留为非小程序环境兜底;小程序原生手机号授权只替代“手动输入手机号 + 短信验证码”这一步,不代表后台静默读取本机号码。
5. 微信后台配置
至少需要在小程序后台配置:
业务域名:承载 H5 的域名。request 合法域名:API_BASE_URL对应的服务器域名。socket 合法域名:若后续小程序原生层直连 WebSocket 才需要;当前不启用。
当前仓库的 H5 仍建议通过同域 /api/* 访问 Rust api-server,避免在小程序和 H5 中分别维护跨域白名单。
6. 当前不做的事
本次不做原生小程序页面迁移,原因是当前主前端依赖:
- React DOM 挂载、浏览器 history 和
window.location。 localStorage/sessionStorage。- 浏览器
fetch与ReadableStreamSSE。 - DOM、Canvas、Three.js 等浏览器渲染能力。
这些能力不能稳定原样运行在原生小程序宿主中。后续如要原生化,应新建小程序端宿主,复用 packages/shared 契约和 api-server BFF,而不是把 src/ 整体搬过去。
本次也不做 openid query 直登。原因是 openid 不是本系统签发的登录凭证,不能表达 token 版本、会话 ID、绑定状态、角色与过期时间,也不能被 H5 直接信任。
7. 验收口径
可重复自动化 smoke:
npm run check:wechat-miniprogram-auth
该命令固定覆盖三段链路:
- 静态确认
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。 - 运行
api-server定向测试wechat_miniprogram_login_returns_system_token_and_marks_session_source,断言小程序登录返回token/bindingStatus/user、写入 refresh cookie,并且/api/auth/sessions能看到clientType=mini_program、clientRuntime=wechat_mini_program、miniProgramAppId。 - 静态确认小程序壳在
pending_bind_phone时使用getPhoneNumber和wechatPhoneCode调用/api/auth/wechat/bind-phone,而不是打开 H5 后再要求手输手机号。 - 运行前端
authService定向测试,断言consumeAuthCallbackResult()会消费#auth_provider=wechat&auth_token=...&auth_binding_status=...、保存 access token,并清理地址栏 hash。
手工联调仍按以下口径确认真实微信与域名配置:
- 微信开发者工具打开项目根目录后,识别
miniprogram/为小程序源码目录。 - 未填写
WEB_VIEW_ENTRY_URL或API_BASE_URL时,页面显示配置提示,不出现空白页。 - 填写已配置业务域名后,小程序先请求
/api/auth/wechat/miniprogram-login。 - 后端返回
token/bindingStatus/user,并写入 refresh cookie。 - 若返回
pending_bind_phone,先看到小程序原生授权手机号按钮;用户同意后,小程序请求/api/auth/wechat/bind-phone且请求体包含wechatPhoneCode。 - 绑定成功后首页全屏打开 H5,URL hash 中包含
auth_provider=wechat、auth_token、auth_binding_status=active。 - H5 内
consumeAuthCallbackResult()消费 hash 后,/api/auth/me能返回当前用户。 /api/auth/sessions能看到来源为mini_program / wechat_mini_program的会话记录。