一、koa+passport+mongoose+redis實現登錄、註冊、驗證和退出的接口
- 創建
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
- 創建
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
}
}
}
}
-
通過
npm i koa-router koa-redis nodemailer
命令下載對應的工具 -
創建
users.js
接口文件,引入koa-router
、koa-redis
、nodemailer
、users
、passport
、config
和axios
,代碼如下所示:
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'
- 創建接口的前綴,以及
redis
的客戶端,代碼如下所示:
let router = new Router({
prefix: '/users'
})
let Store = new Redis().client
- 創建註冊的接口,從
ctx.request.body
中解構賦值出username
,password
,email
,code
的值。驗證驗證碼,在點擊發送驗證碼以後,會將code
值存儲在redis
中,取的時候從redis
中獲取。如果驗證碼存在,從redis
中獲取code
和expire
的值。判斷驗證碼是否相等,如果驗證碼正確,判斷時間是否過期, 驗證碼錯誤和沒有填驗證碼就進行相應的提示。驗證用戶名,根據用戶名去查找是否有重複的。如果用戶名已存在,就提示已被註冊。如果用戶名不存在,寫庫,創建新用戶,並且進行相應的響應,通過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: '註冊失敗'
}
}
})
- 創建登錄的接口,
passport.authenticate( strategy, options, callback )
, 驗證請求用戶,返回一個function
,符合funciton(req, res, next)
的middleware
格式。這個僅在登錄時驗證該登錄請求,登錄成功後,一般需要配合session
策略,將登錄狀態保存在session
中,這樣每個請求的登錄狀態就可以通過session
策略來獲取了。這種場景,需要確認passport.authenticate
傳入的options.session
爲true
(默認值),並將passport.session()
註冊爲middleware
,以便在每個請求時都執行(註冊在static
和initialize
之後)。這個中間件會從每個req
的session
中取出’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)
})
- 創建驗證碼驗證的接口,從請求中獲取
username
的值,從redis
中獲取過期時間,存在過期時間並且當前時間小於過期時間,設置不頻繁請求。nodeMailer
創建傳輸方式,發送對象,接收對象,配置發送的郵箱對象。transporter
發送對象去發送郵箱,攜帶配置對象,err
和info
,有error
報錯,無error
存redis
,通過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分鐘'
}
})
- 創建退出的接口,通過
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
}
}
})
- 最後通過
export default
對外暴露 接口,代碼如下所示:
export default router