使用RBAC模型构建动态路由权限,交由前端动态渲染路由。

2023-10-26

根据RBAC模型生成动态路由并交给前端渲染

  1. 什么是RBAC模型?
  2. 简单的RBAC模型数据库设计。
  3. 后端处理动态的路由表信息
  4. 前端渲染路由的细节与注意

什么是RBAC模型

RBAC(Role-Based Access Control)模型是一种访问控制模型,它基于角色的概念来管理和控制系统中的权限和访问权限。它将权限分配给角色,然后将角色分配给用户,从而实现对系统资源的控制和管理。

在RBAC模型中,主要有以下几个要素:

  1. 角色(Role):角色是一组具有相似职能或权限的用户集合。角色是RBAC模型中的核心概念,用于对用户进行逻辑分组和权限分配。
  2. 权限(Permission):权限是系统中定义的操作或访问资源的权力。它可以是对数据进行读写操作、执行特定功能或访问特定页面等。
  3. 用户(User):用户是系统中的实际使用者,每个用户可以被分配一个或多个角色,从而具有相应的权限。
  4. 权限集(Permission Set):权限集是权限的集合,代表了一个特定角色所拥有的全部权限。
  5. 角色层级(Role Hierarchy):角色层级是角色之间的层级关系。它用于定义角色之间的继承关系,从而简化权限的管理和授权。

RBAC模型的基本原则是将访问控制与角色关联起来,通过将权限分配给角色,然后将角色分配给用户,可以实现灵活而可扩展的访问控制。

RBAC模型的优点包括:

  • 简化访问控制管理:通过将权限分配给角色,而不是直接分配给用户,可以简化对用户权限的管理和控制。
  • 可扩展性:RBAC模型支持角色层级和角色继承,可以轻松添加、修改和删除角色,从而实现系统的可扩展性。
  • 灵活性:RBAC模型可以根据组织结构和业务需求进行灵活的权限分配和角色管理。

RBAC模型广泛应用于各种系统和应用程序,如企业内部系统、网络服务、操作系统等,以确保对系统资源的安全访问和管理。

RBAC模型示例
在这里插入图片描述

简单的RBAC模型数据库设计

sys_user表

