前言
微信小程序登录基本上是每个小程序都必备的功能,但是随着业务的逐渐复杂,需要考虑的情况会越来越多,所以登录功能的健壮和高效是值得重点关注的,我会按照以往经验实现一个较优雅的登录方案
基本登录流程
- 获取微信登录凭证,通过
wx.login
获取,这个api会返回一个带有时效性的code - 发送code给服务端,这一步就是通过你和后端定义的接口发送
- 服务端根据前端发送的code获取用户身份信息,当然这一步就不是前端的逻辑了
- 服务端处理完后,会把用户信息和sessinId发送给前端,然后前端把需要的信息进行存储,接下来的请求就可以带着sessinId来表示身份
- 下面是微信官方的流程图
逻辑封装
针对以上登录流程,我会封装以下几个方法供业务层调用
方法名 | 功能 |
---|---|
getLoginCode |
获取微信登录凭证 |
staticLogin |
静默登录 |
singleLogin |
登录请求封装 |
checkLogin |
判断是否登录 |
getPhoneNum |
获取微信授权手机号 |
我习惯在promise请求中使用await-to方法,这是一个针对异步比较优雅的方案
getLoginCode,一个简单的promise封装获取code
export const getLoginCode = () => { return new Promise((resolve, reject) => { wx.login({ success (res) { if (res.code) { resolve(res.code) } else { reject(res) } }, fail(err) { reject(err) } }) }) }
静默登录封装,获取用户信息,并进行存储
export const staticLogin = async () => { // 如果已经登录,直接返回登录信息 if (checkLogin()) { return getLoginInfo() } const [loginErr, loginRes] = await _to(singleLogin()); if (loginErr) { throw new Error(loginErr); } // 存储用户信息,这个就根据自己的情况了,我这随便定个方法 setLoginInfo({ ...loginRes, }) return loginRes; }
登录请求封装,这里我是用单例模式并返回了一个promise,这样做是为了,你多次调用singleLogin时,比如快速切换页面,快速点击多个需登录区域,在数据没有返回的时候,都会等待,而不是触发多次请求
let loginInstance = null export const singleLogin = () => { if (loginInstance) { return loginInstance } // loginReq:封装请求,包括请求后端和getLoginCode loginInstance = loginReq() loginInstance.catch((err) => { loginInstance = null; return Promise.reject(err); }) return loginInstance }
checkLogin,检查是否登录,这个就看你存储在那个地方了,视情况而定
export const checkLogin = () => { return Boolean(loginInfo?.sid) }
getPhoneNum
export const getPhoneNum = async (iv, encryptedData) => { const [codeErr, codeRes] = await _to(getLoginCode()) if (codeErr) { console.log(codeErr); throw new Error(codeErr); } // decrypt:请求后端 const [phoneErr, phoneRes] = await _to(decrypt({ iv, encryptedData, code: codeRes.code })) if (phoneErr) { console.log(phoneErr); throw new Error(phoneErr); } const phoneNumber = phoneRes.result.phoneNumber setLoginInfo({ phoneNumber, }) return phoneNumber }
业务层
业务层我会在以下几个地方调用
请求前根据options判断是否需登录,并进行处理
export const REQUEST = async (requestObj) => { // isLogin 当前接口请求是否需要登录 if (requestObj.isLogin && !checkLogin()) { const [loginErr, loginRes] = await _to(staticLogin()); if (loginErr) { Toast('登录失败') throw new Error(loginErr); } } ... }
登录按钮,这个主要用于登录失败兜底的情况,让用户主动点击,用于登录弹窗等组件,只需调用
staticLogin
即可获取用户授权手机号按钮
// index.html // 用户主动触发进行授权手机号 <button open-type="getPhoneNumber" bindgetphonenumber="bindPhoneNumber"> <slot></slot> </button> // index.js async bindPhoneNumber(e) { const iv = e.detail.iv; const encryptedData = e.detail.encryptedData; const [phoneErr, phoneRes] = await _to(getPhoneNum(iv, encryptedData)); if (phoneErr) { Toast('获取手机号失败'); return; } this.triggerEvent('getPhoneNumber', phoneRes); }
页面组件,在小程序中一般都有一个页面组件,用于封装一些页面必需的功能,比如loading,导航栏,无效页面等等,登录也是必需的,所以我把登录逻辑封装到页面组件上,如果成功会通过triggerEvent触发给页面,如果失败就弹出登录弹窗,当然这种处理不太细,根据业务的不同改变相关的逻辑
// index.html
<view>
...
<login-modal show={loginShow} {...props} />
</view>
// index.js
async created() {
const [loginErr, loginRes] = await _to(staticLogin());
if (loginErr) {
Toast('登录失败');
this.setData({
loginShow: true,
});
return;
}
this.triggerEvent('login', loginRes);
}
- 还有一些不是太通用的业务组件就不叙述了…