如何實現小程式使用者和公眾號使用者多點登入
需求:
1.現在有一批公眾號使用者,後期又開發了小程式,那如何讓小程式識別到這是來自公眾號的使用者呢?
2.直接使用小程式的使用者實現快速登入?
這是一個很常見的需求場景,要實現小程式和公眾號使用者多點登入就需要任意一方儲存好使用者的unionId,在實現該需求的過程中也有很多細節需要注意的地方,特此寫下這篇部落格記錄一下。
小程式端的程式碼實現流程:
流程1: 獲取使用者的unionId ➡ 從資料中查詢是否有使用者為當前的unionId ➡ 如果存在以當前使用者的openid生成token記錄登入狀態➡獲取使用者資訊快取到本地
流程2: 獲取使用者的unionId ➡ 從資料中查詢是否有使用者為當前的unionId ➡ 如果不存在呼叫小程式介面獲取使用者手機號碼(具體業務場景獲取不同的使用者資料)➡服務端自動完成使用者註冊並生成小程式端token記錄登入狀態➡通過先前小程式介面獲取的使用者資料快取到本地
細節問題:
1. UnionID機制:如果開發者帳號下存在同主體的公眾號或移動應用,並且該使用者已經授權登入過該公眾號或移動應用。開發者也可以直接通過wx.login獲取到該使用者UnionID,無須使用者再次授權。
2. 如果需要呼叫wx.getUserInfo解密使用者需要的session_key不應該存在與或下發到小程式,應該儲存(快取)到服務端,但是要保證服務端每次使用的 session_key是沒有過期的,因此每次呼叫解密使用者資料的服務端介面之前要進行 wx.checkSession驗證。
實現程式碼:(我使用的是wepy框架,當前程式碼是放在app.wpy入口檔案中)
onLaunch() {
// 小程式啟動之後 觸發
myfunction.setToken();
}
setToken函式實現的地方
import wepy from 'wepy';
import * as wr from '../api/request.js';
import api from '../api/router.js';
export const encodeUrl = (url='')=>{
url = url.replace('?','+');
url = url.replace('&','%');
return url;
}
export const buildKeyAndToken = () =>{
let usertoken = wx.getStorageSync('usertoken')||'';
wx.login({
success:function(res) {
if(res.code){ //獲取session_key
wr.request(api.sessionKey,'post',{js_code:res.code},false).then(function(data){
let openid = data.data.openid;
let unionId = data.data.unionid;
//生成使用者token並存儲
wr.request(api.userToken,'post',{openid:openid},false).then(function(data2){
usertoken = data2.data;
wx.setStorage({
key:"usertoken",
data:usertoken
});
return usertoken;
});
//嘗試繫結原使用者
wr.request(api.bindXcx,'get',{},false,{unionid:unionId}).then(
bindUserInfo=> {
//儲存使用者資料到本地
if(bindUserInfo.data.id){
let localUser = bindUserInfo.data;
let saveInfo = {id:localUser.id,phone:localUser.phone,name:localUser.name}
wx.setStorage({
key:"userinfo",
data:saveInfo
});
}else{ //清除本地使用者資料
wx.removeStorageSync('userinfo');
}
}
)
})
}else {
console.log('登入失敗!' + res.errMsg)
}
}
})
}
export default {
sayHi: ()=>{
console.log('hello');
},
setToken: ()=>{
wx.checkSession({
success:function(){
//驗證服務端是否還存在session_key
wr.request(api.hasSessionKey).then(
data =>{
if(data.data=='true'){
//嘗試繫結原使用者
wr.request(api.bindXcx,'get',{},false).then(
bindUserInfo=> {
// console.log(bindUserInfo);
//儲存使用者資料到本地
if(bindUserInfo.data.id){
let localUser = bindUserInfo.data;
let saveInfo = {id:localUser.id,phone:localUser.phone,name:localUser.name}
wx.setStorage({
key:"userinfo",
data:saveInfo
});
}else{ //清除本地使用者資料
wx.removeStorageSync('userinfo');
}
}
)
}else{
buildKeyAndToken();
}
}
)
},
fail:function(){ //session_key失效 重新生成
buildKeyAndToken();
}
})
},
checkLogin: (mag='登入失效,請重新登入',fromUrl='',params={})=>{
var userInfo = wx.getStorageSync('userinfo')||'';
var url = '/mypage/login';
var requestParams = {};
if(fromUrl!=''){
requestParams = Object.assign(requestParams,{uri:fromUrl});
if(Object.keys(params).length>0){
requestParams = Object.assign(requestParams,{params:params});
}
url+='?from_path='+JSON.stringify(requestParams)
}
if(userInfo==''){
wx.showModal({
title:'請您登入',
content:mag,
success: function(res) {
if (res.confirm) {
wx.navigateTo({
url:url,
})
}
}
})
return false;
}else{
return true;
}
},
}
router.js檔案是記錄一些路由uri不方便展示,request.js是我自己封裝的wx.request請求,也是函式wr.request的呼叫實現,檔案內容如下:
import wepy from 'wepy';
export function wRequest(method, requestHandler, isShowLoading = true,header = {}){
let params = requestHandler.params
isShowLoading && wx.showLoading && wx.showLoading({title: '載入中...'})
return new Promise((resolve, reject) => {
wepy.request({
url: requestHandler.url,
data: params,
method: method, // OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
// header: {
// 'Content-Type': method === 'POST' ? 'application/x-www-form-urlencoded' : 'application/json',
// header
// },
header:header,
success: function (res) {
isShowLoading && wx.hideLoading && wx.hideLoading()
if (res.data) {
if(res.data.success===true){
resolve(res.data);
}else if(res.data.errors.code=='401'){ //401使用者登入失效提示,攜帶unionid頭資訊的不提示
if(typeof(header['unionid'])!='undefined'){
resolve(res.data);
}else{ //登入失效重新登入
wx.showModal({
title:'',
content:res.data.errors.message,
showCancel:false,
success: function(res) {
if (res.confirm) {
wx.navigateTo({
url:'/mypage/login'
})
} else if (res.cancel) {
}
}
})
}
}else{
reject(res.data.errors.message);
}
} else {
reject('網路請求失敗')
}
},
fail: function () {
isShowLoading && wx.hideLoading && wx.hideLoading()
reject('網路請求失敗')
},
complete: function () {
}
})
}).catch(function(msg){
wx.showModal({
title:msg,
content:'',
showCancel:false,
});
})
}
export const request = (url,method='get',params={},isloading=true,header={}) => {
return wRequest(method,{url:url,params:params},isloading,header)
}
通過入口檔案中的setToken方法我們就綁定了微信公眾號中存在的使用者,並在本地快取了使用者資訊,在服務端,新繫結的使用者設定了一個小程式tokne欄位,而原來的公眾號使用者設定了微信公眾號token欄位,這樣他們就可以通過各自的openid生成的(同一使用者公眾號和小程式的openid是不一致的)token來獲取到自己的資料了。
現在通過入口檔案中沒有匹配上的使用者就是新使用者了,我們在對應的登入介面就判斷是否存在使用者資料快取,如果不存在就通過小程式介面獲取使用者手機號碼實現快速註冊與登入:
wxml部分(wepy的template部分)
<button open-type="getPhoneNumber" bindgetphonenumber="wxlogin" class="zan-btn mybtn">
微信使用者快捷登入
</button>
js部分
wxlogin(e){
if(e.detail.errMsg=='getPhoneNumber:fail user deny'){
console.log('跳轉到手機號登入');
}
var ivData = e.detail.iv;
var encryptedData = e.detail.encryptedData;
let params = {encrypted_data:encryptedData,iv_data:ivData};
wr.request(api.wxphone,'post',params).then(
phone=>{
var userPhone = phone.data;
//快捷註冊
let addparams = {phone:userPhone,from:'xcx'};
let self = this;
wr.request(api.userRegister,'post',addparams).then(
re=>{
if(re.success){
//快取使用者資料,跳轉頁面
let userInfo = {id:re.data.id,phone:userPhone.phone,name:userPhone};
wx.setStorage({
key:"userinfo",
data:userInfo
});
wx.navigateTo({
url:self.fromUrl
})
}else{
//服務端的報錯資訊wr.request中已處理
}
}
)
}
)
}
而公眾號端只需要做的是在授權登入過後在資料庫中記錄好使用者的unionID就可以了,因為在小程式的新註冊使用者中也存入了使用者的unionID,只需要做好對應的匹配就行了。