CREATE TABLE `sys_user`  (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NULL' COMMENT '用户名',
  `nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NULL' COMMENT '昵称',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NULL' COMMENT '密码',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '账号状态(0正常 1停用)',
  `email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱',
  `phone_number` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号',
  `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户性别(0男,1女,2未知)',
  `head_url` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像',
  `user_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '2' COMMENT '用户类型(0:超级管理员,1: 管理员  , 2:系统用户(普通用户) )',
  `create_by` bigint NULL DEFAULT NULL COMMENT '创建人的用户id',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint NULL DEFAULT NULL COMMENT '更新人',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '删除标志(0代表未删除,1代表已删除)',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `username`(`user_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'admin', 'youyiadmin', '$2a$10$aFl3deLJQ5alg2IcyEHXwe9to/YXJCu/2KbB/OYG4AAmPr1ghdvKy', '0', '', '12345678912', '0', NULL, '0', NULL, '2023-05-26 10:51:49', NULL, NULL, '0');

sys_role表

CREATE TABLE `sys_role`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色权限字符串',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '角色状态(0正常 1停用)',
  `del_flag` int NULL DEFAULT 0 COMMENT 'del_flag',
  `create_by` bigint NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  `update_by` bigint NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '超级管理员', 'admin', '0', 0, 1, '2023-06-01 18:00:59', NULL, NULL, NULL);


sys_user_role中间表

CREATE TABLE `sys_user_role`  (
  `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `role_id` bigint NOT NULL DEFAULT 0 COMMENT '角色id',
  PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1);


sys_menu表

CREATE TABLE `sys_menu`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `parent_id` bigint NULL DEFAULT NULL COMMENT '父id',
  `menu_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'NULL' COMMENT '菜单名',
  `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路由地址',
  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '路由名',
  `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '组件路径',
  `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限标识',
  `redirect` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '重定向位置',
  `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT '#' COMMENT '菜单图标',
  `create_by` bigint NULL DEFAULT NULL,
  `create_time` datetime NULL DEFAULT NULL,
  `update_by` bigint NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  `del_flag` int NULL DEFAULT 0 COMMENT '是否删除(0未删除 1已删除)',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_menu
-- ----------------------------
INSERT INTO `sys_menu` VALUES (1, -1, 'layout路由', '/', 'Layout', '/layout/index.vue', '0', '0', 'sys:admin:list', '/layout/home', '', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (2, -1, '登录', '/login', 'Login', '/views/login/index.vue', '0', '0', 'sys:login', NULL, '', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (3, 1, '主页', '/layout/home', 'Home', '/views/home/index.vue', '1', '0', 'sys:home', '', 'HomeFilled', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (4, -1, '数据大屏', '/screen', 'Screen', '/views/screen/index.vue', '1', '0', 'sys:screen', NULL, 'HomeFilled', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (5, -1, '权限管理', '/acl', 'Acl', '/layout/index.vue', '1', '0', 'sys:acl', '/acl/user', 'Lock', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (6, 5, '用户管理', '/acl/user', 'User', '/views/acl/user/index.vue', '1', '0', 'sys:user', NULL, 'User', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (7, 5, '角色管理 ', '/acl/role', 'Role', '/views/acl/role/index.vue', '1', '0', 'sys:role', NULL, 'Avatar', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (8, 5, '菜单管理', '/acl/permission', 'Permission', '/views/acl/permission/index.vue', '1', '0', 'sys:permission', NULL, 'Operation', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (9, -1, '商品管理 ', '/product', 'Product', '/layout/index.vue', '1', '0', 'sys:product', '/product/trademark', 'Goods', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (10, 9, '品牌管理', '/product/trademark', 'Trademark', '/views/product/trademark/index.vue', '1', '0', 'sys:trademark', NULL, 'ShoppingCart', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (11, 9, '属性管理', '/product/attr', 'Attr', '/views/product/attr/index.vue', '1', '0', 'sys:attr', NULL, 'ChromeFilled', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (12, 9, 'spu管理', '/product/spu', 'Spu', '/views/product/spu/index.vue', '1', '0', 'sys:spu', NULL, 'Calendar', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (13, 9, 'sku管理', '/product/sku', 'Sku', '/views/product/sku/index.vue', '1', '0', 'sys:sku', NULL, 'IceDrink', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (14, -1, '404路由', '/404', '404', '/views/404/index.vue', '0', '0', 'sys:404', NULL, 'Watermelon', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (15, -1, '任意路由', '/:pathMatch(.*)', 'Any', '/views/404/index.vue', '0', '0', 'sys:*', '', 'Watermelon', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (17, -1, 'NULL', NULL, 'NULL', NULL, '0', '0', NULL, NULL, '', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (18, 6, '三级路由测试', 'test', '3Test', 'test', '0', '0', 'test', NULL, '', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `sys_menu` VALUES (19, 18, '四级路由测试', 'test', '4Test', 'test', '0', '0', 'test', NULL, '', NULL, NULL, NULL, NULL, 0, NULL);

sys_role_menu中间表

CREATE TABLE `sys_role_menu`  (
  `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `menu_id` bigint NOT NULL DEFAULT 0 COMMENT '菜单id',
  PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role_menu
-- ----------------------------
INSERT INTO `sys_role_menu` VALUES (1, 1);
INSERT INTO `sys_role_menu` VALUES (1, 2);
INSERT INTO `sys_role_menu` VALUES (1, 3);
INSERT INTO `sys_role_menu` VALUES (1, 4);
INSERT INTO `sys_role_menu` VALUES (1, 5);
INSERT INTO `sys_role_menu` VALUES (1, 6);
INSERT INTO `sys_role_menu` VALUES (1, 7);
INSERT INTO `sys_role_menu` VALUES (1, 8);
INSERT INTO `sys_role_menu` VALUES (1, 9);
INSERT INTO `sys_role_menu` VALUES (1, 10);
INSERT INTO `sys_role_menu` VALUES (1, 11);
INSERT INTO `sys_role_menu` VALUES (1, 12);
INSERT INTO `sys_role_menu` VALUES (1, 13);
INSERT INTO `sys_role_menu` VALUES (1, 14);
INSERT INTO `sys_role_menu` VALUES (1, 15);

后端处理动态的路由表信息

创建获取动态路由所用到的实体

User类

/**
 * @author Mr.Liu
 * @version 1.0
 * @date 2023/6/1 17:11
 */

@Data
@ApiModel("用户")
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty("主键")
    private Long id;
    @ApiModelProperty("用户名")
    private String userName;
    @ApiModelProperty("昵称")
    private String nickName;
    @ApiModelProperty("密码")
    private String password;
    @ApiModelProperty("账号状态(0正常 1停用)")
    private String status;
    @ApiModelProperty("邮箱")
    private String email;
    @ApiModelProperty("手机号")
    private String phoneNumber;
    @ApiModelProperty("用户性别(0男,1女,2未知)")
    private String sex;
    @ApiModelProperty("头像")
    private String headUrl;
    @ApiModelProperty("用户类型(0:超级管理员,1: 管理员  , 2:系统用户(普通用户) ")
    private String userType;
    @ApiModelProperty("创建人id")
    private Long createBy;
    @ApiModelProperty("更新人id")
    private Long updateBy;
    @ApiModelProperty("创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    private LocalDateTime updateTime;
    @ApiModelProperty("删除标志(0代表未删除,1代表已删除)")
    private String defFlag;



}

Menu类

/**
 * @author Mr.Liu
 * @version 1.0
 * @date 2023/6/3 13:34
 */

@Data
@ApiModel("菜单实体")
public class Menu implements Serializable {
    @ApiModelProperty("主键")
    private  Long id;
    @ApiModelProperty("父菜单id -1代表该路由没有双亲")
    private Long parentId;
    @ApiModelProperty("菜单名")
    private String menuName;
    @ApiModelProperty("路由地址")
    private String path;
    @ApiModelProperty("路由名")
    private String name;
    @ApiModelProperty("组件地址")
    private String component;
    @ApiModelProperty("菜单状态(0显示 1隐藏)")
    private String visible;
    @ApiModelProperty("路由重定向")
    private String redirect;
    @ApiModelProperty("菜单状态(0正常 1停用)")
    private String status;
    @ApiModelProperty("权限标识")
    private String perms;
    @ApiModelProperty("图标")
    private String icon;
    @ApiModelProperty("创建人")
    private Long createBy;
    @ApiModelProperty("更新人")
    private Long updateBy;
    @ApiModelProperty("创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    private LocalDateTime updateTime;
    @ApiModelProperty("是否删除(0未删除 1已删除)")
    private String delFlag;
    @ApiModelProperty("描述")
    private String remark;
}

Meta类(分装路由的自定义属性)

/**
 * @author Mr.Liu
 * @version 1.0
 * @date 2023/7/14 20:14
 */

@Data
@NoArgsConstructor
@AllArgsConstructor

@ApiModel("路由的属性")
public class Meta {
    @ApiModelProperty("路由名称")
    private String title;
    @ApiModelProperty("路由是否隐藏")
    private String hidden;
    @ApiModelProperty("路由图标")
    private String icon;
}

MenuDto类(封装好的响应动态路由类)

/**
 * @author Mr.Liu
 * @version 1.0
 * @date 2023/7/14 20:12
 */

@ApiModel(value = "路由表")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MenuDto {


    @ApiModelProperty("主键")
    private Long id;

    @ApiModelProperty(value = "路由")
    private String path;

    @ApiModelProperty("路由名")
    private String name;

    @ApiModelProperty(value = "名称")
    private String menuName;

    @ApiModelProperty(value = "组件路径")
    private String component;

    @ApiModelProperty(value = "重定向")
    private String redirect;

    @ApiModelProperty("路由属性")
    private Meta meta;

    @ApiModelProperty("子路由")
    private List<MenuDto> children;
}

Mapper操作数据库接口sql

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="com.liu.mapper.MenuMapper">

	<!--根据用户id查询用户权限信息-->
    <select id="findByPermsByUserId" resultType="java.lang.String" parameterType="java.lang.Long">
        SELECT
	    DISTINCT m.`perms`
        FROM
	      sys_user_role ur
	      LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
	      LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
	      LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
        WHERE
	    user_id = #{userId}
	    AND r.`status` = 0
	    AND m.`status` = 0
    </select>

	<!--根据用户id查询对应查询的路由菜单(该路由下没有子路由)-->
	<select id="findMenuParentByUerId" resultType="menu" parameterType="java.lang.Long">
        SELECT
	     m.id, m.path, m.menu_name, m.component ,m.icon , m.name,
         m.visible, m.parent_id ,m.redirect
        FROM
	     sys_user_role ur
	     LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
	     LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
	     LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
        WHERE
	    user_id = #{userId}
	    AND r.`status` = 0
	    AND m.`status` = 0
	    AND m.parent_id = -1
	</select>

	<!--根据指定字段查询路由表信息-->
	<select id="findMenuByColumn" resultType="menu" >
		select  id,  path, menu_name,name, component ,icon ,
         visible, parent_id from sys_menu where ${columnName} = #{param}
	</select>
</mapper>

封装动态路由业务层操作

package com.liu.service.impl;

import com.liu.common.CustomException;
import com.liu.common.R;
import com.liu.entity.*;
import com.liu.mapper.MenuMapper;
import com.liu.service.UserService;
import com.liu.utils.BaseContext;
import com.liu.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Mr.Liu
 * @version 1.0
 * @date 2023/6/3 14:09
 */

@Slf4j
@Service("userService")
public class UserServiceImpl implements UserService {

    @Resource
    private MenuMapper menuMapper;

    @Override

    /**
     * 拿到动态路由表
     */
    public R<List<MenuDto>> getMenu() {
        Long userId = BaseContext.getCurrentId();
        if(userId == null){
            new RuntimeException("未知错误!");
        }

        //拿到所有的一级路由
        List<Menu> menuList = menuMapper.findMenuParentByUerId(userId);
        if(menuList.size() == 0  || menuList == null){
            new RuntimeException("未知错误");
        }
        //返回结果集合

        List<MenuDto> menuDtoList =  menuList.stream().map((item) ->{

            MenuDto menuDto = new MenuDto();
            //拿着menu id 去查询与parent_id 相等数据
            List<Menu> menuChildList = menuMapper.findMenuByColumn("parent_id", item.getId());
            if(menuChildList == null || menuChildList.size() == 0){
                Meta meta = new Meta();
                meta.setTitle(item.getMenuName());
                meta.setHidden(item.getVisible());
                meta.setIcon(item.getIcon());
                BeanUtils.copyProperties(item,menuDto);
                menuDto.setName(item.getName());
                menuDto.setMeta(meta);
                return menuDto;
            }else{

                MenuDto menuDto1 = setMenuDto(item);
                return  menuDto1;
            }
        }).collect(Collectors.toList());

        return R.success(menuDtoList,HttpStatus.OK.value());
    }


    //递归设置子路由
    public MenuDto setMenuDto(Menu menu) {
        if (menu == null) return null;
        MenuDto menuDto = new MenuDto();
        List<Menu> childMenuList = menuMapper.findMenuByColumn("parent_id", menu.getId());
        if(childMenuList.size() == 0 || childMenuList == null){
            BeanUtils.copyProperties(menu,menuDto);
            Meta meta = new Meta();
            meta.setIcon(menu.getIcon());
            meta.setTitle(menu.getMenuName());
            meta.setHidden(menu.getVisible());  
            menuDto.setMeta(meta);
            menuDto.setName(menu.getName());
        }else{

            //子路由
            List<MenuDto> listDtoChild = new ArrayList<>();
            //封装子路由集合
            for (Menu menu1 : childMenuList) {
                MenuDto menuDto2 = setMenuDto(menu1);  //递归得到的子路由
                listDtoChild.add(menuDto2);
            }
            BeanUtils.copyProperties(menu,menuDto);
            //设置吗meta属性
            Meta meta = new Meta();
            meta.setIcon(menu.getIcon());
            meta.setTitle(menu.getMenuName());
            meta.setHidden(menu.getVisible());
            menuDto.setMeta(meta);
            menuDto.setName(menu.getName());

            //设置子路由
            menuDto.setChildren(listDtoChild);
        }

        return menuDto;
    }

}

测试接口返回的数据应该长这样

 "data": [
        {
            "id": "1",
            "path": "/",
            "name": "Layout",
            "menuName": "layout路由",
            "component": "/layout/index.vue",
            "redirect": "/layout/home",
            "meta": {
                "title": "layout路由",
                "hidden": "0",
                "icon": ""
            },
            "children": [
                {
                    "id": "3",
                    "path": "/layout/home",
                    "name": "Home",
                    "menuName": "主页",
                    "component": "/views/home/index.vue",
                    "redirect": null,
                    "meta": {
                        "title": "主页",
                        "hidden": "1",
                        "icon": "HomeFilled"
                    },
                    "children": null
                }
            ]
        },
        {
            "id": "2",
            "path": "/login",
            "name": "Login",
            "menuName": "登录",
            "component": "/views/login/index.vue",
            "redirect": null,
            "meta": {
                "title": "登录",
                "hidden": "0",
                "icon": ""
            },
            "children": null
        },
     ···
 ]

前端渲染路由的细节与注意

1.在获取动态路由表前我们也需要几个静态路由

静态路由表

//对外暴露静态路由
export const constantRoutes = [
  {
    //登录路由
    path: '/login',
    name: 'Login',
    component: () => import('@/views/login/index.vue'),
    meta: {
      title: '登录',
      hidden: true, //代表路由的标题在菜单中是否隐藏 true隐藏 反之不隐藏
      icon: 'User', //菜单图标,支持element_Plus所有图标
    },
  },
  {
    path: '/404',
    name: '404',
    component: () => import('@/views/404/index.vue'),
    meta: {
      title: '404',
      hidden: true,
      icon: 'Watermelon',
    },
  },
  {
    path: '/:pathMatch(.*)',
    name: 'Any',
    redirect: '/404',
    meta: {
      title: '任意路由',
      hidden: true,
      icon: 'Watermelon',
    },
  },
]


// console.log("静态路由表",constantRoutes)

2.为了让菜单实时跟新,我让每一次跳转路由之前都会删除之前的路由表,在重新异步获取动态的路由表。

permisstion.ts 文件

//路由鉴权
import router from '.'
import nprogress from 'nprogress'
import 'nprogress/nprogress.css'
import useUserSotre from '@/store/moudules/user'
import setting from '@/setting'
import pinia from '@/store';
import type { Menu, MenuListResponseData, Meta } from '@/api/user/type'
import { reqMenulist } from '@/api/user'


let userStore = useUserSotre(pinia)


//递归转换后端传递的路由格式处理函数
const handlerMenuList = (arr: Menu[]) => {
  let newArr: Menu[] = [];

  //view下的组件实例  
  const compView = import.meta.glob("@/views/**/*.vue");  //只能拿到一级路由的实例
  //layout下的组件实例
  const compLayout = import.meta.glob("@/layout/index.vue")
  arr.forEach((item) => {
    let obj: any = {}
    obj.meta = {}
    obj.path = item.path
    obj.name = item.name
    obj.meta = item.meta
    obj.redirect = item.redirect

    if (item.component) {
      if (item.component.includes('/views')) {
        // console.log("组件实例地址",`/src${item.component}`)
        obj.component = compView[`/src${item.component}`]  //glob必须为string
        // console.log("是否实例化组件成功",obj.component)
      } else if (item.component.includes('/layout')) {
        obj.component = compLayout[`/src${item.component}`];
      }
    } else {
      //组件实例不存在
      obj.component = compView['@/views/404/index.vue'];
    }

    if (item.children) {
      //递归得到子组件实例
      obj.children = handlerMenuList(item.children)
      newArr.push(obj)
    } else {
      newArr.push(obj)
    }

    // console.log(obj)
  });

  if (newArr.length == 0) return []
  return newArr
}

const setAsyncRoutes = async () => {
  //添加动态路由表
  let result: MenuListResponseData = await reqMenulist()
  if (result.code == 200) {
    //TODO 无法实现动态路由
    // console.log("真正的路由表", router.getRoutes());
    // console.log(result);

    //解析后台的路由成为前端真正能用的路由
    const arr = handlerMenuList(result.data);
    // console.log("解析好的路由数组",arr)
    //把解析的好的动态路由放到userStore中
    userStore.menuRoutes = arr;
    //清空之前路由器残留路由
    router.getRoutes().forEach((route: any) => {
      router.removeRoute(route.name)
    })
    //循环添加动态路由到主路由器中
    arr.forEach((item: any) => {
      //添加解析好的路由对象
      // console.log(item)
      router.addRoute(item)
    })

    // console.log("动态获取并添加成功的路由表", router.getRoutes());

  }
}

//全局前置守卫
router.beforeEach(async (to: any, from: any, next: any) => {
  nprogress.start()
  //是否有token 有代表已经登录 没有代表未登录
  let token = userStore.token

  setAsyncRoutes(); //获取动态路由并放入到路由器中
  //获取用户信息
  let username = userStore.username
  console.log(token)
  if (token) {
    if (to.path == '/login') {
      next({ path: '/' })
    } else {
      //登录成功访问非登录页面直接放行
      //还得有用户信息才会放行
      if (username) {
        next()
      } else {
        //没有用户信息发请求获取用户信息再放行
        try {
          // await userStore.userInfo()
          // //获取用户信息以后我再进行放行
          next()
        } catch (e: any) {
          //token过期导致发请求出异常
          //退出登录清空服务器数据并跳转到登录页面
          // await userStore.userLogout()
          next({ path: '/login', query: { redirect: to.path } })
        }
      }
    }
  } else {
    if (to.path == '/login') {
      next()
    } else {
      next({
        path: '/login',
        query: {
          redirect: to.path,
        },
      })
    }
  }
})



//全局后置守卫
router.afterEach(async (to: any, from: any) => {
  nprogress.done()
  document.title = setting.title + '-' + to.meta.title
})

该demo使用vite +vue3 +ts 构成 ,在vite中可以使用import.meta.glob(“xxx”)的形式拿到组件实例对象集合,可以以组件地址(key)的形式拿到组件实例对象(value)以此来构建动态路由表

运行效果展示

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用RBAC模型构建动态路由权限,交由前端动态渲染路由。 的相关文章

  • 如何使用 JavaFX 中的 JCSG 库将 MeshView 转换为 CSG 对象

    我正在使用 JavaFX 的 JCSG 库 我有一些MeshView我想将它们转换成的对象CSG对象 有办法实现吗 最简单的方法是组合javafx scene shape Mesh对象与 CSG 对象 前提是您有TriangleMesh正在
  • Java中如何做系统捷径跨平台集成?

    您可能知道 Mac OS X 中保存的快捷键是Cmd S在 Windows 上是Ctrl S 关闭应用程序的捷径是Cmd QWindows 是Alt F4 但问题是如何在 java 应用程序中执行这些操作 我是否需要找到我在应用程序中使用的
  • 为什么不在下一个 JVM 中删除类型擦除呢?

    Java 在 Java 5 中引入了泛型类型擦除 因此它们可以在旧版本的 Java 上运行 这是兼容性的权衡 我们已经失去了这种兼容性 1 https stackoverflow com questions 22610400 a progr
  • 无法在 Junit 测试中自动装配 JpaRepository - Spring boot 应用程序

    Hi All 我正在尝试在 Spring boot 应用程序中执行 Junit 测试 Junit 应该测试一些 CRUD 操作 我正在使用 Spring Repositories 特别是 JpaRepository 存储库类 package
  • 翻转旋转和图像

    我正在用 Java 编写一个平台游戏 并且正在手动编码玩家动画 我分别为每个肢体设置动画 改变位置和旋转 当玩家面向右时 这工作得很好 但是当玩家面向左时 我不知道如何处理旋转 以使它们在玩家向左转时看起来相同 每个身体部位的位置都是相对于
  • 仅以 int 形式显示和保存小时数

    如何仅显示小时并使用 int 变量 我的意思是打印时间 例如 20 30 44 PM 我只想存储小时 即 int 变量中的 20 小时 怎么做 有谁知道的话可以告诉我密码吗 谢谢 尝试使用日历get http docs oracle com
  • java.lang.NoSuchMethodError: com.google.common.base.Preconditions.checkArgument(ZLjava/lang/String;I)V 通过 Java 使用 IE 和 Selenium

    我正在尝试将 selenium 与 sikuli 一起使用 所以 sikuli 不会使用当前的 java 设置在 64 位上运行 因此在我的项目中将运行时更改为指向 32 位运行时环境 并使用 32 位 iewebdriver 添加我的 s
  • 如何在android中动态添加项目到listview

    有谁能够帮助我 我正在尝试在 Android 中创建一个 ListView 并且我正在尝试使用代码 不使用 XML 将项目加载到其中 这是我到目前为止的代码 tweetList ListView this findViewById R id
  • 如何通过Java编码在运行时将My Jar加载到ClassPath?

    我正在 Net Beans 中使用 Swing 我有自己的 jar 其中包含类和方法 我将使用 JAVA Reflection API 调用这些类和方法 但在此之前我想在运行时将 Jar 加载到类路径中 我有一个 J 按钮 单击该按钮我将获
  • 相机 java.lang.RuntimeException:setParameters 失败

    我使用创建了一个自定义相机应用程序this https github com davidgatti dgCam源代码 但在少数设备上 例如高分辨率设备 我得到 RuntimeException setParameters failed 我面
  • Java:是否有工具可以使代码(在第 3 方 JAR 中)向前兼容(1.4 - 1.6)

    我有一个使用 Java 1 4 编译的第 3 方 JAR 文件 有没有一个工具可以使jar文件兼容Java 1 6 类似于 retrotranslator 但它的反面是什么 我尝试反编译类文件并在 1 6 中重新编译它们 但失败了 问题是这
  • 验证在子类上调用此方法时是否调用了重写的超类方法

    我将用这个例子来展示我的问题 我有一个带有方法的类foo 该类有一个重写此方法的子类 子类的方法调用超类的方法 我可以验证一下吗 我不想测试什么foo在超类中确实如此 我只需要验证它是否被调用 我知道重构可以有所帮助 优先考虑组合而不是继承
  • 如何使用 Jackson 重命名 JSON 序列化中的根密钥

    我正在使用 Jackson 对对象列表进行 JSON 序列化 这是我得到的 ArrayList id 1 name test name 但我想要这个 rootname id 1 name test name ie showing the s
  • 从MySQL php中的特定列获取最大ID和最小ID

    我是新来的php现在尝试从中检索数据MySQL到安卓 这是我的工作细节 table In 检索总小时数函数 我想检索最短 ID 时间 and 最大 ID 超时 from MySQL到安卓通过php最后使用下面的代码来获取总小时数 假设 ID
  • 能够存储微秒的 Date 对象

    我正在寻找一个能够存储到微秒粒度的 Date 对象 有人知道吗 标准Date对象仅存储到毫秒 我知道这是平台限制 我可以通过包装来解决这个问题Date加上自定义类别中的小数数量 然而 我希望避免编写一个带有适当计算等的内容 我需要解析一个b
  • Java:删除链表中的所有元素

    Java中如何删除链表中的所有元素without使用已经可用的clear 方法 这项练习的灵感来自于电话采访中收到的一个问题 说我可以用 C 来做这个 void DeleteAllElement ListElement head ListE
  • 不要使用android内置的org.json

    我写了一个使用的库org json http json org A 来自 json org 假设 Android 使用相同的 在 android 中也称为org json B 只是它遗漏了一些相对关键的功能 现在我想做的是设置我的 grad
  • 使用 Vue.JS 时,我们是否被迫在 CSP 中使用“unsafe-inline”?

    有没有办法让 Vue js 与 CSP 正常配合 当我运行我的spa应用程序 由npm run generate使用 Nuxt js 我会收到几个警告 例如 拒绝应用内联样式 因为它违反了以下规定 内容安全策略指令 style src se
  • 在java中使用共享密钥加密/解密?

    我有客户令牌 我正在从一个 Web 应用程序 如 app1 发送到另一个 Web 应用程序 如 app2 我想加密客户令牌 在 app1 上 并使用在 app1 和 app2 上共享的密钥在 app2 上对其进行解密 我不知道如何开始 这将
  • Java无限信号量

    想知道如何not使用信号量限制连接 或任何东西 所以你可能会想 这听起来很愚蠢 但是 它稍微简化了我的代码 因为它让我可以统一处理有限和无限的情况 请注意 我并不是在寻找有关如何编写类似内容的建议 if limited semaphore

随机推荐

  • centOS-7静态ip配置

    centOS 7静态ip配置 1 确定网关 vmware虚拟机 gt 编辑菜单 gt 虚拟网络编辑器 gt 打开窗口 gt 选中vmnet8虚拟网卡 gt nat设置 gt 查看网关 具体如下图 2 查看可用的ip网段 vmware虚拟机
  • 06-----the inferior stopped because it triggered an exception

    这个问题总结一下 1 指针非法访问或者数组越界导致的 2 相关的静态库 动态库版本与编译器的位数不一致导致的 而我就是第2个问题导致的 因为我出错的地方是一个int型的变量 并非指针 故将QT的MSCV编译位数改成32位后 程序正常 因为我
  • 快手did did_gt edid的注册过程

    接口 https gdfp ksapisrv com rest infra gdfp report kuaishou android did 是本地生成的16进制 或者 获取的 android id did gt 是did生成时间戳 159
  • wx小程序结构目录介绍及创建和删除

    仔细查看之前创建的项目 可以发现项目里生成很多不同类型的文件 json 后缀 JSON 配置文件 wxml 后缀 WXML 模板文件 wxss 后缀 WXSS 样式文件 js 后缀 JS 脚本逻辑文件 1 sitemap json小程序收录
  • 人机交互的困难之一常常在于没有形成有效的你、我、他之间的互换。

    人机交互的困难之一常常在于没有形成有效的你 我 他之间的互换 而要形成交互过程中有效的你 我 他角色的互换 可以考虑以下几个方面 清晰定义角色 在交互开始之前 明确定义每个参与者的角色和身份 机器可以被定义为 你 而用户则为 我 这样可以建
  • 《Cesium 进阶知识点》 - 加载天地图三维地名服务(无Cesium 版本依赖)

    一 解决依赖 天地图官网说只支持 1 52 1 58 1 63 1 这 3个版本 其它版本报错 但我只使用三维地名服务 所以做了如下修改 我在 1 80 版 和 1 84 版中测试有效 操作部署是 1 根据官网安装 cesium tdt 插
  • Python------- if-else语句介绍

    Python的if else语句是一个判断性语句 要判断就需要有条件以及满足条件和不满足条件的情况 以下就此进行说明 1 if else的使用格式 if 条件 满足条件所要做的事情 else 不满足条件所要做的事情 这里需要注意的是 if和
  • 数据库SQL性能优化之详解

    一 问题的提出 在应用系统开发初期 由于开发数据库数据比较少 对于查询SQL语句 复杂视图的的编写等体会不出SQL语句各种写法的性能优劣 但是如果将应用系统提交实际应用后 随着数据库中数据的增加 系统的响应速度就成为目前系统需要解决的最主要
  • c语言 (3×3)矩阵转置

    题目描述 写一个函数 使给定的一个二维数组 转置 即行列互换 输入 一个3x3的矩阵 输出 转置后的矩阵 样例输入 1 2 3 4 5 6 7 8 9 样例输出 1 4 7 2 5 8 3 6 9 废话不说还是直接上代码 include
  • 使用STM32CUBEIDE创建工程,点亮LED

    1 创建LED驱动文件 先在工程下新建一个文件夹命名为icode存放驱动程序 然后对每一个外设新建新的驱动文件夹 如驱动LED就新建文件夹led 然后在led文件夹下创建对应的头文件和源文件 即led h和led c 然后编写对应外设的驱动
  • X.509证书的使用

    总结一下如何使用X 509证书来保护我们的设备的数据传输 证书的签发 以下是证书签发的流程 为了更好的演示 我们需要分别创建两个根证书 并且用每个根证书来颁发一个客户端证书 这两个根证书分别为root 1 crt以及root 2 crt 对
  • Java上传下载ftp文件

    在Java中连接FTP服务器可以使用Apache Commons Net库提供的FTPClient类 以下是一个简单的示例代码 演示如何连接到FTP服务器 进行文件上传和下载操作 import org apache commons net
  • 【Windows上同时安装两个不同版本MYSQL】MySQL安装教程--5.7和8.0版本

    一 MySQL官网下载对应版本的zip文件 最新版本8 0 34下载链接 https dev mysql com downloads mysql MySQL 5 7下载链接 https downloads mysql com archive
  • vue中使用百度地图自定义信息窗口

    场景 点击地图上的标注的时候 希望可以显示自定义的信息弹窗 具体效果如下 注意 如果只是简单显示信息 则使用InfoWindow信息窗口或者标注本身的title属性即可 想自定义就使用infoBox自定义信息窗口工具 效果 效果图是GIF图
  • 【满分】【华为OD机试真题2023B卷 JS】矩阵最大值

    华为OD2023 B卷 机试题库全覆盖 刷题指南点这里 矩阵最大值 知识点矩阵数组 时间限制 1s 空间限制 32MB 限定语言 不限 题目描述 给定一个仅包含0和1的N N二维矩阵 请计算二维矩阵的最大值 计算规则如下 1 每行元素按下标
  • startx analyze

    1 xinit 在说明startx之前 我想我们应该先了解一下xinit 因为startx就是通过调用xinit启动X的 1 1 功能 当我们安装了Ubuntu后 默认就已经安装了xinit 它位于 usr bin下 xinit是一个二进制
  • RabbitMQ(四)消息Ack确认机制

    RabbitMQ 四 消息Ack确认机制 确认种类 RabbitMQ的消息确认有两种 消息发送确认 这种是用来确认生产者将消息发送给交换器 交换器传递给队列的过程中 消息是否成功投递 发送确认分为两步 一是确认是否到达交换器 二是确认是否到
  • AS 从SVN转向Git

    之前的项目都是用SVN 感觉SVN 还是挺不错的 但接触了Git后 才发现长江后浪推前浪 前浪死在沙滩上 果断抛弃了SVN 转向git的怀抱 虽然遇到了很多问题 但在同事的帮助下 至少能上传和check了 之后遇到git上的问题后 在写文章
  • js从数组中提取自己所需的数据

    1 场景一 人员选择相关问题 场景一 从人员选择数据中 提取已选的人员信息 selectedList为已选择的人员信息 只有id groupInfo接口返回的原始数据 所有人员信息 需要找出已选择的人员信息的具体信息 const group
  • 使用RBAC模型构建动态路由权限,交由前端动态渲染路由。

    根据RBAC模型生成动态路由并交给前端渲染 什么是RBAC模型 简单的RBAC模型数据库设计 后端处理动态的路由表信息 前端渲染路由的细节与注意 什么是RBAC模型 RBAC Role Based Access Control 模型是一种访