根据RBAC模型生成动态路由并交给前端渲染
什么是RBAC模型
RBAC(Role-Based Access Control)模型是一种访问控制模型,它基于角色的概念来管理和控制系统中的权限和访问权限。它将权限分配给角色,然后将角色分配给用户,从而实现对系统资源的控制和管理。
在RBAC模型中,主要有以下几个要素:
- 角色(Role):角色是一组具有相似职能或权限的用户集合。角色是RBAC模型中的核心概念,用于对用户进行逻辑分组和权限分配。
- 权限(Permission):权限是系统中定义的操作或访问资源的权力。它可以是对数据进行读写操作、执行特定功能或访问特定页面等。
- 用户(User):用户是系统中的实际使用者,每个用户可以被分配一个或多个角色,从而具有相应的权限。
- 权限集(Permission Set):权限集是权限的集合,代表了一个特定角色所拥有的全部权限。
- 角色层级(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)以此来构建动态路由表
运行效果展示