云计算百科
云计算领域专业知识百科平台

第29章 SpringBoot实现Admin的RBAC功能

在前面的章节中,我们已经完成了对菜单权限的编辑,整体完成了RBAC的功能。

接下来,我们通过一个“文章管理”来说明如何使用RBAC功能。

首先,我们创建两个数据表。

第一个就是文章分类表

对应的SQL脚本如下

CREATE TABLE `article_cat` (
`cat_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT COMMENT '分类id',
`cat_name` varchar(20) NOT NULL DEFAULT '' COMMENT '分类名称',
`sort_order` tinyint(3) unsigned NOT NULL DEFAULT '255' COMMENT '排列顺序',
`is_close` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否关闭',
`add_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '添加时间',
PRIMARY KEY (`cat_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章分类表'

接下来,就是文章详情表

对应的SQL脚本如下

CREATE TABLE `article_info` (
`article_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '文章id',
`cat_id` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '分类id',
`article_title` varchar(50) NOT NULL DEFAULT '' COMMENT '文章标题',
`article_content` text COMMENT '文章内容',
`is_close` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否关闭',
`add_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '添加时间',
PRIMARY KEY (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章信息表'

接下来就是后端对两个表的增删改查操作,这里就不再详细介绍了。

我们修改 MenuController 控制器代码,返回文章管理的菜单列表。

MenuResponse menuArticle = new MenuResponse();
menuArticle.setPath("/article");
menuArticle.setName("Article");
menuArticle.setComponent("Layout");
menuArticle.setRedirect("/article/article_list");
menuArticle.setType(1);
menuArticle.setVisible(1);
MenuMetaResponse metaArticle = new MenuMetaResponse("文章管理","el-icon-menu");
menuArticle.setMeta(metaArticle);

List<MenuResponse> childrenArticle = new ArrayList<>();

MenuResponse menu11 = new MenuResponse();
menu11.setPath("cat_list");
menu11.setName("catList");
menu11.setComponent("article/cat_list");
menu11.setType(2);
menu11.setVisible(1);
MenuMetaResponse meta11 = new MenuMetaResponse("文章分类");
menu11.setMeta(meta11);
childrenArticle.add(menu11);

MenuResponse menu22 = new MenuResponse();
menu22.setPath("article_list");
menu22.setName("articleList");
menu22.setComponent("article/article_list");
menu22.setType(2);
menu22.setVisible(1);
MenuMetaResponse meta22 = new MenuMetaResponse("文章列表");
menu22.setMeta(meta22);
childrenArticle.add(menu22);

MenuResponse menu33 = new MenuResponse();
menu33.setPath("article_edit");
menu33.setName("articleEdit");
menu33.setComponent("article/article_edit");
menu33.setType(3); // 按钮
menu33.setVisible(1); // 显示
MenuMetaResponse meta33 = new MenuMetaResponse("添加文章");
menu33.setMeta(meta33);
childrenArticle.add(menu33);

接下来,我们回到 “my-admin-template” 前端项目上,增加对应的vue文件。

详细代码,我们不在给出,以下是部分截图给到大家。

接下来,我们创建新的角色并关联文章管理菜单权限,并添加一个新用户赋予该角色。

接下来,我们就可以登录 李四 这个用户了。

但是,登录之前我们需要修改一个 MenuController 控制器,因为这个返回的菜单权限是硬代码写死的,我们应该从数据库表里面读取才对。

@Mapper
public interface AdminMenuInfoMapper extends BaseMapper<AdminMenuInfo> {

// 根据用户Id连表查询菜单列表,排序很重要
// 先按照 menu_parent 升序, 再按照 sort_order 升序, 最后按照主键 menu_id 升序
@Select("SELECT m.* FROM `admin_menu_info` AS m " +
"JOIN `admin_role_menu` AS rm ON rm.menu_id = m.menu_id " +
"JOIN `admin_user_role` AS ur ON ur.role_id = rm.role_id AND ur.user_id = #{userId} " +
"WHERE m.menu_status = 1 " +
"ORDER BY m.`menu_parent` ASC, m.`sort_order` ASC, m.`menu_id` ASC")
List<AdminMenuInfo> getAdminMenuList(int adminId);

}

上面我们通过连表方式读取了用户对应的菜单权限

package com.demo.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.demo.mapper.AdminMenuInfoMapper;
import com.demo.model.AdminMenuInfo;
import com.demo.web.response.MenuMetaResponse;
import com.demo.web.response.MenuResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class AdminUserMenuService {

@Autowired
private AdminMenuInfoMapper mapper;

// 根据用户Id查询菜单列表
public List<MenuResponse> getUserMenuList(int userId){

// 先查询用户所有菜单列表,这个排序非常重要
// 先按照 menu_parent 升序, 再按照 sort_order 升序, 最后按照主键 menu_id 升序
List<AdminMenuInfo> menuInfoList = new ArrayList<>();
if(userId > 1){
menuInfoList = mapper.getAdminMenuList(userId);
}else{
menuInfoList = getAllMenuList();
}

// 将菜单构建上下二级结构
List<MenuResponse> menuResponseList = new ArrayList<>();

// 先添加 menu_parent = 0 的一级菜单
int i = 0;
Map<Integer, Integer> menuParentMap = new HashMap<>();
for(AdminMenuInfo temp : menuInfoList){

if(temp.getMenuParent() > 0) continue;
MenuResponse response = new MenuResponse();
response.setName(temp.getRoutesName());
response.setPath(temp.getRoutesPath());
response.setComponent(temp.getRoutesComponent());
response.setRedirect(temp.getRoutesRedirect());
MenuMetaResponse meta = new MenuMetaResponse(temp.getMenuName(), temp.getMenuIcon());
response.setMeta(meta);
response.setType(temp.getMenuType());
response.setVisible(temp.getMenuVisible());
List<MenuResponse> children = new ArrayList<>();
response.setChildren(children);
menuResponseList.add(response);

// 记录一级菜单在 responses 的索引位置
menuParentMap.put(temp.getMenuId(), i++);
}

// 再把二级菜单放入到一级菜单的 child 中
for(AdminMenuInfo temp : menuInfoList){

if(temp.getMenuParent() == 0) continue;
if(menuParentMap.containsKey(temp.getMenuParent())==false) continue;

MenuResponse response = new MenuResponse();
response.setName(temp.getRoutesName());
response.setPath(temp.getRoutesPath());
response.setComponent(temp.getRoutesComponent());
//response.setRedirect(temp.getRoutesRedirect());
MenuMetaResponse meta = new MenuMetaResponse(temp.getMenuName(), temp.getMenuIcon());
response.setMeta(meta);
response.setType(temp.getMenuType());
response.setVisible(temp.getMenuVisible());

int index = menuParentMap.get(temp.getMenuParent());
menuResponseList.get(index).getChildren().add(response);
}

return menuResponseList;
}

// 获取所有菜单列表(超级管理员)
// 先按照 menu_parent 升序, 再按照 sort_order 升序, 最后按照主键 menu_id 升序
public List<AdminMenuInfo> getAllMenuList(){

LambdaQueryWrapper<AdminMenuInfo> wrapper = Wrappers.lambdaQuery();
wrapper.eq(AdminMenuInfo::getMenuStatus, 1);
wrapper.orderByAsc(AdminMenuInfo::getMenuParent);
wrapper.orderByAsc(AdminMenuInfo::getSortOrder);
wrapper.orderByAsc(AdminMenuInfo::getMenuId);
return mapper.selectList(wrapper);
}

}

接下来是对菜单的一个处理,就是将其转化成两级结构。

最后就是 MenuController 控制器代码的修改了

@RestController
public class MenuController {

@Autowired
private TokenUtils tokenUtils;

@Autowired
private AdminUserMenuService service;

@GetMapping("/getRouters")
public JsonResult getRouters(){

// 获取用户Id
Integer userId = tokenUtils.getUserId();
if(userId == 0) return JsonResult.fail();

// 获取用户菜单权限
List<MenuResponse> menuResponseList = service.getUserMenuList(userId);
return JsonResult.success(menuResponseList);

}
}

到此为止,我们的RBAC功能才算完成了。

这里提醒大家的是,我们的user_id=1的admin用户拥有所有权限哦。

我们总结一下RBAC权限功能的实现过程,首先需要五张数据表来存储用户,角色,权限三者的关系数据。其次我们需要可以通过UI页面来完成三者的数据编辑(这个过程可能只有超管来完成)。这里的权限,一般情况下指的就是我们的菜单列表(或者页面中的某个按钮等等),因此用户和权限的关系,可以理解为用户和菜单的关系。我们通过RBAC权限功能操作的数据,其实就是用户和菜单的关系,而菜单则是业务功能的实现。例如,上文中提到的“文章管理”模块,我们可以编辑新用户,新角色,将其赋予“文章管理”的菜单权限,那么新用户登录之后看到的就只有“文章管理”菜单权限了。动态实现UI页面菜单的部分其实就是VUE动态路由的部分。

本工程前端代码下载:https://download.csdn.net/download/richieandndsc/91956859

本工程后端代码下载:https://download.csdn.net/download/richieandndsc/91956862

赞(0)
未经允许不得转载:网硕互联帮助中心 » 第29章 SpringBoot实现Admin的RBAC功能
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!