koa+passport+mongoose+redis实现登录、注册、验证和退出的接口

一、koa+passport+mongoose+redis实现登录、注册、验证和退出的接口

  1. 创建 axios.js 配置文件,通过 npm i axios 下载 axios,通过 axios.create 创建对外暴露的接口,baseURL配置基础路径,判断开发环境,timeout超时时间,headers头部区域,代码如下所示:
import axios from 'axios'

const instance = axios.create({
    baseURL: `http://${process.env.HOST||'localhost'}:${process.env.POST||3000}`,
    timeout: 1000,
    headers: {
    }
})

export default instance

  1. 创建 config.js 配置文件,配置 dbs 数据库, redis远程字典服务以及 smtp 邮件传输协议。在smtp中, 设置host邮箱主机,设置user邮箱账号,设置pass邮箱授权码,最后返回 code 编码和过期时间,代码如下所示:
export default {
    dbs: 'mongodb://127.0.0.1:27017/user',
    redis: {
        get host() {
            return '127.0.0.1'
        },
        get port() {
            return 6379
        }
    },
    smtp: {
        get host() {
            return 'smtp.qq.com'
        },
        get user() {
            return '[email protected]'
        },
        get pass() {
            return 'abcdefg'
        },
        get code() {
            return () => {
                // 转换16进制,大写,四位数随机验证码
                return Math.random().toString(16).slice(2,6).toUpperCase()
            }
        },
        get expire() {
            return () => {
                return new Date().getTime()+60*60*1000
            }
        }
    }
    
}
  1. 通过npm i koa-router koa-redis nodemailer命令下载对应的工具

  2. 创建 users.js 接口文件,引入 koa-routerkoa-redisnodemaileruserspassportconfigaxios,代码如下所示:

import Router from 'koa-router'
import Redis from 'koa-redis'
import nodeMailer from 'nodemailer'
import User from '../dbs/models/users'
import Passport from './untils/passport'
import Email from '../dbs/config'
import axios from './untils/axios'
  1. 创建接口的前缀,以及 redis 的客户端,代码如下所示:
let router = new Router({
    prefix: '/users'
})

let Store = new Redis().client
  1. 创建注册的接口,从 ctx.request.body中解构赋值出username, password, email, code的值。验证验证码,在点击发送验证码以后,会将code值存储在redis中,取的时候从redis中获取。如果验证码存在,从 redis中获取 codeexpire 的值。判断验证码是否相等,如果验证码正确,判断时间是否过期, 验证码错误和没有填验证码就进行相应的提示。验证用户名,根据用户名去查找是否有重复的。如果用户名已存在,就提示已被注册。如果用户名不存在,写库,创建新用户,并且进行相应的响应,通过 ctx.body 返回对应的code值和提示信息,代码如下所示:
// 注册的接口
router.post('/signup', async (ctx) => {
    const { username, password, email, code} = ctx.request.body;
    
    if (code) {
        const saveCode = await Store.hget(`nodemail:${username}`, 'code')
        const saveExpire = await Store.hget(`nodemail:${username}`, 'expire')
        if (code === saveCode) { 
            if (new Date().getTime() - saveExpire > 0) {
                ctx.body = {
                    code: -1,
                    msg: '验证码已过期,请重新尝试'
                }
                return false
            }
        } else {  
            ctx.body = {
                code: -1,
                msg: '请填写正确的验证码'
            }
        }
    } else { 
        ctx.body = {
            code: -1,
            msg: '请填写验证码'
        }
    }

    let user = await User.find({ username })
    if (user.length) {
        ctx.body = {
            code: -1,
            msg: '已被注册'
        }
        return
    }
    let nuser = await User.create({ username, password, email })
    if (nuser) {
        let res = await axios.post('/users/signin', { username, password})
        if (res.data && res.data.code === 0) {
            ctx.body = {
                code: 0,
                msg: '注册成功',
                user: res.data.user
            }
        } else {
            ctx.body = {
                code: -1,
                msg: 'error'
            }
        }
    } else { 
        ctx.body = {
            code: -1,
            msg: '注册失败'
        }
    }
})

  1. 创建登录的接口,passport.authenticate( strategy, options, callback ), 验证请求用户,返回一个function,符合funciton(req, res, next)middleware格式。这个仅在登录时验证该登录请求,登录成功后,一般需要配合session策略,将登录状态保存在session中,这样每个请求的登录状态就可以通过session策略来获取了。这种场景,需要确认passport.authenticate传入的options.sessiontrue(默认值),并将passport.session()注册为middleware,以便在每个请求时都执行(注册在staticinitialize之后)。这个中间件会从每个reqsession中取出’passport'的值,反序列化出user,并赋值给req.user。如果有 error 就异常抛出,无 error 判断用户是否存在,通过 ctx.body 返回对应的code值和消息提示,代码如下所示:
router.post('/signin', async (ctx, next) => {
    return Passport.authenticate('local', function (err, user, info, status ) {
        if (err) { 
            ctx.body = {
                code: -1,
                msg: err
            }
        } else { 
            if (user) {
                ctx.body = {
                    code: 0,
                    msg: '登录成功',
                    user
                }
                return ctx.login(user)
            } else { 
                ctx.body = {
                    code: 1,
                    msg: info
                }
            }
        }
    })(ctx, next)
})
  1. 创建验证码验证的接口,从请求中获取 username的值,从 redis 中获取过期时间,存在过期时间并且当前时间小于过期时间,设置不频繁请求。nodeMailer 创建传输方式,发送对象,接收对象,配置发送的邮箱对象。transporter 发送对象去发送邮箱,携带配置对象,errinfo,有 error 报错,无 errorredis,通过 ctx.body 返回对应的code 值和提示信息,代码如下所示:
router.post('/verify', async (ctx, next) => {
    let username = ctx.request.body.username
    const saveExpire = await Store.hget(`nodemail:${username}`, 'expire')
    if (saveExpire && new Date() - saveExpire < 0) { 
        ctx.body = {
            code: -1,
            msg: '验证请求过于频繁,1分钟内1次'
        }
        return false
    }
    let transporter = nodeMailer.createTransport({
        host: Email.smtp.post,
        port: 587,
        secure: false, // 不使用 SSL 传输
        auth: {
            user: Email.smtp.user,
            pass: Email.smtp.pass
        }
    })
    let ko = {
        code: Email.smtp.code(),
        expire: Email.smtp.expire(),
        email: ctx.request.body.email,
        user: ctx.request.body.username
    }
    let mailOptions = {
        from: `"认证邮件" <${Email.smtp.user}>`,
        to: ko.email,
        subject: '注册码',
        html: `您已经注册,您的邀请码是${ko.code}`
    }
    await transporter.sendMail(mailOptions, (err,info) => {
        if (err) {
            return console.log('error')
        } else { 
            Store.hmset(`nodemail:${ko.user}`, 'code',ko.code, 'expire',ko.expire, 'email',ko.email )
        }
    })
    ctx.body = {
        code: 0,
        msg: '验证码已发送,可能会有延时,有效期1分钟'
    }
})
  1. 创建退出的接口,通过 ctx.logout() 执行退出的操作,通过 ctx.isAuthenticated()进行二次校验,看是否还是登录的状态。登录成功,返回code的值为0,登录失败,返回code的值为-1,代码如下所示:
router.get('/exit', async (ctx, next) => {
    await ctx.logout()
    if (!ctx.isAuthenticated()) { 
        ctx.body = {
            code: 0
        }
    } else { 
        ctx.body = {
            code: -1
        }
    }
})

  1. 最后通过 export default 对外暴露 接口,代码如下所示:
export default router
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章