zoukankan      html  css  js  c++  java
  • 动态路由权限控制

    详解基于vue,vue-router, vuex以及addRoutes进行权限控制

     

    基于vuex, vue-router,vuex的权限控制教程,完整代码地址见 https://github.com/linrunzheng/vue-permission-control

    接下来让我们模拟一个普通用户打开网站的过程,一步一步的走完整个流程。

    首先从打开本地的服务localhost:8080开始,我们知道打开后会进入login页面,那么判断的依据是什么。
    首先是token。
    没有登陆的用户是获取不到token的,而登陆后的角色我们会将token存到local或者seesionStorage 因此,根据当前有没有token即可知道是否登陆。

    为了存取token并且方便我们操作,可以配和vuex实现

    /* state.js */
    export default {
        get UserToken() {
            return localStorage.getItem('token')
        },
        set UserToken(value) {
            localStorage.setItem('token', value)
        }
    }
    /* mutation.js */
    export default {
        LOGIN_IN(state, token) {
            state.UserToken = token
        },
        LOGIN_OUT(state) {
            state.UserToken = ''
        }
    }

    拦截的判断

    1. 没有token进入需要权限的页面:redirect到login页面
    2. 由于我们路由是动态挂载的,包括 ' ' 和404,所以当匹配不到路由时,也重定向到login
    router.beforeEach((to, from, next) => {
        if (!store.state.UserToken) {
            if (
                to.matched.length > 0 &&
                !to.matched.some(record => record.meta.requiresAuth)
            ) {
                next()
            } else {
                next({ path: '/login' })
            }
        } 
    })

    好了,此时用户打开localhost:8080,默认匹配的是''路径,此时我们并没有挂载路由,也没有token,所以来到了login。

    输入用户名密码后,有token了,通过store触发* commit('LOGIN_IN')* 来设置token。

    但是还是没有路由,目前最开始只有login路由

    /* 初始路由 */
    export default new Router({
        routes: [
            {
                path: '/login',
                component: Login
            }
        ]
    })
    
    /* 准备动态添加的路由 */
    export const DynamicRoutes = [
        {
            path: '',
            component: Layout,
            name: 'container',
            redirect: 'home',
            meta: {
                requiresAuth: true,
                name: '首页'
            },
            children: [
                {
                    path: 'home',
                    component: Home,
                    name: 'home',
                    meta: {
                        name: '首页'
                    }
                }
            ]
        },
        {
            path: '/403',
            component: Forbidden
        },
        {
            path: '*',
            component: NotFound
        }
    ]

    我们要根据当前用户的token去后台获取权限。

    由于权限这块逻辑还挺多,所以在vuex添加了一个permission模块来处理权限。

    为了判断是已有路由列表,需要在vuex的permission模块存一个state状态permissionList用来判断,假如permissionList不为null,即已经有路由,如果不存在,就需要我们干活了。

    router.beforeEach((to, from, next) => {
        if (!store.state.UserToken) {
            ...
        } else {
            /* 现在有token了 */
            if (!store.state.permission.permissionList) {
                /* 如果没有permissionList,真正的工作开始了 */
                store.dispatch('permission/FETCH_PERMISSION').then(() => {
                    next({ path: to.path })
                })
            } else {
                if (to.path !== '/login') {
                    next()
                } else {
                    next(from.fullPath)
                }
            }
        }
    })

    来看一下 store.dispatch('permission/FETCH_PERMISSION') 都干了什么

    actions: {
        async FETCH_PERMISSION({ commit, state }) {
           /*  获取后台给的权限数组 */
            let permissionList = await fetchPermission()
    
            /*  根据后台权限跟我们定义好的权限对比,筛选出对应的路由并加入到path=''的children */
            let routes = recursionRouter(permissionList, dynamicRouter)
            let MainContainer = DynamicRoutes.find(v => v.path === '')
            let children = MainContainer.children
            children.push(...routes)
    
            /* 生成左侧导航菜单 */
            commit('SET_MENU', children)
    
            setDefaultRoute([MainContainer])
    
            /*  初始路由 */
            let initialRoutes = router.options.routes
    
            /*  动态添加路由 */
            router.addRoutes(DynamicRoutes)
    
            /* 完整的路由表 */
            commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
        }
    }

    首先,await fetchPermission()获取后台给的权限数组,格式大概如下

    {
        "code": 0,
        "message": "获取权限成功",
        "data": [
            {
                "name": "订单管理",
                "children": [
                    {
                        "name": "订单列表"
                    },
                    {
                        "name": "生产管理",
                        "children": [
                            {
                                "name": "生产列表"
                            }                     
                        ]
                    },
                    {
                        "name": "退货管理"
                    }
                ]
            }
        ]
    }

    其次根据我们写好的路由数组,进行对比,过滤得到我们要的路由

    /* 这里是我们写好的需要权限判断的路由 */
    const dynamicRoutes = [
        {
            path: '/order',
            component: Order,
            name: 'order-manage',
            meta: {
                name: '订单管理'
            },
            children: [
                {
                    path: 'list',
                    name: 'order-list',
                    component: OrderList,
                    meta: {
                        name: '订单列表'
                    }
                },
                {
                    path: 'product',
                    name: 'product-manage',
                    component: ProductManage,
                    meta: {
                        name: '生产管理'
                    },
                    children: [
                        {
                            path: 'list',
                            name: 'product-list',
                            component: ProductionList,
                            meta: {
                                name: '生产列表'
                            }
                        },
                        {
                            path: 'review',
                            name: 'review-manage',
                            component: ReviewManage,
                            meta: {
                                name: '审核管理'
                            }
                        }
                    ]
                },
                {
                    path: 'returnGoods',
                    name: 'return-goods',
                    component: ReturnGoods,
                    meta: {
                        name: '退货管理'
                    }
                }
            ]
        }
    ]
    
    export default dynamicRoutes

    为了对比,我写好了一个递归函数,用name和meta.name进行对比 ,根据这个函数就可以得到我们想要的结果

    /**
     *
     * @param  {Array} userRouter 后台返回的用户权限json
     * @param  {Array} allRouter  前端配置好的所有动态路由的集合
     * @return {Array} realRoutes 过滤后的路由
     */
    
    export function recursionRouter(userRouter = [], allRouter = []) {
        var realRoutes = []
        allRouter.forEach((v, i) => {
            userRouter.forEach((item, index) => {
                if (item.name === v.meta.name) {
                    if (item.children && item.children.length > 0) {
                        v.children = recursionRouter(item.children, v.children)
                    }
                    realRoutes.push(v)
                }
            })
        })
        return realRoutes
    }

    得到过滤后的数组后,加入到path为''的children下面

    {
            path: '',
            component: Layout,
            name: 'container',
            redirect: 'home',
            meta: {
                requiresAuth: true,
                name: '首页'
            },
            children: [
                {
                    path: 'home',
                    component: Home,
                    name: 'home',
                    meta: {
                        name: '首页'
                    }
                },
                <!-- 将上面得到的东西加入到这里 -->
                ...
            ]
        }

    这个时候,path为''的children就是我们左侧的导航菜单了,存到state的sidebarMenu待用。加入到children后,这时DynamicRoutes就可以加入到路由了。

    /*  动态添加路由 */
    router.addRoutes(DynamicRoutes)
    
    
     /*  初始路由 */
    let initialRoutes = router.options.routes
    /* 合并起来,就是完整的路由了 */
    commit('SET_PERMISSION', [...initialRoutes, ...DynamicRoutes])
    路由添加完了,也就是action操作完毕了,即可在action.then里面调用 next({ path: to.path })进去路由,这里要注意, next里面要传参数即要进入的页面的路由信息,因为next传参数后,当前要进入的路由会被废止,转而进入参数对应的路由,虽然是同一个路由,这么做主要是为了确保addRoutes生效了。

    进入路由后,要开始生成左侧菜单,之前我们已经存到sidebarMenu了,现在需要做的只是递归生成菜单而已,虽然用了element的导航菜单,但是为了递归路由,还需要自己封装一下。这里核心的地方是组件的name,在组件里面有children的地方,又再次使用自己,从而遍历整个tree结构的路由。

    <template>
        <div class="menu-container">
            <template v-for="v in menuList">
                <el-submenu :index="v.name" v-if="v.children&&v.children.length>0" :key="v.name">
                    <template slot="title">
                        <i class="iconfont icon-home"></i>
                        <span>{{v.meta.name}}</span>
                    </template>
                    <el-menu-item-group>
                        <my-nav :menuList="v.children"></my-nav>
                    </el-menu-item-group>
                </el-submenu>
                <el-menu-item :key="v.name" :index="v.name" @click="gotoRoute(v.name)" v-else>
                    <i class="iconfont icon-home"></i>
                    <span slot="title">{{v.meta.name}}</span>
                </el-menu-item>
            </template>
        </div>
    </template>
    
    <script>
    export default {
        name: 'my-nav',
        props: {
            menuList: {
                type: Array,
                default: function() {
                    return []
                }
            }
        },
        methods: {
            gotoRoute(name) {
                this.$router.push({ name })
            }
        }
    }
    </script>

    刷新页面后,根据我们router.beforeEach的判断,有token但是没permissionList,我们是会重新触发action去获取路由的,所以无需担心。但是导航菜单active效果会不见。不过我们已经把el-menu-item的key设置为路由的name,那么我们只要在刷新后,在afterEach把当前路由的name赋值给el-menu default-active即可。同理,在afterEach阶段获取所有matched的路由,即可实现面包屑导航。

    if (!store.state.permission.permissionList) {
        store.dispatch('permission/FETCH_PERMISSION').then(() => {
            next({ path: to.path })
        })
    } 
    
    
    
    ...
    router.afterEach((to, from, next) => {
        var routerList = to.matched
        store.commit('setCrumbList', routerList)
        store.commit('permission/SET_CURRENT_MENU', to.name)
    })
    

    退出登陆后,需要刷新页面,因为我们是通过addRoutes添加的,router没有deleteRoutes这个api,所以清除token,清除permissionList等信息,刷新页面是最保险的。

    最后还有一点,每次请求得带上token, 可以对axios封装一下来处理
    var instance = axios.create({
        timeout: 30000,
        baseURL
    })
    
    // 添加请求拦截器
    instance.interceptors.request.use(
        function(config) {
            // 请求头添加token
            if (store.state.UserToken) {
                config.headers.Authorization = store.state.UserToken
            }
            return config
        },
        function(error) {
            return Promise.reject(error)
        }
    )
    
    /* axios请求二次封装 */
    instance.get = function(url, data, options) {
        return new Promise((resolve, reject) => {
            axios
                .get(url, data, options)
                .then(
                    res => {
                        var response = res.data
                        if (response.code === 0) {
                            resolve(response.data)
                        } else {
                            Message.warning(response.message)
                            /* reject(response.message) */
                        }
                    },
                    error => {
                        if (error.response.status === 401) {
                            Message.warning({
                                message: '登陆超时,请重新登录'
                            })
                            store.commit('LOGIN_OUT')
                            window.location.reload()
                        } else {
                            Message.error({
                                message: '系统异常'
                            })
                        }
                        reject(error)
                    }
                )
                .catch(e => {
                    console.log(e)
                })
        })
    }
    
    export default instance
  • 相关阅读:
    C++学习笔记(十六):友元
    C++学习笔记(十五):异常
    C++学习笔记(十四):模板
    C++学习笔记(十三):类、包和接口
    C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类
    C++学习笔记(十一):void*指针、类型转换和动态内存分配
    C++学习笔记(十):类
    quartz 实现调度任务 SchedulerManager
    Session 活化与钝化 与tomcat钝化驱动器
    web listener
  • 原文地址:https://www.cnblogs.com/zhaoxiaobei/p/10634651.html
Copyright © 2011-2022 走看看