小程序Webview

1 4月

小程序里加载webview可以绕过微信代码审核与发版。但个人账号是无法使用webview的,只有企业账号才能使用webview,需要到配置后台配置业务域名,webview只能加载业务域名下的页面。

环境判断

webview加载的是h5页面,可能需要判断该h5页面是否在小程序环境里打开。

可以通过userAgent判断是否在小程序内。也可以自定义些校验方法,例如小程序加载webview时,url的query里加点特殊标记。后续webview间页面跳转时同样记得url上带上这个特殊标记,为防止遗忘,也可以将特殊标记保存在cookie里供后续页面使用。如果url的query里或者cookie都没有特殊标记,h5页面还能通过是否加载了weixin的js sdk来判断是否在小程序里。

<script type="text/javascript" src="/static/js/jweixin-1.6.0.js"></script>

let isInMiniprogram = false
if (/token=\d+/.test(window.location.search)) {             // 例如小程序加载webview时,可以在url上带上token鉴权信息,如果query里有token,说明是小程序访问
    isInMiniprogram = true
    $.cookie("isInMiniprogram", true)
    ...
} else if (/miniProgram/.test(navigator.userAgent)) {     // 通过userAgent看是不是小程序
    isInMiniprogram = true
    $.cookie("isInMiniprogram", true)  
    ...
} else if (($.cookie("isInMiniprogram") || '') == 'true') { // 后续webview间页面跳转可以从cookie里判断是否在小程序内
    isInMiniprogram = true
    ...
} else {                                                    // 兜底策略:通过判断是否加载了weixin的js sdk。js sdk会在window对象上挂weixin特殊变量
    function onWeixinJSBridgeReady() {
        if (window.__wxjs_environment === 'miniprogram') {
            isInMiniprogram = true
            $.cookie("isInMiniprogram", true)  
            ...
        }
    }
    if (!window.WeixinJSBridge || !WeixinJSBridge.invoke) { // 加载js sdk需要一定的时间,如果还没加载完,可以监听js sdk里抛出的ready事件
        document.addEventListener('WeixinJSBridgeReady', onWeixinJSBridgeReady, false)
    } else {
        onWeixinJSBridgeReady()
    }
}

登录

小程序登录和webview登录是两套隔离的登录体系,小程序里的webview页面无法写header,useragent,cookie,只支持小程序通过url向webview页面传递数据。所以想将小程序的登录状态传到webview里,可以通过以下方式:

  • 方式一:小程序直接通过OAuth进行登录,登录后将token放到url的query参数里传给webview。
  • 方式二:在后台创建一个中转页面,通过参数将它与webview的url与登录token关联起来。先将token写入中转页面的cookie里,然后302跳转到目标页面

方式一较简单,也更常用。先在小程序客户端进行登录,具体步骤:

  1. 通过wx.getUserInfo获取:用户基本信息userInfo,加密过的数据encryptedData,解密参数iv。
  2. 通过wx.login来获取用户临时登录凭证code。
  3. 最后将这些参数发送给服务端,在服务端进行解密并验证。
<button style="margin-top:20rpx;" bindgetuserinfo="login" open-type="getUserInfo" type="primary">微信登陆</button>

login(e) {
    let { userInfo, encryptedData, iv } = e.detail
    // 用户基本信息userInfo:{nickName, avatarUrl, city, country, gender, ...}
    // 加密过的数据encryptedData:YQvHU1TDXZYZb4NtLDOGzAT9x......Y4HHtojE0iw8NRiljYLUyImw9YpX2ylk=
    // 解密参数iv:OoMZpC3q4psSyDlVF4ncqw

    wx.login({
        success(res0) {  // {code: "021CAC000EA1sL1QbA100gZ5sJ0CAC0m", errMsg: "login:ok"}
            if (res0.code) {
                wx.request({
                    url: 'https://xxxx/weixin-login',
                    method: 'POST',
                    header: {
                        'content-type': 'application/json'
                    },
                    data: {  // 将这些参数发送给服务端
                        code: res0.code,
                        userInfo,
                        encryptedData,
                        iv
                    },
                    success(res) {
                        getApp().globalData.token = res.data.data.authorizationToken // 登录成功后,将服务端返回的token保存起来
                    },
                    fail(err) {
                        console.log('请求异常', err)
                    }
                })
            } else {
                console.log('登录失败!' + res0.errMsg)
            }
        }
    })
}

