通过小程序扫码登录网站,打造跨端用户互通的极致体验

1,109 阅读7分钟

通过小程序和腾讯云的账号,个人用户或企业用户都能够开通多个免费的云开发环境,用于小程序、PC网站/H5、公众号、小商店、企业微信等微信生态产品的前后端开发。在今后的文章中,我们会持续围绕微信生态的云开发来写更多系列文章,欢迎关注。

小程序扫码登录,你值得拥有

传统的网站登录方式有很多,比如使用手机号、邮箱、用户名和密码、验证码等,这些方式存在安全、隐私以及成本(短信验证码发送成本)等问题,用户体验(需要记账号)也不好。

而通过小程序扫码登录的方式,当用户打开网站时会生成一个携带了参数的小程序码,用微信扫码就会进入到小程序,在小程序上点击确认授权,网站就登录成功了。这种方式既给用户带来了极致的登录体验,还能打通小程序端和网站的用户账户体系,同时还能给你的小程序引流。

小程序扫码登录

虽然我们也可以通过微信开放平台的UnionID来区分用户的唯一性以及实现用户跨小程序和网站登录。但是这仅限于非个人用户,而且需要认证开放平台,那如果是个人用户,或者不想认证的企业用户如何实现小程序的扫码登录呢?在开发上我们可以分为三步:

  • 通过小程序获取用户信息(如用户头像、昵称、自定义信息等)并存储到云数据库;
  • 当用户要登录PC网站时,用云函数生成带有匿名登录uid的小程序码;
  • 用户使用微信扫描这个小程序码,获取scene(也就是uid),将uid与openid绑定,轮询实现PC匿名用户与小程序用户的信息同步。

通过小程序获取用户信息

每一个微信用户在每个小程序里都有唯一的openid,当用户通过小程序链路使用云开发服务时,我们就可以获取到这个openid,也就是通过云开发,我们可以让用户在打开小程序时就能免鉴权登录。

我们可以再借助于button的open-type="getUserInfo" bindgetuserinfo=""来弹出用户信息授权弹窗,在取得用户同意后,就能够获取到用户的头像、昵称、城市等信息。

我们具体来实战一下,打开微信开发者工具的云开发控制台,新建一个user集合,然后自定义user集合的安全规则为:

{
  "read": "doc._openid == auth.openid||doc.uid == auth.uid",
  "write": "doc._openid == auth.openid||doc.uid == auth.uid"
}

然后再使用微信开发者工具给小程序新建一个login的页面,然后按照要求输入以下代码:

  • 我们先调用checkUser()函数判断用户之前是否使用过小程序,如果没有使用过就创建一个记录;
  • 如果使用过,看用户之前是否允许获取用户信息,如果没有就显示“登录”的按钮,用户点击授权就获取用户信息,并将获取的信息存储到user集合内:
//在login.wxml输入以下代码,用来获取用户信息和渲染获取到的用户数据
<view hidden="{{avatarUrl}}">
  <button open-type="getUserInfo" bindgetuserinfo="getUserInfo" lang="zh_CN" >登录</button>
</view>
<image src="{{avatarUrl}}"></image>
<view>{{city}}</view>
<view>{{nickName}}</view>

//在login.js里输入以下代码
const db = wx.cloud.database()
const _ = db.command
Page({
  data: {
    avatarUrl:"",
    city:"",
    nickName:"" 
  },

  async onLoad(options){
    this.checkUser()
  },
  async checkUser() {
    const userData = (await db.collection('user').where({
      _openid:'{openid}' //建议使用安全规则,不开安全规则{openid}会失效;如果没有使用安全规则,数据库查询自带openid的权限,就不需要写这个条件了
    }).get()).data
    console.log("当前用户在数据库的数据信息",userData)

    if(userData.length === 0){       
      const result = await db.collection('user').add({
        data:{
          "nickName": "", 
          "avatarUrl": "",
          "city":""
        }
      })
      console.log("创建结果",result)
    }  
    const {avatarUrl, city, nickName} = userData[0]
    this.setData({
      avatarUrl, city, nickName      
    })
  },

  async getUserInfo (event) {
    let { avatarUrl, city, nickName}= event.detail.userInfo
    avatarUrl = avatarUrl.split("/")
    avatarUrl[avatarUrl.length - 1] = 0;
    avatarUrl = avatarUrl.join('/'); 
    this.setData({
      avatarUrl,city, nickName
    })
    this.uploadMsg(avatarUrl,city, nickName)
  },

  async uploadMsg(avatarUrl,city,nickName){   
    const result = await db.collection('user').where({
      _openid:'{openid}'
    }).update({
      data:{
        avatarUrl,city, nickName
      }
    })
    console.log("更新结果",result)
  }
})

匿名登录与生成小程序码

小程序端有openid来区分用户的唯一性,那网页端呢?我们可以使用匿名登录,用uid来区分当前web用户的唯一性(同一设备的有效期内),然后结合安全规则和权限调用云开发资源,比如调用云函数,将获取到的uid通过scene的方式生成唯一的小程序码。

1、匿名登录

要实现匿名登录,比如你使用的是云开发的静态托管,首先需要我们将网站的域名放置到Web安全域名,以及开启匿名登录。这个需要我们登录到腾讯云云开发控制台,添加域名白名单以及开启匿名登录的授权。 匿名登录

