使用eggjs+WX小程序開(kāi)發(fā)一個(gè)時(shí)間管理小程序(三)——自動(dòng)登錄


系列文章目錄

使用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.流程介紹

  1. 在前端抡草,使用wx.login方法饰及,獲取登錄憑證code,該code有效時(shí)間5分鐘康震,每次都不一樣燎含。
  2. 調(diào)用自己實(shí)現(xiàn)的登錄接口,將code傳過(guò)去腿短。
  3. 在后端屏箍,調(diào)用微信提供的接口绘梦,https://api.weixin.qq.com/sns/jscode2session點(diǎn)我查看官方文檔,該接口需要傳入appid和appSecret赴魁,這倆是注冊(cè)小程序的時(shí)候生成的唯一值卸奉,大家可以把它們存在后端代碼中。該接口將返回用戶的openid颖御。
  4. 接下來(lái)榄棵,判斷該openid是否在用戶表中,如果沒(méi)有就插入一條潘拱,如果有就直接登錄疹鳄。
  5. 在后端,登錄實(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ú)感知的愕够。
  6. 我采用的是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)整:

  1. 調(diào)整上一篇中的myRequest方法,增加邏輯:

當(dāng)接口請(qǐng)求401時(shí)谅阿,進(jìn)行自動(dòng)登錄半哟,登錄成功后重新請(qǐng)求失敗的接口調(diào)用。

  1. 增加一個(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ì)如下:

  1. 無(wú)需在app.js的onLaunch回調(diào)中登錄
  2. 后續(xù)頁(yè)面直接調(diào)接口负甸,如果未登錄流强,則自動(dòng)登錄痹届。
  3. 后續(xù)頁(yè)面調(diào)用接口時(shí)呻待,無(wú)需考慮onLaunch的登錄是否完成,無(wú)需處理onLaunch中登錄接口和頁(yè)面接口之間的時(shí)間差队腐,減少思維量蚕捉。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市柴淘,隨后出現(xiàn)的幾起案子迫淹,更是在濱河造成了極大的恐慌秘通,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敛熬,死亡現(xiàn)場(chǎng)離奇詭異肺稀,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)应民,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)话原,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人诲锹,你說(shuō)我怎么就攤上這事繁仁。” “怎么了归园?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵黄虱,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我庸诱,道長(zhǎng)捻浦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任桥爽,我火速辦了婚禮默勾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘聚谁。我一直安慰自己母剥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布形导。 她就那樣靜靜地躺著环疼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪朵耕。 梳的紋絲不亂的頭發(fā)上炫隶,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音阎曹,去河邊找鬼伪阶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛处嫌,可吹牛的內(nèi)容都是我干的栅贴。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼熏迹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼檐薯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坛缕,失蹤者是張志新(化名)和其女友劉穎墓猎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體赚楚,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毙沾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宠页。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搀军。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖勇皇,靈堂內(nèi)的尸體忽然破棺而出罩句,到底是詐尸還是另有隱情,我是刑警寧澤敛摘,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布门烂,位于F島的核電站,受9級(jí)特大地震影響兄淫,放射性物質(zhì)發(fā)生泄漏屯远。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一捕虽、第九天 我趴在偏房一處隱蔽的房頂上張望慨丐。 院中可真熱鬧,春花似錦泄私、人聲如沸房揭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捅暴。三九已至,卻和暖如春咧纠,著一層夾襖步出監(jiān)牢的瞬間蓬痒,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工漆羔, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梧奢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓演痒,卻偏偏與公主長(zhǎng)得像亲轨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫡霞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容