通过小程序和腾讯云的账号,个人用户或企业用户都能够开通多个免费的云开发环境,用于小程序、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云函数,然后在package.json里的dependencies
里添加@cloudbase/node-sdk
和axios
,然后云端安装这些模块;在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”里留言,一起交流。