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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章