TG-ADMIN 权限管理系统

项目简介

该项目是一款基于 SpringBoot + Vue2 + Jwt + ElementUi的 RBAC模型管理系统。

主要以自定义拦截器和jwt结合进行权限验证

通过自定义指令实现按钮级别权限,使用经典的RBAC模型

什么是RBAC?

1、RBAC模型概述

RBAC模型(Role-Based Access Control:基于角色的访问控制)模型是20世纪90年代研究出来的一种新模型,但其实在20世纪70年代的多用户计算时期,这种思想就已经被提出来,直到20世纪90年代中后期,RBAC才在研究团体中得到一些重视,并先后提出了许多类型的RBAC模型。其中以美国George Mason大学信息安全技术实验室(LIST)提出的RBAC96模型最具有代表,并得到了普遍的公认。

RBAC认为权限授权的过程可以抽象地概括为:Who是否可以对What进行How的访问操作,并对这个逻辑表达式进行判断是否为True的求解过程,也即是将权限问题转换为What、How的问题,Who、What、How构成了访问权限三元组;

2、RBAC的组成

在RBAC模型里面,有3个基础组成部分,分别是:用户、角色和权限。

RBAC通过定义角色的权限,并对用户授予某个角色从而来控制用户的权限,实现了用户和权限的逻辑分离(区别于ACL模型),极大地方便了权限的管理:

  • User(用户):每个用户都有唯一的UID识别,并被授予不同的角色
  • Role(角色):不同角色具有不同的权限
  • Permission(权限):访问权限
  • 用户-角色映射:用户和角色之间的映射关系
  • 角色-权限映射:角色和权限之间的映射

3,RBAC的模型

  1. RBAC0:RBAC0是权限最基础也是核心的模型,它包括用户/角色/权限,其中用户和角色是多对多的关系,角色和权限也是多对多的关系

  2. RBAC1:引用角色继承关系即角色间有上下级关系

    1. 角色间的继承关系可分为一般继承关系和受限继承关系。
    2. 一般继承关系仅要求角色继承关系是一个绝对偏序关系,允许角色间的多继承。而受限继承关系则进一步要求角色继承关系是一个树结构,实现角色间的单继承,这种设计可以给角色分组和分层,一定程度简化了权限管理的工作。

技术选型

1、系统环境

  • Java EE 8
  • Servlet 3.0
  • Apache Maven 3
  • Mysql 5.5
  • nodejs 14.21.3

2、主框架

  • Spring Boot 2.4.2
  • Jwt
  • Hutool
  • commons-pool2
  • poi-ooxml
  • Redis
  • Swagger
  • fastjson
  • commons-pool2
  • Mybatis-Plus-Generator

3、持久层

  • Apache MyBatis -Plus 3.5.x
  • Alibaba Druid 1.2.x

4、视图层

  • Aplayer
  • 一言
  • mavon-editor
  • echarts
  • Element ui
  • Vue2

内置功能

  • 欢迎页:介绍系统技术选型和统计

  • 文章管理:发布和修改文章,以及文章分类。

    • 所有文章
    • 文章分类
    • 文章标签
  • 系统管理:整个系统的管理包括用户菜单

    • 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
    • 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
    • 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
    • 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
    • 文件管理:对系统上传文件进行管理。
  • 系统接口:根据业务代码自动生成相关的api接口文档。

  • 连接池监视:监视当期系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈。

动态路由配置

