

NEWS
將想法與焦點與您一起共享
徹底搞懂微信小程序登錄流程-附小程序和服務端代碼


微信小程序登錄是很適用的功能,相信大家對這個功能的實現很感興趣,那么,今天就為大家介紹下微信小程序登錄的開發實現,有什么問題請聯系實搜客服,實搜客為您解答小程序的各種難題。
微信小程序登錄流程實現
用戶登錄是大部分完整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
})
})
})
}
}
(免責聲明:本網站內容主要網絡,不保證有關資料的準確性及可靠性,讀者在使用前請進一步核實,并對任何自主決定的行為負責。本網站概不負任何法律責任)
標簽:
- 微信公眾號贊賞功能升級作者可直接收到贊賞
2019-10-23 09:30
- 微信小程序如何解決餐飲行業的潛在問題
2019-12-27 08:44
- 微信小程序給我們生活帶來了哪些改變?
2019-12-06 09:26
- 微信公眾號公域流量和私域流量的核心差別
2019-10-25 09:50
- 微信小程序開發零售商城的前景在哪里?
2020-07-17 10:53
- 小程序開發的費用是多少?
2022-01-22 08:51
- 微信營銷怎什么樣的內容可以吸引粉絲
2019-10-31 10:01
- 病毒式微信營銷該怎么做?
2020-01-07 10:58
- 微信小程序開放群相關能力,開發者如何應對?
2020-01-13 09:32