系列文章目錄
使用eggjs+微信小程序開(kāi)發(fā)一個(gè)時(shí)間管理小程序(一)——項(xiàng)目介紹
使用eggjs+微信小程序開(kāi)發(fā)一個(gè)時(shí)間管理小程序(二)——項(xiàng)目搭建
使用eggjs+微信小程序開(kāi)發(fā)一個(gè)時(shí)間管理小程序(三)——自動(dòng)登錄實(shí)現(xiàn)
前言
上篇文章留了一個(gè)尾巴,即如何實(shí)現(xiàn)小程序的自動(dòng)登錄。本篇文章將專(zhuān)題對(duì)該問(wèn)題進(jìn)行深入的解讀和分享矛物。
自動(dòng)登錄在小程序應(yīng)用中非常普遍臀栈,用戶點(diǎn)開(kāi)小程序,當(dāng)即完成登錄/注冊(cè),而不是跳到登錄頁(yè)或其他操作,避免繁雜的操作導(dǎo)致用戶退出。畢竟在現(xiàn)在這種卷死人的社會(huì)環(huán)境下厢破,時(shí)間和效率就是一切。
一治拿、小程序的用戶體系
每個(gè)微信用戶在某個(gè)小程序中摩泪,都有一個(gè)唯一的用戶標(biāo)識(shí)openid
。同一個(gè)微信用戶劫谅,在不同小程序或公眾號(hào)的openid
是不同的见坑。
如果一個(gè)企業(yè)嚷掠,有多種產(chǎn)品類(lèi)型,比如公眾號(hào)荞驴、小程序不皆、app等,該如何打通不同產(chǎn)品的賬號(hào)體系呢熊楼?
這就需要借助微信開(kāi)放平臺(tái)
提供的能力霹娄,企業(yè)在微信開(kāi)放平臺(tái)
創(chuàng)建賬號(hào),然后將公眾號(hào)鲫骗、小程序犬耻、app等綁定到該開(kāi)放平臺(tái)賬號(hào)下。(必須是完成微信認(rèn)證的企業(yè)號(hào)执泰,要花錢(qián)枕磁,要有企業(yè)對(duì)公賬戶)
微信用戶,在使用綁定在同一開(kāi)放平臺(tái)賬號(hào)下的產(chǎn)品時(shí),除了openid
之外,還有一個(gè)統(tǒng)一的unionid
嘹悼。可以通過(guò)unionid
打通賬號(hào)體系沦寂,將不同產(chǎn)品下的用戶識(shí)別關(guān)聯(lián)起來(lái)。
這塊知識(shí)點(diǎn)是一個(gè)拓展淘衙,我的小程序是個(gè)人號(hào)传藏,用戶也只有openid,是最簡(jiǎn)單的情況幔翰。
二漩氨、小程序的登錄流程
在自動(dòng)登錄的背景下西壮,我們必須首先通過(guò)微信獲得用戶的openid遗增,然后以openid為用戶的唯一標(biāo)識(shí)存進(jìn)我們的數(shù)據(jù)庫(kù)。當(dāng)然款青,如果你們有登錄注冊(cè)頁(yè)做修,則完全不用鳥(niǎo)這個(gè)。
1.流程介紹
- 在前端抡草,使用wx.login方法饰及,獲取登錄憑證code,該code有效時(shí)間5分鐘康震,每次都不一樣燎含。
- 調(diào)用自己實(shí)現(xiàn)的登錄接口,將code傳過(guò)去腿短。
- 在后端屏箍,調(diào)用微信提供的接口绘梦,https://api.weixin.qq.com/sns/jscode2session點(diǎn)我查看官方文檔,該接口需要傳入appid和appSecret赴魁,這倆是注冊(cè)小程序的時(shí)候生成的唯一值卸奉,大家可以把它們存在后端代碼中。該接口將返回用戶的openid颖御。
- 接下來(lái)榄棵,判斷該openid是否在用戶表中,如果沒(méi)有就插入一條潘拱,如果有就直接登錄疹鳄。
- 在后端,登錄實(shí)際上就是進(jìn)行身份驗(yàn)證泽铛,登錄時(shí)生成一個(gè)token返給前端尚辑,前端將其存起來(lái),之后其他接口的請(qǐng)求中都在header中帶上這個(gè)token盔腔。當(dāng)請(qǐng)求走到后端時(shí)杠茬,后端驗(yàn)證該token是否有效,如果有效弛随,則正常處理瓢喉,如果失效,則返回401舀透,讓前端重新登錄后再重新請(qǐng)求栓票。這一切在用戶那里,都是無(wú)感知的愕够。
- 我采用的是JWT(JSON WEB TOKEN)這種身份驗(yàn)證方案走贪,該方案在我另一篇文章中有詳細(xì)解釋傳送門(mén)。
2.服務(wù)端代碼
先將小程序的appid和appSecret設(shè)置為全局變量
// config.default.js
exports.miniAppId = "你的appid";
exports.miniAppSecret = "你的appSecret";
登錄接口惑芭,每一步都有詳細(xì)的注釋
// service/user.js
async login() { // 登錄
const { code } = this.ctx.request.body; // 接口傳入的坠狡,通過(guò)wx.login獲得的code
const { miniAppId, miniAppSecret } = this.config; // 從上面config中配置的全局變量
// 調(diào)用微信登錄接口,獲取session_key, unionid, openid
const res = await this.ctx.curl('https://api.weixin.qq.com/sns/jscode2session', {
data: {
appid: miniAppId,
secret: miniAppSecret,
js_code: code,
grant_type: 'authorization_code' // 這個(gè)值是寫(xiě)死的
},
dataType: 'json'
});
if (res && res.data.errcode) { // 如果微信接口調(diào)用失敗遂跟,我們的接口也拋錯(cuò)出去
return {
status: 101,
message: res.data.errmsg,
result: res.data
}
} else { // 微信接口調(diào)用成功了
const { openid, session_key } = res.data;
// 開(kāi)啟事務(wù)
const result = await this.app.mysql.beginTransactionScope(async conn => {
try {
// 檢查當(dāng)前openid是否存在逃沿,不存在則創(chuàng)建一個(gè)用戶
let userObj = {};
const checkAccount = await conn.select('user', {
where: {
open_id: openid
}
});
if (!checkAccount || !checkAccount.length) { // 不存在,創(chuàng)建用戶
const now = Date.now();
const insertOpe = await conn.insert('user', [{
open_id: openid,
create_time: now,
update_time: now,
user_name: '微信用戶', // 咱們不知道微信用戶的昵稱(chēng)和頭像幻锁,所以先默認(rèn)填一個(gè)
url: 'https://img.speschool.com/default_avatar.png'
}]);
if (insertOpe.affectedRows !== 1) { // 如果插入操作由于各種原因失敗了凯亮,則接口拋出錯(cuò)誤
console.log('寫(xiě)入用戶信息出錯(cuò)');
return {
message: '系統(tǒng)異常',
status: 501,
result: null
}
}
// 查出當(dāng)前用戶的信息,賦值給userObj
const queryUser = await conn.select('user', {
where: {
open_id: openid
}
});
if (queryUser && queryUser.length) {
userObj = queryUser[0];
}
} else { // 當(dāng)前用戶存在哄尔,直接將用戶信息賦值給userObj
userObj = checkAccount[0];
}
// 登錄成功假消,簽發(fā)jwt 這里將一些用戶的信息存放到j(luò)wt的對(duì)象中,生成了一個(gè)token岭接。
// 之后調(diào)接口富拗,驗(yàn)證token時(shí)堂鲤,也可以直接獲得這些值,這樣接口就知道當(dāng)前發(fā)起請(qǐng)求的用戶是哪一個(gè)了
const token = JWT.sign({
userId: userObj.user_id,
userName: userObj.user_name,
openid: userObj.open_id,
url: userObj.url
}, this.config.jwt.secret, {
expiresIn: this.config.jwt.expiresIn
});
// 登錄成功媒峡,返回用戶信息和token
return {
message: '登錄成功',
status: 200,
result: { ...userObj, token: token }
}
} catch (err) {
return {
message: '系統(tǒng)異常瘟栖,請(qǐng)稍后再試',
status: 501,
result: null
};
}
});
return result;
}
}
3.前端代碼
前端有如下調(diào)整:
- 調(diào)整上一篇中的myRequest方法,增加邏輯:
當(dāng)接口請(qǐng)求401時(shí)谅阿,進(jìn)行自動(dòng)登錄半哟,登錄成功后重新請(qǐng)求失敗的接口調(diào)用。
- 增加一個(gè)自動(dòng)登錄的方法
來(lái)吧签餐,代碼展示~~~
// utils/request.js
export const myRequest = (config, resFn) => { // 增加了resFn這個(gè)入?yún)⒃⒄牵糜诒A羯弦淮问≌?qǐng)求的回調(diào)
const header = {
authorization: wx.getStorageSync('token') || ''
};
return new Promise((res, rej) => {
let url = config.url.replace(/^\//, '');
wx.request({
url: `${globalConfig.envSet.requestUrl}/${url}`,
data: config.data,
header: Object.assign({}, header, config.header || {}),
method: config.method || 'POST',
responseType: config.responseType,
success(reqRes) {
if (reqRes && reqRes.data) {
if (config.responseType == 'arraybuffer') {
res(reqRes.data);
} else {
if (reqRes.data.status == 200) {
res(reqRes.data);
if (resFn) { // 如果存在resFn,說(shuō)明上一次操作登錄失效了氯檐,要把它的promise回調(diào)也執(zhí)行一次
resFn(reqRes.data);
}
} else if (reqRes.data.status == 401) {
// 登錄一下之后戒良,再重新發(fā)起請(qǐng)求
autoLogin(function () {
myRequest(config, res); // 這里的res是失敗請(qǐng)求的res,也就是myRequest方法入?yún)⒅械膔esFn
})
} else {
wx.showToast({
title: reqRes.data.message,
icon: 'none'
});
rej(reqRes.data);
}
}
}
},
fail(reqErr) {
rej(reqErr);
}
})
});
}
我們給
myRequest
增加了一個(gè)入?yún)?code>resFn冠摄,用于記錄上一次失敗的請(qǐng)求的回調(diào)糯崎。
當(dāng)上一次請(qǐng)求失敗時(shí),發(fā)起自動(dòng)登錄河泳,登錄完重新發(fā)起請(qǐng)求沃呢,并執(zhí)行上次請(qǐng)求的回調(diào),把成功請(qǐng)求的接口返回拆挥,返回給調(diào)用方薄霜。
頁(yè)面P發(fā)起了一次R請(qǐng)求,實(shí)際上先發(fā)起R1報(bào)404纸兔,然后登錄惰瓜,再發(fā)起R2請(qǐng)求,然后把R2的返回結(jié)果返給P汉矿。
自動(dòng)登錄方法代碼來(lái)了崎坊,就很簡(jiǎn)單
// utils/request.js
export const autoLogin = (cb) => {
// 發(fā)送 res.code 到后臺(tái)換取 openId, sessionKey, unionId
wx.login({
success: result => {
// 登錄
myRequest({
url: '/user/login',
data: {
code: result.code
}
}).then(res => {
if (res && res.status == 200) {
wx.setStorageSync('token', res.result.token);
wx.setStorageSync('userInfo', res.result);
if (cb && typeof cb === 'function') {
cb(res.result);
}
}
}, err => {
})
}
})
}
總結(jié)
以上,我們就完成了自動(dòng)登錄的實(shí)現(xiàn)
該方案的優(yōu)勢(shì)如下:
- 無(wú)需在app.js的onLaunch回調(diào)中登錄
- 后續(xù)頁(yè)面直接調(diào)接口负甸,如果未登錄流强,則自動(dòng)登錄痹届。
- 后續(xù)頁(yè)面調(diào)用接口時(shí)呻待,無(wú)需考慮onLaunch的登錄是否完成,無(wú)需處理onLaunch中登錄接口和頁(yè)面接口之間的時(shí)間差队腐,減少思維量蚕捉。