需求:
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部分)
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,只需要做好对应的匹配就行了。