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

文章详细介绍了RBAC(Role-BasedAccessControl)模型,包括角色、权限和用户的概念,以及模型的优点。通过一个简单的RBAC数据库设计示例展示了用户、角色和菜单的关联。后端处理动态路由表信息,通过Mapper接口获取用户权限和路由菜单。前端利用这些信息构建动态路由,并在每次跳转时更新路由表。文章还提供了前端如何处理动态路由的代码示例,包括静态和动态路由的结合使用。
摘要由CSDN通过智能技术生成

根据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)以此来构建动态路由表

运行效果展示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值