彻底搞懂微信小程序登录流程-附小程序和服务端代码
2020-01-15 08:59实搜网络
微信小程序登录是很适用的功能,相信大家对这个功能的实现很感兴趣,那么,今天就为大家介绍下微信小程序登录的开发实现,有什么问题请联系实搜客服,实搜客为您解答小程序的各种难题。
微信小程序登录流程实现
用户登录是大部分完整App必备的流程,一个简单的用户系统需要关注至少这些层面
安全性(加密)
持久化登录态(类似cookie)
登录过期处理
确保用户唯一性,避免出现多账号
授权
绑定用户昵称头像等信息
绑定手机号(实名和密保方式)
很多的业务需求都可以抽象成Restful接口配合CRUD操作
但登录流程却是错综复杂,各个平台有各自的流程,反倒成了项目中费时间的部分,比如小程序的登录流程
对于一个从零开始的项目来说,搞定登录流程,就是一个好的开始,一个好的开始,就是成功的一半
本文就以微信小程序这个平台,讲述一个完整的自定义用户登录流程,一起来啃这块难啃的骨头
名词解释
先给登录流程时序图中出现的名词简单做一个解释
code临时登录凭证,有效期五分钟,通过wx.login()获取
session_key会话密钥,服务端通过code2Session获取
openId用户在该小程序下的用户唯一标识,永远不变,服务端通过code获取
unionId用户在同一个微信开放平台帐号(公众号,小程序,网站,移动应用)下的唯一标识,永远不变
appId小程序唯一标识
appSecret小程序的appsecret,可以和code,appId一起换取session_key
其他名词
rawData不包括敏感信息的原始数据字符串,用于计算签名
encryptedData包含敏感信息的用户信息,是加密的
signature用于校验用户信息是否无篡改
iv加密算法的初始向量
哪些信息是敏感信息呢?手机号,openId,unionId,可以看出这些值都可以唯一定位一个用户,而昵称,头像这些不能定位用户的都不是敏感信息
小程序登录相关函数
wx.login
wx.getUserInfo
wx.checkSession
小程序的promise
我们发现小程序的异步接口都是success和fail的回调,很容易写出回调地狱
因此可以先简单实现一个wx异步函数转成promise的工具函数
constpromisify=original=>{
returnfunction(opt){
returnnewPromise((resolve,reject)=>{
opt=Object.assign({
success:resolve,
fail:reject
},opt)
original(opt)
})
}
}
这样我们就可以这样调用函数了
promisify(wx.getStorage)({key:'key'}).then(value=>{
//success
}).catch(reason=>{
//fail
})
服务端实现
本demo的服务端实现基于express.js
注意,为了demo的简洁性,服务端使用js变量来保存用户数据,也就是说如果重启服务端,用户数据就清空了
如需持久化存储用户数据,可以自行实现数据库相关逻辑
//存储所有用户信息
constusers={
//openId作为索引
openId:{
//数据结构如下
openId:'',//理论上不应该返回给前端
sessionKey:'',
nickName:'',
avatarUrl:'',
unionId:'',
phoneNumber:''
}
}
app
.use(bodyParser.json())
.use(session({
secret:'alittlegirl',
resave:false,
saveUninitialized:true
}))
小程序登录
我们先实现一个基本的oauth授权登录
oauth授权登录主要是code换取openId和sessionKey的过程
前端小程序登录
写在app.js中
login(){
console.log('登录')
returnutil.promisify(wx.login)().then(({code})=>{
console.log(`code:${code}`)
returnhttp.post('/oauth/login',{
code,
type:'wxapp'
})
})
}
服务端实现oauth授权
服务端实现上述/oauth/login这个接口
app
.post('/oauth/login',(req,res)=>{
varparams=req.body
var{code,type}=params
if(type==='wxapp'){
//code换取openId和sessionKey的主要逻辑
axios.get('https://api.weixin.qq.com/sns/jscode2session',{
params:{
appid:config.appId,
secret:config.appSecret,
js_code:code,
grant_type:'authorization_code'
}
}).then(({data})=>{
varopenId=data.openid
varuser=users[openId]
if(!user){
user={
openId,
sessionKey:data.session_key
}
users[openId]=user
console.log('新用户',user)
}else{
console.log('老用户',user)
}
req.session.openId=user.openId
req.user=user
}).then(()=>{
res.send({
code:0
})
})
}else{
thrownewError('未知的授权类型')
}
})
获取用户信息
登录系统中都会有一个重要的功能:获取用户信息,我们称之为getUserInfo
如果已登录用户调用getUserInfo则返回用户信息,比如昵称,头像等,如果未登录则返回'用户未登录'
也就是说此接口还有判断用户是否登录的功效...
小程序的用户信息一般存储在app.globalData.userInfo中(模板如此)
我们在服务端加上前置中间件,通过session来获取对应的用户信息,并放在req对象中
app
.use((req,res,next)=>{
req.user=users[req.session.openId]
next()
})
然后实现/user/info接口,用来返回用户信息
app
.get('/user/info',(req,res)=>{
if(req.user){
returnres.send({
code:0,
data:req.user
})
}
thrownewError('用户未登录')
})
小程序调用用户信息接口
getUserInfo(){
returnhttp.get('/user/info').then(response=>{
letdata=response.data
if(data&&typeofdata==='object'){
//获取用户信息成功则保存到全局
this.globalData.userInfo=data
returndata
}
returnPromise.reject(response)
})
}
专为小程序发请求设计的库
小程序代码通过http.get,http.post这样的api来发请求,背后使用了一个请求库,@chunpu/http1是一个专门为小程序设计的http请求库,可以在小程序上像axios一样发请求,支持拦截器等强大功能,甚至比axios更顺手
初始化方法如下
importhttpfrom'@chunpu/http'
http.init({
baseURL:'http://localhost:9999',//定义baseURL,用于本地测试
wx//标记是微信小程序用
})
具体使用方法可参照文档https://github.com/chunpu/http#readme
自定义登录态持久化
浏览器有cookie,然而小程序没有cookie,那怎么模仿出像网页这样的登录态呢?
这里要用到小程序自己的持久化接口,也就是setStorage和getStorage
为了方便各端共用接口,或者直接复用web接口,我们自行实现一个简单的读cookie和种cookie的逻辑
先是要根依据返回的httpresponseheaders来种上cookie,此处我们用到了@chunpu/http中的response拦截器,和axios用法一样
http.interceptors.response.use(response=>{
//种cookie
var{headers}=response
varcookies=headers['set-cookie']||''
cookies=cookies.split(/,*/).reduce((prev,item)=>{
item=item.split(/;*/)[0]
varobj=http.qs.parse(item)
returnObject.assign(prev,obj)
},{})
if(cookies){
returnutil.promisify(wx.getStorage)({
key:'cookie'
}).catch(()=>{}).then(res=>{
res=res||{}
varallCookies=res.data||{}
Object.assign(allCookies,cookies)
returnutil.promisify(wx.setStorage)({
key:'cookie',
data:allCookies
})
}).then(()=>{
returnresponse
})
}
returnresponse
})
当然我们还需要在发请求的时候带上所有cookie,此处用的是request拦截器
http.interceptors.request.use(config=>{
//给请求带上cookie
returnutil.promisify(wx.getStorage)({
key:'cookie'
}).catch(()=>{}).then(res=>{
if(res&&res.data){
Object.assign(config.headers,{
Cookie:http.qs.stringify(res.data,';','=')
})
}
returnconfig
})
})
登录态的有效期
我们知道,浏览器里面的登录态cookie是有失效时间的,比如一天,七天,或者一个月,也许有朋友会提出疑问,直接用storage的话,小程序的登录态有效期怎么办?
小程序已经帮我们实现好了session有效期的判断wx.checkSession
它比cookie更智能,官方文档描述如下
通过wx.login接口获得的用户登录态拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能失效。反之如果用户一直在使用小程序,则用户登录态一直保持有效
也就是说小程序还会帮我们自动renew咱们的登录态,简直是人工智能cookie,点个赞�
那具体在前端怎么操作呢?代码写在app.js中
onLaunch:function(){
util.promisify(wx.checkSession)().then(()=>{
console.log('session生效')
returnthis.getUserInfo()
}).then(userInfo=>{
console.log('登录成功',userInfo)
}).catch(err=>{
console.log('自动登录失败,重新登录',err)
returnthis.login()
}).catch(err=>{
console.log('手动登录失败',err)
})
}
要注意,这里的session不仅是前端的登录态,也是后端session_key的有效期,前端登录态失效了,那后端也失效了需要更新session_key。理论上小程序也可以自定义登录失效时间策略,但这样的话我们需要考虑开发者自己的失效时间和小程序接口服务的失效时间,还不如保持统一来的简单。确保每个Page都能获取到userInfo,如果在新建小程序项目中选择建立普通快速启动模板,我们会得到一个可以直接运行的模板,点开代码一看,大部分代码都在处理userInfo....
注释里写着
由于getUserInfo是网络请求,可能会在Page.onLoad之后才返回,所以此处加入callback以防止这种情况。
但这样的模板并不科学,这样仅仅是考虑了首页需要用户信息的情况,如果扫码进入的页面也需要用户信息呢?还有直接进入跳转的未支付页活动页等...
如果每个页面都这样判断一遍是否加载完用户信息,代码显得过于冗余
此时我们想到了jQuery的ready函数$(function),只要documentready了,就可以直接执行函数里面的代码,如果document还没ready,就等到ready后执行代码
就这个思路了!我们把小程序的App当成网页的document
我们的目标是可以这样在Page中不会出错的获取userInfo
Page({
data:{
userInfo:null
},
onLoad:function(){
app.ready(()=>{
this.setData({
userInfo:app.globalData.userInfo
})
})
}
})
此处我们使用min-ready2来实现此功能,代码实现依然写在app.js中
importReadyfrom'min-ready'
constready=Ready()
App({
getUserInfo(){
//获取用户信息作为全局方法
returnhttp.get('/user/info').then(response=>{
letdata=response.data
if(data&&typeofdata==='object'){
this.globalData.userInfo=data
//获取userInfo成功的时机就是appready的时机
ready.open()
returndata
}
returnPromise.reject(response)
})
},
ready(func){
//把函数放入队列中
ready.queue(func)
}
})
绑定用户信息和手机号
仅仅获取用户的openId是远远不够的,openId只能标记用户,连用户的昵称和头像都拿不到。如何获取这些用户信息然后存到后端数据库中呢?
我们在服务端实现这两个接口,绑定用户信息,绑定用户手机号
app
.post('/user/bindinfo',(req,res)=>{
varuser=req.user
if(user){
var{encryptedData,iv}=req.body
varpc=newWXBizDataCrypt(config.appId,user.sessionKey)
vardata=pc.decryptData(encryptedData,iv)
Object.assign(user,data)
returnres.send({
code:0
})
}
thrownewError('用户未登录')
})
.post('/user/bindphone',(req,res)=>{
varuser=req.user
if(user){
var{encryptedData,iv}=req.body
varpc=newWXBizDataCrypt(config.appId,user.sessionKey)
vardata=pc.decryptData(encryptedData,iv)
Object.assign(user,data)
returnres.send({
code:0
})
}
thrownewError('用户未登录')
})
小程序个人中心wxml实现如下
wx:if='{{!userInfo.nickName}}'
type='primary'
open-type='getUserInfo'
bindgetuserinfo='bindUserInfo'>获取头像昵称
{{userInfo.nickName}}
wx:if='{{!userInfo.phoneNumber}}'
type='primary'
style='margin-top:20px;'
open-type='getPhoneNumber'
bindgetphonenumber='bindPhoneNumber'>绑定手机号
{{userInfo.phoneNumber}}
小程序中的bindUserInfo和bindPhoneNumber函数,根据微信最新的策略,这俩操作都需要用户点击按钮统一授权才能触发
bindUserInfo(e){
vardetail=e.detail
if(detail.iv){
http.post('/user/bindinfo',{
encryptedData:detail.encryptedData,
iv:detail.iv,
signature:detail.signature
}).then(()=>{
returnapp.getUserInfo().then(userInfo=>{
this.setData({
userInfo:userInfo
})
})
})
}
},
bindPhoneNumber(e){
vardetail=e.detail
if(detail.iv){
http.post('/user/bindphone',{
encryptedData:detail.encryptedData,
iv:detail.iv
}).then(()=>{
returnapp.getUserInfo().then(userInfo=>{
this.setData({
userInfo:userInfo
})
})
})
}
}
(免责声明:本网站内容主要网络,不保证有关资料的准确性及可靠性,读者在使用前请进一步核实,并对任何自主决定的行为负责。本网站概不负任何法律责任)
标签: