木灵鱼儿

木灵鱼儿

阅读:369

最后更新:2021/03/21/ 14:28:47

koa教程2 用户登录

登录类型

用户注册之后,自然是用户登录,登录的时候我们需要考虑到登录的方式,往往在正式的项目里面,登录的方式可能会有很多种:

  1. 账号密码登录
  2. 手机号登录
  3. 小程序一键登录

那么我们登录的时候就要进行判断,设置一个type的属性,用于在登录时判断用户是什么类型的登录,这个由前端那边设定参数。

type:1为账号密码登录,2为手机号登录,3为小程序一键登录

作为判断,我们可以写一个查询表(枚举),在项目根目录创建一个lib目录,在里面创建一个enum.js文件。

enum.js:

//通用判断是否存在该类型
function isThisType(val) {
  let flag = false;
  Object.keys(this).find(key => {
    if (this[key] === val) {
      return flag = true;
    }
  });
  return flag;
};


//登录类型
const LoginType = {
  USER_MINI_PROGRAM: 1,  //小程序
  USER_EMAIL: 2, //邮箱
  USER_MOBILE: 3, //手机
  isThisType,
};


module.exports = {
  LoginType
}

这样判断起来也很简单。

const { LoginType } = require("@/lib/enum");

LoginType.isThisType(value);  //true&false

这样我们在登录校验参数的时候就可以判断是不是存在对应的登录类型,如果不存在,我们就可以报错。

const { LoginType } = require("@/lib/enum");

if(!LoginType.isThisType(value)){
  new ValidateError("登录类型不存在!");
}

至此登录类型就完事了。

登录校验-邮箱密码

登录的时候,一般需要账号和密码,但我们有可能只有账号,不一定需要密码,比如手机短信登录,那么密码就不一定是必填项了,所以这里也没由用required表示密码必填。

const Joi = require("joi");
const { ValidateError } = require("@/core/http-error");
const { User } = require("@/models/user");
const { LoginType } = require("@/lib/enum");

//判断登录类型是否存在
const loginTypeExist = (value, helpers) => {
  if (!LoginType.isThisType(value)) {
    return helpers.error(9999);
  }
  return value;
};


//登录账号
const loginValidate = (data, helpers) => {
  const joiObject = Joi.object({
    // 账号 必填
    account: Joi.string().email({ minDomainSegments: 2 })
      .required().error(new ValidateError("邮箱不合法!")),
    // 密码 非必填-多种登录类型
    password: Joi.string().min(6).max(32)
      .error(new ValidateError("密码必须是1-32位!")),
    // 登录类型 必填
    type: Joi.number().custom(loginTypeExist, "判断类型是否存在")
      .error(([errors]) => {
        if (errors.code === 9999) {
          return new ValidateError("登录类型不存在!");
        }
        return new ValidateError("登录类型错误")
      })
  });

  return joiObject.validateAsync(data);
};

module.exports = {
  loginValidate
}

在joi中,custom自定义校验里面,如果throw抛出错误,依旧会被error方法覆盖,也就是说在custom里即便你抛出错误,最终用的也是error里面的,所以我们需要做个判断。

通过helpers.error(9999)我们自定义一个错误code,它本身会根据传入的值创建一个joi错误对象,我们在error中可以接收到这个错误对象,然后判断,如果code值为9999说明type类型不存在,我们new出一个
"登录类型不存在!"的错误对象。

简单校验通过后,我们就需要进行登录。

登录-邮箱密码

//登录
router.post("/login", async (ctx) => {
  const { account, password, type } =
    await loginValidate(ctx.request.body);
  //根据登录类型进行登录
  switch (type) {
    case LoginType.USER_EMAIL:
      // 邮箱+密码
      
      break;
    case LoginType.USER_MINI_PROGRAM:
      // 小程序
      break;
    case LoginType.USER_MOBILE:
      // 手机
      break;
    default:
      throw new NotFound("没有相应的处理函数!")
  }

  //成功
  // throw new Success("登录成功!");

});

这么我们针对不同的登录类型都有对应的方法,当然我们也要考虑到,有时候登录类型存在,但是我们没有写对应的登录处理方法,所以要在switch中加default,然后抛出一个错误用于提示。

那么我们怎么处理邮箱密码登录呢?