服务端接口收到客户端传来的code,userInfo,encryptedData,iv进行解密并获取token返回给客户端,具体步骤:

  1. 通过微信公开接口:https://api.weixin.qq.com/sns/jscode2session,用code去取token
  2. 用encryptedData,iv和token里的session_key去解密,拿到解密后的用户信息decryptedUserInfo(内含openId)
  3. 将解密后的用户信息decryptedUserInfo,token里的session_key,保存到db里
  4. 用decryptedUserInfo里的openId,token里的session_key等信息,通过JSON Web Token(jwt)生成服务端token
  5. 将通过jwt生成服务端token返回给客户端,后续客户端将token带在url或cookie里可以自动通过jwt校验
const WeixinAuth = require("koa2-weixin-auth")
const jsonwebtoken = require('jsonwebtoken')

router.post("/weixin-login", async (ctx) => {
    let { 
        code,
        userInfo,
        encryptedData,
        iv,
    } = ctx.request.body

    const weixinAuth = new WeixinAuth(config.miniProgram.appId, config.miniProgram.appSecret) // 用微信小程序的appid,appSecret初始化
    const token = await weixinAuth.getAccessToken(code) // 通过微信公开接口:https://api.weixin.qq.com/sns/jscode2session 用用户临时登录凭证code去取token
    // 微信服务端用户token:
    // {
    //   data: { 
    //     session_key: 'G/hkdgl...pr6lpSg==',
    //     expires_in: 7200,
    //     openid: 'omObr...Snk0KE8',
    //     create_at: 1617246457260 
    //   } 
    // }
    const sessionKey = token.data.session_key

    var pc = new WXBizDataCrypt(config.miniProgram.appId, sessionKey)
    let decryptedUserInfo = pc.decryptData(encryptedData, iv) // 用encryptedData,iv和token里的session_key去解密,拿到解密后的用户信息decryptedUserInfo(内含openId)
    // decryptedUserInfo: { openId, nickName, gender, language, city, province, country, avatarUrl, watermark }

    // 将解密后的用户信息decryptedUserInfo,与token里的session_key,保存到db里
    ...
    
    let authorizationToken = jsonwebtoken.sign({ //用decryptedUserInfo里的openId,token里的session_key等信息,通过jwt生成服务端token
        uid: user.id,
        nickName: decryptedUserInfo.nickName,
        avatarUrl: decryptedUserInfo.avatarUrl,
        openId: decryptedUserInfo.openId,
        sessionKey: sessionKey
    },
        config.jwtSecret,
        { expiresIn: '3d' }
    )
    Object.assign(decryptedUserInfo, { authorizationToken })

    // 将通过jwt生成的服务端token返回给客户端,后续客户端将token带在url或cookie里可以自动通过jwt校验
    ctx.status = 200
    ctx.body = {
        code: 200,
        msg: 'ok',
        data: decryptedUserInfo
    }
})

客户端拿到服务端token后,后续webview页面跳转都可以将token带到url上,或cookie里,在服务端jwt校验通过后,就实现了免登陆:

// 打开webview
<web-view src="{{url}}"></web-view>

onLoad: function (options) {
    let token = getApp().globalData.token
    let url = `http://xxxx/web-view?token=${token}`
    this.setData({ url })
},

总结:小程序客户端用open-type=”getUserInfo”拿到encryptedData,iv,用wx.login拿到code。服务器端用code拿到session_key,用session_key加上appid,appSecret去解密encryptedData,iv。用解密出的openId生成jwt服务端token。登录流程图见官网

分享

对于webview的网页,可以通过wx.miniProgram.postMessage向小程序发消息,告知小程序分享卡片的title和截图path(不提供,小程序会自动生成当前页面的截图作为分享卡片的img)等信息,postMessage的数据会被存储在本地。

wx.miniProgram.postMessage({
    data: JSON.stringify({
        action: 'share',
        title: window.document.title
    })
});

webview有3种情况会触发pages的默认回调函数onReceivedMessage

  • 小程序后退
  • 小程序分享
  • webview被销毁

在小程序里分享有两种:一种是单击胶囊按钮分享,二是点击open-type为share的button分享,都互触发小程序pages的默认回调函数onShareAppMessage

两个结合起来在onReceivedMessage获取webview通过postMessage发送来的数据,在onShareAppMessage里生成分享卡片。

onReceivedMessage(e) {
    let data = e.detail.data  // data可能是多次postMessage的参数组成的数组,拿到webview传给小程序的页面数据
    if (Array.isArray(data)) data = data[0]
    this.setData({
      xxxx
    })
},
onShareAppMessage(options) {
    return {
        title: xxxx,
        path: xxxx
    }
},

发表评论

邮箱地址不会被公开。 必填项已用*标注