cli3解决 ie11语法错误 vue_基于 Vue + Koa2 + MongoDB + Redis 实现一个完整的登录注册...
項目地址:https://github.com/caochangkui/vue-element-responsive-demo/tree/login-register
通過 vue-cli3.0 + Element 構(gòu)建項目前端,Node.js + Koa2 + MongoDB + Redis 實現(xiàn)數(shù)據(jù)庫和接口設計,包括郵箱驗證碼、用戶注冊、用戶登錄、查看刪除用戶等功能。
1. 技術棧
- 前端
- 初始化項目:vue-cli3.0
- 組件庫:Element-ui
- 路由控制/攔截:Vue-router
- 狀態(tài)管理:Vuex
- 服務端
- 運行環(huán)境:Node.js
- 后臺開發(fā)框架:Koa2
- 路由中間件:Koa-router
- 發(fā)送郵件: nodemailer
- HTTP通訊
- 接口請求/攔截:Axios
- Token認證:jsonwebtoken
- 數(shù)據(jù)庫
- MongoDB
- 數(shù)據(jù)庫操作:Mongoose
- 緩存工具:Redis
2. 項目依賴:
"dependencies": {"axios": "^0.18.0","crypto-js": "^3.1.9-1","element-ui": "^2.4.5","js-cookie": "^2.2.0","jsonwebtoken": "^8.5.0","koa": "^2.7.0","koa-bodyparser": "^4.2.1","koa-generic-session": "^2.0.1","koa-json": "^2.0.2","koa-redis": "^3.1.3","koa-router": "^7.4.0","mongoose": "^5.4.19","nodemailer": "^5.1.1","nodemon": "^1.18.10","vue": "^2.5.21","vue-router": "^3.0.1","vuex": "^3.0.1"}3. 前端實現(xiàn)步驟
3.1 登錄注冊頁面
通過 vue-cli3.0 + Element 構(gòu)建項目前端頁面
登錄頁(@/view/users/Login.vue):
注冊頁(@/view/users/Register.vue):
發(fā)送驗證碼前需要驗證用戶名和郵箱,用戶名必填,郵箱格式需正確。
用戶設置頁(@/view/users/setting/Setting.vue)
用戶登錄后,可以進入用戶設置頁查看用戶和刪除用戶
3.2 Vuex 狀態(tài)管理
通過 vuex 實現(xiàn)保存或刪除用戶 token,保存用戶名等功能。
由于使用單一狀態(tài)樹,應用的所有狀態(tài)會集中到一個比較大的對象。當應用變得非常復雜時,store 對象就有可能變得相當臃腫。
為了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter。
根目錄下新建store文件夾,創(chuàng)建modules/user.js:
const user = {state: {token: localStorage.getItem('token'),username: localStorage.getItem('username')},mutations: {BIND_LOGIN: (state, data) => {localStorage.setItem('token', data)state.token = data},BIND_LOGOUT: (state) => {localStorage.removeItem('token')state.token = null},SAVE_USER: (state, data) => {localStorage.setItem('username', data)state.username = data}} }export default user創(chuàng)建文件 getters.js 對數(shù)據(jù)進行處理輸出:
const getters = {sidebar: state => state.app.sidebar,device: state => state.app.device,token: state => state.user.token,username: state => state.user.username} export default getters創(chuàng)建文件 index.js 管理所有狀態(tài):
import Vue from 'vue' import Vuex from 'vuex' import user from './modules/user' import getters from './getters'Vue.use(Vuex)const store = new Vuex.Store({modules: {user},getters })export default store3.3 路由控制/攔截
路由配置(router.js):
import Vue from 'vue' import Router from 'vue-router' const Login = () => import(/* webpackChunkName: "users" */ '@/views/users/Login.vue') const Register = () => import(/* webpackChunkName: "users" */ '@/views/users/Register.vue') const Setting = () => import(/* webpackChunkName: "tables" */ '@/views/setting/Setting.vue')Vue.use(Router)const router = new Router({base: process.env.BASE_URL,routes: [{path: '/login',name: 'Login',component: Login,meta: {title: '登錄'}},{path: '/register',name: 'Register',component: Register,meta: {title: '注冊'}},{path: '/setting',name: 'Setting',component: Setting,meta: {breadcrumb: '設置',requireLogin: true},}] })路由攔截:
關于vue 路由攔截參考:https://www.cnblogs.com/cckui/p/10319013.html
// 頁面刷新時,重新賦值token if (localStorage.getItem('token')) {store.commit('BIND_LOGIN', localStorage.getItem('token')) }// 全局導航鉤子 router.beforeEach((to, from, next) => {if (to.meta.title) { // 路由發(fā)生變化修改頁面titledocument.title = to.meta.title}if (to.meta.requireLogin) {if (store.getters.token) {if (Object.keys(from.query).length === 0) { // 判斷路由來源是否有query,處理不是目的跳轉(zhuǎn)的情況next()} else {let redirect = from.query.redirect // 如果來源路由有queryif (to.path === redirect) { // 避免 next 無限循環(huán)next()} else {next({ path: redirect }) // 跳轉(zhuǎn)到目的路由}}} else {next({path: '/login',query: { redirect: to.fullPath } // 將跳轉(zhuǎn)的路由path作為參數(shù),登錄成功后跳轉(zhuǎn)到該路由})}} else {next()} })export default router3.4 Axios 封裝
封裝 Axios
// axios 配置 import axios from 'axios' import store from './store' import router from './router'//創(chuàng)建 axios 實例 let instance = axios.create({timeout: 5000, // 請求超過5秒即超時返回錯誤headers: { 'Content-Type': 'application/json;charset=UTF-8' }, })instance.interceptors.request.use(config => {if (store.getters.token) { // 若存在token,則每個Http Header都加上tokenconfig.headers.Authorization = `token ${store.getters.token}`console.log('拿到token')}console.log('request請求配置', config)return config},err => {return Promise.reject(err)})// http response 攔截器 instance.interceptors.response.use(response => {console.log('成功響應:', response)return response},error => {if (error.response) {switch (error.response.status) {case 401:// 返回 401 (未授權(quán)) 清除 token 并跳轉(zhuǎn)到登錄頁面store.commit('BIND_LOGOUT')router.replace({path: '/login',query: {redirect: router.currentRoute.fullPath}})breakdefault:console.log('服務器出錯,請稍后重試!')alert('服務器出錯,請稍后重試!')}}return Promise.reject(error.response) // 返回接口返回的錯誤信息} )export default {// 發(fā)送驗證碼userVerify (data) {return instance.post('/api/verify', data)},// 注冊userRegister (data) {return instance.post('/api/register', data)},// 登錄userLogin (data) {return instance.post('/api/login', data)},// 獲取用戶列表getAllUser () {return instance.get('/api/alluser')},// 刪除用戶delUser (data) {return instance.post('/api/deluser', data)} }4. 服務端和數(shù)據(jù)庫實現(xiàn)
在根目錄下創(chuàng)建 server 文件夾,存放服務端和數(shù)據(jù)庫相關代碼。
4.1 MongoDB和Redis
創(chuàng)建 /server/dbs/config.js ,進行數(shù)據(jù)庫和郵箱配置
// mongo 連接地址 const dbs = 'mongodb://127.0.0.1:27017/[數(shù)據(jù)庫名稱]'// redis 地址和端口 const redis = {get host() {return '127.0.0.1'},get port() {return 6379} }// qq郵箱配置 const smtp = {get host() {return 'smtp.qq.com'},get user() {return '1********@qq.com' // qq郵箱名},get pass() {return '*****************' // qq郵箱授權(quán)碼},// 生成郵箱驗證碼get code() {return () => {return Math.random().toString(16).slice(2, 6).toUpperCase()}},// 定義驗證碼過期時間rules,5分鐘get expire() {return () => {return new Date().getTime() + 5 * 60 * 1000}} }module.exports = {dbs,redis,smtp }使用 qq 郵箱發(fā)送驗證碼,需要在“設置/賬戶”中打開POP3/SMTP服務和MAP/SMTP服務。
4.2 Mongo 模型
創(chuàng)建 /server/dbs/models/users.js:
// users模型,包括四個字段 const mongoose = require('mongoose') const Schema = mongoose.Schema const UserSchema = new Schema({username: {type: String,unique: true,required: true},password: {type: String,required: true},email: {type: String,required: true},token: {type: String,required: true} })module.exports = {Users: mongoose.model('User', UserSchema) }4.3 接口實現(xiàn)
創(chuàng)建 /server/interface/user.js:
const Router = require('koa-router') const Redis = require('koa-redis') // key-value存儲系統(tǒng), 存儲用戶名,驗證每個用戶名對應的驗證碼是否正確 const nodeMailer = require('nodemailer') // 通過node發(fā)送郵件 const User = require('../dbs/models/users').Users const Email = require('../dbs/config')// 創(chuàng)建和驗證token, 參考4.4 const createToken = require('../token/createToken.js') // 創(chuàng)建token const checkToken = require('../token/checkToken.js') // 驗證token// 創(chuàng)建路由對象 const router = new Router({prefix: '/api' // 接口的統(tǒng)一前綴 })// 獲取redis的客戶端 const Store = new Redis().client// 接口 - 測試 router.get('/test', async ctx => {ctx.body = {code: 0,msg: '測試',} })// 發(fā)送驗證碼 的接口 router.post('/verify', async (ctx, next) => {const username = ctx.request.body.usernameconst saveExpire = await Store.hget(`nodemail:${username}`, 'expire') // 拿到過期時間console.log(ctx.request.body)console.log('當前時間:', new Date().getTime())console.log('過期時間:', saveExpire)// 檢驗已存在的驗證碼是否過期,以限制用戶頻繁發(fā)送驗證碼if (saveExpire && new Date().getTime() - saveExpire < 0) {ctx.body = {code: -1,msg: '發(fā)送過于頻繁,請稍后再試'}return}// QQ郵箱smtp服務權(quán)限校驗const transporter = nodeMailer.createTransport({/*** 端口465和587用于電子郵件客戶端到電子郵件服務器通信 - 發(fā)送電子郵件。* 端口465用于smtps SSL加密在任何SMTP級別通信之前自動啟動。* 端口587用于msa*/host: Email.smtp.host,port: 587,secure: false, // 為true時監(jiān)聽465端口,為false時監(jiān)聽其他端口auth: {user: Email.smtp.user,pass: Email.smtp.pass}})// 郵箱需要接收的信息const ko = {code: Email.smtp.code(),expire: Email.smtp.expire(),email: ctx.request.body.email,user: ctx.request.body.username}// 郵件中需要顯示的內(nèi)容const mailOptions = {from: `"認證郵件" <${Email.smtp.user}>`, // 郵件來自to: ko.email, // 郵件發(fā)往subject: '邀請碼', // 郵件主題 標題html: `您正在注冊****,您的邀請碼是${ko.code}` // 郵件內(nèi)容}// 執(zhí)行發(fā)送郵件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: '驗證碼已發(fā)送,請注意查收,可能會有延時,有效期5分鐘'} })// 接口 - 注冊 router.post('/register', 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') // 過期時間console.log(ctx.request.body)console.log('redis中保存的驗證碼:', saveCode)console.log('當前時間:', new Date().getTime())console.log('過期時間:', saveExpire)// 用戶提交的驗證碼是否等于已存的驗證碼if (code === saveCode) {if (new Date().getTime() - saveExpire > 0) {ctx.body = {code: -1,msg: '驗證碼已過期,請重新申請'}return}} else {ctx.body = {code: -1,msg: '請?zhí)顚懻_的驗證碼'}return}} else {ctx.body = {code: -1,msg: '請?zhí)顚戲炞C碼'}return}// 用戶名是否已經(jīng)被注冊const user = await User.find({ username })if (user.length) {ctx.body = {code: -1,msg: '該用戶名已被注冊'}return}// 如果用戶名未被注冊,則寫入數(shù)據(jù)庫const newUser = await User.create({username,password,email,token: createToken(this.username) // 生成一個token 存入數(shù)據(jù)庫})// 如果用戶名被成功寫入數(shù)據(jù)庫,則返回注冊成功if (newUser) {ctx.body = {code: 0,msg: '注冊成功',}} else {ctx.body = {code: -1,msg: '注冊失敗'}} })// 接口 - 登錄 router.post('/login', async (ctx, next) => {const { username, password } = ctx.request.bodylet doc = await User.findOne({ username })if (!doc) {ctx.body = {code: -1,msg: '用戶名不存在'}} else if (doc.password !== password) {ctx.body = {code: -1,msg: '密碼錯誤'}} else if (doc.password === password) {console.log('密碼正確')let token = createToken(username) // 生成tokendoc.token = token // 更新mongo中對應用戶名的tokentry {await doc.save() // 更新mongo中對應用戶名的tokenctx.body = {code: 0,msg: '登錄成功',username,token}} catch (err) {ctx.body = {code: -1,msg: '登錄失敗,請重新登錄'}}} })// 接口 - 獲取所有用戶 需要驗證 token router.get('/alluser', checkToken, async (ctx, next) => {try {let result = []let doc = await User.find({})doc.map((val, index) => {result.push({email: val.email,username: val.username,})})ctx.body = {code: 0,msg: '查找成功',result}} catch (err) {ctx.body = {code: -1,msg: '查找失敗',result: err}} })// 接口 - 刪除用戶 需要驗證 token router.post('/deluser', checkToken, async (ctx, next) => {const { username } = ctx.request.bodytry {await User.findOneAndRemove({username: username})ctx.body = {code: 0,msg: '刪除成功',}} catch (err) {ctx.body = {code: -1,msg: '刪除失敗',}} })module.exports = {router }上面實現(xiàn)了五個接口:
- 發(fā)送驗證碼至郵箱: router.post('/verify')
- 注冊:router.post('/register')
- 登錄:router.post('/login')
- 獲取用戶列表:router.get('/alluser')
- 刪除數(shù)據(jù)庫中的某個用戶:router.post('/deluser')
分別對應了前面 3.4 中 axios 中的5個請求地址
4.4 JSON Web Token 認證
JSON Web Token(縮寫 JWT)是目前最流行的跨域認證解決方案。詳情參考:http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
分別創(chuàng)建 /server/token/createToken.js 和 /server/token/checkToken.js
// 創(chuàng)建token const jwt = require('jsonwebtoken')module.exports = function (id) {const token = jwt.sign({id: id},'cedric1990',{expiresIn: '300s'})return token } // 驗證token const jwt = require('jsonwebtoken')// 檢查 token module.exports = async (ctx, next) => {// 檢驗是否存在 token// axios.js 中設置了 authorizationconst authorization = ctx.get('Authorization')if (authorization === '') {ctx.throw(401, 'no token detected in http headerAuthorization')}const token = authorization.split(' ')[1]// 檢驗 token 是否已過期try {await jwt.verify(token, 'cedric1990')} catch (err) {ctx.throw(401, 'invalid token')}await next() }4.5 服務端入口
根目錄創(chuàng)建 server.js:
// server端啟動入口 const Koa = require('koa') const app = new Koa(); const mongoose = require('mongoose') const bodyParser = require('koa-bodyparser') const session = require('koa-generic-session') const Redis = require('koa-redis') const json = require('koa-json') // 美化json格式化 const dbConfig = require('./server/dbs/config')const users = require('./server/interface/user.js').router// 一些session和redis相關配置 app.keys = ['keys', 'keyskeys'] app.proxy = true app.use(session({store: new Redis()}) )app.use(bodyParser({extendTypes: ['json', 'form', 'text'] }))app.use(json())// 連接數(shù)據(jù)庫 mongoose.connect(dbConfig.dbs,{ useNewUrlParser: true } )mongoose.set('useNewUrlParser', true) mongoose.set('useFindAndModify', false) mongoose.set('useCreateIndex', true)const db = mongoose.connection mongoose.Promise = global.Promise // 防止Mongoose: mpromise 錯誤db.on('error', function () {console.log('數(shù)據(jù)庫連接出錯') })db.on('open', function () {console.log('數(shù)據(jù)庫連接成功') })// 路由中間件 app.use(users.routes()).use(users.allowedMethods())app.listen(8888, () => {console.log('This server is running at http://localhost:' + 8888) })5. 跨域處理
詳情參考:https://www.cnblogs.com/cckui/p/10331432.html
vue 前端啟動端口9527 和 koa 服務端啟動端口8888不同,需要做跨域處理,打開vue.config.js:
devServer: {port: 9527,https: false,hotOnly: false,proxy: {'/api': {target: 'http://127.0.0.1:8888/', // 接口地址changeOrigin: true,ws: true,pathRewrite: {'^/': ''}}}}6. 接口對接
import axios from '../../axios.js' import CryptoJS from 'crypto-js' // 用于MD5加密處理發(fā)送驗證碼:
// 用戶名不能為空,并且驗證郵箱格式 sendCode() {let email = this.ruleForm2.emailif (this.checkEmail(email) && this.ruleForm2.username) {axios.userVerify({username: encodeURIComponent(this.ruleForm2.username),email: this.ruleForm2.email}).then((res) => {if (res.status === 200 && res.data && res.data.code === 0) {this.$notify({title: '成功',message: '驗證碼發(fā)送成功,請注意查收。有效期5分鐘',duration: 1000,type: 'success'})let time = 300this.buttonText = '已發(fā)送'this.isDisabled = trueif (this.flag) {this.flag = false;let timer = setInterval(() => {time--;this.buttonText = time + ' 秒'if (time === 0) {clearInterval(timer);this.buttonText = '重新獲取'this.isDisabled = falsethis.flag = true;}}, 1000)}} else {this.$notify({title: '失敗',message: res.data.msg,duration: 1000,type: 'error'})}})} }注冊:
submitForm(formName) {this.$refs[formName].validate(valid => {if (valid) {axios.userRegister({username: encodeURIComponent(this.ruleForm2.username),password: CryptoJS.MD5(this.ruleForm2.pass).toString(),email: this.ruleForm2.email,code: this.ruleForm2.smscode}).then((res) => {if (res.status === 200) {if (res.data && res.data.code === 0) {this.$notify({title: '成功',message: '注冊成功。',duration: 2000,type: 'success'})setTimeout(() => {this.$router.push({path: '/login'})}, 500)} else {this.$notify({title: '錯誤',message: res.data.msg,duration: 2000,type: 'error'})}} else {this.$notify({title: '錯誤',message: `服務器請求出錯, 錯誤碼${res.status}`,duration: 2000,type: 'error'})}})} else {console.log("error submit!!");return false;}}) },登錄:
login(formName) {this.$refs[formName].validate(valid => {if (valid) {axios.userLogin({username: window.encodeURIComponent(this.ruleForm.name),password: CryptoJS.MD5(this.ruleForm.pass).toString()}).then((res) => {if (res.status === 200) {if (res.data && res.data.code === 0) {this.bindLogin(res.data.token)this.saveUser(res.data.username)this.$notify({title: '成功',message: '恭喜,登錄成功。',duration: 1000,type: 'success'})setTimeout(() => {this.$router.push({path: '/'})}, 500)} else {this.$notify({title: '錯誤',message: res.data.msg,duration: 1000,type: 'error'})}} else {this.$notify({title: '錯誤',message: '服務器出錯,請稍后重試',duration: 1000,type: 'error'})}})}}) },7. 啟動項目 測試接口
7.1 vue端:
$ npm run serve7.2 啟動mogod:
$ mongod7.3 啟動Redis:
$ redis-server7.4 啟動服務端server.js:
安裝 nodemon 熱啟動輔助工具:
$ npm i nodemon $ nodemon server.js總結(jié)
以上是生活随笔為你收集整理的cli3解决 ie11语法错误 vue_基于 Vue + Koa2 + MongoDB + Redis 实现一个完整的登录注册...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bom实现方块移动_从0开始实现一个俄罗
- 下一篇: vue数组变化视图_vue对象数组数据变