本篇內(nèi)容主要講解“vue2.0+koa2+MongoDB怎么實現(xiàn)注冊登錄”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“vue2.0+koa2+mongodb怎么實現(xiàn)注冊登錄”吧!
創(chuàng)新互聯(lián)公司長期為千余家客戶提供的網(wǎng)站建設服務,團隊從業(yè)經(jīng)驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為金華企業(yè)提供專業(yè)的網(wǎng)站建設、網(wǎng)站制作,金華網(wǎng)站改版等技術服務。擁有十年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。
系統(tǒng)環(huán)境:MacOS 10.13.3
關于npm安裝速度慢或不成功
使用淘寶鏡像安裝
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
然后所有的 npm install
改為 cnpm install
項目啟動
1.初始化項目
$ npm install
2.啟動項目
$ npm run dev
3.啟動MongoDB
$ mongod --dbpath XXX
xxx是項目里 data 文件夾(也可以另行新建,數(shù)據(jù)庫用于存放數(shù)據(jù))的路徑,也可直接拖入終端。
4.啟動服務端
$ node server.js
前端UI
vue的首選UI庫我是選擇了餓了么的Element-UI了,其他諸如 iview 、 vue-strap 好像沒有ele全面。
安裝Element-UI
$ npm i element-ui -S
引入Element-UI
//在項目里的mian.js里增加下列代碼 import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI);
利用UI里面的選項卡切換做注冊和登錄界面的切換,以login組件作為整個登錄系統(tǒng)的主界面,register組件作為獨立組件切入。Element-UI的組成方式,表單驗證等API請查閱官網(wǎng)。
//login組件登錄 重置
接下來是注冊組件
//register組件注冊 重置
vue-router
vue-router 是vue創(chuàng)建單頁項目的核心,可以通過組合組件來組成應用程序,我們要做的是將組件(components)映射到路由(routes),然后告訴 vue-router 在哪里渲染它們。
上面的代碼里已有涉及到一些路由切換,我們現(xiàn)在來完善路由:
安裝
$ cnpm i vue-router
引入
import Router from 'vue-router' Vue.use(Router)
在src文件夾下面新建 router(文件夾)/index.js
我們引入了三個組件:
HelloWorld 登錄后的展示頁
login 登錄主界面
register 注冊組件
路由守衛(wèi)
利用 router.beforeEach
路由守衛(wèi)設置需要先登錄的頁面。通過 requiresAuth 這個字段來判斷該路由是否需要登錄權限,需要權限的路由就攔截,然后再判斷是否有token(下文會講到token),有就直接登錄,沒有就跳到登錄頁面。
import Vue from 'vue' import Router from 'vue-router' import HelloWorld from '@/components/HelloWorld' import login from '@/components/login' import register from '@/components/register' Vue.use(Router) const router = new Router({ mode: 'history', routes: [{ path: '/', name: 'home', component: HelloWorld, meta: { requiresAuth: true } }, { path: '/HelloWorld', name: 'HelloWorld', component: HelloWorld, }, { path: '/login', name: 'login', component: login, }, { path: '/register', name: 'register', component: register, }, ] }); //注冊全局鉤子用來攔截導航 router.beforeEach((to, from, next) => { //獲取store里面的token let token = store.state.token; //判斷要去的路由有沒有requiresAuth if (to.meta.requiresAuth) { if (token) { next(); } else { next({ path: '/login', query: { redirect: to.fullPath } // 將剛剛要去的路由path作為參數(shù),方便登錄成功后直接跳轉(zhuǎn)到該路由 }); } } else { next(); } }); export default router;
我們可以看到路由守衛(wèi)中token是從store里面獲取的,意味著我們是把token的各種狀態(tài)存放到store里面,并進行獲取,更新,刪除等操作,這就需要引入vuex狀態(tài)管理。
vuex
解釋一下為什么一個簡單的注冊登錄單頁需要用到vuex:項目中我們各個組件的操作基本都需要獲取到token進行驗證,如果組件A存儲了一個token,組件B要獲取這個token就涉及到了組件通信,這會非常繁瑣。引入vuex,不再是組件間的通信,而是組件和store的通信,簡單方便。
安裝
$ cnpm i vuex --S
引入
在main.js引入store,vue實例中也要加入store
//引入store import store from './store'
然后在需要使用vuex的組件中引入
//store index.js import Vuex from 'vuex' Vue.use(Vuex)
在src文件夾下面新建 store(文件夾)/index.js
//store index.js import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex); //初始化時用sessionStore.getItem('token'),這樣子刷新頁面就無需重新登錄 const state = { token: window.sessionStorage.getItem('token'), username: '' }; const mutations = { LOGIN: (state, data) => { //更改token的值 state.token = data; window.sessionStorage.setItem('token', data); }, LOGOUT: (state) => { //登出的時候要清除token state.token = null; window.sessionStorage.removeItem('token'); }, USERNAME: (state, data) => { //把用戶名存起來 state.username = data; window.sessionStorage.setItem('username', data); } }; const actions = { UserLogin({ commit }, data){ commit('LOGIN', data); }, UserLogout({ commit }){ commit('LOGOUT'); }, UserName({ commit }, data){ commit('USERNAME', data); } }; export default new Vuex.Store({ state, mutations, actions });
可以看到我們通過actions提交mutation,進行token的更改、清除以及用戶名儲存的操作。
此時啟動項目,可以看到初步的注冊登錄界面,點擊注冊或登錄按鈕可以切換到相應界面,并有基礎的表單驗證,登錄后會進入helloworld頁面。
我們寫好了基礎界面,接下來就是要把表單數(shù)據(jù)發(fā)送到后臺并進行一系列處理。現(xiàn)在還沒有后端接口沒關系,我們先寫好前端axios請求。
axios
vue的通訊之前使用 vue-resource ,有很多坑。直到vue2.0來臨,直接拋棄 vue-resource ,而使用 axios 。
用途:
封裝ajax,用來發(fā)送請求,異步獲取數(shù)據(jù)。以Promise為基礎的HTTP客戶端,適用于:瀏覽器和node.js。
具體API中文說明: https://www.kancloud.cn/yunye/axios/234845
安裝
$ cnpm i -S axios
引入
import axios from 'axios'
攔截器
在設置vue-router那部分加入了路由守衛(wèi)攔截需要登錄的路由,但這種方式只是簡單的前端路由控制,并不能真正阻止用戶訪問需要登錄權限的路由。當token失效了,但token依然保存在本地。這時候你去訪問需要登錄權限的路由時,實際上應該讓用戶重新登錄。這時候就需要攔截器 interceptors + 后端接口返回的http狀態(tài)碼來判斷。
在src文件夾下面新建axios.js(和App.vue同級)
//axios.js import axios from 'axios' import store from './store' import router from './router' //創(chuàng)建axios實例 var instance = axios.create({ timeout: 5000, //請求超過5秒即超時返回錯誤 headers: { 'Content-Type': 'application/json;charset=UTF-8' }, }); //request攔截器 instance.interceptors.request.use( config => { //判斷是否存在token,如果存在的話,則每個http header都加上token if (store.state.token) { config.headers.Authorization = `token ${store.state.token}`; } return config; } ); //respone攔截器 instance.interceptors.response.use( response => { return response; }, error => { //默認除了2XX之外的都是錯誤的,就會走這里 if (error.response) { switch (error.response.status) { case 401: router.replace({ //跳轉(zhuǎn)到登錄頁面 path: 'login', query: { redirect: router.currentRoute.fullPath } // 將跳轉(zhuǎn)的路由path作為參數(shù),登錄成功后跳轉(zhuǎn)到該路由 }); } } return Promise.reject(error.response); } ); export default { //用戶注冊 userRegister(data){ return instance.post('/api/register', data); }, //用戶登錄 userLogin(data){ return instance.post('/api/login', data); }, //獲取用戶 getUser(){ return instance.get('/api/user'); }, //刪除用戶 delUser(data){ return instance.post('/api/delUser', data); } }
代碼最后暴露了四個請求方法,分別對應注冊(register)、登錄(login)、獲取(user)、刪除(delUser)用戶,并且都在/api下面,四個請求接口分別是:
http://localhost:8080/api/login
http://localhost:8080/api/register
http://localhost:8080/api/user
http://localhost:8080/api/delUser
后面我們再利用這四個方法寫相對應的后臺接口。
服務端 server
注意
文章從這里開始進入服務端,由于服務端需要和數(shù)據(jù)庫、http安全通訊(jwt)共同搭建,因此請結合本節(jié)和下面的數(shù)據(jù)庫、jwt章節(jié)閱讀。
koa2可以使用可以使用async/await語法,免除重復繁瑣的回調(diào)函數(shù)嵌套,并使用ctx來訪問Context對象。
現(xiàn)在我們用koa2寫項目的API服務接口。
安裝
$ cnpm i koa $ cnpm i koa-router -S //koa路由中間件 $ cnpm i koa-bodyparser -S //處理post請求,并把koa2上下文的表單數(shù)據(jù)解析到ctx.request.body中
引入
const Koa = require('koa');
在項目根目錄下面新建server.js,作為整個server端的啟動入口。
//server.js const Koa = require('koa'); const app = new Koa(); //router const Router = require('koa-router'); //父路由 const router = new Router(); //bodyparser:該中間件用于post請求的數(shù)據(jù) const bodyParser = require('koa-bodyparser'); app.use(bodyParser()); //引入數(shù)據(jù)庫操作方法 const UserController = require('./server/controller/user.js'); //checkToken作為中間件存在 const checkToken = require('./server/token/checkToken.js'); //登錄 const loginRouter = new Router(); loginRouter.post('/login', UserController.Login); //注冊 const registerRouter = new Router(); registerRouter.post('/register', UserController.Reg); //獲取所有用戶 const userRouter = new Router(); userRouter.get('/user', checkToken, UserController.GetAllUsers); //刪除某個用戶 const delUserRouter = new Router(); delUserRouter.post('/delUser', checkToken, UserController.DelUser); //裝載上面四個子路由 router.use('/api',loginRouter.routes(),loginRouter.allowedMethods()); router.use('/api',registerRouter.routes(),registerRouter.allowedMethods()); router.use('/api',userRouter.routes(),userRouter.allowedMethods()); router.use('/api',delUserRouter.routes(),delUserRouter.allowedMethods()); //加載路由中間件 app.use(router.routes()).use(router.allowedMethods()); app.listen(8888, () => { console.log('The server is running at http://localhost:' + 8888); });
代碼里可以看到,獲取用戶和刪除用戶都需要驗證token(詳見下文jwt章節(jié)),并且我們把四個接口掛在到了/api上,和前面axios的請求路徑一致。
接口地址配置
另外由于我們的項目啟動端口是8080,koa接口監(jiān)聽的端口是8888,于是需要在config/index.js文件里面,在dev配置里加上:
proxyTable: { '/api': { target: 'http://localhost:8888', changeOrigin: true } }, jsonwebtoken(JWT)
JWT能夠在HTTP通信過程中,幫助我們進行身份認證。
具體API詳見: https://segmentfault.com/a/1190000009494020
Json Web Token是怎么工作的?
1、客戶端通過用戶名和密碼登錄服務器;
2、服務端對客戶端身份進行驗證;
3、服務端對該用戶生成Token,返回給客戶端;
4、客戶端將Token保存到本地瀏覽器,一般保存到cookie(本文是用sessionStorage,看情況而定)中;
5、客戶端發(fā)起請求,需要攜帶該Token;
6、服務端收到請求后,首先驗證Token,之后返回數(shù)據(jù)。服務端不需要保存Token,只需要對Token中攜帶的信息進行驗證即可。無論客戶端訪問后臺的哪臺服務器,只要可以通過用戶信息的驗證即可。
在server文件夾,下面新建/token(文件夾)里面新增checkToken.js和createToken.js,分別放置檢查和新增token的方法。
安裝
$ cnpm i jsonwebtoken -S createToken.js const jwt = require('jsonwebtoken'); module.exports = function(user_id){ const token = jwt.sign({user_id: user_id}, 'zhangzhongjie', {expiresIn: '60s' }); return token; };
創(chuàng)建token時,我們把用戶名作為JWT Payload的一個屬性,并且把密鑰設置為‘zhangzhongjie',token過期時間設置為60s。意思是登錄之后,60s內(nèi)刷新頁面不需要再重新登錄。
checkToken.js
const jwt = require('jsonwebtoken'); //檢查token是否過期 module.exports = async ( ctx, next ) => { //拿到token const authorization = ctx.get('Authorization'); if (authorization === '') { ctx.throw(401, 'no token detected in http headerAuthorization'); } const token = authorization.split(' ')[1]; let tokenContent; try { tokenContent = await jwt.verify(token, 'zhangzhongjie');//如果token過期或驗證失敗,將拋出錯誤 } catch (err) { ctx.throw(401, 'invalid token'); } await next(); };
先拿到token再用jwt.verify進行驗證,注意此時密鑰要對應上createToken.js的密鑰‘zhangzhongjie'。如果token為空、過期、驗證失敗都拋出401錯誤,要求重新登錄。
數(shù)據(jù)庫 mongodb
MongoDB是一種文檔導向數(shù)據(jù)庫管理系統(tǒng),旨在為 WEB 應用提供可擴展的高性能數(shù)據(jù)存儲解決方案。用node鏈接MongoDB非常方便。
安裝
$ cnpm i mongoose -S
MongoDB的連接有好幾種方式,這里我們用connection。connection是mongoose模塊的默認引用,返回一個Connetion對象。
在server文件夾下新建db.js,作為數(shù)據(jù)庫連接入口。
//db.js const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/vue-login'); let db = mongoose.connection; // 防止Mongoose: mpromise 錯誤 mongoose.Promise = global.Promise; db.on('error', function(){ console.log('數(shù)據(jù)庫連接出錯!'); }); db.on('open', function(){ console.log('數(shù)據(jù)庫連接成功!'); }); //聲明schema const userSchema = mongoose.Schema({ username: String, password: String, token: String, create_time: Date }); //根據(jù)schema生成model const User = mongoose.model('User', userSchema) module.exports = User;
除了我們用的 connetion ,還有 connect() 和 createConnection() 連接方式。
Schema定義表的模板,讓這一類document在數(shù)據(jù)庫中有一個具體的構成、存儲模式。但也僅僅是定義了Document是什么樣子的,至于生成document和對document進行各種操作(增刪改查)則是通過相對應的model來進行的,那我們就需要把userSchema轉(zhuǎn)換成我們可以使用的model,也就是說model才是我們可以進行操作的handle。
編譯完model我們就得到了一個名為 User 的model。
注意你在這里定義的schema表,后面寫注冊入庫時數(shù)據(jù)的存儲需要對應這個表。
在server文件夾下新建controller(文件夾)/user.js,存放數(shù)據(jù)庫的操作方法。
先安裝一些功能插件
$ cnpm i moment -s //用于生成時間 $ cnpm i objectid-to-timestamp -s //用于生成時間 $ cnpm i sha1 -s //安全哈希算法,用于密碼加密 //user.js const User = require('../db.js').User; //下面這兩個包用來生成時間 const moment = require('moment'); const objectIdToTimestamp = require('objectid-to-timestamp'); //用于密碼加密 const sha1 = require('sha1'); //createToken const createToken = require('../token/createToken.js'); //數(shù)據(jù)庫的操作 //根據(jù)用戶名查找用戶 const findUser = (username) => { return new Promise((resolve, reject) => { User.findOne({ username }, (err, doc) => { if(err){ reject(err); } resolve(doc); }); }); }; //找到所有用戶 const findAllUsers = () => { return new Promise((resolve, reject) => { User.find({}, (err, doc) => { if(err){ reject(err); } resolve(doc); }); }); }; //刪除某個用戶 const delUser = function(id){ return new Promise(( resolve, reject) => { User.findOneAndRemove({ _id: id }, err => { if(err){ reject(err); } console.log('刪除用戶成功'); resolve(); }); }); }; //登錄 const Login = async ( ctx ) => { //拿到賬號和密碼 let username = ctx.request.body.name; let password = sha1(ctx.request.body.pass);//解密 let doc = await findUser(username); if(!doc){ console.log('檢查到用戶名不存在'); ctx.status = 200; ctx.body = { info: false } }else if(doc.password === password){ console.log('密碼一致!'); //生成一個新的token,并存到數(shù)據(jù)庫 let token = createToken(username); console.log(token); doc.token = token; await new Promise((resolve, reject) => { doc.save((err) => { if(err){ reject(err); } resolve(); }); }); ctx.status = 200; ctx.body = { success: true, username, token, //登錄成功要創(chuàng)建一個新的token,應該存入數(shù)據(jù)庫 create_time: doc.create_time }; }else{ console.log('密碼錯誤!'); ctx.status = 200; ctx.body = { success: false }; } }; //注冊 const Reg = async ( ctx ) => { let user = new User({ username: ctx.request.body.name, password: sha1(ctx.request.body.pass), //加密 token: createToken(this.username), //創(chuàng)建token并存入數(shù)據(jù)庫 create_time: moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss'),//將objectid轉(zhuǎn)換為用戶創(chuàng)建時間 }); //將objectid轉(zhuǎn)換為用戶創(chuàng)建時間(可以不用) user.create_time = moment(objectIdToTimestamp(user._id)).format('YYYY-MM-DD HH:mm:ss'); let doc = await findUser(user.username); if(doc){ console.log('用戶名已經(jīng)存在'); ctx.status = 200; ctx.body = { success: false }; }else{ await new Promise((resolve, reject) => { user.save((err) => { if(err){ reject(err); } resolve(); }); }); console.log('注冊成功'); ctx.status = 200; ctx.body = { success: true } } }; //獲得所有用戶信息 const GetAllUsers = async( ctx ) => { //查詢所有用戶信息 let doc = await findAllUsers(); ctx.status = 200; ctx.body = { succsess: '成功', result: doc }; }; //刪除某個用戶 const DelUser = async( ctx ) => { //拿到要刪除的用戶id let id = ctx.request.body.id; await delUser(id); ctx.status = 200; ctx.body = { success: '刪除成功' }; }; module.exports = { Login, Reg, GetAllUsers, DelUser };
上面這些方法構成了項目中數(shù)據(jù)庫操作的核心,我們來剖析一下。
首先定義了公用的三個基礎方法:findUser、findAllUsers、delUser。其中findUser需要傳入 username 參數(shù),delUser需要傳入 id 參數(shù)。
注冊方法
拿到用戶post提交的表單信息,new之前按數(shù)據(jù)庫設計好的并編譯成model的User,把獲取到的用戶名,密碼(需要用sha1哈希加密),token(利用之前創(chuàng)建好的createToken方法,并把用戶名作為jwt的payload參數(shù)),生成時間存入。
此時要先搜索數(shù)據(jù)庫這個用戶名是否存在,存在就返回失敗,否則把user存入數(shù)據(jù)庫并返回成功。
登錄方法
拿到用戶post的表單信息,用戶名和密碼(注冊用了哈希加密,此時要解密)。從數(shù)據(jù)庫搜索該用戶名,判斷用戶名是否存在,不存在返回錯誤,存在的話判斷數(shù)據(jù)庫里存的密碼和用戶提交的密碼是否一致,一致的話給這個用戶生成一個新的token,并存入數(shù)據(jù)庫,返回成功。
獲得所有用戶信息
就是把上面公用findAllUsers方法封裝了一下并把信息放在result里面,讓后面helloworld頁面可以獲取到這個數(shù)據(jù)并展示出來。
刪除某個用戶
注意要先拿到需要刪除的用戶id,作為參數(shù)傳入。
寫完這些方法,就可以把前面沒有完善的注冊登錄功能完善了。
數(shù)據(jù)庫可視化
當我們注冊完,數(shù)據(jù)入庫,此時我們想查看一下剛才注冊入庫的數(shù)據(jù),要用到數(shù)據(jù)庫可視化工具。我是用 MongoBooster ,操作簡單。
由下圖可以看到示例中注冊的兩條數(shù)據(jù),包含了id、username、password、token、time。那串長長的密碼是由于哈希加密編譯而成。
整合完善注冊組件
在register.vue的表單驗證后加上下列代碼
//register.vue if (valid) { axios.userRegister(this.ruleForm) .then(({}) => { if (data.success) { this.$message({ type: 'success', message: '注冊成功' }); } else { this.$message({ type: 'info', message: '用戶名已經(jīng)存在' }); } }) }
完善登錄組件
登錄組件我們之前沒有任何數(shù)據(jù)提交,現(xiàn)在在驗證成功后加入一系列方法完成登錄操作:
引入axios
import axios from '../axios.js'
然后在login.vue的表單驗證后加上下列代碼
//login.vue if (valid) { axios.userLogin(this.ruleForm) .then(({ data }) => { //賬號不存在 if (data.info === false) { this.$message({ type: 'info', message: '賬號不存在' }); return; } //賬號存在 if (data.success) { this.$message({ type: 'success', message: '登錄成功' }); //拿到返回的token和username,并存到store let token = data.token; let username = data.username; this.$store.dispatch('UserLogin', token); this.$store.dispatch('UserName', username); //跳到目標頁 this.$router.push('HelloWorld'); } }); }
將表單數(shù)據(jù)提交到后臺,返回data狀態(tài),進行賬號存在與否的判斷操作。登錄成功需要拿到返回的token和username存到store,跳到目標HelloWorld頁。
完善目標頁組件
注冊登錄成功后,終于到了實際的展示頁了——helloworld!
我們來完善這個組件,讓它展示出目前所有的已注冊用戶名,并給出刪除按鈕。
//Helloworld.vue
- {{ index + 1 }}.{{ item.username }}
刪除 注銷
輸出頁面比較簡單,這里說幾個要點:
1.要在實例創(chuàng)建完成后( created() )立即請求getUser()接口,請求失敗要清楚掉token,請求成功要把返回數(shù)據(jù)放入user以供頁面渲染。
2. thisID 要寫成對象格式,否則會報錯
3.注銷時要清除掉token
到此,相信大家對“vue2.0+koa2+mongodb怎么實現(xiàn)注冊登錄”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!