一步一步学Vue
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一步一步学Vue相关的知识,希望对你有一定的参考价值。
接上篇,这次是真的接上篇,针对上篇未完成的部分,增加鉴权功能,开始之前,我们先要介绍一个新的知识,路由元数据。
在vue-router中,定义元数据的方式:
const router = new VueRouter({ routes: [ { path: \'/foo\', component: Foo, children: [ { path: \'bar\', component: Bar, // a meta field meta: { requiresAuth: true } } ] } ] })
那么如何访问这个 meta
字段呢?
首先,我们把routes
配置中的每个路由对象叫做路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,他可能匹配多个路由记录
例如,根据上面的路由配置,/foo/bar
这个 URL 将会匹配父路由记录以及子路由记录。
一个路由匹配到的所有路由记录会暴露为 $route
对象(还有在导航钩子中的 route 对象)的 $route.matched
数组。因此,我们需要遍历 $route.matched
来检查路由记录中的 meta
字段。
所以在vue-router官方文档中,我们可以看到下面的代码,其实就是前端路由授权的粗糙实现方式(代码不做过多解释,里面我加入了详细的注释):
router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuth)) { // 如果路由配置了元数据requiresAuth为true,则需要鉴权,这是需要判断是否登录 // 如果没有登录则跳转到login页面 if (!auth.loggedIn()) { next({ path: \'/login\', //这里传递fullPath,是为了登录之后作为return back query: { redirect: to.fullPath } }) } else { //如果已经登录过,直接执行进入下一步 next() } } else { //对没有配置requiresAuth的路由进行处理,如果不加入,则路由未配置requiresAuth,无法进入,所以确保一定要调用 next() next() } })
好了,基础知识介绍完毕,现在我们把我们的路由加入meta信息,启用权限验证:
var router = new VueRouter({ routes: [{ name: \'home\', path: \'/home\', component: HomeComponent }, { name: \'customers\', path: \'/customers\', component: CustomerListComponent, meta: { auth: true } }, { name: \'detail\', path: \'/detail/:id\', component: CustomerComponent, meta: { auth: true } }, { name: \'login\', path: \'/login\', component: LoginComponent } ] });
//注册全局事件钩子 router.beforeEach(function (to, from, next) { //如果路由中配置了meta auth信息,则需要判断用户是否登录; if (to.matched.some(r => r.meta.auth)) { //登录后会把token作为登录的标示,存在localStorage中 if (!localStorage.getItem(\'token\')) { console.log("需要登录"); next({ path: \'/login\', query: { to: to.fullPath } }) } else { next(); } } else { next() } });
更新代码后,可以跟目录运行node app.js ,打开8110端口,查看,运行效果如下:
这个时候,无论从浏览器地址栏还是通过跳转方式,在点击配置了 meta:{auth:true}的路由时,如果没有登录,都会跳转到登录页面,并记录return back url。
下面我们加入登录逻辑,并修改后台接口,支持用户授权,后台我们使用jwt的一个实现https://github.com/auth0/node-jsonwebtoken ,直接使用npm 安装即可,对jwt不太了解的同学,可以搜索 json web token (jwt)(另外为了读取http body,我们这里会使用 body-parser,可以直接使用npm install --save body-parser 安装)。
首先修改我们的登录组件:
methods: { login: function () { var self = this; axios.post(\'/login\', this.user) .then(function (res) { console.log(res); if (res.data.success) { localStorage.setItem(\'token\', res.data.token); console.log(self.$router); self.$router.push({ path: self.$route.query.to }); } else { alert(res.data.errorMessage); } }) .catch(function (error) { console.log(error); }); } }
并添加全局拦截器,在任何ajax请求中加入token 头,如果熟悉angular拦截器的同学对axios实现的拦截器应该很熟悉的,这和jquery 对Ajax.setting的设置类似:
// request 拦截器 ,对所有请求,加入auth axios.interceptors.request.use( cfg => { // 判断是否存在token,如果存在,则加上token if (localStorage.getItem(\'token\')) { cfg.headers.Authorization = localStorage.getItem(\'token\'); } return cfg; }, err => { return Promise.reject(err); }); // http response 拦截器 axios.interceptors.response.use( res => { return res; }, err => { if (err.response) { switch (err.response.status) { case 401: //如果未授权访问,则跳转到登录页面 router.replace({ path: \'/login\', query: {redirect: router.currentRoute.fullPath} }) } } return Promise.reject(err.response.data) });
这样,我们再每次请求的时候,如果token存在,则就会带上token;
接着,修改我们的后端部分,加入处理登录,以及生成解析token的部分,修改我们的authMiddleware.js文件:
var jwt = require(\'jsonwebtoken\'); /** * 有效用户列表 */ var validUsers = [{ username: \'zhangsan\', password: \'123456\' }, { username: \'lisi\', password: \'123456\' }]; //FIXME:这个作为密钥,一定要安全的,这里我为了简单就直接写了一大段字符串 const secretKey = \'dhahr3uiudfu93u43i3uy43&*&$#*&437hjhfjdjhfdfjsy8&*&*JNFSJDJHH??>:LP\'; /** * 创建token * @param {用户对象} user */ var createToken = function (user) { /** * 创建token 并设置过期时间为一个小时 */ return jwt.sign({ data: user, exp: Math.floor(Date.now() / 1000) + (60 * 60) }, secretKey); } /** * 解析token * @param {用户需要验证的token} token */ var parseToken = function (token, callback) { jwt.verify(token, secretKey, function (err, result) { callback && callback(err, result); }); } module.exports = function (req, res, next) { //如果是登录请求 console.log(req.path); if (req.path === "/login") { var username = req.body.username; var password = req.body.password; //判断用户名和密码是否正确 var user = validUsers.filter(u => u.username === username && u.password === password)[0]; //如果用户用户名密码匹配成功,直接创建token并返回 if (user) { res.json({ success: true, token: createToken(user) }) } else { res.json({ success: false, errorMessage: \'username or password is not correct,please retry again\' }) } } else {//如果不是登录请求,则需要检查token 的合法性 var token = req.get(\'Authorization\'); console.log(token); if (token) { parseToken(token, function (err, result) { if (err) {//如果解析失败,则返回失败信息 res.status(401).json( { success: false, errorMessage: JSON.stringify(err) }) } else { next(); } }) }else{ res.status(401).json({ success:false, errorMessage:\'未授权的访问\' }); } } }
上述代码加上注释应该没什么复杂度的,各位同学应该可以看的明白,这样之后,我们启用我们的授权中间件,修改/app.js文件:
var express = require("express"); var bodyParser = require("body-parser"); var authMiddleware = require(\'./middleware/authMiddleware\'); var customerRouter = require(\'./router/customers\'); var app = express(); app.use(express.static(\'public\')); app.get(\'/portal\', function (req, res) { res.json({ data: [ { visits: 12, clicks: 100 }, { location: \'BeiJing\', total: 17 } ] }) }) app.use(bodyParser.json()) app.use(authMiddleware); app.use(\'/api\', customerRouter);
运行我们的代码可以看到如下效果:
博客园对图片大小有要求,不能很好的截取,就只截取了一部分,这是登录后的效果,登录前的效果,大家可以自己测试,完整代码如下:
/app.js
var express = require("express"); var bodyParser = require("body-parser"); var authMiddleware = require(\'./middleware/authMiddleware\'); var customerRouter = require(\'./router/customers\'); var app = express(); app.use(express.static(\'public\')); app.get(\'/portal\', function (req, res) { res.json({ data: [ { visits: 12, clicks: 100 }, { location: \'BeiJing\', total: 17 } ] }) }) app.use(bodyParser.json()) app.use(authMiddleware); app.use(\'/api\', customerRouter); app.listen(8110, function () { console.log("port 8110 is listenning!!!"); });
/public/app.js
var LoginComponent = { template: ` <div class="login" > username:<input type="text" v-model="user.username" /> password:<input type="password" v-model="user.password" /> <input type="button" @click="login()" value="login" /> </div> `, data: function () { return { user: { username: \'\', password: \'\' } } }, methods: { login: function () { var self = this; axios.post(\'/login\', this.user) .then(function (res) { console.log(res); if (res.data.success) { localStorage.setItem(\'token\', res.data.token); console.log(self.$router); self.$router.push({ path: self.$route.query.to }); } else { alert(res.data.errorMessage); } }) .catch(function (error) { console.log(error); }); } } } var CustomerListComponent = { template: ` <div> <div> <input type="text" v-model="keyword" /> <input type="button" @click="getCustomers()" value="search" /> </div> <ul> <router-link v-for="c in customers" tag="li" :to="{name:\'detail\',params:{id:c.id}}" :key="c.id">{{c.name}}</router-link> </ul> </div> `, data: function () { return { customers: [], keyword: \'\' } }, created: function () { this.getCustomers(); }, methods: { getCustomers: function () { axios.get(\'/api/getCustomers\', { params: { keyword: this.keyword } }) .then(res => { this.customers = res.data; console.log(res) }) .catch(err => console.log(err)); }, } } var CustomerComponent = { template: ` <div> {{customer}} </div> `, data: function () { return { customer: {} } }, created: function () { var id = this.$route.params.id; this.getCustomerById(id); }, watch: { \'$route\': function () { console.log(this.$route.params.id); } }, methods: { getCustomerById: function (id) { axios.get(\'/api/customer/\' + id) .then(res => this.customer = res.data) .catch(err => console.log(err)); } } } var HomeComponent = { template: `<div> <h1>Home 页面,portal页</h1> <h2>以下数据来自服务端</h2> {{stat}} </div>`, data: function () { return { stat: \'\'//代表相关统计信息等 } }, methods: { getStat: function () { return axios.get(\'/portal\'); } }, created: function () { this.getStat().then(res => { this.stat = JSON.stringify(res.data); }).catch(err => { console.log(err); }) } } var router = new VueRouter({ routes: [{ name: \'home\', path: \'/home\', component: HomeComponent }, { name: \'customers\', path: \'/customers\', component: CustomerListComponent, meta: { auth: true } }, { name: \'detail\', path: \'/detail/:id\', component: CustomerComponent, meta: { auth: true } }, { name: \'login\', path: \'/login\', component: LoginComponent } ] }); //注册全局事件钩子 router.beforeEach(function (to, from, next) { //如果路由中配置了meta auth信息,则需要判断用户是否登录; if (to.matched.some(r => r.meta.auth)) { //登录后会把token作为登录的标示,存在localStorage中 if (!localStorage.getItem(\'token\')) { console.log("需要登录"); next({ path: \'/login\', query: { to: to.fullPath } }) } else { next(); } } else { next() } }); // request 拦截器 ,对所有请求,加入auth axios.interceptors.request.use( cfg => { // 判断是否存在token,如果存在,则加上token if (localStorage.getItem(\'token\')) { cfg.headers.Authorization = localStorage.getItem(\'token\'); } return cfg; }, err => { return Promise.reject(err); }); // http response 拦截器 axios.interceptors.response.use( res => { return res; }, err => { if (err.response) { switch (err.response.status) { case 401: //如果未授权访问,则跳转到登录页面 router.replace({ path: \'/login\', query: {redirect: router.currentRoute.fullPath} }) } } return Promise.reject(err.response.data) }); var app = new Vue({ router: router, template: ` <div> <router-link :to="{name:\'home\'}" >Home</router-link> <router-link :to="{name:\'customers\'}" >Customers</router-link> <router-view></router-view> </div> `, el: \'#app\' });
/public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>demo3</title> <script src="https://cdn.bootcss.com/vue/2.4.1/vue.js"></script> <script src="https://cdn.bootcss.com/vue-router/2.7.0/vue-router.js"></script> <script src="https://cdn.bootcss.com/axios/0.16.2/axios.js"></script> </head> <body> <div id="app"> </div> <script src="./app.js"></script> </body> </html>
/router/customers.js
var router = require("express").Router(); var db = require(\'./fakeData\'); router.get(\'/getCustomers\', function (req, res) { var list = db.data; list = list.filter(v => v.name.indexOf(req.query.keyword) !== -1); res.json(list); }); router.get(\'/customer/:id\',function(req,res){ var list=db.data; var obj=list.filter(v=>v.id==req.params.id)[0]; res.json(obj); }) module.exports = router;
/router/fakeData.json
{ "data": [ { "id":1, "name": "zhangsan", "age": 14, "sexy": "男", "majar": "学生" }, { "id":2, "name": "lisi", "age": 19, "sexy": "女", "majar": "学生" }, { "id":3, "name": "wangwu", "age": 42, "sexy": "男", "majar": "工人" }, { "id":4, "name": "maliu", "age": 10, "sexy": "男", "majar": "学生" }, { "id":5, "name": "wangermazi", "age": 82, "sexy": "男", "majar": "画家" }, { "id":6, "name": "liudehua", "age": 55, "sexy": "男", "majar": "天王" }, { "id":7, "name": "zhoujielun", "age": 14, "sexy": "男", "majar": "歌手" }, { "id":8, "name": "wangfei", "age": 50, "sexy": "女", "majar": "歌手" }, { "id":9, "name": "mayun", "age": 60, "sexy": "男", "majar": "老板" } ] }
/middleware/authMiddleware.js
var jwt = require(\'jsonwebtoken\'); /** * 有效用户列表 */ var validUsers = [{ username: \'zhangsan\', password: \'123456\' }, { username: \'lisi\', password: \'123456\' }]; //FIXME:这个作为密钥,一定要安全的,这里我为了简单就直接写了一大段字符串 const secretKey = \'dhahr3uiudfu93u43i3uy43&*&$#*&437hjhfjdjhfdfjsy8&*&*JNFSJDJHH??>:LP\'; /** * 创建token * @param {用户对象} user */ var createToken = function (user) { /** * 创建token 并设置过期时间为一个小时 */ return jwt.sign({ data: user, exp: Math.floor(Date.now() / 1000) + (60 * 60) }, secretKey); } /** * 解析token * @param {用户需要验证的token} token */ var parseToken = function (token, callback) { jwt.verify(token, secretKey, function (err, result) { callback && callback(err, result); }); } module.exports = function (req, res, next) { //如果是登录请求 console.log(req.path); if (req.path === "/login") { var username = req.body.username; var password = req.body.password; //判断用户名和密码是否正确 var user = validUsers.filter(u => u.username === username && u.password === password)[0]; //如果用户用户名密码匹配成功,直接创建token并返回 if (user) { res.json({ success: true, token: createToken(user) }) } else { res.json({ success: false, errorMessage: \'username or password is not correct,please retry again\' }) } } else {//如果不是登录请求,则需要检查token 的合法性 var token = req.get(\'Authorization\'); console.log(token); if (token) { parseToken(token, function (err, result) { if (err) {//如果解析失败,则返回失败信息 res.status(401).json( { success: false, errorMessage: JSON.stringify(err) }) } else { next(); } }) }else{ res.status(401).json({ success:false, errorMessage:\'未授权的访问\' }); } } }
/package.json
{ "name": "vue_demo3", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \\"Error: no test specified\\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "body-parser": "^1.17.2", "express": "^4.15.3", "jsonwebtoken": "^7.4.1" } }
以上是关于一步一步学Vue的主要内容,如果未能解决你的问题,请参考以下文章