前言:你是否遇到过这样的场景,在日常开发中,一个需求但是要开发多端:H5端、微信小程序端、支付宝小程序端、app端。一样的功能写了很多遍,一样的bug也要改很多遍,是不是很难受?所以,我决定多端共用H5页面,使用webview进行集成。
一、WebView 嵌 H5的优势
小程序有审核严、包体积紧、发版节奏固定等特点;而很多业务早已有一套成熟的 H5 应用——功能全、迭代快、多端复用。如果能在小程序里「开一扇窗」,用 WebView 直接加载 H5,就能:
- 复用现有 H5,不必把整块业务重写进小程序;
- 统一体验:H5 在浏览器、APP、小程序里同一套代码;
- 灵活发版:H5 更新即可生效,不依赖小程序审核。
二、整体架构
| 角色 | 职责 |
|---|---|
| 小程序 | 提供 WebView 容器,打开 H5 的「入口页」;与 H5 双向通信(如支付结果回传)。 |
| 后端服务 | 根据当前用户与业务状态,生成「带鉴权信息的 H5 地址」;统一鉴权(如 Session)。 |
| H5 应用 | 在 WebView 里运行,识别是否来自小程序,并按渠道做登录、返回、支付等适配。 |
| 原生 APP(可选) | 同样通过 WebView 打开 H5,与小程序共用一套跳转逻辑,仅入口不同。 |
数据流可以简化为:
- 用户在小程序里点击某个业务入口;
- 小程序调后端「获取 H5 跳转地址」接口;
- 后端用当前 Session 用户信息生成带加密参数的 H5 URL,返回给小程序;
- 小程序跳转到通用 WebView 页,
src设为该 URL; - H5 打开后解析 URL 参数,用其中的信息完成静默登录或业务态恢复;
- 需要支付、返回等时,H5 通过
postMessage等与小程序通信,由小程序调原生能力再回传结果。
三、小程序端:WebView 怎么用
3.1 一个通用 WebView 页就够了
不需要每个业务一个 WebView 页面,一个通用页 + URL 参数即可:
<web-view
class="web-view"
:src="src"
@message="onMessage"
:id="webViewId"
/>
- src:要加载的 H5 完整地址(从后端拿到后解码再赋值)。
- @message:接收 H5 发来的消息(例如「请调起支付」)。
- id:支付宝小程序里必填,且需固定不变,后面用
my.createWebViewContext(id)做双向通信时会用到;微信无此要求,但统一写一个 id 也无妨。
页面 onLoad 里只做两件事:从路由参数里取 url 并解码赋给 src;在支付宝下创建 webViewContext:
onLoad(option) {
this.src = decodeURIComponent(option.url)
// 支付宝小程序:创建 WebView 上下文,用于后续向 H5 发消息
// #ifdef MP-ALIPAY
this.webViewContext = my.createWebViewContext(this.webViewId)
// #endif
}
3.2 如何打开 H5、传参
原则:参数全部放在「一个 H5 地址」里。 小程序不单独拼业务参数,只负责「跳转到通用 WebView 页 + 把完整 URL 带过去」:
// 先请求后端,拿到带鉴权信息的 H5 完整地址
const h5Url = await api.getUrl({ state: xxx })
uni.navigateTo({
url: '/pages/common/web-view/index?url=' + encodeURIComponent(h5Url)
})
也就是说:传参 = 后端在生成 H5 URL 时就把 state、渠道、加密用户信息等全部塞进 query;小程序只做 encodeURIComponent 和跳转,保证地址完整、可解码即可。
3.3 小程序与 H5 的双向通信
微信小程序
- H5 → 小程序:
wx.miniProgram.navigateBack({ delta: 1 })返回、wx.miniProgram.navigateTo({ url })跳转小程序内页(如支付页)。 - 小程序 → H5:微信侧由小程序在
onShow等时机通过postMessage下发;H5侧接收即可。
支付宝小程序
- H5 → 小程序:
my.postMessage({ name: 'xxx', ... })发消息,my.navigateBack()返回; - 小程序 → H5:在 H5 里挂
my.onMessage,将收到的action、content转发到事件总线,业务按 action 订阅:
if (window.my) {
window.my.onMessage = (e) => {
eventBus.$emit(e.action, e.content)
}
}
四、后端:跳转地址与鉴权
4.1 提供「获取 H5 跳转地址」接口
后端提供一个接口,例如:根据当前登录态与业务 state,返回可直接在 WebView 里打开的 H5 地址。
- 入参:如
state(业务态标识)、clientType(微信/支付宝等)。 - 鉴权:接口必须要求用户已登录(如用 Session:从 Cookie 取 JSESSIONID,Session 里存
MP_ACCOUNT等)。可对请求体中的clientType与 Session 里存的渠道做一致性校验,防止混用。 - 逻辑:用当前用户信息(及必要业务参数)按安全规则拼成 H5 的落地页 URL。常见做法是:把用户关键信息用 RSA 公钥加密 成一段
info,再拼到 H5 域名下的落地页后,如:https://h5.example.com/entry?info=xxxxxx。这样 H5 端只需解密info即可完成静默登录或恢复态,无需再传明文 token。
不同平台(微信/支付宝)可用不同 RSA 公钥,配置在服务端即可。
五、H5 端:识别环境与通信
5.1 判断是否在小程序 WebView 里
常用两种方式配合使用:
(1)getEnv
- 微信小程序 WebView:
wx.miniProgram.getEnv。 - 支付宝小程序WebView:
my.getEnv()。
ps:在微信里若要用 wx.miniProgram 等能力,需要在 H5 里动态注入微信 JS-SDK(如 jweixin-1.4.0.js),可仅在检测到「微信小程序」UA 时再加载,避免在普通浏览器里多拉一份脚本。
(2)URL 参数(渠道码)
后端在生成 H5 地址时往往会带上「渠道」参数(如 qd,可自定义)。H5 启动时解析 URL,把渠道码存到全局状态,后续根据渠道码判断:
这样即使用户从别的入口把链接拷到浏览器打开,也能通过缺省渠道码或 UA 做降级。
5.2 登录与用户信息
- 静默登录:H5 落地页 URL 中已带加密的
info(或类似参数),H5 先解密,再调后端「用该信息换 Session / 用户信息」的接口,完成登录态建立。 - 带 code 的授权:若采用 code 换 openid 等流程,可在 URL 上带
code,H5 用code调后端换用户信息并写入本地状态。 - 后续请求都带 Cookie(同域)或按后端要求带 token,与小程序侧共用同一套后端鉴权即可。
六、约定与注意事项
6.1 通信协议约定
- 建议统一约定消息格式,例如:
{ action: string, content: object }。 - 常见 action 示例:H5 请求调起支付、小程序回传支付结果、H5 请求关闭/返回等。名称由前后端、多端一起定,避免歧义。
6.2 平台差异
- 支付宝:WebView 的
id必填,否则createWebViewContext无法正确关联,双向通信会失败。 - 微信:需在后台配置「业务域名」,否则 WebView 无法打开该域名下的页面;H5 内使用
wx.miniProgram前需先注入微信 JS-SDK。
6.3 与原生 APP 的共用
若同一套 H5 还要在原生 APP 的 WebView 里打开,可以:
- 在 H5 里根据 UA 或 URL 渠道码识别「APP / 微信小程序 / 支付宝小程序」;
- 统一封装「返回」「支付」等能力:在 APP 里调提供的原生方法,在小程序里调
wx.miniProgram/my.postMessage,但是对业务层暴露同一套接口(如backService.back()),便于维护。
七、小结
在小程序里用 WebView 集成 H5,本质是:小程序只做「容器 + 桥」,H5 的 URL 由后端按登录态与安全规则生成,参数与鉴权都在这条 URL 上完成;
双向通信则依赖各平台的 postMessage / onMessage, 以及小程序提供的 navigateBack、支付插件等能力,并在一套约定好的 action/content 协议下协作。
搭建虽然费些力气,但是一想到能减少70%的开发工作量,又觉得都是值得的!