我们有几点需要处理:

  1. 邮箱是否存在
  2. 密码是否正确
  3. 以上都正确,我们需要返回token

邮箱密码的校验,我们需要访问数据库,所以邮箱和密码的校验我们写在user模型里面。

const bcrypt = require("bcryptjs");
const { Sequelize, Model, DataTypes } = require("sequelize");
const { AuthFailed } = require("@core/http-error");

class User extends Model {
  static async verifyEmailPassword(email, password) {
    //查找对应email用户
    const user = await User.findOne({
      where: { email }
    });
    if (!user) throw new AuthFailed("账号不存在!");
    //判断密码是否正确
    const correct = bcrypt.compareSync(password, user.password);
    if (!correct) throw new AuthFailed("密码不正确!");
    //存在
    return user;
  }
};

由于我们之前注册时,密码已经加密了,所以我们需要通过bcrypt.compareSync方法进行比较,它有两个参数,登录时用户的密码和数据库里面的加密后的密码。

最后会返回布尔值,如果false则表示密码不正确。

如果账号和密码都严重通过了,我们就将user返回出去,因为我们还需要创建token,还要用到用户的信息。

创建token

在core目录我们创建一个token.js文件。

token.js:

const jwt = require("jsonwebtoken");
const { tokenConfig } = require("@config/config");

//创建token
const createToken = function (uid, scope) {
  console.log(uid, scope)
  return jwt.sign({ uid, scope }, tokenConfig.key, {
    expiresIn: tokenConfig.expiresIn
  });
};

module.exports = {
  createToken
}

这里jwt有三个参数,第一个是一个对象,用于我们在token中存入的信息,这里我们存放了一个uid,一个scope,uid其实就是用户的自增的id,scope则是用户的权限级别,这个待会再说。

第二个参数是加密用的key,这个key一般都是随机生成的

第三个参数是一些其他设置,这里我们使用的是过期时间,jwt的过期时间有几种写法:

  1. 纯数字 单位默认为s 200 200秒
  2. 字符: "2d" 2天
  3. 字符: "2h" 2小时
  4. 字符: "2" 2ms

jwt的key和其他数据都是经常需要用到的,我们将它存在config文件中。

config.js:

module.exports = {
  //dev、prod
  env: "dev",
  //数据库配置
  database: {
    dbName: "koa-test", //数据库名
    host: "localhost", //数据库地址
    port: 3306, //数据库端口
    user: "root", //数据库用户名
    password: "123456", //数据库密码
  },
  //token配置
  tokenConfig: {
    key: "askdhjasgdhjasghgu&^%^^%&**2164.as21s8",  //加密用的key
    //token过期时间,数字为s,字符则需要加上d(天),h(小时),否则默认为ms
    expiresIn: "2h",
  }
};

这样我们邮箱密码也校验了,token创建方法也有了,我们可以写邮箱密码的登录方法了。

const { User } = require("@models/user");
const { createToken } = require("@core/token");

//邮箱+密码登录
async function emailLogin(account, password) {
  const user =
    await User.verifyEmailPassword(account, password);
  //生成token
  return createToken(user.id, 8);
};

这里我们scope传入了一个数字8,也就是他的权限。权限一般建议是有间隔的。

比如:

  1. 普通用户 8
  2. 管理员 16
  3. 站长 32

这样,如果我们在普通用户增加一个vip用户,权限的调整范围就有盈余了,设置为9,10都行。

//登录
router.post("/login", async (ctx) => {
  const { account, password, type } =
    await loginValidate(ctx.request.body);
  //根据登录类型进行登录
  switch (type) {
    case LoginType.USER_EMAIL:
      // 邮箱+密码
      const token = await emailLogin(account, password);
      ctx.body = { token }
      break;
    case LoginType.USER_MINI_PROGRAM:
      // 小程序
      break;
    case LoginType.USER_MOBILE:
      // 手机
      break;
    default:
      throw new NotFound("没有相应的处理函数!")
  }

  //成功
  // throw new Success("登录成功!");

});

//邮箱+密码登录
async function emailLogin(account, password) {
  const user =
    await User.verifyEmailPassword(account, password);
  //生成token
  return createToken(user.id, 8);
};

这样一个简单的登录就有了,我们直接设置的body,当然,按照以往的方式,我们这里就应该new出一个成功的错误对象。

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 369

相关文章