然后将下列的html代码里的云开发环境env换成自己的,然后上传到静态托管,这段代码会实现匿名登录,匿名登录之后就调用云开发环境里的weblogin云函数:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>小程序扫码登录</title>
  <script src="//imgcache.qq.com/qcloud/cloudbase-js-sdk/1.4.0/cloudbase.full.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.0/axios.min.js"></script>
  <script>
    const app = cloudbase.init({
      env: 'hackweek'  // 此处填入你的环境ID
    });

    window.onload= async function(){
      const auth = app.auth({
        persistence: 'session' //在窗口关闭时清除身份验证状态,也可以使用local
      })

      async function login(){
        const loginState = await auth.getLoginState();
        if(!loginState){
          auth.anonymousAuthProvider()
            .signIn()
            .then(() => {
              console.log("匿名登录成功")
            })
            .catch((err) => {
            });
        }else{
          console.log("您已经匿名登录啦")
        }
      }

      await login()

      async function getQRCode(){
      const result = await app.callFunction({
        name: "weblogin",
      })

      const {uid,fileId}= result.result

      const linkresult = await app.getTempFileURL({
        fileList: [fileId]
      })
      const QRCodeUrl = linkresult.fileList[0].tempFileURL
      window.uid = uid
      window.QRCodeUrl = QRCodeUrl
      const qrcodeimg = document.createElement('img'); 
      qrcodeimg.src = QRCodeUrl
      document.getElementById('qrcode').appendChild(qrcodeimg);
    }

    await getQRCode()
    }
  </script>
</head>
<body>
    <div id = "qrcode"></div>
    <div id = "avatar"></div>
</body>
</html>

2、weblogin云函数

使用开发者工具新建一个weblogin云函数,专门用于获取PC网站用户匿名登录之后的uid,然后将uid作为scene生成小程序码,不过在PC端调用weblogin云函数之前,需要我们先开启未登录用户访问云开发资源的权限:

开启未登录用户访问云开发资源的权限

然后开启让未登录用户调用weblogin云函数的权限: 匿名登录调用weblogin的权限

使用微信开发者工具,新建一个weblogin云函数,然后在package.json里的dependencies里添加@cloudbase/node-sdkaxios,然后云端安装这些模块;在index.js输入如下代码,然后增量更新:

const cloud = require('wx-server-sdk')
cloud.init({
  env:cloud.DYNAMIC_CURRENT_ENV 
})
const tcb = require("@cloudbase/node-sdk")
const app = tcb.init({ env: "hackweek" })
const auth = app.auth();
const axios = require('axios')
const APPID = "wx14b...fc8"  //换成你的小程序appid
const APPSECRET= "9d673....488fbac5b" //换成你的小程序key
exports.main = async (event, context) => {
  try {
    const { userInfo } = await auth.getEndUserInfo()
    console.log(userInfo)
    const {uid} = userInfo
    const tokenurl = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`
    const res = await axios.get(tokenurl)
    const {access_token} = res.data

    const qrcodeurl=`https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${access_token}`
    const wxacodeResult = await axios({
      method: 'post',
      url: qrcodeurl,
      responseType: 'arraybuffer',
      data: {
        scene:uid,
        page:"pages/login/login"
      }
    });

    const uploadResult = await cloud.uploadFile({
      cloudPath: `${uid}.jpeg`,
      fileContent: wxacodeResult.data
    })

    return {
      uid:uid,
      fileId:uploadResult.fileID
    }
  } catch (error) {
    console.log(error.message);
  }
}
  • 当我们再来访问静态托管里的网站链接时,会先匿名登录然后调用weblogin云函数,
  • 云函数会先从auth.getEndUserInfo()里获取到匿名登录用户的uid,然后再调用getwxacodeunlimit的接口生成小程序码,值得注意的是使用getwxacodeunlimit需要小程序已经发布上线,而且page页面也是已经发布的页面,
  • 生成小程序码之后会将小程序码上传到云存储,并返回该图片在云存储的fileID给返回给页面
  • 页面将获取到的fileID转换成HTTP临时地址tempFileURL,并通过操作DOM的方式渲染到页面,这样用户就能在网页上看到小程序码了

用户信息的互通与同步

当用户使用微信扫描小程序码,就会进入到小程序,我们可以通过onLoad的options获取到scene里的uid并将uid更新到用户信息的记录里,实现uid与openid的绑定

1、打通uid与openid

在小程序的login页面里的onLoad生命周期函数里输入以下代码,这里我们直接将uid更新到记录,你也可以在交互上设置一个只有用户确认登录网页才将uid更新到记录里:

if(options.scene){
  const scene = decodeURIComponent(options.scene)
  const result = await db.collection('user').where({
    _openid:'{openid}'
  }).update({
    data:{
      uid:scene
    }
  })
  console.log("更新结果",result)
}

2、轮询并渲染用户信息到网页

我们可以通过查询数据库里是否有用户的uid来了解用户是否扫码确认登录了,当用户在扫码登录之前,页面会对数据库进行每5秒的轮询,当用户扫码登录之后,会终止轮询并获取数据库里用户信息,并将用户信息渲染到网页上,这样就实现了用户的信息同步:

  async function getUserInfo () {
    const uid = window.uid
    const userInfo = await app.database().collection("user")
    .where({
      uid:uid   
    })
    .get()
    console.log("获取到的用户信息",userInfo)
    return userInfo
  }
  
  const interval = async function () {
    const userData = await getUserInfo();  
    if(userData.data.length == 0){
      setTimeout(interval, 5000);
    }else{
      clearTimeout(interval)
      const {avatarUrl,city,nickName} = userData.data[0]
      document.getElementById('qrcode').style.visibility='hidden' 
      document.getElementById('avatar').innerHTML = `<img width="100" height="100" src="${avatarUrl}"><br>${nickName}<br>${city}`
    }
  }

  interval()

本文几乎涉及了扫码登录的所有关键细节,由于篇幅有限,所以代码并没有完整的展示,如果你在运行时晕倒什么问题,欢迎到公众号“李东bbsky”里留言,一起交流。