一步一步学vue

author author     2022-09-11     436

关键词:

接上篇,这次是真的接上篇,针对上篇未完成的部分,增加鉴权功能,开始之前,我们先要介绍一个新的知识,路由元数据。

 

在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!!!");
});
View Code

/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'
});
View Code

/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>
View Code

/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;
View Code

/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": "老板"
        }
    ]
}
View Code

/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:'未授权的访问'
            });

        }


    }

}
View Code

/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"
  }
}
View Code

 

一步一步学vue

本篇是是vue路由的开篇,会以一个简单的demo对vue-router进行一个介绍,主要覆盖以下几个常用场景:1、路由跳转2、嵌套路由3、路由参数 1、Vue-Router  一般来说,路由定义就是定义地址访问规则,然后由路由引擎根据这些... 查看详情

一步一步学vue

  前言:我以后在文章最后再也不说我下篇博文要写什么,之前说的大家也可以忽略,如果你不忽略,会失望的??,不过说出去的话还是要表示一下的,简单介绍一下路由钩子:  正如其名,vue-router 提供的导航钩子主要... 查看详情

一步一步学vue

本篇完成如下场景:1、系统包含首页、客户信息查询、登录三个模块2、默认进入系统首页,如果要进行用户查询,则需要进行登录授权3、查询用户后点击列表项,则进入详情页面基于上述场景需求描述,在客户端我们考虑,需... 查看详情

一步一步学vue

...不同,我们会对其进行增删改查的基本操作,之后进行进一步的完善,按照常规的系统使用经验,一般我们新增和编辑都是在模态框中处理,这里我们不会去构建复杂的模态框,只用一个简单的div层来代替,后期接下来的文章中... 查看详情

一步一步学rendermonkey

http://blog.csdn.net/tianhai110/article/details/5668832 转载请注明出处:http://blog.csdn.net/tianhai110/ 网上一些关于renderMonkey的教程:《RenderMonkey的基本使用方法》http://www.cnblogs.com/mixiyou/archive/2009/10/05/ 查看详情

一步一步学习vue

本篇继续学习vuex,还是以实例为主;我们以一步一步学Vue(四)中讲述的例子为基础,对其改造,基于vuex重构一遍,这是原始的代码:todolist.js;(function(){varlist=[];varTodo=(function(){varid=1;returnfunction(title,desc){this.title=title;this.desc=desc... 查看详情

一步一步学jvm-垃圾回收算法

标记-清除算法        算法分为标记和清除两个阶段:首先标记所有需要回收的对象,在标记完成后统一回收所有被标记的对象。        该算法存在的缺点:  1、 ... 查看详情

一步一步学nlp:熟悉nlp

NLP学习AI工程师必备的核心技能现实生活中的问题---->数学优化问题---->通过合适的工具解决whatisNLPNLP=NLP+NLUNLU:语音/文本->意思(meaning)Natural+langugeUnderstandingNLG:意思->文本/语音Natural+LangugeGenerationwhatis... 查看详情

一步一步学nlp:熟悉nlp

NLP学习AI工程师必备的核心技能现实生活中的问题---->数学优化问题---->通过合适的工具解决whatisNLPNLP=NLP+NLUNLU:语音/文本->意思(meaning)Natural+langugeUnderstandingNLG:意思->文本/语音Natural+LangugeGenerationwhatis... 查看详情

一步一步学多线程-synchronized

  当线程执行请求synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线程。      请求的线程会首先被加入到线程排队队列中,线程阻塞,当某个拥有线程锁的线程unlock之后,则排队队列里的线程... 查看详情

一步一步学jvm-垃圾回收

  垃圾回收器在对对象进行回收前,首先要判断对象是否还“活着”。判断方法有以下两种引用计数法        给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1。当引用失... 查看详情

一步一步学zookeeper-zookeeper初了解

角色        Zookeeper中的角色主要有以下三类        领导者(Leader)             查看详情

一步一步学jvm-运行时数据区域

程序计数器(ProgramCounterRegister)        像我们平时读书一样,当我们在去做别的事情之前,我们会对我们读到什么地方了做一个标记,方便我们再回来的时候接着重新读。如果这本书有很多人读呢... 查看详情

一步一步学jvm-垃圾回收器

Serial收集器        Serial收集器是最基本、历史最悠久的收集器。这个收集器是一个单线程的收集器。它在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。Serial收集器是新生代的收... 查看详情

一步一步学java:入门的基础知识

​​ JAVA入门的基础知识学的再多,也要记得复习复习基础知识丫;​ 基本类型及其转换数字中有byte,short,char,int,long,float,double的类型*在使用过程中:要注意在float后面加上F,在long后面加L;longi=10L;floatm=56.345F;char后... 查看详情

一步一步学jvm-java内存模型

主内存与工作内存        Java内存模型的主要目标是定义程序中各个变量的访问规则。即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。这里的变量和Java编程中所说的变量有所区... 查看详情

linux一步一步学linux——dnsdomainname命令(174)(代码片段)

00.目录文章目录00.目录01.命令概述02.命令格式03.常用选项04.参考示例05.附录01.命令概述dnsdomainname命令用于定义DNS系统中FQDN名称中的域名。dnsdomainname=hostname-d02.命令格式用法:dnsdomainname[-v]03.常用选项--help 显示帮助文档--ve... 查看详情

一步一步学ef系列6ioc之autofac

前言     之前的前5篇作为EF方面的基础篇,后面我们将使用MVC+EF并且使用IOC,Repository,UnitOfWork,DbContext来整体来学习。因为后面要用到IOC,所以本篇先单独先学习一下IOC,我们本本文单独主要学习Autofac,其实... 查看详情