使用eggjs+微信小程序开发一个时间管理小程序(四)——自动登录实现



系列文章目录

使用eggjs+微信小程序开发一个时间管理小程序(一)——项目介绍
使用eggjs+微信小程序开发一个时间管理小程序(二)——后端项目搭建
使用eggjs+微信小程序开发一个时间管理小程序(三)——前端项目搭建
使用eggjs+微信小程序开发一个时间管理小程序(四)——自动登录实现

前言

上篇文章留了一个尾巴,即如何实现小程序的自动登录。本篇文章将专题对该问题进行深入的解读和分享。

自动登录在小程序应用中非常普遍,用户点开小程序,当即完成登录/注册,而不是跳到登录页或其他操作,避免繁杂的操作导致用户退出。毕竟在现在这种卷死人的社会环境下,时间和效率就是一切。


一、小程序的用户体系

每个微信用户在某个小程序中,都有一个唯一的用户标识openid。同一个微信用户,在不同小程序或公众号的openid是不同的。

如果一个企业,有多种产品类型,比如公众号、小程序、app等,该如何打通不同产品的账号体系呢?

这就需要借助微信开放平台提供的能力,企业在微信开放平台创建账号,然后将公众号、小程序、app等绑定到该开放平台账号下。(必须是完成微信认证的企业号,要花钱,要有企业对公账户)
微信用户,在使用绑定在同一开放平台账号下的产品时,除了openid之外,还有一个统一的unionid。可以通过unionid打通账号体系,将不同产品下的用户识别关联起来。

这块知识点是一个拓展,我的小程序是个人号,用户也只有openid,是最简单的情况。

二、小程序的登录流程

在自动登录的背景下,我们必须首先通过微信获得用户的openid,然后以openid为用户的唯一标识存进我们的数据库。当然,如果你们有登录注册页,则完全不用鸟这个。

1.流程介绍

  1. 在前端,使用wx.login方法,获取登录凭证code,该code有效时间5分钟,每次都不一样。
  2. 调用自己实现的登录接口,将code传过去。
  3. 在后端,调用微信提供的接口,https://api.weixin.qq.com/sns/jscode2session点我查看官方文档,该接口需要传入appid和appSecret,这俩是注册小程序的时候生成的唯一值,大家可以把它们存在后端代码中。该接口将返回用户的openid。
  4. 接下来,判断该openid是否在用户表中,如果没有就插入一条,如果有就直接登录。
  5. 在后端,登录实际上就是进行身份验证,登录时生成一个token返给前端,前端将其存起来,之后其他接口的请求中都在header中带上这个token。当请求走到后端时,后端验证该token是否有效,如果有效,则正常处理,如果失效,则返回401,让前端重新登录后再重新请求。这一切在用户那里,都是无感知的。
  6. 我采用的是JWT(JSON WEB TOKEN)这种身份验证方案,该方案在我另一篇文章中有详细解释传送门

2.服务端代码

先将小程序的appid和appSecret设置为全局变量

// config.default.js
exports.miniAppId = "你的appid";
exports.miniAppSecret = "你的appSecret";

登录接口,每一步都有详细的注释

// service/user.js
async login() { // 登录
        const { code } = this.ctx.request.body; // 接口传入的,通过wx.login获得的code
        const { miniAppId, miniAppSecret } = this.config; // 从上面config中配置的全局变量
        // 调用微信登录接口,获取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' // 这个值是写死的
            },
            dataType: 'json'
        });
        if (res && res.data.errcode) { // 如果微信接口调用失败,我们的接口也抛错出去
            return { 
                status: 101,
                message: res.data.errmsg,
                result: res.data
            }
        } else { // 微信接口调用成功了
            const { openid, session_key } = res.data;
            // 开启事务
            const result = await this.app.mysql.beginTransactionScope(async conn => {
                try {
                    // 检查当前openid是否存在,不存在则创建一个用户
                    let userObj = {};
                    const checkAccount = await conn.select('user', {
                        where: {
                            open_id: openid
                        }
                    });
                    if (!checkAccount || !checkAccount.length) { // 不存在,创建用户
                        const now = Date.now();
                        const insertOpe = await conn.insert('user', [{
                            open_id: openid,
                            create_time: now,
                            update_time: now,
                            user_name: '微信用户', // 咱们不知道微信用户的昵称和头像,所以先默认填一个
                            url: 'https://img.speschool.com/default_avatar.png'
                        }]);
                        if (insertOpe.affectedRows !== 1) { // 如果插入操作由于各种原因失败了,则接口抛出错误
                            console.log('写入用户信息出错');
                            return {
                                message: '系统异常',
                                status: 501,
                                result: null
                            }
                        }
                        // 查出当前用户的信息,赋值给userObj
                        const queryUser = await conn.select('user', {
                            where: {
                                open_id: openid
                            }
                        });
                        if (queryUser && queryUser.length) {
                            userObj = queryUser[0];
                        }
                    } else { // 当前用户存在,直接将用户信息赋值给userObj
                        userObj = checkAccount[0];
                    }
                    // 登录成功,签发jwt 这里将一些用户的信息存放到jwt的对象中,生成了一个token。
                    // 之后调接口,验证token时,也可以直接获得这些值,这样接口就知道当前发起请求的用户是哪一个了
                    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: '系统异常,请稍后再试',
                        status: 501,
                        result: null
                    };
                }
            });
            return result;
        }
    }

3.前端代码

前端有如下调整:

  1. 调整上一篇中的myRequest方法,增加逻辑:

当接口请求401时,进行自动登录,登录成功后重新请求失败的接口调用。

  1. 增加一个自动登录的方法

来吧,代码展示~~~

// utils/request.js
export const myRequest = (config, resFn) => { // 增加了resFn这个入参,用于保留上一次失败请求的回调
    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,说明上一次操作登录失效了,要把它的promise回调也执行一次
                                resFn(reqRes.data);
                            }
                        } else if (reqRes.data.status == 401) {
                            // 登录一下之后,再重新发起请求
                            autoLogin(function () {
                                myRequest(config, res); // 这里的res是失败请求的res,也就是myRequest方法入参中的resFn
                            })
                        } else {
                            wx.showToast({
                                title: reqRes.data.message,
                                icon: 'none'
                            });
                            rej(reqRes.data);
                        }
                    }
                }
            },
            fail(reqErr) {
                rej(reqErr);
            }
        })
    });
}

我们给myRequest增加了一个入参resFn,用于记录上一次失败的请求的回调。
当上一次请求失败时,发起自动登录,登录完重新发起请求,并执行上次请求的回调,把成功请求的接口返回,返回给调用方。
页面P发起了一次R请求,实际上先发起R1报404,然后登录,再发起R2请求,然后把R2的返回结果返给P。

自动登录方法代码来了,就很简单

// utils/request.js
export const autoLogin = (cb) => {
    // 发送 res.code 到后台换取 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 => {
            })
        }
    })
}

总结

以上,我们就完成了自动登录的实现

该方案的优势如下:

  1. 无需在app.js的onLaunch回调中登录
  2. 后续页面直接调接口,如果未登录,则自动登录。
  3. 后续页面调用接口时,无需考虑onLaunch的登录是否完成,无需处理onLaunch中登录接口和页面接口之间的时间差,减少思维量。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值