javascript 复制代码
//该文件专门用来创建和管理整个应用的路由器
import Vue from "vue";
import VueRouter from "vue-router"
​
Vue.use(VueRouter)
//地址和组件的对应关系
const routes = [
    {
        path: '/login',
        name: 'login',
        meta: {
            title: '登录'
        },
        component: () => import('@/views/Login')
    },
    {
        path: '/register',
        name: 'register',
        meta: {
            title: '注册'
        },
        component: () => import('@/views/Register')
    },
    {
        path: '/404',
        name: 'NotFound',
        component: () => import('@/views/404')
    },
    {
        path: '/401',
        name: 'NotFound',
        component: () => import('@/views/401')
    }
]
const router = new VueRouter({
    routes,
    base: process.env.BASE_URL,
    mode: "history"
})
​
export const resetRoutes = () => {
    router.matcher = new VueRouter({
        routes,
        base: process.env.BASE_URL,
        mode: "history"
    })
}
​
​
export const setRoutes = () => {
    const storeMenus = localStorage.getItem("menus");
    if (storeMenus) {
        //一级路由
        const homeRoutes = {
            path: '/', name: 'Home', component: () => import('../views/Home.vue'), redirect: '/index',
            meta: {
                isAuto: false,//是否需要路由组件拦截
                title: '首页'
            },
            children: [{
                path: 'person',
                name: 'Person',
                component: () => import('@/views/Person'),
                meta: {title: '个人主页'},
            }, {
                path: 'upwd',
                name: 'Upwd',
                component: () => import('../views/Upwd.vue'),
                meta: {title: '修改密码'}
            }]
        }
        const menus = JSON.parse(storeMenus);
        let itemPMenu;
        let itemCMenu;
        console.log(menus)
        //二级路由
        menus.forEach(item => {
            if (item.type ==='L' && item.path) {
                let itemMenu = {
                    path: item.path,
                    name: item.name,
                    component: () => import('../views/' + item.component + '.vue'),
                    meta: {
                        title: item.name,
                        pid: item.pid,
                        visible: item.visible
                    },
                    children: []
                }
                homeRoutes.children.push(itemMenu);
            } else if (item.type ==='M' && item.children.length) {
                itemPMenu = {
                    path: item.path,
                    name: item.name,
                    component: {render(c) {return c('router-view')}},
                    meta: {
                        title: item.name,
                    },
                    children: []
                }
                //三级路由
                item.children.forEach(child => {
                    console.log(child.name,child.type === 'C' && child.pid === item.id && child.type === 'C' && child.visible && child.status)
                    if (child.type === 'C' && child.pid === item.id && child.type === 'C' && child.visible && child.status) {
                        itemCMenu = {
                            path: child.path.replace("/", ""),
                            name: child.name,
                            component: () => import('../views/' + child.component + '.vue'),
                            meta: {
                                title: child.name,
                                pid: child.pid
                            }
                        }
                        itemPMenu.children.push(itemCMenu);
                    }
                })
                homeRoutes.children.push(itemPMenu);
            }
        })
        console.log(homeRoutes)
        const currentRoutes = router.getRoutes().map(v => v.name);
        if (!currentRoutes.includes("Home")) {
            router.addRoute(homeRoutes)
        }
    }
}
setRoutes();
​
// 前置路由守卫 在每个路由之前
// to 到哪去
// from 由哪来
// next 是否放行
router.beforeEach((to, from, next) => {
    if (to.matched.length === 0) {
        const storeMenus = localStorage.getItem("menus");
        if (storeMenus) {
            // next('/404');
        } else {
            next('/login');
        }
    } else {
        next();
    }
​
});
// 后置路由守卫
router.afterEach((to, from) => {
    document.title = to.meta.title;
​
})
export default router

自定义指令

permission.js

ini 复制代码
/*
自定义指令全局注册  按钮鉴权
*/
import Vue from "vue";
​
// 全局注册指令
Vue.directive('permission', {
    inserted(el, binding) {
        // console.log(el, binding);
        let {value} = binding;
        //获取当前用户权限表
        let permissionsList = JSON.parse(localStorage.getItem("permissions"));
        // console.log(permissionsList)
        //当前权限
        let prem = value[0];
        // 按钮图标
        let icon = value[0].icon;
        // console.log(prem);
        //当前用户获取的权限表为所有权限*
        if (permissionsList && permissionsList.length) {
            if (permissionsList[0] === "*") {
                return;
            }
            // 检测当前权限
            let permission = permissionsList.filter(item => item.permission === prem);
            // console.log(permission[0])
            // 没有权限,删除当前按钮
            if (permission.length === 0) {
                el.remove();
            } else {
                // console.log(el.children[0])
                el.children[0].className = permission[0].icon;
                el.children[1].textContent = permission[0].name;
                // console.log(el.classList.value)
            }
        }
​
    }
});

使用,如下列所示:

ini 复制代码
v-permission="['user:list:del']"

运行

1,导入创建数据库并导入sql文件

2,导入idea中,修改配置文件

3,运行服务,http://localhost:8082/

4,运行前端:

arduino 复制代码
npm install
或
yarn

npm run serve
或
yarn serve

5,访问 http://localhost:8081/

部分效果图展示

代码地址

gitee.com/lt199934/tg...

​结尾

到这里我们的RBAC就讲完了!!欢迎注册涛哥博客哟!

相关推荐
devlei6 小时前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
pshdhx_albert7 小时前
AI agent实现打字机效果
java·http·ai编程
沉鱼.447 小时前
第十二届题目
java·前端·算法
努力的小郑8 小时前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
赫瑞8 小时前
数据结构中的排列组合 —— Java实现
java·开发语言·数据结构
Victor3569 小时前
MongoDB(87)如何使用GridFS?
后端
Victor3569 小时前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁9 小时前
单线程 Redis 的高性能之道
redis·后端
GetcharZp9 小时前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
周末也要写八哥9 小时前
多进程和多线程的特点和区别
java·开发语言·jvm